8 領域・502 機能のインベントリと既存テストのカバレッジ一覧(TDD 機能テスト計画)
| 領域 (Area) | 機能数 | FULL | PARTIAL | NONE |
|---|---|---|---|---|
| Engine core | 65 | 52 | 9 | 4 |
| Tools | 56 | 32 | 22 | 2 |
| Pieces | 32 | 18 | 13 | 1 |
| Reflection | 61 | 51 | 10 | 0 |
| Bridge core | 50 | 39 | 9 | 2 |
| Bridge auth & spaces | 66 | 48 | 17 | 1 |
| Services & DB | 87 | 69 | 13 | 5 |
| UI | 85 | 43 | 31 | 11 |
| 合計 | 502 | 352 | 124 | 26 |
本ページの数値は 8 つのインベントリ表をパースして算出した実数(502 件)。設計仕様 (design spec) の概算目標値は 483 件 (FULL 318 / PARTIAL 137 / NONE 29)。差分はインベントリ確定時の項目追加・分類更新による。
| ID | 領域 | 機能 | 挙動 | ソース | 既存テスト | カバレッジ | ギャップ |
|---|---|---|---|---|---|---|---|
ENG-001 | Engine core | Parallel read-tool execution | Consecutive PARALLEL_SAFE tools run via Promise.all; side-effecting tools act as a sequential barrier | agent-loop.ts (`canExecuteInParallel`, `PARALLEL_SAFE_TOOL_NAMES`, executeMovement) | agent-loop.test.ts "runs consecutive safe tool calls in parallel"; "keeps side-effecting tools sequential as a barrier"; "executes regular tools before transition even if transition appears mid-batch" | FULL | — |
ENG-002 | Engine core | transition tool (non-terminal) | buildTransitionTool enum = only rules[].next; validateTransition rejects targets outside rules; processTransitionCalls records lessons | agent-loop.ts (`buildTransitionTool`, `validateTransition`, `processTransitionCalls`) | agent-loop.test.ts "non-terminal transition (movement-to-movement) still works"; "transition({next_step:…})"; "lets transition/complete win even if it shares a batch with a repeated call" (tool-loop) | PARTIAL | buildTransitionTool / validateTransition exercised only indirectly via executeMovement; no unit test that an out-of-rules next is rejected in isolation |
ENG-003 | Engine core | complete tool — status routing | success→COMPLETE, aborted→ABORT, needs_user_input→ASK via COMPLETE_STATUS_TO_NEXT / buildMovementResultFromComplete | agent-loop.ts (`buildCompleteTool`, `parseCompleteArgs`, `buildMovementResultFromComplete`) | agent-loop.test.ts §7.1 "success status…becomes movement output"; "aborted status routes via ABORT next"; "needs_user_input routes via ASK next" | FULL | — |
ENG-004 | Engine core | complete arg validation | success requires non-empty result; aborted requires abort_reason; needs_user_input requires missing_info; invalid → forced retry (no accumulatedText fallback) | agent-loop.ts (`parseCompleteArgs`, `validateCompleteArgs`) | agent-loop.test.ts §7.1 "rejects success with empty result"; "rejects aborted without abort_reason"; (needs_user_input/missing_info path covered via routing test) | PARTIAL | No explicit test that needs_user_input with empty missing_info is rejected (validateCompleteArgs branch); covered for success/aborted only |
ENG-005 | Engine core | Multiple complete precedence | 0 completes → continue; >1 conflicting args → retry; >1 identical → first used | agent-loop.ts (complete dedup logic ~L1503-1535) | agent-loop.test.ts §7.2 "invalid native complete forces retry"; "two native completes with conflicting args → retry"; "two native completes with identical args → first one used" | FULL | — |
ENG-006 | Engine core | Conversation-history integrity on retry | Every tool_use id gets a matching tool_result even when complete is rejected | agent-loop.ts (retry path) | agent-loop.test.ts §7.7 "all tool_use ids get a tool_result on retry" | FULL | — |
ENG-007 | Engine core | Tool-call loop detection (intra-movement) | Identical tool-call batch repeated ≥ maxToolLoopRepeats (default 5) → ABORT; counter resets on arg change | agent-loop.ts (`DEFAULT_MAX_TOOL_LOOP_REPEATS`, `buildToolLoopAbortMessage`, consecutiveToolRepeats) | agent-loop.tool-loop.test.ts (5 tests: limit abort, custom limit, under-limit, arg-change reset, transition wins) | FULL | — |
ENG-008 | Engine core | Loop-watchdog one-shot warning | At maxToolLoopRepeats-2 (min 2) inject a single [loop watchdog] reminder before aborting | agent-loop.ts (`warnAt`, `toolLoopWarned`, `injectLoopWarning`) | (implicit in tool-loop tests' under-limit path) | PARTIAL | No assertion that the watchdog warning message is actually injected exactly once before abort |
ENG-009 | Engine core | Max-iterations abort | Movement exceeding safety.maxIterations aborts with buildMaxIterationsAbortMessage | agent-loop.ts (`buildMaxIterationsAbortMessage`) | agent-loop.test.ts "aborts when maxIterations is exceeded" | FULL | — |
ENG-010 | Engine core | Text-only-response handling | NON-terminal movement aborts after N text-only turns; STRICTLY-terminal (defaultNext COMPLETE, no rules) salvages prose as completion; counter resets on tool use | agent-loop.ts (`TEXT_ONLY_REMIND_EMPTY`, textOnlyOutcome) | agent-loop.test.ts "aborts after text-only responses in a NON-terminal movement"; "salvages text-only prose…STRICTLY-terminal"; "does NOT salvage…still has rules"; "resets text-only counter when tool calls happen" | FULL | — |
ENG-011 | Engine core | Context-overflow forced transition (runtime usage) | ContextManager threshold 95% → force_transition builds result via movement.defaultNext; falls to ABORT when defaultNext is terminal/absent | agent-loop.ts (`buildContextOverflowResult`, `buildForceTransitionResult`) | agent-loop.test.ts "triggers force_transition when context manager signals exhaustion"; "fires onContextAction callback when context threshold crossed" | FULL | — |
ENG-012 | Engine core | Oversized initial prompt guard | Prompt over budget pre-send → dedup→compact→summarize; aborts/force-transitions when still oversized | agent-loop.ts + context/prompt-guard.ts (`guardPromptBeforeSend`) | agent-loop.test.ts "aborts when initial prompt is oversized and defaultNext is terminal"; "falls back to ABORT when oversized prompt has no defaultNext"; prompt-guard.test.ts (full stage matrix) | FULL | — |
ENG-013 | Engine core | Cancel signal handling | Already-aborted cancelSignal returns ABORT immediately; mid-movement cancel returns ABORT "cancelled" | agent-loop.ts (cancelSignal checks) | agent-loop.test.ts "returns ABORT immediately when cancelSignal is already aborted" | PARTIAL | Mid-iteration cancel (after work started) not asserted at agent-loop level; only the pre-loop case |
ENG-014 | Engine core | ContextManager threshold ladder | 70%→warn, 85%→prompt, 95%→force_transition; each fires once; isExhausted ≥0.99 | context-manager.ts (`ContextManager`) | context-manager.test.ts (13 tests: each threshold, fire-once, defaults, limitTokens, isExhausted, hasUsageData, char fallback) | FULL | — |
ENG-015 | Engine core | Ollama context-limit autodetect | fetchOllamaContextLimit prefers num_ctx > model_info.context_length; llama.cpp /props fallback; default on failure | context-manager.ts | context-manager.test.ts (5 tests incl. num_ctx precedence, /props fallback, n_ctx absent) | FULL | — |
ENG-016 | Engine core | Tool catalog injection into system prompt | buildSystemPrompt / buildGlobalPreamble auto-inject available-tools list + 1-line summaries; script section gated on Bash presence | agent-loop.ts (`buildGlobalPreamble`, `buildSystemPrompt`) | agent-loop.test.ts "injects the script section when Bash is among the presented tools"; "omits…when Bash is not presented"; preamble byte-stability tests | FULL | — |
ENG-017 | Engine core | Preamble/guidance split (job-stable vs movement) | buildGlobalPreamble byte-identical across movements; buildMovementGuidance carries persona/instruction/transitions | agent-loop.ts | agent-loop.test.ts "preamble is byte-identical across two different movements"; "guidance carries movement-specific instruction, persona, transitions"; "buildSystemPrompt wrapper still contains both halves" | FULL | — |
ENG-018 | Engine core | Progressive pressure (intra-movement revisit) | visitCount==2 → caution; >=3 → warning injected into guidance | agent-loop.ts (`buildMovementGuidance` L672) | agent-loop.test.ts "guidance injects progressive-pressure warning on revisit" | PARTIAL | Only the revisit-warning presence is asserted; the visitCount==2 (caution) vs >=3 (warning) distinction is not separately tested |
ENG-019 | Engine core | Checklist watchdog | One-shot reminder after 5 iterations with no checklist tool; suppressed if CreateChecklist used early | agent-loop.ts | agent-loop.test.ts "injects a one-shot reminder after 5 iterations"; "does NOT fire when CreateChecklist is called early"; traceability "watchdog_fire" | FULL | — |
ENG-020 | Engine core | Cross-movement Read cache | Cacheable read served to later movement; errors not cached; non-allowlisted tools (Bash) skipped | agent-loop.ts + context/tool-result-cache.ts | agent-loop.test.ts "returns a cached Read result…"; "does not cache error results"; "skips caching tools outside the cacheable allowlist" | FULL | — |
ENG-021 | Engine core | Cache invalidation | Edit/Write invalidate affected path; Grep entries all-evicted on edit; Bash conservatively invalidates all file entries; no invalidate on tool error | agent-loop.ts + context/invalidation.ts + tool-result-cache.ts | agent-loop.test.ts Phase 2/4 (7 tests); invalidation.test.ts (5); tool-result-cache.test.ts (28) | FULL | — |
ENG-022 | Engine core | Cache key construction | Deterministic v1-prefixed keys for Read/Grep/Glob/WebFetch/Office; URL scheme/host normalization; workspace isolation | context/cache-key.ts | cache-key.test.ts (25); tool-result-cache.test.ts key tests | FULL | — |
ENG-023 | Engine core | File-read dedup | Older Reads of same file replaced with placeholder, most-recent kept; ignores non-Read tools; idempotent | context/file-read-dedup.ts | file-read-dedup.test.ts (10) | FULL | — |
ENG-024 | Engine core | History summarization | summarizeHistory replaces middle turns with summary, preserves tail; LLM-failure → summarized=false; update-mode with prior summary | context/history-compactor.ts | history-compactor.test.ts (21: splitIntoTurns, buildSummaryPrompt, selectTailTurnStartIndex, summarizeHistory, summarizeForceTransition) | FULL | — |
ENG-025 | Engine core | Token estimation | char→token by script class (ASCII/CJK/other), per-message overhead, image budget, tool serialization | context/token-estimate.ts | token-estimate.test.ts (23) | FULL | — |
ENG-026 | Engine core | Prompt-guard staged compaction | dedup→compact→summarize ladder; reserve-cap headroom; stage-3 skipped when disabled/no isolated LLM; ok:false when all fail | context/prompt-guard.ts | prompt-guard.test.ts (27) | FULL | — |
ENG-027 | Engine core | Conversation class (seed/enter/persist) | seed order (preamble+guidance+task); enterMovement appends guidance w/o reset; flush/loadFrom round-trip; rewrite truncates | context/conversation.ts | conversation.test.ts (in-memory + persistence, 8) | FULL | — |
ENG-028 | Engine core | Conversation.replayableTurns | Strips system + control-flow tool_calls (complete/transition) + dangling/orphan tool messages; keeps valid pairs | context/conversation.ts | conversation.test.ts (7 replayableTurns cases) | FULL | — |
ENG-029 | Engine core | Conversation.seedContinuation / hasReplayableTranscript | Builds [preamble,guidance,...priorTurns,user] & rewrites transcript; hasReplayableTranscript guards undefined/missing/system-only | context/conversation.ts | conversation.test.ts (seedContinuation + 4 hasReplayableTranscript) | FULL | — |
ENG-030 | Engine core | Continuation seed in executeMovement | priorTurns replayed & handoff block suppressed; plain seed when absent | agent-loop.ts | agent-loop.test.ts "replays priorTurns and suppresses handoff block"; "without priorTurns, uses plain seed"; "with empty priorTurns array, behaves like plain seed" | FULL | — |
ENG-031 | Engine core | Atomic JSON persistence | writeAtomicJson (tmp+rename, mkdir parents); readSafeJson returns missing/corrupt/wrong-version; quarantineCorruptFile | context/atomic-json.ts | atomic-json.test.ts (11) | FULL | — |
ENG-032 | Engine core | Workspace path normalization | Rejects absolute/backslash/drive/UNC/NUL/empty; canonicalizes ../.; prefixWorkspacePath join with traversal guard | context/path-normalize.ts | path-normalize.test.ts (15) | FULL | — |
ENG-033 | Engine core | Piece loop guards (cross-movement) | enforceLoopGuards: consecutive revisits > max_consecutive_revisits (default 4) → abort; counters reset on movement change | piece-runner.ts (`enforceLoopGuards`) | piece-runner.test.ts "aborts when loop detection fires due to consecutive revisits" | PARTIAL | Per-movement max_consecutive_revisits override and the counter-reset-on-movement-change branch not separately asserted |
ENG-034 | Engine core | ASK limit fallback | ASK result resolves default_next or first non-self/non-ASK/non-ABORT rule; aborts when no fallback; resolves COMPLETE default_next via ASK-limit fallback | piece-runner.ts (result.next==='ASK' branch) | piece-runner.test.ts "falls back to default_next when ASK limit is reached"; "aborts when ASK limit reached and no fallback"; "resolves a COMPLETE default_next reached via the ASK-limit fallback" | FULL | — |
ENG-035 | Engine core | WAIT_SUBTASKS pause | result.next==='WAIT_SUBTASKS' pauses piece run (waiting_subtasks) | piece-runner.ts (L1144) | piece-runner.test.ts "does NOT write snapshot on waiting_subtasks (transient pause)" | PARTIAL | The pause/resume return contract is only asserted via the snapshot-suppression side effect; no direct test of the waiting_subtasks PieceRunResult shape |
ENG-036 | Engine core | SpawnSubTask depth-limit skip | Movement requiring SpawnSubTask skipped at depth limit (incl. via shared_tools); resolves COMPLETE default_next on skip | piece-runner.ts (`pieceUsesSpawn`, spawn skip) | piece-runner.test.ts "resolves a COMPLETE default_next when a SpawnSubTask movement is skipped at the depth limit"; "skips a SpawnSubTask movement at the depth limit even when…shared_tools" | FULL | — |
ENG-037 | Engine core | WAITING_HUMAN_BROWSER / _TOOL_REQUEST pauses | Non-ASK human-wait sentinels pause the run | piece-runner.ts (L1159, L1176) | (none found) | NONE | No test exercises the WAITING_HUMAN_BROWSER or WAITING_HUMAN_TOOL_REQUEST branches |
ENG-038 | Engine core | Review-feedback carry-forward | verify movement output carried into later execute/process/plan/analyze; truncated per/combined caps; git status+diff appended after verify | piece-runner.ts (`buildInstructionWithFeedback`, `shouldCarryReviewFeedback`, truncate/trim) | piece-runner.test.ts "carries cumulative verify feedback into later execute/analyze movements"; "appends safe git status and diff context after verify loops" | FULL | — |
ENG-039 | Engine core | Lessons accumulation & injection | transition/complete lessons accumulated, capped (MAX_LESSONS_LENGTH 2000), injected into next movement; written to lessons.jsonl | piece-runner.ts (`buildLessonsContext`, `writeLessonLog`, lessonsAccumulator) | (no direct test of buildLessonsContext/writeLessonLog; snapshot tests reference lessons) | PARTIAL | Lessons capture appears in cancel-snapshot tests but the injection-into-next-movement and 2000-char trim logic is not directly asserted; writeLessonLog untested |
ENG-040 | Engine core | loadPiece terminal-rule validation | rejects rules[].next ∈ {COMPLETE,ABORT,ASK}; accepts default_next COMPLETE & WAIT_SUBTASKS sentinel; all bundled pieces valid | piece-runner.ts (`validatePieceDef`, `loadPiece`) | piece-runner.test.ts (Phase 6b: 6 tests incl. all bundled pieces load) | FULL | — |
ENG-041 | Engine core | loadPiece multi-dir resolution | custom dir(s) win over builtin; first dir wins on dup name; string|string[] forms | piece-runner.ts (`loadPiece`) | piece-runner.test.ts "resolves from a list of custom dirs"; "first dir wins"; "string form still works" | FULL | — |
ENG-042 | Engine core | normalizeRequiredMcp | retains valid slugs, drops invalid, undefined when absent, coerces non-array to [] | piece-runner.ts | piece-runner.test.ts (4 required_mcp tests) | FULL | — |
ENG-043 | Engine core | normalizeSharedTools / mergeToolNames | shared_tools sanitized; mergeToolNames unions shared+movement first-seen order, drops dups/non-strings | piece-runner.ts | piece-runner.test.ts (shared_tools 4 + mergeToolNames 3) | FULL | — |
ENG-044 | Engine core | validateAllowedSshConnections | validates id format / wildcard / empty-deny; tolerates missing allowlist (A4 workspace-policy gating); reports offenders | piece-runner.ts | piece-runner.test.ts (allowed_ssh_connections 15 + loader-tolerance 1) | FULL | — |
ENG-045 | Engine core | Workspace tool-policy resolution & injection | resolveWorkspaceTools (safe default, Bash/browser opt-in); runPiece injects workspaceTools overriding movement.allowed_tools; grantedTools unioned | piece-runner.ts (runPiece) + workspace-tool-policy.ts | piece-runner.test.ts (resolveWorkspaceTools 5 + runPiece injection 4); workspace-tool-policy.test.ts | FULL | — |
ENG-046 | Engine core | runPiece max_movements default | iterates when max_movements missing / 0 / negative | piece-runner.ts (runPiece) | piece-runner.test.ts "still iterates when piece.max_movements is missing"; "…is 0 or negative" | FULL | — |
ENG-047 | Engine core | buildFollowupNotice | detects follow-up when output/ or subtasks/ has non-hidden files; ignores dotfiles; empty for fresh ws | piece-runner.ts | piece-runner.test.ts (5 buildFollowupNotice tests) | FULL | — |
ENG-048 | Engine core | Cancel/terminal memory snapshots | snapshot+meta-event on cancelled (pre/mid movement) and aborted; NOT on success or waiting_subtasks; v2 captures finalOutput/history/lessons w/ truncation | piece-runner.ts | piece-runner.test.ts (7 snapshot tests) | FULL | — |
ENG-049 | Engine core | Conversation continuity end-to-end | movement 2 sees movement 1 history; transcript.jsonl written; continuation job replays prior turns & suppresses LIMIT-1 handoff | piece-runner.ts + conversation.ts | piece-runner.conversation.test.ts (2) | FULL | — |
ENG-050 | Engine core | Piece classification prompt | buildClassificationPrompt includes task+descriptions+files; biases toward chat; routes workspace-app | piece-classifier.ts | piece-classifier.test.ts (buildClassificationPrompt 4) | FULL | — |
ENG-051 | Engine core | Classification response parsing | parseClassificationResponse extracts valid name, handles noise, prefers longest match, null on invalid/empty | piece-classifier.ts | piece-classifier.test.ts (parseClassificationResponse 5) | FULL | — |
ENG-052 | Engine core | classifyPiece (async LLM orchestration) | Builds prompt, calls LLM, parses + falls back to default piece | piece-classifier.ts (`classifyPiece`) | (none) | NONE | The end-to-end classify path (LLM call wiring, fallback when parse returns null, error handling) is untested; only its two pure helpers are |
ENG-053 | Engine core | loadAllPieceTriggers | Loads triggers/keywords from all pieces across dirs; custom wins on dup name; skips invalid pieces | piece-runner.ts (`loadAllPieceTriggers`) | (none) | NONE | No test; dir-precedence and skip-on-load-error branches uncovered |
ENG-054 | Engine core | buildChecklistContext | Builds checklist context block from logs root | piece-runner.ts | (referenced in piece-runner.test.ts import only; no describe block) | PARTIAL | Symbol imported in test file but no behavior assertion targets it specifically |
ENG-055 | Engine core | buildLocalConversationContext | Renders current instruction + recent-conversation + workspace files; interjection precedence; truncation; omitRecentConversation flag | local-context.ts | local-context.test.ts (7) | FULL | — |
ENG-056 | Engine core | PieceCatalog caching/layering | Built-ins + user pieces (custom wins); TTL cache; invalidate(userId); per-folder cache isolation | piece-catalog.ts | piece-catalog.test.ts (7) | FULL | — |
ENG-057 | Engine core | stripThinkingTokens | Strips <think>/<|thinking|>/gemma channel blocks; leaves unclosed intact; preserves unicode | strip-thinking.ts | strip-thinking.test.ts (11) + agent-loop.test.ts (6) | FULL | — |
ENG-058 | Engine core | prompt-coach scoring | buildCoachMessages (source inclusion + truncation); normalizeCoachResult (clamp/defaults); runPromptCoach (normalize + error propagation) | prompt-coach.ts | prompt-coach.test.ts (9) | FULL | — |
ENG-059 | Engine core | Console screen injection | Injects SSH console screen each iteration when SshConsole* allowed + session exists; guards on missing taskId / no session / non-console piece; tail truncation | agent-loop.ts (buildSystemPrompt) | agent-loop.test.ts (5 console) + agent-loop-console.test.ts (4) | FULL | — |
ENG-060 | Engine core | Handoff block construction | Static Continue block always; dynamic block only with handoffContext; null prevResult handled; long prevResult truncated head+tail | agent-loop.ts (buildSystemPrompt) | agent-loop.test.ts (5 handoff tests) | FULL | — |
ENG-061 | Engine core | Per-user AGENTS.md / memory / working-dir injection | Injects AGENTS.md + persistence protocol + Working Directory + approach/error-recovery sections, gated on userId/workspacePath | agent-loop.ts (buildSystemPrompt) | agent-loop.user-agents.test.ts (9) | FULL | — |
ENG-062 | Engine core | Existing-workspace-files injection | Injects existing input-files section when present, omits when empty | agent-loop.ts (buildSystemPrompt) | agent-loop.test.ts (2) | FULL | — |
ENG-063 | Engine core | Traceability event emission | movement_start/tool_call/result/movement_complete; cache_set/hit/invalidate; watchdog_fire; run_start/complete; followup_detected; shared runId | agent-loop.ts + piece-runner.ts | agent-loop.test.ts (T-1, 5) + piece-runner.test.ts (T-2, 2) | FULL | — |
ENG-064 | Engine core | ownerId/userId defaulting | Defaults to 'local'/synthetic when unowned; preserves real owner in auth mode | piece-runner.ts (runPiece) | piece-runner.test.ts "defaults ownerId and userId to…"; "preserves a real ownerId/userId" | FULL | — |
ENG-065 | Engine core | Mission char-budget trimming | buildGlobalPreamble trims mission text when total exceeds MISSION_TOTAL_CHAR_BUDGET | agent-loop.ts (L428 overflow trim) | (none) | NONE | The mission/preamble char-budget overflow trim is not asserted by any test |
TOOL-001 | Tools | Read | Read file slice (offset/limit); delegates binary detection; rejects directories (EISDIR), enforces readonly/ | | core.test.ts (88 it: binary detector, readonly/ enforce, symlink escape) | FULL | EISDIR/dir-reject path (MEMORY: prior crash) not explicitly named in describes — verify a dir-reject test exists |
TOOL-002 | Tools | Write | Write to workspace; assertWritable blocks readonly/ + outside-workspace | | core.test.ts (readonly/ enforcement, resolveAndGuard symlink escape) | FULL | — |
TOOL-003 | Tools | Edit | Exact-string replace; same write-guard path as Write | | core.test.ts | PARTIAL | Edit-specific match-fail / replace_all behavior not clearly enumerated in describes |
TOOL-004 | Tools | Bash | Shell exec; AbortSignal propagation, path-scope guard, install-pattern blocking, unrestricted mode, env scrub, history log | | core.test.ts (AbortSignal, checkBashPathScope, install rejection, bashUnrestricted, env scrub, history) | FULL | Strong coverage incl. cancel-traceability |
TOOL-005 | Tools | Glob | Pattern file match | | core.test.ts (core tools) | PARTIAL | Few dedicated Glob assertions; edge cases (no-match, ignore) unclear |
TOOL-006 | Tools | Grep | Content search | | core.test.ts | PARTIAL | Same — limited Grep-specific cases |
TOOL-007 | Tools | WebSearch | SearXNG search w/ fallback + history; query sanitize | | web.test.ts (sanitizeQuery, searchViaSearxng, fallback history, parseSearchResultsFromText) | FULL | — |
TOOL-008 | Tools | WebFetch | Fetch URL → readability text; SSRF guard; persistent context mgmt | | web.test.ts (web tools, persistent context), shared/ssrf.test.ts, shared/readability.test.ts, web.binary.test.ts | FULL | SSRF tested at shared layer; binary handling covered |
TOOL-009 | Tools | DownloadFile | Download to input/ or source/; source-library index.jsonl; raw log-only | | web.test.ts (source-library routing 2 it), raw-save | PARTIAL | Size-limit / large-file / redirect handling on download not asserted |
TOOL-010 | Tools | ReadImage | Read image → vision; usage tracking | | image.test.ts (13 it), image.vision-usage.test.ts | FULL | — |
TOOL-011 | Tools | AnnotateImage | Draw annotations on image | | image.test.ts | PARTIAL | Annotation rendering correctness lightly covered |
TOOL-012 | Tools | ReadExcel | Parse xlsx/xls/xlsm/xlsb; size limit 10MB; format-mismatch + CFB/HTML/CSV disguise rejection; optional styles | | office.test.ts (format rejection 8 it, styles), excel-styles.test.ts | PARTIAL | **Size-limit (>10MB) enforcement path not tested** — only format mismatch |
TOOL-013 | Tools | ReadDocx | Parse docx/docm; size limit 10MB | | office.test.ts | PARTIAL | Size-limit path untested; happy-path light |
TOOL-014 | Tools | ReadPdf | Extract PDF text; query/grep mode (regex, case-insensitive, no-match, empty-query); size limit 10MB; OOXML-as-pdf rejection | | office.test.ts (ReadPdf query mode 5 it, format rejection) | PARTIAL | Size-limit path untested |
TOOL-015 | Tools | ReadPPTX | Parse pptx/pptm; slide order resolution; notes; **ZIP-bomb detection (uncompressed >200MB)**; size limit 50MB | | office.test.ts | NONE | **ZIP-bomb detection AND PPTX size-limit have NO test** — highest-risk office gap |
TOOL-016 | Tools | ReadMsg | Parse Outlook .msg | | msg.test.ts (41 it) | FULL | — |
TOOL-017 | Tools | SplitExcelSheets | Split workbook into per-sheet files | | office.test.ts | PARTIAL | Split correctness lightly covered |
TOOL-018 | Tools | SplitDocxSections | Split docx into section files | | office.test.ts | PARTIAL | Same |
TOOL-019 | Tools | PdfToImages | Render PDF pages to images; edit-not-allowed error, missing-file error, invalid page_range error | | office.test.ts (3 error-path it) | FULL | Error paths covered |
TOOL-020 | Tools | SQLite | Run SQL on workspace .db; SELECT happy path, write guards (editAllowed), always-blocked DDL, PRAGMA, errors, path-traversal guard, large result sets | | data.test.ts (25 it across 9 describe) | FULL | Strong — guards + traversal + DDL block |
TOOL-021 | Tools | BatchReviewTextWithLLM | Per-file isolated LLM review; output-path prefix enforcement | | review.test.ts (3 it) | PARTIAL | Only 3 it; LLM-failure / partial-file handling not covered |
TOOL-022 | Tools | MergeReviewedResults | Merge reviewed JSON → markdown; rejects outputs outside prefixes | | review.test.ts | PARTIAL | Merge edge cases (malformed JSON) untested |
TOOL-023 | Tools | BrowseWeb | Playwright browse + actions mode; URL validation, SSRF/localhost block, file-URL workspace containment, traversal reject, auth-expiry, download filename sanitize, unique-path, recording, source persist | | browser.test.ts (36 it), browser.runpageactions.test.ts, browser-frame-chain.e2e.test.ts | FULL | Validation/SSRF/recording very strong; live page-action correctness is e2e-gated (env-dependent) |
TOOL-024 | Tools | SpawnSubTask | Spawn parallel subtask job | | orchestration.test.ts (8 it) | FULL | — |
TOOL-025 | Tools | XSearch | X/Twitter search | | x.test.ts (18 it) | PARTIAL | Live transaction-id breakage (MEMORY) means runtime failures not covered by unit tests |
TOOL-026 | Tools | XUserPosts | X user timeline posts | | x.test.ts | PARTIAL | Same upstream breakage caveat |
TOOL-027 | Tools | XPostDetail | Single X post detail | | x.test.ts | PARTIAL | Same |
TOOL-028 | Tools | XFetchCardMedia | Fetch X card media | | x.test.ts | PARTIAL | Likely thin coverage |
TOOL-029 | Tools | XTimeline | X home/timeline | | x.test.ts | PARTIAL | Likely thin coverage |
TOOL-030 | Tools | SearchPlaces | Maps place search | | maps.test.ts (27 it) | FULL | — |
TOOL-031 | Tools | GetDirections | Maps routing | | maps.test.ts | FULL | — |
TOOL-032 | Tools | ReverseGeocode | Coords → address | | maps.test.ts | FULL | — |
TOOL-033 | Tools | GetYouTubeTranscript | Fetch YouTube transcript | | youtube.test.ts (20 it) | FULL | — |
TOOL-034 | Tools | SearchYouTube | YouTube search | | youtube.test.ts | FULL | — |
TOOL-035 | Tools | SearchAmazon | Amazon product search | | amazon.test.ts (13 it) | FULL | — |
TOOL-036 | Tools | TranscribeAudio | Speech-to-text | | speech.test.ts (14 it) | FULL | — |
TOOL-037 | Tools | CreateChecklist | Create task checklist (META) | | checklist.test.ts (24 it incl. META_TOOL describe) | FULL | — |
TOOL-038 | Tools | CheckItem | Mark checklist item (META) | | checklist.test.ts | FULL | — |
TOOL-039 | Tools | GetChecklist | Read checklist (META) | | checklist.test.ts | FULL | — |
TOOL-040 | Tools | ListPieces | List available pieces | | pieces.test.ts (11 it) | FULL | — |
TOOL-041 | Tools | GetPiece | Read a piece YAML | | pieces.test.ts | FULL | — |
TOOL-042 | Tools | CreatePiece | Create custom piece (per-user fork) | | pieces.test.ts | PARTIAL | Validation/lint reject paths (COMPLETE/ABORT in next) coverage unclear here |
TOOL-043 | Tools | UpdatePiece | Update custom piece | | pieces.test.ts | PARTIAL | Same |
TOOL-044 | Tools | InstallSkill | Install skill from registry (META) | | skills.test.ts (26 it) | FULL | — |
TOOL-045 | Tools | ReadSkill | Read skill content (META, always available) | | skills.test.ts | FULL | — |
TOOL-046 | Tools | ListSkills | List skills (META) | | skills.test.ts | FULL | — |
TOOL-047 | Tools | ReadToolDoc | Read tool doc incl. MCP tools via cache (META, always available); name-required error, bad-MCP-name error | | docs.test.ts (10 it) | FULL | — |
XCUT-001 | Tools | Dynamic tool loading + dispatch order | index.ts tryLoadModule lazy-imports each module; executeToolInner dispatches in fixed order (mcp → web → image → data → office → review → x → orchestration → browser → maps → … → skills); failed loads silently skipped | | index.test.ts (1 it: SshConsole catalog) | NONE | **Dispatch order, first-match-wins, and silent-skip-on-load-failure are essentially untested** (single catalog test) |
XCUT-002 | Tools | META_TOOLS always-injected at dispatch | index.ts keeps a LARGER META_TOOLS list (incl. RequestTool, Mission, UserFolder, AppDocs, Skills) injected into getToolDefs regardless of movement allowed_tools | | (indirect via tool-categories.test.ts subset) | PARTIAL | index.ts's runtime META injection (20 names) not directly asserted; only tool-categories' SUBSET set is tested |
XCUT-003 | Tools | tool-categories: name→category map | first-write-wins map built from ALL_TOOL_DEFS('core') + MODULE_SPECS; memoised; listToolCategories; SENSITIVE_CATEGORIES={ssh,browser}; SENSITIVE_TOOLS={Bash}; META_TOOLS subset | | tool-categories.test.ts (12 it: META_TOOLS entries, sensitive sets, map) | FULL | — |
XCUT-004 | Tools | RequestTool flow | META tool; classifies request as already_available / available_not_allowed(requested) / unknown(hard-error); name+reason required; interactive→pending approval, non-interactive→auto_denied; records via ctx.recordToolRequest | | tool-request.test.ts (9 it), db/repository.tool-requests.test.ts | FULL | All 3 classification branches + interactive/auto-deny covered |
XCUT-005 | Tools | workspace-tool-policy resolution | resolveWorkspaceTools: safe categories default-on (minus disabledSafe), sensitive categories+tools opt-in only (enabledSensitive), META always unioned, Bash special-cased, mcp__* always carried; parseToolPolicy tolerant JSON | | workspace-tool-policy.test.ts, workspace-tool-policy.regression.test.ts, db/repository.tool-policy.test.ts, bridge/space-api.tool-policy.test.ts | FULL | Strong; regression test guards Bash toggle (MEMORY PR #653) |
XCUT-006 | Tools | raw-save result logging | saveRawData writes {logsRoot}/raw/<tool>-<ts>.txt + rawdata-history.jsonl for RAW_SAVE_TOOLS (web/x/youtube/amazon/speech/ms-learn); logRawDownload records DownloadFile path-only; errors swallowed to logger.warn | | raw-save.test.ts (5 it) | PARTIAL | Helper, not a tool. Per-tool wiring (that each RAW_SAVE_TOOL actually triggers save) is not asserted end-to-end |
XCUT-007 | Tools | structured-blocks | Rich-UI structured-data save helper (no TOOL_DEFS) | | structured-blocks.test.ts (4 it) | PARTIAL | Helper; basic coverage |
XCUT-008 | Tools | binary-detect shared helper | Magic-byte/binary detection used by Read + WebFetch | | binary-detect.test.ts (26 it) | FULL | Well covered; underpins Read/WebFetch binary handling |
XCUT-009 | Tools | conflict-guard (persistent workspace) | Detects concurrent-edit conflicts in shared space | | conflict-guard.test.ts (9 it), core.test.ts (conflict detection) | FULL | — |
PIECE-001 | Pieces | chat | | piece-classifier.test (as default-bias target); reflection/silent-fork & applier.fuzz use 'chat'; piece-runner all-load sweep | PARTIAL | No test asserts the single-movement respond contract or its 43-tool allowlist; only used as a name fixture + load-sweep | |
PIECE-002 | Pieces | general | | piece-runner.test (loadPiece('general') line 401, used in review-prompt/shared_tools tests); pieces-api uses 'general' fixture; load sweep | PARTIAL | Loaded & used as a fixture for unrelated assertions (review prompts, SpawnSubTask skip), but its 4-movement decompose/verify loop is not asserted as a contract | |
PIECE-003 | Pieces | research | | piece-runner.test loadPiece('research') (line 403, review-prompt structure assertion); load sweep | PARTIAL | Review-prompt/plan-aware structure asserted for it, but the dig/analyze/verify transition logic is not exercised | |
PIECE-004 | Pieces | research-sub | | load sweep only | PARTIAL | No direct test; only schema-load smoke. Spawn-from-parent linkage untested | |
PIECE-005 | Pieces | data-process | | piece-classifier.test (longer-match-wins: data-process over general); load sweep | PARTIAL | Classifier disambiguation asserted; movement contract not | |
PIECE-006 | Pieces | office-process | | piece-runner.test loadPiece('office-process') (line 402); piece-classifier fixture; load sweep | PARTIAL | Loaded for review-prompt assertion; 2-movement contract not exercised | |
PIECE-007 | Pieces | slide | | piece-classifier.test (chat vs slide routing fixture); load sweep | PARTIAL | Used as classifier fixture name; movement/tool contract untested | |
PIECE-008 | Pieces | brainstorming | | load sweep only | PARTIAL | No direct test; schema-load smoke only | |
PIECE-009 | Pieces | sns-research | | load sweep only | PARTIAL | No direct test | |
PIECE-010 | Pieces | x-ai-digest | | load sweep only | PARTIAL | No direct test | |
PIECE-011 | Pieces | piece-builder | | piece-classifier.test (workspace-app routed AWAY from piece-builder); load sweep | PARTIAL | Only the negative-routing case touches it; its own contract untested | |
PIECE-012 | Pieces | workspace-app | | piece-runner.test dedicated movement-structure assertions (build→verify, default_next checks, lines 542-557); piece-classifier routing test (line 41-52); workspace-app E2E harness (separate subsystem) | FULL | Movement structure + default_next + classifier routing all asserted; strongest-covered piece | |
PIECE-013 | Pieces | ssh-console | | piece-runner.test dedicated assertion (interact default_next: COMPLETE, line 563-572); tools/ssh-console.test; ssh allowed_ssh_connections validation tests | FULL | Movement + ssh-connection allowlist contract asserted | |
PIECE-014 | Pieces | ssh-ops | | piece-runner.test dedicated assertion (line 576+); ssh allowlist validation tests; load sweep | FULL | Loaded + structurally asserted (execute/verify); ssh allowlist format validation covered. Lighter than ssh-console but real assertions exist | |
PIECE-015 | Pieces | help | | load sweep only | PARTIAL | No direct test | |
PINFRA-001 | Pieces | loadPiece (custom-dir priority + builtin fallback) | Resolves piece by name across custom dirs (string|string[]), custom wins, fallback to builtin | | piece-runner.test "loadPiece multi-dir" (string|string[], per-user wins, first-dir wins, backward-compat); worker.test loadPiece('local-piece') | FULL | Multi-dir resolution well covered |
PINFRA-002 | Pieces | validatePieceDef (Phase 6b terminal-next rejection) | Throws if any rules[].next ∈ {COMPLETE,ABORT,ASK}; default_next exempt | | piece-runner.test "loadPiece terminal-rule validation (Phase 6b)": rejects COMPLETE/ABORT/ASK in rules[].next, accepts default_next: COMPLETE, accepts movement-to-movement + WAIT_SUBTASKS | FULL | Core invariant directly asserted with error-message matching |
PINFRA-003 | Pieces | default_next sentinel (engine-internal fallback) | Used for context-overflow / ASK-limit / SpawnSubTask-skip; allows COMPLETE/ABORT/ASK | | piece-runner.test "falls back to default_next when ASK limit is reached", "resolves a COMPLETE default_next reached via ASK-limit fallback", SpawnSubTask-skip→default_next tests | FULL | All three sentinel paths exercised |
PINFRA-004 | Pieces | lint-pieces.mjs (CI lint, terminal-next ban) | Standalone script: hard-fails any piece with COMPLETE/ABORT/ASK in rules[].next | | **none** (no *.test for the script; runtime equivalent in validatePieceDef IS tested) | NONE | Script itself has no test; logic duplicated/covered by PINFRA-002 but the CLI (file collection, exit codes, parse-error path) is untested |
PINFRA-005 | Pieces | CreatePiece tool validation | Rejects missing/0 max_movements, non-array shared_tools, bad rules[].next; writes to dirs[0] (per-user) | | tools/pieces.test "CreatePiece" block (missing max_movements, max_movements:0, valid write, shared_tools array/non-array) | FULL | Validation + write-target asserted |
PINFRA-006 | Pieces | UpdatePiece tool (built-in protection) | Refuses overwriting built-in (isBuiltinOnly); allows update when custom override exists | | tools/pieces.test "UpdatePiece" block (refuses built-in overwrite regression, allows custom override) | FULL | Built-in immutability regression covered |
PINFRA-007 | Pieces | rules[].next / default_next domain validation (tool path) | validRuleNexts = movements ∪ WAIT_SUBTASKS; validDefaultNexts adds COMPLETE/ABORT/ASK | | Covered via CreatePiece tests + pieces-api "rejects rules[].next: COMPLETE" / "accepts default_next: COMPLETE" | FULL | Both accept & reject branches asserted at API + tool level |
PINFRA-008 | Pieces | ListPieces / GetPiece (custom+builtin merge, priority) | Lists from per-user + global-custom + builtin; GetPiece prefers dirs[0] | | tools/pieces.test "customPiecesDir as array — P1-a regression" (ListPieces both dirs, GetPiece second-dir, GetPiece prefers per-user) | FULL | Multi-dir merge + priority covered |
PINFRA-009 | Pieces | piece classifier — buildClassificationPrompt | Builds LLM prompt from task + all piece descriptions + keyword hints; biases to chat; routes workspace-app | | piece-classifier.test "buildClassificationPrompt" (includes text/descriptions, file names, chat bias, workspace-app routing) | FULL | Prompt construction + routing bias asserted |
PINFRA-010 | Pieces | piece classifier — parseClassificationResponse | Extracts valid piece name from noisy LLM output; longer-match-wins; null on invalid/empty | | piece-classifier.test "parseClassificationResponse" (valid, noisy, longer-match data-process>general, invalid→null, empty→null) | FULL | Parse edge cases covered |
PINFRA-011 | Pieces | piece catalog (loadAllPieceTriggers / catalog merge) | Enumerates all pieces' triggers; custom overrides builtin by name | | piece-catalog.test (builtin+custom merge, custom override wins, triggers exposed) | FULL | Catalog merge & override asserted |
PINFRA-012 | Pieces | pieces-api CRUD + per-user/builtin authz | GET/PUT/POST/DELETE with per-user custom dirs, builtin immutability, cross-user authz, ?source= | | pieces-api.test — extensive: no-auth legacy + auth-aware (built-in 403, own-custom 200, cross-user 403, admin edit-but-not-delete builtin, duplicate-name reject, source resolution) | FULL | One of the most thoroughly tested infra areas |
PINFRA-013 | Pieces | SSH allowlist validation (allowed_ssh_connections) | Validates UUID/wildcard/empty allowlist; rejects SshExec without allowlist & bad formats | | piece-runner.test (id-format reject, wildcard, multi-movement offenders) + pieces-api.test (SshExec w/o allowlist, UUID, wildcard, empty, bad format, non-array) | FULL | Both engine + API layers covered |
PINFRA-014 | Pieces | shared_tools / required_mcp normalization | Drops invalid entries, warns on SSH shared_tools w/o connections | | piece-runner.test imports normalizeRequiredMcp/normalizeSharedTools/mergeToolNames; tools/pieces + pieces-api shared_tools array/non-array tests | FULL | Normalization + merge covered |
PINFRA-015 | Pieces | custom/default piece namespace separation (reflection silent fork) | Reflection forks built-in into data/users/{u}/pieces with forked_from_commit instead of mutating builtin | | reflection/silent-fork.test (silentFork chat/data-process into per-user dir) | FULL | Silent-fork path covered (reflection subsystem) |
PINFRA-016 | Pieces | all-built-in-pieces load sweep | Loops every pieces/*.yaml asserting loadPiece().not.toThrow() | | piece-runner.test (~line 531-537) | FULL | This is what gives every piece PARTIAL; it validates schema but NOT each piece's movement/rule semantics |
PINFRA-017 | Pieces | per-user piece resolution in worker/scheduler/continue paths | customPiecesDir threaded through worker, continue-job, classifier, pieces-api | | worker.test loadPiece('local-piece', piecesDir, customPieceDirs); partly via pieces-api auth tests | PARTIAL | Worker path has one test; full propagation across scheduler/continue paths (per the known "no-auth local fallback" checklist) not comprehensively asserted |
REFL-001 | Reflection | Enqueue: enabled gate (default OFF) | maybeEnqueueReflection returns early when cfg.enabled=false | src/worker.ts (363) | worker.reflection-enqueue.test.ts ("does nothing when enabled=false") | FULL | DEFAULT_REFLECTION default value (enabled:false) itself not asserted in a config test |
REFL-002 | Reflection | Enqueue: no-recursion on reflection jobs | returns early when job.taskKind==='reflection' | src/worker.ts (371) | worker.reflection-enqueue.test.ts ("does NOT recurse") | FULL | — |
REFL-003 | Reflection | Enqueue: no-auth owner fallback to 'local' | ownerId ?? 'local' for namespace + budget key | src/worker.ts (377) | enqueue.test.ts (2 cases: fallback + budget keyed to 'local') | FULL | — |
REFL-004 | Reflection | Enqueue: worker_required gate | skip when workerRequired and no worker has role 'reflection' | src/worker.ts (379-388) | enqueue.test.ts (3 cases: true+none/true+exists/false) | FULL | — |
REFL-005 | Reflection | Enqueue: per-user daily token budget cap | skip when spent >= cap; cap=0 = unlimited; UTC-day window | src/worker.ts (392-405) | enqueue.test.ts (at/under cap, cap=0, UTC 25h, independent users) | FULL | — |
REFL-006 | Reflection | Enqueue: space_id propagation (Space-as-Principal) | carries job.spaceId into payload (null for personal) | src/worker.ts (407-420) | enqueue.test.ts (carries space_id / null personal) | FULL | — |
REFL-007 | Reflection | Dispatch: reflection job routes to runReflectionJob | worker picks task_kind='reflection', imports + runs runner, role-keyed routing | src/worker.ts (1129-1130, 2044-2073) | worker.reflection-dispatch.test.ts (2 cases) | PARTIAL | dispatch test is small (~2 cases); gateway routing-key + 401-without-key path not asserted end-to-end |
REFL-008 | Reflection | Runner: loadReflectionInputs (memory/activity/comments/feedback + observedRevisions) | assembles inputs, builds bodyRevision CAS map | load-inputs.ts | load-inputs.test.ts (~8 cases, CAS) | FULL | — |
REFL-009 | Reflection | Runner: forced submit_reflection tool_call (LLM) | callReflectionLlm forces the tool, parses result, tokens | llm-client.ts | llm-client.test.ts (~10 cases) | FULL | — |
REFL-010 | Reflection | Runner: prompt construction | buildSystemPrompt / buildUserPrompt | reflection-prompt.ts | reflection-prompt.test.ts (~18 cases) | FULL | — |
REFL-011 | Reflection | Runner: schema validation of LLM output | reflection-schema parse of memory_changes/piece_changes | reflection-schema.ts | reflection-schema.test.ts (~10 cases, all ops) | FULL | — |
REFL-012 | Reflection | Runner: end-to-end happy path + outcome + metric row | runReflectionJob applies, snapshots, records metric | reflection-runner.ts | reflection-runner.test.ts (~12 cases: applied/abstained/failed, snapshot, lock, space, budget) | FULL | — |
REFL-013 | Reflection | Runner: failure fallbacks record outcome='failed' | loadInputs/LLM/apply throw → metric 'failed', return 'failed' | reflection-runner.ts | reflection-runner.test.ts (failed) | PARTIAL | only some of the 3 throw-sites (loadInputs/LLM/apply+snapshot) explicitly covered; writeSnapshot-throws-but-continue branch not clearly asserted |
REFL-014 | Reflection | Runner: Space-as-Principal storage resolution | space job → data/spaces/{id}; personal → data/users/{userId}; attribution stays per-user | reflection-runner.ts | reflection-runner.test.ts (space-principal), revisions.test.ts | FULL | — |
REFL-015 | Reflection | Validator: rejected_unknown_type | type not in {user,feedback,project,reference} | semantic-validator.ts | semantic-validator.test.ts; applier.fuzz.test.ts | FULL | — |
REFL-016 | Reflection | Validator: rejected_bad_name | name fails isValidMemoryName | semantic-validator.ts | semantic-validator.test.ts; applier.test.ts; fuzz | FULL | — |
REFL-017 | Reflection | Validator: rejected_body_too_large | body > maxBodyBytes (UTF-8) | semantic-validator.ts | semantic-validator.test.ts; fuzz | FULL | — |
REFL-018 | Reflection | Validator: rejected_injected_directive | prompt-injection heuristics in body/description | semantic-validator.ts | semantic-validator.test.ts | FULL | only validator test hits it; not exercised through applier (low risk, static check) |
REFL-019 | Reflection | Validator: rejected_missing_target | non-add op missing merge_target OR target not in index | semantic-validator.ts | semantic-validator.test.ts; applier.test.ts; fuzz | FULL | — |
REFL-020 | Reflection | Validator: rejected_name_collision | add with already-existing name | semantic-validator.ts | semantic-validator.test.ts; fuzz | FULL | — |
REFL-021 | Reflection | Validator: rejected_target_piece_mismatch | piece_changes.target_piece ≠ input.pieceName | semantic-validator.ts | semantic-validator.test.ts; applier.test.ts; fuzz | FULL | — |
REFL-022 | Reflection | Validator: rejected_invalid_yaml | piece new_yaml fails YAML parse | semantic-validator.ts | semantic-validator.test.ts; fuzz | FULL | — |
REFL-023 | Reflection | Validator: rejected_invalid_piece | new_yaml parses but fails piece-lint | semantic-validator.ts | semantic-validator.test.ts; fuzz | FULL | — |
REFL-024 | Reflection | Validator: rejected_dangerous_piece | COMPLETE/ABORT/ASK in rules[].next | semantic-validator.ts | semantic-validator.test.ts; fuzz | FULL | — |
REFL-025 | Reflection | Apply CAS: rejected_stale_target | merge_target body revision != snapshot → reject at write time | applier.ts | applier.test.ts; fuzz | FULL | raised by applier (not static validator); covered |
REFL-026 | Reflection | Apply op: add | upsertMemoryEntry for new name | applier.ts (applyOne) | applier.test.ts; schema; runner | FULL | — |
REFL-027 | Reflection | Apply op: update (+CAS) | upsert replacing body/desc/type, requires CAS pass | applier.ts | applier.test.ts (ops + CAS) | FULL | — |
REFL-028 | Reflection | Apply op: merge_into (+CAS) | read existing, append timestamped section, upsert merged | applier.ts | applier.test.ts | FULL | — |
REFL-029 | Reflection | Apply op: remove (+CAS) | removeMemoryEntry (trash + index), requires CAS | applier.ts | applier.test.ts | FULL | — |
REFL-030 | Reflection | Apply: missing-target-on-disk at write time | non-add op, target gone from disk → rejected_missing_target | applier.ts | applier.test.ts (rejected_missing_target) | FULL | — |
REFL-031 | Reflection | Apply: 3-change hard cap | memory_changes truncated to 3 with WARN | applier.ts | applier.test.ts / fuzz | PARTIAL | cap value 3 truncation likely covered by fuzz inputs but a dedicated ">3 dropped with WARN" assertion not confirmed |
REFL-032 | Reflection | Apply: applyOne write error → code='failed' on decision | per-change try/catch records failed | applier.ts | — | PARTIAL | the per-change applyOne throw path (decision.code='failed') not obviously asserted (distinct from runner-level 'failed') |
REFL-033 | Reflection | Outcome decision: abstained | abstain_reason + no applied + no rejected | applier.ts (decideOutcome) | applier.test.ts (abstained); runner | FULL | — |
REFL-034 | Reflection | Outcome decision: partial | any applied AND any rejected/dropped | applier.ts | applier.test.ts (partial) | FULL | — |
REFL-035 | Reflection | Outcome decision: applied | any applied, none rejected | applier.ts | applier.test.ts (applied); runner | FULL | — |
REFL-036 | Reflection | Outcome decision: rejected | any rejected/dropped, none applied | applier.ts | applier.test.ts (rejected); fuzz | FULL | — |
REFL-037 | Reflection | Outcome decision: fallback abstained | empty input (0 changes, no piece) | applier.ts | applier.test.ts | PARTIAL | the empty-input fallback branch (vs explicit abstain_reason) not clearly distinguished in tests |
REFL-038 | Reflection | Piece write: cooldown gate (2 edits/24h) | >=2 edits in window → {written:false, reason:'cooldown'} → pieceCooldownDropped | piece-writer.ts; applier.ts | piece-writer.test.ts (cooldown); applier.test.ts (cooldown) | FULL | — |
REFL-039 | Reflection | Piece write: cooldown drop vs semantic reject distinction | pieceCooldownDropped separate from pieceRejectCode in outcome | applier.ts | applier.test.ts; piece-writer.test.ts | FULL | — |
REFL-040 | Reflection | Piece write: atomic temp+rename + catalog invalidate + recordPieceEdit | writePiece tmp.{pid} → rename, repo.recordPieceEdit, catalog.invalidate | piece-writer.ts | piece-writer.test.ts (~8 cases) | FULL | atomic-rename crash-safety (partial-write) not stress-tested |
REFL-041 | Reflection | Silent fork of built-in piece | builtin source → copy to data/users/{userId}/pieces with forked_from_commit/forked_at; idempotent; throws if source missing | silent-fork.ts | silent-fork.test.ts (~9 cases) | FULL | — |
REFL-042 | Reflection | Drift detection (forked_from_commit vs latest builtin commit) | detectDrift returns drifted flag; graceful no-op without git | drift-detect.ts | drift-detect.test.ts (~7 cases) | FULL | — |
REFL-043 | Reflection | Snapshot write (.reflection-history/{ts}-{jobId} + index.jsonl append) | writeSnapshot persists before/after files, appends index.jsonl atomically under lock | snapshot.ts; reflection-runner.ts | snapshot.test.ts (~34 cases) | FULL | — |
REFL-044 | Reflection | Snapshot: before/after memory file capture | runner builds beforeFiles/afterFiles from accepted decisions (remove uses merge_target) | reflection-runner.ts | reflection-runner.test.ts; snapshot.test.ts | PARTIAL | the remove-op before-file naming branch (merge_target vs name) in runner not specifically asserted |
REFL-045 | Reflection | Snapshot: piece before/after YAML capture | reads custom piece path pre/post for snapshot | reflection-runner.ts | reflection-runner.test.ts | PARTIAL | piece YAML before/after capture lightly covered; existsSync(customPath) edge not asserted |
REFL-046 | Reflection | bodyRevision (SHA-1 of parsed body) consistency | same helper for loader observedRevisions and applier CAS | revisions.ts | revisions.test.ts (~8 cases) | FULL | — |
REFL-047 | Reflection | Per-user file lock (withUserLock) | serializes apply+snapshot critical section (lockfile) | user-lock.ts; applier.ts; reflection-runner.ts | user-lock.test.ts; applier.test.ts (lock); runner | FULL | concurrent contention/torn-write scenario only indirectly assured |
REFL-048 | Reflection | Activity log summarizer | summarizes activity.log to bounded size for prompt | activity-summarizer.ts | activity-summarizer.test.ts (~5 cases) | FULL | — |
REFL-049 | Reflection | DB: reflection_metrics row (outcome applied/partial/abstained/rejected/failed) | recordReflectionMetric inserts one row per reflection job | src/db/repository.ts (~4093-4127) | reflection-runner.test.ts; migrate.reflection-columns.test.ts | FULL | — |
REFL-050 | Reflection | DB: reflection_piece_edits cooldown tracking | recordPieceEdit insert + count-in-window query | src/db/repository.ts (~4068-4090) | piece-writer.test.ts; migrate test | FULL | — |
REFL-051 | Reflection | DB: aggregateReflectionMetrics (budget) | sums tokens since UTC-day start for budget gate | src/db/repository.ts | enqueue.test.ts (budget) | FULL | — |
REFL-052 | Reflection | DB: task_kind / payload columns + reflection job creation | migrate adds task_kind, payload; reflection job created with taskKind='reflection' | src/db/repository.ts; migrate.ts | migrate.reflection-columns.test.ts (~23 cases) | FULL | — |
REFL-053 | Reflection | API: GET /history (paged, owner-scoped, nextCursor) | lists snapshot index entries, most-recent-first, cursor pagination | bridge/reflection-api.ts | reflection-api.test.ts (~29 cases) | FULL | — |
REFL-054 | Reflection | API: GET /history/:snapshotId (404 no-leak for non-owner/missing) | detail or 404 (no existence leak) | bridge/reflection-api.ts | reflection-api.test.ts | FULL | — |
REFL-055 | Reflection | API: POST /history/:snapshotId/revert (idempotent) | revertSnapshotForUser; {reverted:true} first, {reverted:false} after | bridge/reflection-api.ts; snapshot.ts | reflection-api.test.ts (revert); snapshot.test.ts (revert) | FULL | — |
REFL-056 | Reflection | API: space-aware store resolution + canEditInSpace authz | resolves per-user vs per-space store; space history needs edit rights; 404 on mismatch | bridge/reflection-api.ts | reflection-api.test.ts (space-principal) | PARTIAL | canEditInSpace negative path (member-without-edit-rights → 404) not clearly asserted as a distinct multiuser case |
REFL-057 | Reflection | API: GET /metrics | aggregated reflection metrics endpoint | bridge/reflection-api.ts | reflection-api.test.ts | PARTIAL | endpoint exists; depth of assertion (per-outcome breakdown) unverified |
REFL-058 | Reflection | API: GET /latest-for-task/:taskId (admin) | latest snapshot for a task; null (not 404) when none; admin-gated | bridge/reflection-api.ts | reflection-api.test.ts | PARTIAL | admin-gating + null-vs-404 distinction not confirmed asserted |
REFL-059 | Reflection | Retention: pruneOldSnapshots (age) | delete snapshot dirs older than retentionDays; index.jsonl untouched | retention.ts | retention.test.ts (~17 cases) | FULL | — |
REFL-060 | Reflection | Retention: enforceDiskCap (oldest-first) | prune oldest until under capBytes | retention.ts | retention.test.ts | FULL | — |
REFL-061 | Reflection | Retention: runReflectionRetentionSweep (per-user + per-space) | sweeps both data/users and data/spaces trees | retention.ts | retention.test.ts (space-principal) | FULL | — |
APIC-001 | Bridge core | GET /api/local/tasks | List tasks filtered by buildVisibilityWhere (owner/admin/org/public/space members) | local-tasks-api.ts:123 | local-tasks-api.test.ts (visibility list: owner/admin/same-org/diff-org bystander) | FULL | — |
APIC-002 | Bridge core | POST /api/local/tasks | Create task+job; visibility enum validation, org-scope check, spaceId forces private, owner from req.user (or local no-auth) | local-tasks-api.ts:134 | local-tasks-api.test.ts (visibility, no-auth owner, runtime_dir, attachments, spaceId), spaces.test.ts | FULL | — |
APIC-003 | Bridge core | GET /api/local/tasks/:taskId | Get one task; canViewTask visibility gate | local-tasks-api.ts:342 | space-view.test.ts (membership read gate), spaces.test.ts | FULL | — |
APIC-004 | Bridge core | GET /api/local/tasks/:taskId/tool-requests | List pending tool requests; canViewTask gate | local-tasks-api.ts:361 | local-tasks-api.test.ts:175 (lists pending) | PARTIAL | view-gate denial path not directly asserted on this route |
APIC-005 | Bridge core | POST /api/local/tasks/:taskId/tool-requests/:reqId/decide | Approve/deny a parked tool request; write-gate (403 view-only / 404 non-member), re-queue parked job, 409 already-decided | local-tasks-api.ts:380 | local-tasks-api.test.ts:143 (approve/deny/409/invalid value/park-contract guard) | FULL | — |
APIC-006 | Bridge core | PUT /api/local/tasks/:taskId/feedback | Submit rating/feedback; owner-or-admin (404 to others even if public) | local-tasks-api.ts:418 | local-tasks-api.test.ts:740 (owner/admin/non-owner 404), spaces.test.ts:268 | FULL | — |
APIC-007 | Bridge core | PUT /api/local/tasks/:taskId/mission | Set/update task mission text | local-tasks-api.ts:442 | none found (only auth.test.ts matches the word "mission") | NONE | No test exercises this route — authz + validation unverified |
APIC-008 | Bridge core | GET /api/local/tasks/:taskId/comments | List comments; canViewTask gate | local-tasks-api.ts:474 | covered indirectly via comment-write-gate.test.ts setup; no dedicated read-gate assertion | PARTIAL | GET read-gate not directly asserted |
APIC-009 | Bridge core | POST /api/local/tasks/:taskId/comments | Post comment (user message → spawns/reuses job); **comment write gate** (owner/admin/editor 201, viewer 403, non-member 404), attachment save, tool_request gate atomicity, idle-job reuse | local-tasks-api.ts:492 | comment-write-gate.test.ts (owner/admin/editor/viewer-403/non-member-404/personal-404), local-tasks-api.test.ts:816 (ownership, attachments, job reuse, terminal→fresh job) | FULL | — |
APIC-010 | Bridge core | PATCH /api/local/tasks/:taskId | Update task (visibility change, etc.); owner/admin, org-scope validation | local-tasks-api.ts:623 | local-tasks-api.test.ts:451 (visibility change, invalid enum, org scope), spaces.test.ts:255 (non-member 404) | FULL | — |
APIC-011 | Bridge core | POST /api/local/tasks/:taskId/regenerate-title | Regenerate AI title; owner/admin only | local-tasks-api.ts:681 | spaces.test.ts:296 (owner regenerate, +line 318 case) | PARTIAL | Only exercised in space context; non-owner-denied / no-generator paths not asserted |
APIC-012 | Bridge core | POST /api/local/tasks/evaluate-prompt | Prompt-coach: evaluate an instruction via LLM | local-tasks-api.ts:717 | local-tasks-api.test.ts:1370 (no-generator, blank, happy paths) | FULL | — |
APIC-013 | Bridge core | DELETE /api/local/tasks/:taskId | Delete task; owner-or-admin (404 to others even public) | local-tasks-api.ts:756 | local-tasks-api.test.ts:336 (owner/admin/non-owner-404), spaces.test.ts:69 | FULL | — |
APIC-014 | Bridge core | POST /api/local/tasks/:taskId/cancel | Cancel running job; write-gate (editor 200, viewer 403, non-member 404) | local-tasks-api.ts:778 | comment-write-gate.test.ts:117 (editor/viewer-403/non-member-404), spaces.test.ts:98 | FULL | — |
APIC-015 | Bridge core | POST /api/local/tasks/:taskId/continue | Spawn child job with new piece; 409 job_in_progress / no_previous_job, handoff comment, instruction persist | local-tasks-api.ts:818 | local-tasks-api.test.ts:1072 (happy/handoff/persist/409 in-progress/409 no-prev), spaces.test.ts:122 (space_id carry, non-member 404) | FULL | — |
APIC-016 | Bridge core | GET /api/local/tasks/:taskId/stream | SSE stream of job events (tool calls, deltas) | local-tasks-api.ts:914 | none (/stream matches only in app-share/share/space-api tests) | NONE | SSE event mapping, visibility gate, reconnection all untested |
APIC-017 | Bridge core | GET /api/local/tasks/:taskId/files | List files in a section (input/output/logs/workspace); canViewTask gate; traversal 400 | local-files-api.ts:41 | local-files-api.test.ts:74 (default section, subdir, unknown section 400, private-hidden, traversal 400, logs from runtime_dir + workspace_path fallback) | FULL | — |
APIC-018 | Bridge core | GET /api/local/tasks/:taskId/files/content | Serve file as text/plain; traversal 400, dir 400, missing 404, leading-slash strip | local-files-api.ts:86 | local-files-api.test.ts:152 (text, requires-path, 404 not 500, dir 400, traversal 400, abs-path strip) | FULL | — |
APIC-019 | Bridge core | GET /api/local/tasks/:taskId/files/raw | Serve raw bytes by extension; trusted=1 drops CSP sandbox (owner-trust model), setUntrustedFileResponseHeaders, ensurePathWithin traversal guard (400) | local-files-api.ts:136 | local-files-api.test.ts:197 (type-by-ext, sandbox default, trusted for owner/admin/non-owner-shared-viewer/ownerless, no-user-still-sandboxed, no-auth, non-HTML stays sandboxed, traversal 400) | FULL | Strong coverage incl. CSP trust matrix |
APIC-020 | Bridge core | GET /api/local/tasks/:taskId/files/office-preview | Excel→cells JSON / PPTX→slide PNGs; section/path validation 400, soffice missing → 503 | local-files-api.ts:203 | office-preview.test.ts (file-type detect, spreadsheet preview incl. formula, soffice-missing throws) tests the *module*; the HTTP route itself not directly hit | PARTIAL | Module logic FULL; HTTP route (section enum 400, canViewTask gate, 503 mapping) not exercised end-to-end |
APIC-021 | Bridge core | PUT /api/local/tasks/:taskId/files/content | Write/overwrite output file; owner/admin only, output-section-only, refuse while running, traversal 400, string-content validation | local-files-api.ts:240 | local-files-api.test.ts:290 (owner write, non-owner 404, admin, refuse-running, output-only, requires-string, traversal 400) | FULL | — |
APIC-022 | Bridge core | POST /api/local/tasks/:taskId/files/upload | Upload files to input/ (≤60mb); owner/admin, O_EXCL rename-on-collision, name sanitize, refuse-running, section/empty/traversal 400 | local-files-api.ts:324 | local-files-api.test.ts:366 (input upload, subdir, collision rename, name sanitize, non-owner 404, admin, refuse-running, logs/workspace 400, empty 400, traversal 400) | FULL | — |
APIC-023 | Bridge core | POST /api/local/tasks/:taskId/files/delete | Delete files; owner/admin, idempotent skip-missing, skip-dirs, refuse-running, validate-all-first (no partial), traversal 400 | local-files-api.ts:392 | local-files-api.test.ts:470 (delete, single path, skip-missing/dirs, non-owner 404, refuse-running, logs 400, empty 400, traversal 400, validate-all-first) | FULL | — |
APIC-024 | Bridge core | POST /api/local/tasks/:taskId/files/download-zip | Zip selected files; read-gated (non-owner of public OK), all sections, safeZipEntryName zip-slip protection, traversal 400, empty 400 | local-files-api.ts:447 | local-files-api.test.ts:547 (owner zip, logs section, non-owner-public read, private 404, 404-nothing-matches, traversal 400, empty 400, zip-slip backslash, ..-strip) | FULL | — |
APIC-025 | Bridge core | GET /api/pieces | List pieces (builtin + global-custom + caller's user-custom, priority-merged, no hiding) | pieces-api.ts:251 | pieces-api.test.ts (no-auth list, auth-aware merge, same-name no-hide, source tagging) | FULL | — |
APIC-026 | Bridge core | GET /api/pieces/:name | Get one piece; ?source= selector, invalid source → 400, priority resolution | pieces-api.ts:325 | pieces-api.test.ts (404 unknown, ?source=builtin, priority resolution, invalid-source typo 400) | FULL | — |
APIC-027 | Bridge core | PUT /api/pieces/:name | Update; builtin by non-admin 403, admin can edit builtin, own user-custom 200, others' 403, lint (rules[].next COMPLETE reject, SshExec allowlist, shared_tools) | pieces-api.ts:370 | pieces-api.test.ts (builtin-non-admin 403, own 200, other-user 403, admin builtin, ?source typo 400, lint rules) | FULL | — |
APIC-028 | Bridge core | POST /api/pieces | Create; dup-name reject, builtin-name reject, no-auth→piecesDir vs user-custom, 503 when userPiecesRootDir unset for authed non-admin/admin, returns actual source | pieces-api.ts:430 | pieces-api.test.ts (create, dup reject, builtin-name reject, 503 fallback hole, source return, no-auth local user-custom) | FULL | — |
APIC-029 | Bridge core | DELETE /api/pieces/:name | Delete; builtin non-deletable 403 (even admin), own user-custom 200, ?source typo 400 no-destructive-fallback | pieces-api.ts:490 | pieces-api.test.ts (builtin 403, own 200, admin-cannot-delete-builtin, ?source=user-custom target, typo 400) | FULL | — |
APIC-030 | Bridge core | GET /api/config | Return v2 config + ETag; omits legacy provider/flat-storage keys; admin-only mount | config-api.ts:87 | config-api.test.ts (v2 shape+etag, omits legacy, exposes storage.*) | FULL | Note: sensitive-masking observed in tests as worker api_key exclusion (APIC-033), not on this raw GET |
APIC-031 | Bridge core | PUT /api/config | Update with If-Match ETag optimistic lock → **409 on stale etag**; legacy-key reject 400; force config_version=2; YAML round-trip | config-api.ts:93 | config-api.test.ts (round-trip, force-version, legacy-key 400s ×6, storage round-trip, **409 stale etag**) | FULL | — |
APIC-032 | Bridge core | POST /api/config/reload | Reload config from file (500 on parse error) | config-api.ts:117 | config-api.test.ts:208 (reload from file) | PARTIAL | 500 error path not asserted |
APIC-033 | Bridge core | GET /api/workers | List workers, allowlisted fields only (**no api_key leak**), synthesized default, proxy/proxyType exposed; 401 unauth | config-api.ts:126 | config-api.test.ts (default synth, allowlist fields, proxy no-key-leak, 401 unauth, 200 authed) | FULL | This is the de-facto sensitive-masking test |
APIC-034 | Bridge core | GET /api/workers/:workerId/backends | Fetch /v1/models from proxy worker; 404 unknown, direct→empty, 60s cache, scheme/URL validation (no apiKey leak to fetch), 502 upstream fail, 401 unauth | config-api.ts:149 | config-api.test.ts (404, direct empty, fetch list, cache, file://+data: reject, malformed reject, 502, 401) | FULL | — |
APIC-035 | Bridge core | GET /api/tools | Runtime tool catalog (builtin+meta+MCP+ssh w/ availability flags); ?legacy=1 flat array; 401 unauth | tools-api.ts:242 | tools-api.test.ts (catalog, source/category tags, meta scope, ssh avail/unavail, MCP authed/offline/no-user, 401, legacy flat, placeholder, TestWorkspaceApp) | FULL | — |
APIC-036 | Bridge core | GET /api/scheduled-tasks | List; visibility filter, ?spaceId= scope | scheduled-tasks-api.ts:105 | scheduled-tasks-api.test.ts (list, visibility filter owner/non-owner/admin, ?spaceId scope) | FULL | — |
APIC-037 | Bridge core | GET /api/scheduled-tasks/:id | Get one scheduled task | scheduled-tasks-api.ts:120 | scheduled-tasks-api.test.ts (covered via list/manage setup) | PARTIAL | No dedicated GET-by-id view-gate assertion |
APIC-038 | Bridge core | POST /api/scheduled-tasks | Create; visibility enum+org-scope validation, spaceId forces private + edit-rights fallback to NULL, no-auth synthetic local owner, requires body | scheduled-tasks-api.ts:133 | scheduled-tasks-api.test.ts (visibility, org reject, spaceId force-private/edit-fallback/see-not-edit-fallback, no-auth local, browserSessionProfile owner check, daily, require-body) | FULL | — |
APIC-039 | Bridge core | PATCH /api/scheduled-tasks/:id | Update; canManageSchedule (owner/admin/space-editor); **non-owner may manage LIFECYCLE only (pause/resume/reschedule/visibility) — content edits owner-only (P3 confused-deputy guard)**; keeps space schedule private | scheduled-tasks-api.ts:267 | scheduled-tasks-api.test.ts (pause/resume, owner-or-admin 404 paths, admin any, space-editor manage, lost-edit-rights, keep-private) | FULL | Confused-deputy split (lifecycle vs content) is the known P3 design caveat |
APIC-040 | Bridge core | DELETE /api/scheduled-tasks/:id | Delete; owner/admin/space-editor (non-owner 404) | scheduled-tasks-api.ts:461 | scheduled-tasks-api.test.ts (owner, admin any, non-owner 404, space-editor survives-creator-leaving) | FULL | — |
APIC-041 | Bridge core | POST /api/scheduled-tasks/:id/trigger | Manually trigger a run; canManageSchedule gate | scheduled-tasks-api.ts:480 | scheduled-tasks-api.test.ts:203 (creator who lost edit rights can no longer trigger) | PARTIAL | Happy-path trigger spawning a run not directly asserted; only the denial path |
APIC-042 | Bridge core | GET /api/local/tasks/:id/subtasks/activities | Aggregate subtask activity tree; canViewTask gate (404 hidden, public allowed) | subtask-activity-api.ts:28 | subtask-activity-api.test.ts (nested when waiting_subtasks, 404 not-found, 404 cannot-see, public allowed) | FULL | — |
APIC-043 | Bridge core | GET /api/local/tasks/:id/subtasks/:jobId/activity | Single subtask activity; 404 subtask-not-found | subtask-activity-api.ts:68 | subtask-activity-api.test.ts:211 (404 subtask not found) | PARTIAL | view-gate denial on this specific route not separately asserted |
APIC-044 | Bridge core | GET /api/local/tasks/:id/subtasks/:jobId/files | List subtask worktree files; canViewTask, isJobWithinWorkspace containment 404, 404 no-worktree | subtask-files-api.ts:12 | subtask-files-api.test.ts:67 (grouped listing, 400 bad id, 404 missing/private/no-worktree/outside-workspace) | FULL | — |
APIC-045 | Bridge core | GET /api/local/tasks/:id/subtasks/:jobId/files/* | Serve subtask file; resolve+startsWith(base+sep) traversal → 403, 404 missing, canViewTask, org-member allowed, sendFile | subtask-files-api.ts:55 | subtask-files-api.test.ts:121 (serve inside worktree, 404 missing, 404 cannot-see, org-member allowed) | PARTIAL | The 403 traversal branch (resolved≠base path-escape) is not directly asserted on this route |
APIC-046 | Bridge core | isJobWithinWorkspace | Separator-bounded containment (rejects /12 vs /123 prefix sibling) | local-api-helpers.ts | local-api-helpers.test.ts:8 (descendants, sibling-prefix reject, null/empty) | FULL | — |
APIC-047 | Bridge core | ensurePathWithin / isPathEscapeError | Path-traversal + **symlink-escape** hardening, sibling-prefix bypass guard | local-api-helpers.ts | local-api-helpers.test.ts:92,106 (inside ok, traversal reject, sibling-prefix, symlink-escape, in-root resolve) | FULL | — |
APIC-048 | Bridge core | safeZipEntryName | Neutralizes .., backslash, Windows drive-letter in zip entry names (zip-slip) | local-api-helpers.ts | local-api-helpers.test.ts:25 (plain path, never-absolute, drive-letter→C_) | FULL | — |
APIC-049 | Bridge core | setUntrustedFileResponseHeaders | nosniff + CSP sandbox on untrusted file responses | local-api-helpers.ts | local-api-helpers.test.ts:78 | FULL | — |
APIC-050 | Bridge core | office-preview module (getSpreadsheetPreview/getPresentationPreview/SofficeUnavailableError) | Excel cells incl. formula results, PPTX via soffice, throws when soffice absent (→503) | office-preview.ts | office-preview.test.ts (file detect, spreadsheet+formula, soffice-missing throws) | FULL | Module-level only; see APIC-020 for the HTTP wiring gap |
APIS-001 | Bridge auth & spaces | OAuth2 Google login (GET /auth/google, callback) | Passport Google strategy, status gate (active→app, pending→/auth/pending) | auth.ts | auth.test.ts | FULL | |
APIS-002 | Bridge auth & spaces | OAuth2 Gitea login (GET /auth/gitea, callback) | Passport OAuth2 Gitea; pending→admin auto-promote if adminEmails | auth.ts | auth.test.ts | PARTIAL | callback strategy logic tested indirectly; live OAuth handshake not E2E |
APIS-003 | Bridge auth & spaces | Local auth login (POST /auth/local) | username/password login w/ rate limiter | auth.ts, login-rate-limit.ts | auth.local.test.ts, login-rate-limit.test.ts | FULL | |
APIS-004 | Bridge auth & spaces | Local signup (POST /auth/local/signup) | self-register → pending status awaiting approval | auth.ts | auth.local.test.ts | FULL | |
APIS-005 | Bridge auth & spaces | Login rate limiting | LoginRateLimiter + per-IP throttle scope | login-rate-limit.ts, auth.ts | login-rate-limit.test.ts | FULL | |
APIS-006 | Bridge auth & spaces | requireAuth middleware | authenticated AND status==='active' else 401/redirect | auth.ts | auth.test.ts | FULL | |
APIS-007 | Bridge auth & spaces | requireAdmin middleware | role==='admin' else 401/403 | auth.ts | auth.test.ts | PARTIAL | exercised via admin routers; few direct unit cases |
APIS-008 | Bridge auth & spaces | GET /auth/status / /login / /pending / /logout | session state pages + logout | auth.ts | auth.test.ts | PARTIAL | logout/pending HTML paths lightly covered |
APIS-009 | Bridge auth & spaces | Session store (persistent secret, survive restart) | auto-gen session secret, short-secret warning | auth.ts | auth.session-store.test.ts | FULL | |
APIS-010 | Bridge auth & spaces | Gitea orgs fetch (/api/v1/user/orgs) | populates session.orgIds for 'org' visibility | auth.ts (`resolveOrgIds`) | auth.test.ts, local-orgs.integration.test.ts | PARTIAL | fetch failure/network-error path not asserted |
APIS-011 | Bridge auth & spaces | Change password (requireAuth + current-password) | requires current pw, 204 on success | auth.ts | auth.test.ts | PARTIAL | wrong-current-pw rejection coverage thin |
APIS-012 | Bridge auth & spaces | Admin users CRUD (GET/POST/PATCH/DELETE /api/admin/users) | **admin-only** (guard); create/approve/disable/set-password | admin-api.ts | admin-api.test.ts, admin-api.local.test.ts | FULL | |
APIS-013 | Bridge auth & spaces | Admin orgs CRUD + members (/api/admin/orgs*) | **admin-only**; org create/edit/delete, add/remove member | admin-api.ts | admin-api.test.ts, local-orgs.integration.test.ts | FULL | |
APIS-014 | Bridge auth & spaces | **No-auth admin passthrough** | when authActive=false, admin guard → passthrough | admin-api.ts, branding-api.ts | admin-api.local.test.ts | PARTIAL | branding adminGuard no-auth path not asserted |
APIS-015 | Bridge auth & spaces | Task share create (POST /api/local/tasks/:id/share) | **owner-or-admin only** (checkTaskOwnership) → token | share-api.ts | share-api.test.ts | FULL | |
APIS-016 | Bridge auth & spaces | Task unshare (DELETE .../share) | **owner-or-admin only** | share-api.ts | share-api.test.ts | FULL | |
APIS-017 | Bridge auth & spaces | Public shared task read (GET /api/shared/:token) | token-only (no auth); invalid/revoked token → 404 | share-api.ts | share-api.test.ts | FULL | |
APIS-018 | Bridge auth & spaces | Shared task comments/files/raw (/api/shared/:token/...) | token-scoped read of files/comments/raw content | share-api.ts | share-api.test.ts | PARTIAL | path-escape on /files/content lightly tested |
APIS-019 | Bridge auth & spaces | Shared subtask activities/files (/api/shared/:token/subtasks/*) | token-scoped subtask containment (isJobWithinWorkspace prefix guard) | share-api.ts | share-api.test.ts | PARTIAL | wildcard /subtasks/:jobId/files/* traversal not directly asserted |
APIS-020 | Bridge auth & spaces | Public app-share resolve (GET /api/app-share/:token) | token→(spaceId,appName); revoked/invalid→404 | app-share-api.ts | app-share-api.test.ts | FULL | |
APIS-021 | Bridge auth & spaces | App-share file read (content/raw/list) | **server-side path containment**: outside allowed route→403; escape→403 | app-share-api.ts | app-share-api.test.ts | FULL | strong path-escape/forbidden coverage |
APIS-022 | Bridge auth & spaces | App-share is GET-only | no write/delete endpoints (read-only public share) | app-share-api.ts | app-share-api.test.ts | FULL | |
APIS-023 | Bridge auth & spaces | Space list (GET /spaces/) | visibility-scoped list via repo viewer gate | space-api.ts | space-api.test.ts | FULL | |
APIS-024 | Bridge auth & spaces | Space get (GET /spaces/:id) | getSpace({viewer}) null→404 (private/org/public gate) | space-api.ts | space-api.test.ts | FULL | |
APIS-025 | Bridge auth & spaces | Space create (POST /spaces/) | authenticated user creates space (owner) | space-api.ts | space-api.test.ts | FULL | |
APIS-026 | Bridge auth & spaces | Space update/archive (PATCH /:id, POST /:id/archive) | **canManageSpace** (owner/admin/member-owner) else 403 | space-api.ts | space-api.test.ts, space-api.members.test.ts | FULL | |
APIS-027 | Bridge auth & spaces | Space file list/content/raw/office-preview (GET /:id/files*) | read gated by getSpace({viewer}) visibility; not-found→404 | space-api.ts, office-preview.ts | space-api.test.ts, office-preview.test.ts, local-files-api.test.ts | PARTIAL | non-member-on-public read path not asserted per-endpoint |
APIS-028 | Bridge auth & spaces | Space file upload/delete (POST /:id/files/upload,/delete) | **canEditInSpace** (owner/admin/editor); viewer→403 | space-api.ts | space-api.write-gates.test.ts | FULL | strong write-gate authz coverage (20 authz assertions) |
APIS-029 | Bridge auth & spaces | Space file mkdir/move/download-zip (POST /:id/files/...) | **canEditInSpace**; protected top-dirs blocked | space-api.ts | space-api.write-gates.test.ts, space-api.write-endpoint.test.ts | FULL | |
APIS-030 | Bridge auth & spaces | Space file write (POST /:id/files/write) | **canEditInSpace** write gate | space-api.ts | space-api.write-endpoint.test.ts, space-api.write-gates.test.ts | FULL | |
APIS-031 | Bridge auth & spaces | Space calendar read (GET /:id/calendar, /calendar/day) | visibility-gated read | space-api.ts, cross-calendar-api.ts | space-api.calendar.test.ts, cross-calendar-api.test.ts | FULL | |
APIS-032 | Bridge auth & spaces | Space calendar event CRUD (POST/PATCH/DELETE /:id/calendar/events*) | edit gate; multi-day mirror | space-api.ts | space-api.calendar.test.ts | PARTIAL | non-editor write rejection coverage lighter than file gates |
APIS-033 | Bridge auth & spaces | Space members list (GET /:id/members) | members visible; **email masked** unless admin/owner/self-member | space-api.ts | space-api.members.test.ts | FULL | PII masking explicitly tested |
APIS-034 | Bridge auth & spaces | Space member add/role/remove (POST/PATCH/DELETE /:id/members*) | **canManageSpace**; owner-add blocked(400); self-removal allowed; non-member→404 | space-api.ts | space-api.members.test.ts | FULL | 27 authz assertions |
APIS-035 | Bridge auth & spaces | Space invite issue/get/revoke (GET/POST/DELETE /:id/invite) | **canManageSpace**; no-auth→404 | space-api.ts | space-api.invite.test.ts | FULL | |
APIS-036 | Bridge auth & spaces | Invite preview + accept (GET /invite/:token, POST /invite/:token/accept) | authed user joins via valid token; invalid/expired→404; no-auth→404 | space-api.ts | space-api.invite.test.ts | FULL | maxUses/expiry validity via isSpaceInviteValid |
APIS-037 | Bridge auth & spaces | App share per-space (GET/POST/DELETE /:id/apps/:app/share) | manage-gated issue/revoke of public app link | space-api.ts | space-api.app-share.test.ts | PARTIAL | 6 authz assertions; non-manager revoke path thin |
APIS-038 | Bridge auth & spaces | Space tool-policy (GET /:id/tool-policy, PUT) | GET visibility-gated; **PUT canManageSpace**; sensitive (Bash/SSH/browser/MCP) opt-in | space-api.ts | space-api.tool-policy.test.ts | PARTIAL | only 9 authz assertions; Bash/sensitive write-path round-trip (PR #653 regression area) under-covered |
APIS-039 | Bridge auth & spaces | Browser captcha-pool (GET/DELETE /captcha-pool) | captcha session pool read/clear | browser-api.ts | browser-api.test.ts | FULL | |
APIS-040 | Bridge auth & spaces | Browser task-session (GET/POST /task-session/:taskId*) | per-task browser session acquire/release | browser-api.ts | browser-api.test.ts | FULL | |
APIS-041 | Bridge auth & spaces | Browser profile list (GET / ) | space-aware list; **decryptableByViewer** flag (DEK owner-bound) | browser-session-api.ts | browser-session-api.test.ts | FULL | non-owner sees but cannot decrypt — asserted |
APIS-042 | Bridge auth & spaces | Browser profile create (POST) | space ctx → **canEditInSpace**; personal → owner-scoped; spaceId ignored when no spaceAccess | browser-session-api.ts | browser-session-api.test.ts | FULL | |
APIS-043 | Bridge auth & spaces | Browser profile delete (DELETE /:id) | space editor/owner OR personal owner-only | browser-session-api.ts | browser-session-api.test.ts | FULL | |
APIS-044 | Bridge auth & spaces | Browser interactive login session (POST /profiles/:id/login) | spawn noVNC session for owner; DEK owner-bound | browser-session-api.ts, novnc-proxy.ts | browser-session-api.test.ts, novnc-proxy.test.ts | PARTIAL | noVNC proxy authz/upgrade path lightly covered |
APIS-045 | Bridge auth & spaces | Console WS attach (attachConsoleWs upgrade) | decideAccess: visibility gate + canWrite (owner OR admin) | console-ws-api.ts | console-ws-api.test.ts | FULL | decideAccess unit-tested |
APIS-046 | Bridge auth & spaces | Console status router (createConsoleStatusRouter) | requireAuth; status poll for owner/admin | console-ws-api.ts | console-ws-api.test.ts, console-session-api.test.ts | FULL | |
APIS-047 | Bridge auth & spaces | Console session router (createConsoleSessionRouter) | requireAuth task-scoped session resolution | console-ws-api.ts | console-session-api.test.ts | FULL | |
APIS-048 | Bridge auth & spaces | Console admin (GET /ssh/console-sessions, POST .../:taskId/kill) | **requireAdmin** list/kill console sessions | console-admin-api.ts | console-admin-api.test.ts | FULL | |
APIS-049 | Bridge auth & spaces | Gateway admin CRUD (POST/PATCH/DELETE /:id, rotate/revoke keys) | **requireAdmin** backend + API-key mgmt | admin-gateway-api.ts | admin-gateway-api.test.ts, .budget-rate.test.ts, .metric-labels.test.ts | FULL | 18 authz assertions; budget/rate/labels covered |
APIS-050 | Bridge auth & spaces | Gateway usage (GET /:id/usage) | **requireAdmin** per-backend usage | admin-gateway-api.ts | admin-gateway-api.test.ts | FULL | |
APIS-051 | Bridge auth & spaces | Gateway status (GET / status) | non-admin status snapshot | admin-gateway-status-api.ts | admin-gateway-status-api.test.ts | FULL | |
APIS-052 | Bridge auth & spaces | Gateway mount/proxy (mountGateway, classifyGatewayPath) | LLM gateway request routing/classification middleware | gateway-mount.ts | gateway-mount.test.ts | PARTIAL | path classification + config-equiv heavily tested; auth on proxied LLM calls (Bearer/key) not asserted in this file |
APIS-053 | Bridge auth & spaces | Notifications VAPID key (GET /api/notifications/vapid-public-key) | requireAuth | notifications-api.ts | notifications-api.test.ts | FULL | |
APIS-054 | Bridge auth & spaces | Notifications subscriptions (GET/POST/DELETE /subscriptions) | requireAuth; web-push subscribe/unsubscribe | notifications-api.ts | notifications-api.test.ts | FULL | |
APIS-055 | Bridge auth & spaces | Notifications preferences (GET/PUT /preferences) | requireAuth pref read/write | notifications-api.ts | notifications-api.test.ts | FULL | |
APIS-056 | Bridge auth & spaces | Notifications test/send (POST send endpoints) | requireAuth push trigger | notifications-api.ts | notifications-api.test.ts | PARTIAL | send/test push delivery path lightly asserted |
APIS-057 | Bridge auth & spaces | Branding read (GET /api/branding) | public branding config read | branding-api.ts | branding-api.test.ts | FULL | |
APIS-058 | Bridge auth & spaces | Branding static serve (/branding/*) | static asset serving | branding-api.ts | branding-api.test.ts | PARTIAL | static traversal not asserted |
APIS-059 | Bridge auth & spaces | Branding upload/delete (POST/DELETE /api/branding/upload) | **adminGuard** (passthrough in no-auth) | branding-api.ts | branding-api.test.ts | FULL | |
APIS-060 | Bridge auth & spaces | Security headers / HSTS opt-in | HSTS default off (max-age=0 clear), opt-in enable; 180d max-age | security-headers.ts | security-headers.test.ts | FULL | 15 expects (no authz; correct) |
APIS-061 | Bridge auth & spaces | Server TLS listener | HTTPS listener creation; cert handling | server-tls-listener.ts | server-tls-listener.test.ts, server.tls.test.ts | FULL | |
APIS-062 | Bridge auth & spaces | Dashboard API (createDashboardApi) | worker/node (GPU) status tabs | dashboard-api.ts | dashboard-api.test.ts | PARTIAL | no explicit auth guard visible in router; visibility of dashboard data not gated/asserted |
APIS-063 | Bridge auth & spaces | Dashboard workers | worker status aggregation | dashboard-workers.ts | dashboard-workers.test.ts | FULL | |
APIS-064 | Bridge auth & spaces | buildVisibilityWhere / buildSpaceVisibilityWhere | SQL WHERE for private/org/public | visibility.ts | visibility.test.ts | FULL | 53 expects |
APIS-065 | Bridge auth & spaces | canManageSpace / canEditInSpace / canEditEntity / canUserSeeTask | role/owner/admin authz predicates | visibility.ts | visibility.test.ts | FULL | central predicates well unit-tested |
APIS-066 | Bridge auth & spaces | Cross-space credential transfer (G3/#006) | copy MCP/SSH creds between spaces, per-user token non-copy | (PR #642 — NOT in main) | (n/a in main) | NONE | **Not merged to main** — no endpoint present; out of scope for current tree |
SVC-001 | Services & DB | Worker poll/claim loop | processNext/acquireJobOrRequeue claim a queued job or requeue when issue lock held | src/worker.ts | worker.test.ts ("requeues a claimed job when the issue lock is already held") | PARTIAL | poll cadence, pokePoll, empty-queue path not directly asserted |
SVC-002 | Services & DB | Role / task_class matching | canClaimRole/supportsRole/getSupportedRoles gate which jobs a worker claims | src/worker.ts, src/db/repository.ts (claimNextJob) | repository.test.ts ("claims only jobs matching a healthy worker role set"; "does not let unhealthy workers claim queued jobs") | FULL | role-set matching + healthy filter covered at repo layer |
SVC-003 | Services & DB | Title-only / reflection role filters | roles:['title'] skips agent jobs; roles:['reflection'] runs reflection handler | src/worker.ts (handleReflectionJob, supportsRole) | worker.reflection-dispatch.test.ts; worker.reflection-enqueue.test.ts (16) | PARTIAL | reflection enqueue + dispatch well covered; title-only worker skip path has no direct test |
SVC-004 | Services & DB | Job lifecycle transitions | queued→dispatching→running→succeeded/failed/waiting_human/waiting_subtasks via updateJob | src/worker.ts, src/db/repository.ts | worker.test.ts; repository.test.ts; spaces-task-ops (cancel flips running→cancelled) | PARTIAL | terminal mapping covered (metrics test); waiting_human/waiting_subtasks transitions only indirectly exercised |
SVC-005 | Services & DB | Retry / maxAttempts policy | terminal vs transient classification; one recovery retry for max_iterations; give up on repeat | src/worker.ts (executeJob retry logic) | worker.test.ts (5 retry cases incl. self-abort, max_iterations 1st/2nd hit, llm_error budget) | FULL | strong branch coverage of terminal vs transient + attempt budget |
SVC-006 | Services & DB | Retry handoff summary | buildRetryHandoffSummary/writeRetryHandoffSummary carry lessons+diagnostics into retry | src/worker.ts | worker.test.ts ("writes retry handoff summary"; "builds retry handoff summary from diagnostics and lessons") | FULL | |
SVC-007 | Services & DB | Deadline / hard-abort guard | shouldDeadlineAbort aborts past deadline; job-guard timer force-aborts | src/worker.ts | worker.job-guard.test.ts (8: before/after deadline, disabled=0, no re-abort, cancel mid-run, hard deadline) | FULL | |
SVC-008 | Services & DB | Cancellation check | cancelCheck returns true when DB job status is cancelled | src/worker.ts, repository.requestJobCancel | worker.test.ts ("cancelCheck returns true when cancelled"); repository.test.ts (requestJobCancel 3 cases) | FULL | running→cancelled, queued no-op, unknown id all covered |
SVC-009 | Services & DB | max_concurrency slot scheduling | runs up to N concurrent, refills freed slot, single-flight when unset | src/worker.ts | worker.concurrency.test.ts (5: concurrent, single-flight, waitForCompletion, decrement-on-throw) | FULL | |
SVC-010 | Services & DB | Initialize / health probe | initialize probes /api/tags + /v1/models, forwards Bearer, stays healthy on llama-server compat | src/worker.ts | worker.test.ts (3 initialize cases) | FULL | |
SVC-011 | Services & DB | Unhealthy on LLM conn error | requeues jobs + marks worker unhealthy on connection errors | src/worker.ts | worker.test.ts ("requeues jobs and marks the worker unhealthy on LLM connection errors") | FULL | |
SVC-012 | Services & DB | resolveToolSpaceId (per-space MCP/SSH) | NULL space + owner → personal space; keeps explicit; fails CLOSED on resolver error | src/worker.ts | worker.test.ts (5 resolveToolSpaceId cases) | FULL | fail-closed sentinel asserted |
SVC-013 | Services & DB | Piece resolution for null-owner job | no-auth job loads piece from data/users/local/pieces consistently | src/worker.ts | worker.test.ts (2 null-ownerId piece cases) | FULL | |
SVC-014 | Services & DB | prepareJobWorkspace resolution | uses stored workspacePath or falls back to legacy local/{taskId} + backfills | src/worker.ts, src/spaces/workspace-resolver.ts | worker.test.ts (2 cases); workspace-resolver.test.ts (6) | FULL | persistent/ephemeral/personal + backfill covered |
SVC-015 | Services & DB | maybeEnqueueReflection budget gate | enqueue/skip by enabled flag, daily UTC budget cap, worker-required, per-user keying | src/worker.ts | worker.reflection-enqueue.test.ts (16) | FULL | strong: cap=0, 25h window, no-recursion, independent budgets |
SVC-016 | Services & DB | resolveModel / role→model mapping | selects backend model for the job role | src/worker.ts (resolveModel) | worker/sticky-backend.test.ts (resolver follow-current); scheduling.test.ts (normalizeJobRole/parseUiRole) | PARTIAL | role parsing well covered; resolveModel's own fallback branches not directly tested |
SVC-017 | Services & DB | Sticky backend resolver | persists first backend, advances on CHANGE, retries on persist failure | src/worker.ts (createStickyBackendResolver) | worker/sticky-backend.test.ts (6) | FULL | |
SVC-018 | Services & DB | Idle-routing yield | pickIdlerIndex yields to strictly-idler serving sibling, ignores unhealthy/non-serving | src/worker.ts (findIdlerCompetitor) | worker/idle-routing.test.ts (9) | FULL | |
SVC-019 | Services & DB | answerSubtaskAsk / subtask resume | answers a subtask ASK; parent requeue when all subtasks done | src/worker.ts, repository.requeueParentJobIfAllSubtasksDone | (none direct in scope) | NONE | repo helper requeueParentJobIfAllSubtasksDone + worker answerSubtaskAsk untested |
SVC-020 | Services & DB | Worker shutdown hooks | installs + drains shutdown hooks in order, tolerates throwing hook | src/worker.ts / worker-bootstrap | worker-bootstrap.test.ts (4) | FULL | |
SVC-021 | Services & DB | Worker metrics | counters increment on labelled inc; terminal-status mapping complete | src/worker.ts | worker.metrics.test.ts (3) | FULL | |
SVC-022 | Services & DB | WorkerManager differential rebuild | rebuild on config change: keep busy/unchanged worker, retire changed/removed without requeue, prune retired | src/worker-manager.ts | worker-manager.test.ts (8) | FULL | live-job protection + def-signature diff covered |
SVC-023 | Services & DB | WorkerManager shutdown requeue | requeues running jobs only on shutdown when worker fails to drain; not on clean shutdown | src/worker-manager.ts | worker-manager.test.ts (2 shutdown cases) | FULL | |
SVC-024 | Services & DB | Worker dep injection setters | setMcpDeps/setWorkerMetrics/setSkillCatalog/setPushService wire shared deps | src/worker-manager.ts | (none direct) | NONE | low-risk wiring; uncovered |
SVC-025 | Services & DB | Scheduler cron conversion | convertToCron maps daily/weekly/monthly→cron; passes cron through; once special-cased | src/scheduler.ts | scheduler.test.ts (convertToCron 5 cases) | FULL | |
SVC-026 | Services & DB | calcNextRun / toSqliteDatetime | next-run from cron; SQLite-compatible datetime; once→null | src/scheduler.ts | scheduler.test.ts (calcNextRun 3 + toSqliteDatetime 2) | FULL | |
SVC-027 | Services & DB | Scheduler skip-on-in-progress | skips when prior job running/waiting_subtasks; starts new on waiting_human | src/scheduler.ts (IN_PROGRESS_STATUSES) | scheduler.test.ts (4 skip cases) | FULL | waiting_human-no-longer-blocks asserted |
SVC-028 | Services & DB | Schedule ownership inheritance | propagates ownerId/visibility/scopeOrg + space_id to spawned local_task; NULL owner for system | src/scheduler.ts (executeScheduledTask) | scheduler.test.ts (multiple ownership + space-bound vs personal cases) | FULL | |
SVC-029 | Services & DB | Scheduled pieceName / classifier | falls back to classifier when pieceName unset; preserves explicit pieceName | src/scheduler.ts | scheduler.test.ts (pieceName cases) | FULL | |
SVC-030 | Services & DB | Scheduled script (task_kind=script) | runs browser-macro/script, writes logs to runtimeDir + output to space tree; failure paths; gate+allowlist | src/scheduler.ts (executeScriptScheduledTask) | scheduler.test.ts (script cases incl. gate disabled, not-in-allowlist, audit row, missing script, null scriptName guard) | FULL | |
SVC-031 | Services & DB | executeById manual run | runs a scheduled task on demand | src/scheduler.ts | (none direct) | NONE | manual-trigger path untested |
CFG-001 | Services & DB | transformKeys snake↔camel | toCamel/toSnake/toSnakeKeys round-trip incl. nested/arrays | src/config.ts | config.test.ts (toSnakeKeys 5 cases) | FULL | |
CFG-002 | Services & DB | loadConfig provider.retry | loads from YAML or default; deprecated profiles→roles shim; proxy/proxyType defaults | src/config.ts | config.test.ts (provider.retry + roles/proxy cases) | FULL | |
CFG-003 | Services & DB | validateConfig | rejects bad concurrency/maxMovements/ask/maxStreamMinutes/maxJobMinutes/maxDepth/retry/worker fields/proxy type | src/config.ts | config.test.ts (~35 validateConfig cases) | FULL | exhaustive boundary coverage |
CFG-004 | Services & DB | Env overrides (config.ts) | OLLAMA_BASE_URL/OLLAMA_MODEL/CONCURRENCY/WORKTREE_DIR/AAO_* override loaded config | src/config.ts | config.audit-regression.test.ts (OLLAMA_BASE_URL targets executed worker only, leaves persisted llm + extra workers) | PARTIAL | only OLLAMA_BASE_URL asserted; CONCURRENCY/WORKTREE_DIR/OLLAMA_MODEL/AAO_* overrides untested |
CFG-005 | Services & DB | normalizeConfig version handling | v2 pass-through; missing→v1; unsupported version throws; null input→empty | src/config-normalize.ts | config-normalize.test.ts (version handling, ~6) | FULL | |
CFG-006 | Services & DB | normalizeConfig v1→v2 llm | proxy→connection_type, worker.model inheritance, single default worker, profiles→roles, retry/metrics map | src/config-normalize.ts | config-normalize.test.ts (v1→v2 block, ~10) | FULL | |
CFG-007 | Services & DB | config-normalizer storage mirror | top-level flat keys ↔ storage.* both directions; storage.* precedence (#369); worktreeDir survival | src/config-normalize.ts, src/config.ts | config-normalize.test.ts (storage migration + backwards-compat, ~8); config.audit-regression.test.ts (trash_retention_days takes effect) | FULL | precedence trap (default-merge-before-normalize) regression-guarded |
CFG-008 | Services & DB | Env-ref preservation | ${VAR} / env: prefix preserved verbatim through normalize | src/config-normalize.ts | config-normalize.test.ts (3 env-ref cases) | FULL | |
CFG-009 | Services & DB | browser.display_mode migration | legacy captchaSolve=novnc → displayMode=novnc, key removed | src/config-normalize.ts (migrateBrowserDisplayMode) | config-normalize.test.ts (display_mode case) | FULL | |
CFG-010 | Services & DB | normalize fixtures (real configs) | v1-single-ollama / multi-worker-proxy / gateway-with-keys / mcp-and-ssh normalize correctly | src/config-normalize.ts | config-normalize.test.ts (4 fixture cases) | FULL | |
CFG-011 | Services & DB | Auth config loading | parses auth section snake_case + primary_provider; undefined when absent | src/config.ts | config-auth.test.ts (4) | PARTIAL | parsing covered; provider validation/exclusivity not asserted here |
CFG-012 | Services & DB | ConfigManager read + ETag | getConfig/getConfigForApi; etag from mtime; reloadFromFile | src/config-manager.ts | config-manager.test.ts (load, etag, reload, masked-for-API) | FULL | |
CFG-013 | Services & DB | ConfigManager write + CAS | updateConfig writes YAML, rejects stale etag, creates file on fresh install, rejects unparseable | src/config-manager.ts | config-manager.test.ts (update, stale-etag reject, fresh-install create, invalid YAML reject) | FULL | |
CFG-014 | Services & DB | ConfigManager masking preservation | masks apiKey (********); preserves masked field on update; matches workers/backends by id; drops mask when no prior key | src/config-manager.ts (mergeWithMaskPreservation) | config-manager.test.ts (mask + preserve by-id for llm.workers + gateway.backends, drop-mask path) | FULL | |
CFG-015 | Services & DB | ConfigManager change events | emits config-changed on update | src/config-manager.ts | config-manager.test.ts ("emits config-changed on update") | FULL | |
CFG-016 | Services & DB | config-manager env overrides | OLLAMA_*/WORKTREE_DIR/CONCURRENCY/DB_PATH applied at runtime read | src/config-manager.ts | (none direct in config-manager.test.ts) | NONE | DB_PATH + runtime env override path untested at config-manager layer |
CFG-017 | Services & DB | Server TLS / listen-port config | mergeServerConfig TLS defaults, http_redirect port normalize/collision, resolveListenPort + PORT env | src/server/config.ts | server/config.test.ts (24) | FULL | (PORT/LOG_LEVEL env live here, not config.ts) |
CFG-018 | Services & DB | migrate-config CLI | --help/unknown flag/missing file/dry-run/in-place+backup/already-v2/version-99 | src/scripts/migrate-config.ts | scripts/migrate-config.test.ts (8) | FULL | |
DB-001 | Services & DB | createJob / getJob round-trip | persists job incl. space_id, runtime_dir, continued_from; subtask inherits parent space_id | src/db/repository.ts | spaces-resolution.test.ts; runtime-dir.test.ts; migrate.test.ts | FULL | |
DB-002 | Services & DB | claimNextJob / retry / peek | role+health-aware claim; claimNextRetryJob; peekNextClaimable | src/db/repository.ts | repository.test.ts (worker-aware scheduling 2) | PARTIAL | claimNextRetryJob + peekNextClaimable not individually asserted |
DB-003 | Services & DB | requeueRunningJobs / recovery | requeueRunningJobs, recoverOrphanedJobs, recoverStuckRunningJobs, resumeMcpWaitingJobs | src/db/repository.ts | (worker-manager shutdown exercises requeue indirectly) | PARTIAL | recoverOrphaned/recoverStuck/resumeMcp* have no direct unit test |
DB-004 | Services & DB | requestJobCancel | running→cancelled; queued no-op; unknown id false | src/db/repository.ts | repository.test.ts (3) | FULL | |
DB-005 | Services & DB | createLocalTask / getLocalTask | persists owner/visibility/space_id/runtime_dir; reads back | src/db/repository.ts | repository.test.ts; repository-auth.test.ts; spaces-resolution.test.ts | FULL | |
DB-006 | Services & DB | listLocalTasks + latestJob/subtasks | joins latest job, subtask info, ownerName/org name; ownerId filter | src/db/repository.ts | repository.test.ts (listLocalTasks suite); repository-auth.test.ts (ownerId filter) | FULL | |
DB-007 | Services & DB | deleteLocalTask whitelist | deletes task+jobs; refuses when running; whitelists only ephemeral/local per-task dirs; protects shared space tree | src/db/repository.ts | repository.test.ts (4); spaces-task-ops.test.ts (~9 delete cases incl. symlink/trailing-slash/null-space) | FULL | data-loss regression strongly guarded |
DB-008 | Services & DB | local_task_comments | addLocalTaskComment, listLocalTaskComments, getUninjected/markInjected, getLatestResultComment, attachments | src/db/repository.ts | repository.test.ts (Feedback/Share); migrate.test.ts (attachments column) | PARTIAL | comment inject/uninject + getLatestResultComment lifecycle not directly asserted |
DB-009 | Services & DB | buildVisibilityWhere | admin 1=1; owner/public; same-org branch; spaceColumn membership+owner OR; admin ignores spaceColumn | src/db/repository.ts | bridge/visibility.test.ts (33 incl. param-order, byte-identical back-compat) | FULL | param ordering + admin-ignores-membership asserted |
DB-010 | Services & DB | canManageSpace / canEditInSpace | role-gated write checks (admin/owner/member roles, viewer read-only) | src/db/repository.ts | bridge/visibility.test.ts (canManage + canEdit suites) | FULL | |
DB-011 | Services & DB | Spaces CRUD | createSpace/getSpace/listSpaces (visibility-filtered)/updateSpace/archiveSpace | src/db/repository.ts | spaces-repository.test.ts (12) | FULL | |
DB-012 | Services & DB | ensurePersonalSpace invariant | exactly one private personal space/user, idempotent; never hard-deletable | src/db/repository.ts | spaces-repository.test.ts (ensurePersonalSpace + hardDelete invariant) | FULL | |
DB-013 | Services & DB | hardDeleteSpace crypto-shred | shreds case space DEK on delete; never personal | src/db/repository.ts | spaces-repository.test.ts (crypto-shred case) | FULL | |
DB-014 | Services & DB | Space members CRUD + visibility | add/list/getRole/updateRole/remove; membership-based read; non-member leak prevention; revocation | src/db/repository.ts | repository.shared-space.test.ts (23) | FULL | collaboration + leak + revocation all asserted |
DB-015 | Services & DB | Personal space owner-only visibility | even admin cannot see another user's personal space (list+get); no-auth synthetic owner path | src/db/repository.ts | repository.personal-space-visibility.test.ts (8) | FULL | |
DB-016 | Services & DB | Per-space resource isolation | space-A task sees only space-A MCP/SSH (not space-B/global); personal sees no case-space resources | src/db/repository.ts | repository.spaces-resource-isolation.test.ts (4) | FULL | |
DB-017 | Services & DB | Space tool_policy persistence | default null; round-trips JSON; clear via null | src/db/repository.ts | repository.tool-policy.test.ts (5) | FULL | |
DB-018 | Services & DB | Space invites | create (no-expiry default), regenerate replaces, expiry validity, revoke, FK cascade | src/db/repository.ts | repository.space-invites.test.ts (7) | FULL | |
DB-019 | Services & DB | App share links | create/get/revoke/resolveAppShareToken | src/db/repository.ts | repository.app-share.test.ts (out-of-listed but present) | PARTIAL | present file not fully read; assume happy-path coverage |
DB-020 | Services & DB | Tool requests + grant overlay | record/list newest-first, de-dup by job/task/piece, idempotent decide, granted-tools round-trip, per-piece aggregate | src/db/repository.ts | repository.tool-requests.test.ts (7) | FULL | |
DB-021 | Services & DB | User CRUD + OAuth link | createUser/getById/getByEmail/findOrCreateUserByOAuth (link by email)/listUsers/updateUser/deleteUser cascade | src/db/repository.ts | repository-auth.test.ts (17) | FULL | |
DB-022 | Services & DB | Local-auth credentials | setLocalPassword/verify round-trip, per-user salt, createLocalUser email-takeover reject, upsertLocalSystemAdmin idempotent, delete refuses local user | src/db/repository.ts | repository-local-auth.test.ts (12) | FULL | |
DB-023 | Services & DB | Local orgs | createLocalOrg (lorg: id), rename, member add/remove idempotent, listUserLocalOrgs, delete cascade + downgrade scoped resources | src/db/repository.ts | repository-local-orgs.test.ts (10) | FULL | |
DB-024 | Services & DB | ensureLocalUser DEK fix | creates per-user row so DEK/SSH create succeeds; idempotent | src/db/repository.ts | ensure-local-user.test.ts (4) | FULL | |
DB-025 | Services & DB | ScheduledTasks CRUD | create/get/list/getDue/update/delete; space_id persisted (not dead-wired) | src/db/repository.ts | repository.test.ts (ScheduledTasks 5); spaces-resolution.test.ts (space_id round-trip) | FULL | |
DB-026 | Services & DB | Share token (local task) | shareLocalTask token, idempotent, unshare clears, getByShareToken null on unknown/after-unshare | src/db/repository.ts | repository.test.ts (Share feature 6) | FULL | |
DB-027 | Services & DB | addAuditLog | single audit_log insert helper | src/db/repository.ts | scheduler.test.ts (user_script_run audit row asserted via path) | PARTIAL | addAuditLog itself not unit-tested; only one consumer asserted |
DB-028 | Services & DB | Gateway virtual keys | find/list/revoke/delete/touchLastUsed + usage | src/db/repository.ts | repository.gateway-keys.test.ts; gateway-usage.test.ts | FULL | (gateway-adjacent; in repo file) |
DB-029 | Services & DB | LLM usage aggregation | incrementLlmUsage (daily) + hourly + queryLlmUsageDaily + org map | src/db/repository.ts | repository.llm-usage.test.ts; llm-usage-hourly.test.ts | FULL | |
DB-030 | Services & DB | Push subscriptions / prefs | upsert/list/get/delete/markSuccess/markFailure; notification prefs | src/db/repository.ts | (push-service.test.ts at higher layer) | PARTIAL | repo-level push CRUD not directly asserted |
DB-031 | Services & DB | Calendar events | getCalendarEvent/deleteCalendarEvent | src/db/repository.ts | repository.calendar.test.ts; cross-calendar.test.ts | FULL | |
DB-032 | Services & DB | resumeToolRequestJob | resumes a job waiting on a tool grant | src/db/repository.ts | (none direct) | NONE | resume-after-grant path untested at repo layer |
DB-033 | Services & DB | Schema migrations (idempotent ALTER) | runMigrations adds owner_id/continued_from/last_backend_id/attachments; idempotent | src/db/migrate.ts, schema.sql | migrate.test.ts (16); migrate.space-id/ssh-space/mcp-space/reflection-columns/gateway-2b/privatize/rename tests | FULL | each major migration has its own test file |
DB-034 | Services & DB | MCP table migrations | creates mcp_servers/tokens/tools/oauth_pending idempotent; BLOB cols; auth_kind default; owner index | src/db/migrate.ts | migrate.test.ts (MCP suite); migrate.mcp-auth-columns.test.ts | FULL | |
DB-035 | Services & DB | Space backfill migrations | backfillMcpServer/Ssh/BrowserSession space_ids; privatize space rows; rename personal title | src/db/migrate.ts | migrate.mcp-space/ssh-space/privatize-space-rows/rename-personal-space-title.test.ts | FULL | |
DB-036 | Services & DB | resolveTaskWorkspaceDir / dirs | persistent→space files tree; ephemeral→throwaway; personal-space; ensureWorkspaceDirs creates subdirs | src/spaces/workspace-resolver.ts | workspace-resolver.test.ts (6); spaces-resolution.test.ts (resolveTaskFolderContext) | FULL | |
DB-037 | Services & DB | Credential DEK encryption | encrypt under SPACE DEK for space profile, OWNER DEK for personal; migration fallback; cross-DEK isolation; throws with no material | src/crypto/profile-dek.ts | crypto/profile-dek.test.ts (6) | FULL | space-vs-owner DEK separation + non-decryptability asserted |
DB-038 | Services & DB | Branding storage | branding config/assets read-write (gitignored data/branding) | src/bridge/branding-api.ts | bridge/branding-api.test.ts | PARTIAL | API-layer test exists; storage-layer branch coverage unverified |
UI-001 | UI | Calendar month grid + bars | month grid, week split, range/time fmt, multi-day bar layout | `lib/calendar.ts` | lib/calendar.test.ts | FULL | — |
UI-002 | UI | Command palette | build/filter/group commands, hotkey gate | `lib/command-palette.ts` | lib/command-palette.test.ts | FULL | — |
UI-003 | UI | Cron → form state | parse cron string into schedule fields | `lib/cronForm.ts` | lib/cronForm.test.ts | PARTIAL | only cronToFormState exported/tested; reverse (form→cron) lives in ScheduleFields.tsx untested |
UI-004 | UI | File move resolution | basename/parentDir, resolve drag-move plan (conflicts) | `lib/fileMove.ts` | lib/fileMove.test.ts | FULL | — |
UI-005 | UI | File type categorization | split name, category, color class | `lib/fileType.ts` | lib/fileType.test.ts | FULL | — |
UI-006 | UI | File view sort/format | sort entries, toggle sort, fmt timestamp/size | `lib/fileView.ts` | lib/fileView.test.ts | FULL | — |
UI-007 | UI | Help docs frontmatter/render | split/validate frontmatter, parse, slugify, render HTML, filter | `lib/help.ts` | lib/help.test.ts | FULL | — |
UI-008 | UI | Live-workspace auto-focus | decide auto-focus on new live task | `lib/live-workspace.ts` | lib/live-workspace.test.ts | FULL | — |
UI-009 | UI | Memory summary/filter | summarize + filter memory entries by type | `lib/memorySummary.ts` | lib/memorySummary.test.ts | FULL | — |
UI-010 | UI | Notifications logic | status→event map, shouldNotify, debounce, build options | `lib/notifications.ts` | lib/notifications.test.ts | FULL | createNotification/permission are browser-API thin wrappers (not unit-covered, acceptable) |
UI-011 | UI | Output-path detection/linkify | regex split text by output paths, linkify escaped HTML | `lib/output-path-detect.ts` | lib/output-path-detect.test.ts | FULL | — |
UI-012 | UI | Owner display name | derive owner label | `lib/owner.ts` | lib/owner.test.ts | FULL | — |
UI-013 | UI | Public route matching | match login/join/shared public routes | `lib/publicRoute.ts` | lib/publicRoute.test.ts | FULL | — |
UI-014 | UI | Push subscribe helpers | isPushSupported/iOS/PWA, base64→Uint8Array | `lib/push-subscribe.ts` | lib/push-subscribe.test.ts | FULL | — |
UI-015 | UI | Shared view URLs | shared image base + raw url builders | `lib/sharedView.ts` | lib/sharedView.test.ts | FULL | — |
UI-016 | UI | Sharing scope preview | visibility preview text + tooltip | `lib/sharingScope.ts` | lib/sharingScope.test.ts | FULL | — |
UI-017 | UI | Space branding vars | parse color, derive brand CSS vars | `lib/spaceBranding.ts` | lib/spaceBranding.test.ts | FULL | — |
UI-018 | UI | Space rail sort | order spaces for rail | `lib/spaceSort.ts` | lib/spaceSort.test.ts | FULL | — |
UI-019 | UI | Space task filter/count | filter tasks for space, count running | `lib/spaceTasks.ts` | lib/spaceTasks.test.ts | FULL | — |
UI-020 | UI | Split pieces (custom/default) | split + resolve piece options | `lib/splitPieces.ts` | lib/splitPieces.test.ts | FULL | — |
UI-021 | UI | Streaming field extract | extract a field from partial SSE stream | `lib/streamFieldExtract.ts` | lib/streamFieldExtract.test.ts | FULL | — |
UI-022 | UI | Tab swipe physics | axis lock, resist, commit, neighbor index | `lib/tab-swipe.ts` | lib/tab-swipe.test.ts | FULL | — |
UI-023 | UI | Task list filter/sort/group | query match, group-by-status, counts, filter+sort | `lib/taskFilter.ts` | lib/taskFilter.test.ts | FULL | — |
UI-024 | UI | Task scope filter | filter tasks by scope tab | `lib/taskScope.ts` | lib/taskScope.test.ts | FULL | — |
UI-025 | UI | Theme pref + apply | resolve/store/apply theme, system-dark, events | `lib/theme.ts` (+ `theme-css-parity.test.ts`) | lib/theme.test.ts, lib/theme-css-parity.test.ts | FULL | — |
UI-026 | UI | Tool policy split/patch | split categories, build policy patch, count enabled | `lib/toolPolicy.ts` | lib/toolPolicy.test.ts | PARTIAL | known Bash/sensitive-tools write-path gap (MEMORY: PR #653) — verify both category + sensitiveTools systems covered |
UI-027 | UI | Unsaved-changes guard | detect unsaved, confirm discard, hook | `lib/unsavedGuard.ts` | lib/unsavedGuard.test.ts | PARTIAL | useUnsavedGuard hook portion needs jsdom; pure parts covered |
UI-028 | UI | URL state (useUrlState) | read/build search params, legacy ?page=tasks redirect | `lib/urlState.ts` | lib/urlState.test.ts | FULL | hook wrapper useUrlState.ts itself untested (jsdom) but pure core covered |
UI-029 | UI | Misc utils + activity log parse | relativeTime, status tones, previewable-by-type, parseActivityLog | `lib/utils.ts` | lib/utils.test.ts | FULL | — |
UI-030 | UI | Workspace dir role | classify input/output/logs/subtasks dir | `lib/workspaceDirs.ts` | lib/workspaceDirs.test.ts | FULL | — |
UI-031 | UI | Pet state machine | pet mood/animation state transitions | `lib/pets/petState.ts` | lib/pets/petState.test.ts | FULL | lib/pets/toolIconMap.ts is a static map (no test, low value) |
UI-032 | UI | i18n workspace terminology | locale keys use "ワークスペース" consistently | `i18n/index.ts`, `i18n/locales` | i18n/layout-workspace.test.ts | PARTIAL | only layout/workspace terms checked; full key-parity across locales not asserted |
UI-033 | UI | Service worker push handler | SW push event → notification payload | `sw/push-handler.ts` | sw/push-handler.test.ts | FULL | — |
UI-034 | UI | App share URL builder | build shareable app URL/token | `components/spaces/appShareUrl.ts` | components/spaces/appShareUrl.test.ts | FULL | — |
UI-035 | UI | App↔iframe postMessage bridge | validate/route postMessage between app iframe + host | `components/spaces/app-bridge.ts` | components/spaces/app-bridge.test.ts (31 cases) | FULL | heaviest pure suite; strong |
UI-036 | UI | App file gateway (shared/auth) | route file reads through gateway, prevent 401 drift | `components/spaces/app-file-gateway.ts` | components/spaces/app-file-gateway.test.ts | FULL | — |
UI-037 | UI | Chat detail split logic | split chat vs detail pane state | `components/spaces/ChatDetailSplit.tsx` (logic) | components/spaces/ChatDetailSplit.test.ts | PARTIAL | pure split logic tested; component render not |
UI-038 | UI | Space detail keying | stable key/identity for space detail | `components/spaces/SpaceDetail.tsx` (logic) | components/spaces/spaceDetailKeying.test.ts | PARTIAL | keying logic only |
UI-039 | UI | App auto-approve gate | decide auto-approve of app file ops | `components/spaces/AppRunner.tsx` (logic) | components/spaces/AppRunner.autoapprove.test.tsx | PARTIAL | only auto-approve branch; AppRunner iframe lifecycle untested |
UI-040 | UI | App-share API client | app-share fetch client behavior | `src/api.ts` (app-share) | api.app-share.test.ts | PARTIAL | only app-share endpoints; rest of api.ts untested |
UI-041 | UI | Harness gateway | test-harness gateway (mounts real AppRunner) | `harness/` | harness/harness-gateway.test.ts | FULL | — |
UI-042 | UI | Shared tabs (public view) | which tabs show in SharedView/SharedAppView | `pages/SharedView.tsx`/`SharedAppView.tsx` (logic) | pages/sharedTabs.test.ts | PARTIAL | tab-selection logic only |
UI-043 | UI | Detail tab selection | detail tab list / readonly gating | `components/detail/*` (logic) | detail/detailTabs.test.ts, detail/detail-readonly.test.ts | PARTIAL | tab + readonly logic; not full panels |
UI-044 | UI | Task data source resolution | choose live vs shared data source for task | `components/detail/task-data-source.tsx` (logic) | detail/task-data-source.test.ts | FULL | — |
UI-045 | UI | Console key handling | terminal key → escape sequences | `detail/tabs/console/*` | detail/tabs/console/keys.test.ts | PARTIAL | key mapping only; TerminalView render/websocket untested |
UI-046 | UI | TopBar collapse logic | which tabs collapse into overflow | `components/layout/TopBar.tsx` (logic) | layout/topbar-collapse.test.ts | PARTIAL | collapse math only |
UI-047 | UI | Settings sidebar sections | section list/visibility logic | `components/settings/SettingsSidebar.tsx` (logic) | settings/settingsSidebar.test.ts | PARTIAL | section logic; per-form rendering untested |
UI-048 | UI | Rotating tips | pick/rotate chat tip strings | `components/chat/RotatingTips.tsx` (logic) | chat/RotatingTips.tips.test.ts | FULL | — |
UI-049 | UI | File drag-n-drop transfer | build/read DataTransfer drag sources for files | `lib/fileDnd.ts` (`dragSources`, `readDragSources`) | none | NONE | pure, easy to test now; DnD bugs are user-visible |
UI-050 | UI | Files → base64 | convert File[] to base64 upload payload | `lib/fileBase64.ts` | none | NONE | uses FileReader (jsdom/node-FileReader needed); logic small but worth a test |
UI-051 | UI | Local date helpers | localToday, tz offset, shiftDay | `lib/localDate.ts` | none | NONE | pure date math — high regression risk (tz), trivially testable |
UI-052 | UI | Space color | derive deterministic space color | `lib/spaceColor.ts` | none | NONE | pure; deterministic-output test trivial |
UI-053 | UI | Polling/stale constants | POLLING + STALE_TIME tables | `lib/constants.ts` | none | NONE | low value (constants) — skip unless invariants needed |
UI-054 | UI | Help content loader | load+assemble help sections from raw md | `lib/help-content.ts` (`loadHelpSections`) | none | PARTIAL | help.ts parsing covered, but the loader/aggregation step is untested |
UI-055 | UI | FilePreview render (md/csv/jsonl/image/pdf) | render each preview type; **relative-image rewrite** to /api/local/tasks/:id/files/raw | `components/files/FilePreview.tsx` (`resolvedHref`, line ~320) | none (only lib/utils isPreviewable flags) | PARTIAL | the relative-image rewrite is core, user-visible, untested. Could extract resolveImageHref to lib → sandbox-testable |
UI-056 | UI | FileBrowser (grid/list, sort, select, move) | tile/detail views, multi-select, drag-move, breadcrumb | `components/files/FileBrowser.tsx` + `FileTileGrid/FileDetailList/FileBreadcrumb/MoveTargetDialog` | e2e spaces.spec.ts (icon grid/upload/delete/multiselect/download) | PARTIAL | core flows covered by e2e; jsdom unit tests absent (sort/select logic is in tested libs UI-006/UI-004) |
UI-056b | UI | useFileBrowser/useFileView/useFilePreview hooks | data fetching + view state for files | `hooks/useFileBrowser.ts`, `useFileView.ts`, `useFilePreview.ts` | none | NONE | jsdom |
UI-057 | UI | ChatPane / ChatMessage / MovementGroup | message stream, movement grouping, tool-calls section | `components/chat/*` | e2e (inline chat create/open, cancel) | PARTIAL | rendering untested in jsdom; movement-group grouping logic not extracted to lib |
UI-058 | UI | Tool-request approval card | inline Approve/Deny grants + resolves request | `components/chat/ToolRequestApproval.tsx` | e2e tool-request.spec.ts (approve, deny) | FULL | jsdom unit absent; e2e covers both branches |
UI-059 | UI | Settings ConfigForm + per-section forms | edit each config.yaml section (Tools/LLM/Context/Safety/SSH/MCP/Gateway/Branding/Pets/Reflection/...) | `components/settings/*Form.tsx` (~45 files) | settingsSidebar (sections), toolPolicy (UI-026) | PARTIAL | huge surface; only sidebar + tool-policy logic tested. Per-form validation/round-trip largely untested |
UI-060 | UI | Memory & Learning tab | edit memory entries, reflection history + revert | `components/settings/MemoryLearningForm.tsx`, `ReflectionForm.tsx` | e2e spaces.spec.ts (memory persists A vs B), memorySummary lib (UI-009) | PARTIAL | persistence covered by e2e; revert button + reflection-history list render untested |
UI-061 | UI | Usage dashboard | per-user daily LLM usage charts | `components/usage/UsagePage.tsx`, `settings/GatewayKeyUsagePanel.tsx` | none | NONE | jsdom; aggregation/format logic not extracted to lib |
UI-062 | UI | Worker/Node status widgets | rail GPU panel, per-backend rows, worker pills | `components/dashboard/*Widget.tsx`, `hooks/useWorkerStatus/useNodeStatus` | e2e spaces.spec.ts (GPU panel toggle, proxy per-backend rows) | PARTIAL | e2e smoke; widget render/empty-state untested in unit |
UI-063 | UI | Browser session panel + PiP | live browser session view, picture-in-picture | `components/browser/*`, `hooks/usePictureInPicture.ts` | e2e spaces.spec.ts (BROWSER panel groups render) | PARTIAL | PiP controller untested; panel render only via e2e |
UI-064 | UI | SSH config/console UI | console terminal, grants, audit, key rotation | `components/settings/Ssh*.tsx`, `userfolder/Ssh*.tsx`, `useConsoleSession` | none (types only ssh-console-types.ts) | NONE | large untested surface; console session hook + websocket untested |
UI-065 | UI | Mobile edge-swipe / swipeable tabs | edge swipe nav, tab swipe gestures | `hooks/useEdgeSwipe.ts`, `components/mobile/SwipeableTabs.tsx` | hooks/useEdgeSwipe.test.ts (DOM — **fails in sandbox**), lib/tab-swipe.ts (UI-022) | PARTIAL | physics covered as lib; the DOM hook test exists but can't run here |
UI-066 | UI | Pets overlay + frame analysis | pet sprite overlay, tool-spark, frame analysis | `components/pets/*`, `hooks/usePetFrameAnalysis/useActivePet` | lib/pets/petState.test.ts (UI-031) | PARTIAL | state machine covered; overlay render + frame analysis untested |
UI-067 | UI | CreateTaskDialog + PromptCoach + ScheduleFields | new chat/task dialog, prompt coaching, schedule builder | `components/create/*` | e2e (+新規 opens dialog; ephemeral warning), cronForm lib (UI-003) | PARTIAL | dialog open + ephemeral warning via e2e; prompt-coach + form→cron untested |
UI-068 | UI | Monaco file editor (userfolder) | edit AGENTS.md/MCP/skills/memory files | `components/userfolder/MonacoFileEditor.tsx` + panels | e2e (AGENTS.md persistence, panels render) | PARTIAL | persistence via e2e; editor component untested in unit |
UI-069 | UI | Command palette UI | open palette, fuzzy filter, execute command | `components/command/CommandPalette.tsx` | lib/command-palette.test.ts (UI-002) | PARTIAL | all logic in lib (FULL); only the render/keyboard wiring is jsdom |
UI-070 | UI | Embed cards (X/Maps/YouTube/Amazon) | render structured-block embeds + detail modals | `components/embed/*` | none | NONE | jsdom; parsing of block data could be extracted to lib |
UI-071 | UI | Setup wizard | first-run setup steps | `components/setup/SetupWizard.tsx`, `hooks/useSetupState` | none | NONE | jsdom |
UI-072 | UI | Toast / notifications hooks | toast queue, task notifications dispatch | `hooks/useToast.ts`, `useTaskNotifications.ts` | lib/notifications.test.ts (UI-010) | PARTIAL | decision logic in lib; hook dispatch/debounce wiring untested |
UI-073 | UI | Spaces foundation + rail + detail tabs | create case space, open detail, mobile rail↔detail | `components/spaces/*` | e2e/spaces.spec.ts (37 cases) | FULL | most thorough e2e suite (chat CRUD, files, calendar, settings, apps, members, MCP isolation) |
UI-074 | UI | Cross-space calendar | top-bar tab, grid, month nav, per-space dots | `components/spaces/CrossSpaceCalendar.tsx` | e2e/calendar.spec.ts | FULL | — |
UI-075 | UI | Tasks→workspace migration | Tasks tab removed, personal WS auto-select, legacy redirect | `App.tsx`, `lib/urlState.ts` | e2e/tasks.spec.ts | FULL | also lib-covered (UI-028) |
UI-076 | UI | Sharing scope (multi-user) | manager/member/stranger visibility (200/404), invite picker | space visibility | e2e-auth/sharing-scope.auth.spec.ts | FULL | requires auth seed |
UI-077 | UI | Shared space membership | invite→member row+avatars, role change, removal | `components/spaces/SpaceMembersPanel.tsx` | e2e-auth/shared-space.auth.spec.ts | FULL | — |
UI-078 | UI | Space invite links | join via token, invalid/revoked states | `components/spaces/JoinSpace.tsx` | e2e-auth/space-invite.auth.spec.ts | FULL | — |
UI-079 | UI | Workspace file input flow | input/output folders show, next-action guidance, ephemeral warning | Files tab + create dialog | e2e-auth/workspace-file-input.auth.spec.ts | FULL | — |
UI-080 | UI | App-share public view | login-free read-only shared app view | `pages/SharedAppView.tsx`, `SharedView.tsx` | none (only lib appShareUrl/sharedTabs/sharedView) | PARTIAL | public viewer flow has no e2e; logic libs covered |
UI-081 | UI | Help page | render help sections by category, search | `pages/HelpPage.tsx` | none (lib help.ts FULL) | PARTIAL | rendering + search wiring not e2e/jsdom tested |
UI-082 | UI | Pieces page (CRUD) | list/edit custom vs default pieces, movement editor | `pages/PiecesPage.tsx`, `settings/PieceEditor.tsx`, `MovementForm.tsx`, `RulesTable.tsx` | none (lib splitPieces FULL) | PARTIAL | piece editor / movement rules UI untested |
UI-083 | UI | Schedules page | list/create scheduled tasks (5 types) | `pages/SchedulesPage.tsx` | none (lib cronForm partial) | PARTIAL | schedule CRUD UI untested |
UI-084 | UI | Admin / users page | user CRUD, captcha, org form | `pages/UsersPage.tsx`, `AdminCaptchaPage.tsx`, `admin/*`, `settings/OrgsForm.tsx` | none | NONE | no UI tests for admin user management |
各領域インベントリの「Top gaps」を集約。最もリスクの高い未テスト挙動。
Highest-risk untested behaviors, ranked:
classifyPiece async path (NONE). Routing every task to a piece runs through this LLM call + fallback. Only the two pure helpers are tested; the wiring, the null-parse→default-piece fallback, and LLM-error handling have zero coverage. A regression here silently misroutes all tasks.default_next trap.writeLessonLog. Silent loss of cross-movement learning would be invisible.loadAllPieceTriggers (NONE). Trigger/keyword loading feeds the classifier hint layer; the custom-dir precedence and skip-on-invalid-piece branches are uncovered.max_consecutive_revisits and the counter-reset-on-movement-change branch are not — the exact knobs that prevent runaway loops. Likewise mid-work cancellation (vs pre-loop) is only covered via snapshot side effects.office.ts:2124-2130 rejects total-uncompressed > 200MB and validateOfficeFile enforces the 50MB file cap, but office.test.ts contains no test that crafts an over-limit / high-compression-ratio PPTX. This is the only declared anti-DoS guard in the tools layer and it is completely unverified. A malformed/regressed check would silently allow a decompression bomb.stats.size > maxSize → warning emission. The resolveMaxSize config override (tools.office_*_max_size_mb) is also untested.index.ts:411 (RequestTool, MissionUpdate, all UserFolder/AppDocs tools, etc.) is what actually makes these always-available regardless of a movement's allowed_tools, yet only the smaller tool-categories.ts SUBSET is asserted. The two lists can drift (they already differ by design); nothing tests that the runtime injection list matches expectations.loadPiece().not.toThrow() would catch it — which it won't, since those are semantic not schema errors. Highest-value targets: research-sub (spawned, complex 3-movement loop), brainstorming, sns-research, x-ai-digest.allowed_tools as plain strings; nothing asserts every name in every piece resolves to an actual registered tool. A typo (or a renamed/removed tool) leaves a dead allowlist entry that silently never gets offered to the LLM — exactly the class of drift the maintenance-checklist warns about. This would be a cheap, high-leverage sweep test across all 15 pieces.loadPiece/validatePieceDef does NOT verify that every rules[].next points to an existing movement, nor that initial_movement exists, nor that every movement is reachable. A built-in YAML with a typo'd next: would load fine and only fail at runtime ("Movement not found"). No test guards this for the shipped pieces.decision.code='failed'): when a memory write throws mid-loop, the applier records that single change as failed and continues. This distinct per-change failure branch (vs runner-level outcome='failed') is not clearly asserted. It is the only place a memory mutation can partially fail under the lock, so it directly affects partial/rejected outcome correctness.canEditInSpace → 404): the reflection-api space-store resolution depends on canEditInSpace. Tests cover the space-principal happy path, but a space *member without edit rights* hitting history/revert (must 404, no leak) is not asserted as a distinct multiuser case — exactly the kind of cross-principal authz gap that whole-branch reviews catch but per-unit tests miss.writeSnapshot-throws-but-record-metric-anyway branch, plus the before/after capture edge cases (remove-op uses merge_target for the before-file name; piece-YAML pre/post capture when customPath doesn't yet exist), are only lightly exercised. A torn snapshot here silently degrades the revert feature.worker.reflection-dispatch.test.ts is small (~2 cases). The gateway role-keyed routing (reflectionRoutingKey, roleFallback:'reflection') and the "send worker API key or gateway 401s" requirement are documented in code comments but not asserted end-to-end — a deploy-shaped risk (401 storms) that unit tests don't catch.decideOutcome empty-input fallback-to-abstained (distinct from an explicit abstain_reason) lack dedicated assertions. Both are silent-correctness branches: a regression would mis-drop changes or mislabel outcomes without any test going red.toolBuf/event-name reconstruction logic around line 976) has zero direct tests. This is the live job-progress feed the UI depends on; a regression here is invisible to CI./files/office-preview endpoint wiring is not: section-enum 400, canViewTask gate, and the SofficeUnavailableError→503 mapping are unexercised end-to-end (only handleOfficePreviewError would do the 503).GET /subtasks/:jobId/files/* has the explicit resolved !== base && !startsWith(base+sep) → 403 escape guard, but no test drives a traversal payload to assert the 403. The containment helper is unit-tested (APIC-046) but the route-level enforcement is not.Highest risk first (authorization-weighted):
PUT /:id/tool-policy gates by canManageSpace, but only 9 authz assertions and — per MEMORY (PR #653) — the Bash/sensitive-tool write path (buildPolicyPatch only walked categories, missing the SENSITIVE_TOOLS separate channel) was a real save-loss bug. The read/validate/render/write round-trip for sensitive tools across both delivery systems is not asserted end-to-end. A regression here silently grants/denies Bash/SSH/browser/MCP. Add an integration test that PUTs a sensitive-tool policy as a non-manager (expect 403) and as owner (expect persisted both-channels).mountGateway proxies LLM traffic; classification and config-equivalence are well tested, but the authentication of proxied requests (Bearer/API-key acceptance/rejection, 401 on bad key) is not asserted in gateway-mount.test.ts. An unauthenticated path slipping through the gateway mount would expose backend LLM spend. Deploy-order note in MEMORY (gateway→client) hints this boundary is operationally fragile.createDashboardApi router shows no requireAuth/requireAdmin in its route definitions and the test asserts 0 authz cases. Worker/node (GPU) status may leak infra topology to unauthenticated/non-admin users. Confirm guard is applied at mount site (server.ts) and add an authz assertion.GET /api/shared/:token/subtasks/:jobId/files/* is a public token route with a wildcard path segment; containment relies on isJobWithinWorkspace prefix guard. No direct test asserts path-traversal/prefix-collision rejection on this wildcard. This is exactly the class fixed previously (startsWith prefix collision) — add a traversal/escape test.authActive=false, admin guards downgrade to passthrough (admin-api covered; branding adminGuard no-auth path not asserted). If a deployment runs no-auth on a shared network, branding upload/delete is fully open. Document/assert the no-auth posture for every admin route, not just user CRUD.Lower-priority partials worth noting: APIS-018/APIS-027 (per-endpoint path-escape / non-member-on-public read not asserted), APIS-032 (calendar non-editor write rejection lighter than file gates), APIS-044 (noVNC proxy upgrade authz), APIS-056 (push send delivery).
recoverOrphanedJobs, recoverStuckRunningJobs, resumeMcpWaitingJobs, resumeToolRequestJob, and requeueParentJobIfAllSubtasksDone have no direct unit tests. These run on worker restart / subtask completion and directly affect whether jobs get stuck or double-run — exactly the class of bug seen in the config-rebuild double-execution incident. Highest-risk gap because failures are silent (stuck or duplicated jobs in prod).OLLAMA_BASE_URL is asserted (in config.audit-regression). CONCURRENCY, WORKTREE_DIR, OLLAMA_MODEL, DB_PATH, and the AAO_* metrics vars have no test. Env overrides are a documented public interface and a known footgun (storage mirror precedence already bit the project in #368/#369).answerSubtaskAsk / subtask ASK plumbing (SVC-019). The path where a parent worker answers a child subtask's ASK and the parent requeues on all-subtasks-done is untested end-to-end. This is core to the waiting_subtasks/waiting_human lifecycle and has tangled state.addAuditLog not unit-tested (DB-027). Only one consumer (script run) is asserted indirectly. Audit log is a security/compliance surface; no test guards the row shape or that mutating ops actually write audit rows.getUninjectedComments / markCommentsInjected / getLatestResultComment drive what the agent sees from user comments and what the user sees as the final result — no direct assertion of the inject→mark→re-query cycle, despite being on the hot path for continuation jobs.resolveImageHref/resolvedHref, FilePreview.tsx ~L320) — core, user-visible (broken images in markdown), and currently untested. *Sandbox-testable IF the href-rewrite function is extracted to lib/ (pure string transform); today it's inline in a component so as-is it needs jsdom.*SettingsSidebar section logic + toolPolicy are covered. Per-form validation/round-trip is jsdom/e2e. Mitigation that IS sandbox-testable: extract each form's validation/serialization into pure helpers (the established pattern in this repo) and unit-test those.fileDnd, localDate, spaceColor) — immediately sandbox-testable, no extraction needed. localDate (tz math) is the highest regression risk of the three.buildPolicyPatch only walked categories and missed SENSITIVE_TOOLS/Bash). Test exists but should assert both the category system and the separate sensitiveTools array across read/validate/render/write. Sandbox-testable now (pure).