deepflow 0.1.109 → 0.1.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -83,9 +83,11 @@ function parsePlan(text) {
83
83
  }
84
84
 
85
85
  // Match pending task header: - [ ] **T{N}**...
86
- const taskMatch = line.match(/^\s*-\s+\[\s+\]\s+\*\*T(\d+)\*\*(?:\s+\[[^\]]*\])?[:\s]*(.*)/);
86
+ // Captures optional [TAG] as group 2 (e.g. INTEGRATION, SPIKE, OPTIMIZE)
87
+ const taskMatch = line.match(/^\s*-\s+\[\s+\]\s+\*\*T(\d+)\*\*(?:\s+\[([^\]]*)\])?[:\s]*(.*)/);
87
88
  if (taskMatch) {
88
- const rest = taskMatch[2].trim();
89
+ const rawTag = taskMatch[2] ? taskMatch[2].trim().toUpperCase() : null;
90
+ const rest = taskMatch[3].trim();
89
91
 
90
92
  // Extract inline blocked-by (from " | Blocked by: T1, T2")
91
93
  let inlineBlockedBy = [];
@@ -118,6 +120,7 @@ function parsePlan(text) {
118
120
  files: null,
119
121
  effort: inlineEffort,
120
122
  spec: currentSpec,
123
+ tag: rawTag,
121
124
  };
122
125
  tasks.push(current);
123
126
  continue;
@@ -275,13 +278,14 @@ function buildWaves(tasks, stuckIds) {
275
278
 
276
279
  /**
277
280
  * Format waves as a JSON array of task objects, each with a `wave` field.
278
- * Fields: id, description, model, files, effort, blockedBy, spec, wave
281
+ * Fields: id, description, model, files, effort, blockedBy, spec, tag, isIntegration, isSpike, isOptimize, wave
279
282
  */
280
283
  function formatWavesJson(waves) {
281
284
  const result = [];
282
285
  for (let i = 0; i < waves.length; i++) {
283
286
  const waveNum = i + 1;
284
287
  for (const t of waves[i]) {
288
+ const tag = t.tag || null;
285
289
  result.push({
286
290
  id: t.id,
287
291
  description: t.description || null,
@@ -290,6 +294,10 @@ function formatWavesJson(waves) {
290
294
  effort: t.effort || null,
291
295
  blockedBy: t.blockedBy,
292
296
  spec: t.spec || null,
297
+ tag: tag,
298
+ isIntegration: tag === 'INTEGRATION',
299
+ isSpike: tag === 'SPIKE',
300
+ isOptimize: tag === 'OPTIMIZE',
293
301
  wave: waveNum,
294
302
  });
295
303
  }
@@ -41,38 +41,44 @@ function parseArgs() {
41
41
 
42
42
  function findNodeModules(root) {
43
43
  const results = [];
44
-
45
- // Root node_modules
46
- const rootNM = path.join(root, 'node_modules');
47
- if (fs.existsSync(rootNM)) {
48
- results.push('node_modules');
44
+ const seen = new Set();
45
+
46
+ function addIfNM(relDir) {
47
+ if (seen.has(relDir)) return;
48
+ const abs = path.join(root, relDir, 'node_modules');
49
+ if (fs.existsSync(abs)) {
50
+ seen.add(relDir);
51
+ results.push(relDir === '.' ? 'node_modules' : path.join(relDir, 'node_modules'));
52
+ }
49
53
  }
50
54
 
51
- // Scan common monorepo directory patterns for nested node_modules
52
- const monorepoPatterns = ['packages', 'apps', 'libs', 'services', 'modules', 'plugins'];
53
-
54
- for (const dir of monorepoPatterns) {
55
- const dirPath = path.join(root, dir);
56
- if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) continue;
57
-
55
+ // Root node_modules
56
+ addIfNM('.');
57
+
58
+ // Walk every top-level directory up to 2 levels deep looking for node_modules.
59
+ // This covers both flat layouts (go/frontend, web/admin) and monorepo layouts
60
+ // (packages/foo, apps/bar) without hardcoding directory names.
61
+ function walk(relDir, depth) {
62
+ if (depth > 2) return;
63
+ const abs = path.join(root, relDir);
58
64
  let entries;
59
65
  try {
60
- entries = fs.readdirSync(dirPath);
66
+ entries = fs.readdirSync(abs, { withFileTypes: true });
61
67
  } catch (_) {
62
- continue;
68
+ return;
63
69
  }
64
-
65
70
  for (const entry of entries) {
66
- const entryPath = path.join(dirPath, entry);
67
- if (!fs.statSync(entryPath).isDirectory()) continue;
68
-
69
- const nm = path.join(entryPath, 'node_modules');
70
- if (fs.existsSync(nm)) {
71
- results.push(path.join(dir, entry, 'node_modules'));
72
- }
71
+ if (!entry.isDirectory()) continue;
72
+ if (entry.name.startsWith('.')) continue;
73
+ if (entry.name === 'node_modules') continue;
74
+ const childRel = relDir === '.' ? entry.name : path.join(relDir, entry.name);
75
+ addIfNM(childRel);
76
+ walk(childRel, depth + 1);
73
77
  }
74
78
  }
75
79
 
80
+ walk('.', 1);
81
+
76
82
  return results;
77
83
  }
78
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepflow",
3
- "version": "0.1.109",
3
+ "version": "0.1.111",
4
4
  "description": "Doing reveals what thinking can't predict — spec-driven iterative development for Claude Code",
5
5
  "keywords": [
6
6
  "claude",
@@ -42,8 +42,5 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "playwright": "^1.58.2"
45
- },
46
- "devDependencies": {
47
- "typescript": "^6.0.2"
48
45
  }
49
46
  }
@@ -40,44 +40,55 @@ Each task = one background agent. **NEVER use TaskOutput** (100KB+ transcripts e
40
40
  `--continue` → load `.deepflow/checkpoint.json`, verify worktree exists (else error "Use --fresh"), skip completed. `--fresh` → delete checkpoint. Checkpoint exists → prompt "Resume? (y/n)".
41
41
  Shell: `` !`cat .deepflow/checkpoint.json 2>/dev/null || echo 'NOT_FOUND'` `` / `` !`git diff --quiet && echo 'CLEAN' || echo 'DIRTY'` ``
42
42
 
43
- ### 1.5. CREATE WORKTREE
43
+ ### 1.5. CREATE WORKTREES (per spec)
44
44
 
45
- Require clean HEAD. Derive SPEC_NAME from `specs/doing-*.md`. Create `.deepflow/worktrees/{spec}` on branch `df/{spec}`. Reuse if exists; `--fresh` deletes first. If `worktree.sparse_paths` non-empty: `git worktree add --no-checkout`, `sparse-checkout set {paths}`, checkout.
45
+ Require clean HEAD. Discover **all** specs in execution scope:
46
+ ```
47
+ DOING_SPECS=!`ls specs/doing-*.md 2>/dev/null | sed 's|specs/doing-||;s|\.md$||' | tr '\n' ' ' || echo 'NOT_FOUND'`
48
+ ```
49
+
50
+ For **each** `{spec}` in `DOING_SPECS`, create `.deepflow/worktrees/{spec}` on branch `df/{spec}`. Reuse if exists; `--fresh` deletes first. If `worktree.sparse_paths` non-empty: `git worktree add --no-checkout`, `sparse-checkout set {paths}`, checkout.
51
+
52
+ Build an in-memory map `SPEC_WORKTREES = {spec → {path, branch}}`. This map drives per-task routing in §5 and §5.5 and is persisted in `.deepflow/checkpoint.json` under `spec_worktrees`. Tasks from spec A run in worktree A; tasks from spec B run in worktree B. No cross-spec commits share a branch.
46
53
 
47
- ### 1.5.1. SYMLINK DEPENDENCIES
54
+ Then run §1.5.1, §1.6, and §1.7 **per worktree** before any wave spawns.
48
55
 
49
- After worktree creation, symlink `node_modules` from the main repo so TypeScript/LSP/build can resolve dependencies without a full install:
56
+ ### 1.5.1. SYMLINK DEPENDENCIES (per worktree)
57
+
58
+ After each worktree is created, symlink `node_modules` from the main repo so TypeScript/LSP/build can resolve dependencies without a full install:
50
59
  ```bash
51
- node "${HOME}/.claude/bin/worktree-deps.js" --source "$(git rev-parse --show-toplevel)" --worktree "${WORKTREE_PATH}"
60
+ node "${HOME}/.claude/bin/worktree-deps.js" --source "$(git rev-parse --show-toplevel)" --worktree "${SPEC_WORKTREES[spec].path}"
52
61
  ```
53
62
  The script finds `node_modules` at root and inside monorepo directories (`packages/`, `apps/`, etc.) and creates symlinks in the worktree. Outputs JSON: `{"linked": N, "total": M}`. Errors are non-fatal — log and continue.
54
63
 
55
- ### 1.6. RATCHET SNAPSHOT
64
+ ### 1.6. RATCHET SNAPSHOT (per worktree)
56
65
 
57
- Snapshot pre-existing test files — only these count for ratchet (agent-created excluded):
66
+ For each spec worktree, snapshot pre-existing test files — only these count for ratchet (agent-created excluded):
58
67
  ```bash
59
- git -C ${WORKTREE_PATH} ls-files | grep -E '\.(test|spec)\.[^/]+$|^test_|_test\.[^/]+$|^tests/|__tests__/' > .deepflow/auto-snapshot.txt
68
+ git -C ${SPEC_WORKTREES[spec].path} ls-files | grep -E '\.(test|spec)\.[^/]+$|^test_|_test\.[^/]+$|^tests/|__tests__/' > .deepflow/auto-snapshot-{spec}.txt
60
69
  ```
61
70
 
71
+ Each spec has its own snapshot file. Ratchet checks in §5.5 pass the snapshot file matching the task's spec.
72
+
62
73
  ### 1.7. NO-TESTS BOOTSTRAP
63
74
 
64
75
  <!-- AC-1: zero test files triggers bootstrap before wave 1 -->
65
76
  <!-- AC-2: bootstrap success re-snapshots auto-snapshot.txt; subsequent tasks use updated snapshot -->
66
77
  <!-- AC-3: bootstrap failure with default model retries with Opus; double failure halts with specific message -->
67
78
 
68
- **Gate:** After §1.6 snapshot, check `auto-snapshot.txt`:
79
+ **Gate (per spec):** After §1.6 snapshot, check each spec's snapshot file independently:
69
80
  ```bash
70
- SNAPSHOT_COUNT=$(wc -l < .deepflow/auto-snapshot.txt | tr -d ' ')
81
+ SNAPSHOT_COUNT=$(wc -l < .deepflow/auto-snapshot-{spec}.txt | tr -d ' ')
71
82
  ```
72
- If `SNAPSHOT_COUNT` is `0` (zero test files found), MUST spawn bootstrap agent before wave 1. No implementation tasks may start until bootstrap completes successfully.
83
+ If `SNAPSHOT_COUNT` is `0` for a given spec (zero test files found), MUST spawn a bootstrap agent for **that spec** before any implementation task from that spec runs. Other specs with non-empty snapshots proceed normally.
73
84
 
74
- **Bootstrap flow:**
75
- 1. Spawn `Agent(model="{default_model}", ...)` with Bootstrap prompt (§6). End turn, wait for notification.
76
- 2. **On success (TASK_STATUS:pass):** Re-snapshot immediately:
85
+ **Bootstrap flow (per empty-snapshot spec):**
86
+ 1. Spawn `Agent(model="{default_model}", ...)` with Bootstrap prompt (§6), `Working directory: ${SPEC_WORKTREES[spec].path}`. End turn, wait for notification.
87
+ 2. **On success (TASK_STATUS:pass):** Re-snapshot immediately for that spec:
77
88
  ```bash
78
- git -C ${WORKTREE_PATH} ls-files | grep -E '\.(test|spec)\.[^/]+$|^test_|_test\.[^/]+$|^tests/|__tests__/' > .deepflow/auto-snapshot.txt
89
+ git -C ${SPEC_WORKTREES[spec].path} ls-files | grep -E '\.(test|spec)\.[^/]+$|^test_|_test\.[^/]+$|^tests/|__tests__/' > .deepflow/auto-snapshot-{spec}.txt
79
90
  ```
80
- All subsequent tasks use this updated snapshot as their ratchet baseline. Proceed to wave 1.
91
+ All subsequent tasks for that spec use this updated snapshot as their ratchet baseline. Proceed to wave 1.
81
92
  3. **On failure (TASK_STATUS:fail) with default model:** Retry ONCE with `Agent(model="opus", ...)` using the same Bootstrap prompt.
82
93
  - Opus success → re-snapshot (same command above) → proceed to wave 1.
83
94
  - Opus failure → halt with message: `"Bootstrap failed with both default and Opus — manual intervention required"`. Do not proceed.
@@ -137,17 +148,19 @@ Context ≥50% → checkpoint and exit. Before spawning: `TaskUpdate(status: "in
137
148
 
138
149
  **Token tracking start:** Store `start_percentage` (from context.json) and `start_timestamp` (ISO 8601) keyed by task_id. Omit if unavailable.
139
150
 
140
- **NEVER use `isolation: "worktree"`.** Deepflow manages a shared worktree so wave 2 sees wave 1 commits. **Spawn ALL ready tasks in ONE message** except file conflicts.
151
+ **NEVER use `isolation: "worktree"`.** Deepflow manages one worktree **per spec** (§1.5). Tasks from the same spec commit to the same branch so wave 2 sees wave 1 commits; tasks from different specs commit to different branches and never interleave. **Spawn ALL ready tasks in ONE message** except file conflicts.
141
152
 
142
- **File conflicts (1 file = 1 writer):** Check `Files:` from wave-runner JSON output or from mini-plan detail files (`.deepflow/plans/doing-{specName}.md`). Overlap spawn lowest-numbered only; rest stay pending. Log: `" T{N} deferred — file conflict with T{M} on {filename}"`
153
+ **Per-spec routing (CRITICAL):** Each task in `WAVE_JSON` carries a `spec` field (from `bin/wave-runner.js`). When building the agent prompt (§6), you MUST set `Working directory: ${SPEC_WORKTREES[task.spec].path}` — the worktree for that task's spec, NOT the first spec in the map. Cross-spec contamination (spawning a task from spec B into spec A's worktree) corrupts branch history and breaks `/df:verify`. If `task.spec` is absent from the JSON, fall back to deriving it from the task's mini-plan file `.deepflow/plans/doing-{specName}.md`; if still unresolvable, defer the task and log `" T{N} deferred — cannot resolve spec"`.
143
154
 
144
- **≥2 [SPIKE] tasks same problem →** Parallel Spike Probes (§5.7). **[OPTIMIZE] tasks →** Optimize Cycle (§5.9), one at a time.
155
+ **File conflicts (1 file = 1 writer):** Check `Files:` from wave-runner JSON output or from mini-plan detail files (`.deepflow/plans/doing-{specName}.md`). File-conflict rule applies **only within the same spec** — two tasks from different specs touching files with identical paths are actually in different worktrees and cannot collide. Overlap within a spec → spawn lowest-numbered only; rest stay pending. Log: `"⏳ T{N} deferred — file conflict with T{M} on {filename}"`
156
+
157
+ **≥2 [SPIKE] tasks same problem →** Parallel Spike Probes (§5.7). **[OPTIMIZE] tasks →** Optimize Cycle (§5.9), one at a time. **[INTEGRATION] tasks** (`task.isIntegration === true` in WAVE_JSON) **→** use the Integration Task prompt template (§6 Integration Task), not the Standard Task template. Integration tasks always land in the final wave via `Blocked by:` — wave-runner guarantees this, so they execute after all producer/consumer implementation tasks have committed. Route them to the **consumer spec's** worktree via `SPEC_WORKTREES[task.spec].path` (plan.md §4.8.2 places the integration task under the consumer's section header, so `task.spec` is already the consumer).
145
158
 
146
159
  ### 5.5. RATCHET CHECK
147
160
 
148
- Run `node "${HOME}/.claude/bin/ratchet.js"` in the worktree directory after each agent completes:
161
+ Run `node "${HOME}/.claude/bin/ratchet.js"` in the **task's spec worktree** after each agent completes, using that spec's snapshot file:
149
162
  ```bash
150
- node "${HOME}/.claude/bin/ratchet.js" --worktree ${WORKTREE_PATH} --snapshot .deepflow/auto-snapshot.txt --task T{N}
163
+ node "${HOME}/.claude/bin/ratchet.js" --worktree ${SPEC_WORKTREES[task.spec].path} --snapshot .deepflow/auto-snapshot-{task.spec}.txt --task T{N}
151
164
  ```
152
165
 
153
166
  The script handles all health checks internally and outputs structured JSON:
@@ -205,7 +218,7 @@ If no `DECISIONS:` line in agent output → skip silently (mechanical tasks don'
205
218
  **Edit scope validation:** `git diff HEAD~1 --name-only` vs allowed globs. Violation → revert, report.
206
219
  **Impact completeness:** diff vs Impact callers/duplicates. Gap → advisory warning (no revert).
207
220
 
208
- **Metric gate (Optimize only):** Run `eval "${metric_command}"` with cwd=`${WORKTREE_PATH}` (never `cd && eval`). Parse float (non-numeric → revert). Compare using `direction`+`min_improvement_threshold`. Both ratchet AND metric must pass → keep. Ratchet pass + metric stagnant → revert. Secondary metrics: regression > `regression_threshold` (5%) → WARNING in auto-report.md (no revert).
221
+ **Metric gate (Optimize only):** Run `eval "${metric_command}"` with cwd=`${SPEC_WORKTREES[task.spec].path}` (never `cd && eval`). Parse float (non-numeric → revert). Compare using `direction`+`min_improvement_threshold`. Both ratchet AND metric must pass → keep. Ratchet pass + metric stagnant → revert. Secondary metrics: regression > `regression_threshold` (5%) → WARNING in auto-report.md (no revert).
209
222
 
210
223
  **Token tracking result (on pass):** Read `end_percentage`. Sum token fields from `.deepflow/token-history.jsonl` between start/end timestamps (awk ISO 8601 compare). Write to `.deepflow/results/T{N}.yaml`:
211
224
  ```yaml
@@ -255,7 +268,7 @@ Git operations that produce large output (diff, stash, cherry-pick conflict outp
255
268
  **Pattern:**
256
269
  ```
257
270
  Spawn Agent(model="haiku", run_in_background=false):
258
- Working directory: {WORKTREE_PATH}
271
+ Working directory: ${SPEC_WORKTREES[task.spec].path}
259
272
  Run: {git command}
260
273
  Return exactly ONE line: "{operation}: {N lines changed / N files / outcome}"
261
274
  Do NOT output the raw diff or full command output.
@@ -330,7 +343,9 @@ REPEAT:
330
343
 
331
344
  ### 6. PER-TASK (agent prompt)
332
345
 
333
- **Common preamble (all):** `Working directory: {worktree_absolute_path}. All file ops use this path. Commit format: {type}({spec}): {desc}`
346
+ **Common preamble (all):** `Working directory: ${SPEC_WORKTREES[task.spec].path}. All file ops use this path. Commit format: {type}({spec}): {desc}`
347
+
348
+ Resolve `task.spec` from the `WAVE_JSON` entry for this task (fallback: scan `.deepflow/plans/doing-*.md` for the task's block). Never hand an agent a worktree path that belongs to a different spec.
334
349
 
335
350
  **Task detail loading (before building agent prompt):** Check for `.deepflow/plans/doing-{task_id}.md` (shell injection):
336
351
  ```
@@ -357,6 +372,17 @@ Steps (only when `Files:` list is non-empty):
357
372
 
358
373
  <!-- AC-6: Backward-compatible no-op — when neither Domain Model section exists in the spec nor Existing Types extraction yields content (EXISTING_TYPES is empty string), the Standard Task prompt contains no extra context blocks and is identical to the pre-injection baseline. Zero prompt overhead, zero tool calls for tasks that lack these context sources. -->
359
374
 
375
+ **Template selection (deterministic, from WAVE_JSON):**
376
+
377
+ | Flag | Template |
378
+ |-----------------------|------------------------------------|
379
+ | `isIntegration: true` | Integration Task (below) |
380
+ | `isSpike: true` | Spike |
381
+ | `isOptimize: true` | Optimize Task |
382
+ | (none) | Standard Task |
383
+
384
+ Read these fields from `WAVE_JSON` entries. Do NOT re-parse the task description for tags — the flags are authoritative. If `isIntegration` is true, skip Standard Task entirely and jump to Integration Task (below).
385
+
360
386
  **Standard Task** (`Agent(model="{Model}", ...)`):
361
387
  ```
362
388
  --- START ---
@@ -498,7 +524,18 @@ Skills: `atomic-commits`, `browse-fetch`. Agents: Implementation (`general-purpo
498
524
  | sonnet/medium | `Agent(model="sonnet")` | `Direct and efficient. Explain only non-obvious logic.` |
499
525
  | opus/high | `Agent(model="opus")` | _(none)_ |
500
526
 
501
- **Checkpoint:** `.deepflow/checkpoint.json`: `{"completed_tasks":["T1"],"current_wave":2,"worktree_path":"...","worktree_branch":"df/..."}`
527
+ **Checkpoint:** `.deepflow/checkpoint.json`:
528
+ ```json
529
+ {
530
+ "completed_tasks": ["T1"],
531
+ "current_wave": 2,
532
+ "spec_worktrees": {
533
+ "upload": {"path": ".deepflow/worktrees/upload", "branch": "df/upload"},
534
+ "auth": {"path": ".deepflow/worktrees/auth", "branch": "df/auth"}
535
+ }
536
+ }
537
+ ```
538
+ One entry per `doing-*` spec in scope. `--continue` rehydrates this map before wave scheduling.
502
539
 
503
540
  ## Failure Handling
504
541
 
@@ -349,11 +349,17 @@ If no shared interfaces found, return:
349
349
 
350
350
  **Skip if:** Interface Map returns "(none detected — specs are independent)".
351
351
 
352
- For each group of specs sharing interfaces, generate ONE integration task appended AFTER all spec tasks in the consolidated plan. Integration tasks are always the last wave.
352
+ For each group of specs sharing interfaces, generate ONE integration task per interface cluster.
353
+
354
+ **Placement (CRITICAL for worktree routing):** Integration tasks must be placed under the **consumer spec's** `### {consumer-spec-name}` section in the consolidated PLAN.md, NOT at the end of the file and NOT under their own header. `bin/wave-runner.js` assigns `task.spec` from the nearest preceding `### ` header, and `/df:execute` uses that field to route the task to the correct per-spec worktree (`SPEC_WORKTREES[task.spec].path`). If an integration task lands under a header that is not a real spec (e.g. `### Integration`), execute will fail to resolve a worktree and defer the task.
355
+
356
+ **Consumer selection:** The "consumer" is the spec that reads/calls the interface (e.g. frontend consumes API produced by backend → frontend is consumer). The fix-the-consumer rule in execute.md §6 Integration Task template means the integration agent will modify consumer-side code, which matches the consumer's worktree. If a cluster has multiple consumers, emit one integration task per consumer under each consumer's section.
357
+
358
+ The `[INTEGRATION]` tag is parsed deterministically by `bin/wave-runner.js` and surfaced as `isIntegration: true` in its JSON output; execute.md §6 uses that flag (not the task description) to pick the Integration Task prompt.
353
359
 
354
360
  **Integration task format:**
355
361
  ```markdown
356
- - [ ] **T{N}** [INTEGRATION]: Verify {spec_a} ↔ {spec_b} contracts
362
+ - [ ] **T{N}** [INTEGRATION]: Verify {producer_spec} ↔ {consumer_spec} contracts
357
363
  - Files: {files at integration boundaries — API handlers, adapters, shared types, migrations}
358
364
  - Integration ACs:
359
365
  - End-to-end flow: {producer} → {consumer} works with real data
@@ -221,6 +221,8 @@ Objective: ... | Approach: ... | Why it worked: ... | Files: ...
221
221
  - Don't auto-fix — add fix tasks to PLAN.md, then `/df:execute --continue`
222
222
  - Capture learnings for significant approaches
223
223
  - **Terse output** — Output ONLY the compact report format (section 3)
224
+ - **No LSP diagnostics** — Use ONLY build/test command exit codes and output for L0/L4. Do NOT use the LSP tool to collect TypeScript diagnostics — worktree environments have incomplete `node_modules` symlinks that produce false-positive module-resolution errors (2307, 2875). If the build command exits 0, L0 passes — do not second-guess it with LSP.
225
+ - **No narration of false positives** — Never output diagnostics and then explain they are false positives. If you know they are false positives, suppress them entirely. Wasted output tokens cost money.
224
226
 
225
227
  ## Post-Verification: Worktree Merge & Cleanup
226
228