devflow-kit 3.7.0 → 3.9.0

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.
Files changed (205) hide show
  1. package/CHANGELOG.md +479 -1
  2. package/README.md +15 -15
  3. package/dist/agents/code.md +6 -8
  4. package/dist/agents/design.md +1 -1
  5. package/dist/agents/fix.md +7 -8
  6. package/dist/agents/git.md +3 -3
  7. package/dist/agents/harden.md +1 -1
  8. package/dist/agents/refactor.md +2 -4
  9. package/dist/agents/review.md +4 -4
  10. package/dist/agents/skim.md +2 -2
  11. package/dist/agents/triage.md +9 -2
  12. package/dist/cli/commands/capture.js +1 -1
  13. package/dist/cli/commands/curation-move-io.js +102 -0
  14. package/dist/cli/commands/curation-move-step.js +374 -0
  15. package/dist/cli/commands/curation.js +808 -0
  16. package/dist/cli/commands/gate-level-prompts.js +428 -0
  17. package/dist/cli/commands/gates.js +35 -11
  18. package/dist/cli/commands/init-seed.js +11 -8
  19. package/dist/cli/commands/init.js +244 -57
  20. package/dist/cli/commands/memory.js +60 -13
  21. package/dist/cli/commands/prompt-io.js +37 -0
  22. package/dist/cli/commands/uninstall.js +157 -17
  23. package/dist/cli.js +2 -2
  24. package/dist/commands/bug-analysis.md +25 -36
  25. package/dist/commands/code-review.md +40 -44
  26. package/dist/commands/debug.md +5 -5
  27. package/dist/commands/dynamic-build.md +26 -25
  28. package/dist/commands/dynamic-plan.md +5 -5
  29. package/dist/commands/dynamic-profile.md +5 -5
  30. package/dist/commands/dynamic-tickets.md +5 -5
  31. package/dist/commands/explore.md +6 -6
  32. package/dist/commands/implement.md +53 -82
  33. package/dist/commands/plan.md +6 -6
  34. package/dist/commands/release.md +5 -5
  35. package/dist/commands/research.md +6 -6
  36. package/dist/commands/resolve.md +39 -41
  37. package/dist/commands/self-review.md +5 -13
  38. package/dist/core/area-pause-notice.js +44 -0
  39. package/dist/core/assets.js +20 -20
  40. package/dist/core/curate-tuning-config.js +5 -6
  41. package/dist/core/curation-move-notice.js +84 -0
  42. package/dist/core/curation-move-skip.js +35 -0
  43. package/dist/core/curation-move.js +35 -0
  44. package/dist/core/curation-queue-cleanup.js +135 -0
  45. package/dist/core/curation-store.js +99 -0
  46. package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
  47. package/dist/core/echo-value.js +11 -5
  48. package/dist/core/evidence-policy.js +11 -9
  49. package/dist/core/feature-switch.js +95 -19
  50. package/dist/core/fs-atomic.js +1 -1
  51. package/dist/core/gate-level.js +10 -2
  52. package/dist/core/git.js +16 -14
  53. package/dist/core/ledger-root.js +82 -22
  54. package/dist/core/linked-path.js +4 -4
  55. package/dist/core/manifest-lock.js +238 -0
  56. package/dist/core/manifest.js +58 -14
  57. package/dist/core/migrations.js +1 -13
  58. package/dist/core/observations.js +2 -2
  59. package/dist/core/plugins.js +19 -19
  60. package/dist/core/pool.js +26 -0
  61. package/dist/core/project-paths.js +103 -34
  62. package/dist/core/queue-drain.js +2 -2
  63. package/dist/core/tracked-areas.js +159 -0
  64. package/dist/core/tracker.js +1 -1
  65. package/dist/{learning-off → curation-off}/agents/code.md +6 -8
  66. package/dist/{learning-off → curation-off}/agents/fix.md +7 -8
  67. package/dist/{learning-off → curation-off}/agents/refactor.md +2 -4
  68. package/dist/{learning-off → curation-off}/agents/review.md +3 -3
  69. package/dist/{learning-off → curation-off}/agents/triage.md +8 -1
  70. package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
  71. package/dist/{learning-off → curation-off}/commands/code-review.md +37 -41
  72. package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
  73. package/dist/{learning-off → curation-off}/commands/dynamic-build.md +23 -22
  74. package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
  75. package/dist/{learning-off → curation-off}/commands/implement.md +50 -79
  76. package/dist/{learning-off → curation-off}/commands/plan.md +2 -2
  77. package/dist/{learning-off → curation-off}/commands/resolve.md +36 -38
  78. package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
  79. package/dist/hud/components/{learning-counts.js → curation-counts.js} +63 -17
  80. package/dist/hud/config.js +1 -1
  81. package/dist/hud/index.js +6 -6
  82. package/dist/hud/render.js +3 -3
  83. package/dist/skills/git/references/pr/post-resolution-summary.md +12 -11
  84. package/dist/skills/git/references/pr/post-review-summary.md +12 -11
  85. package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
  86. package/dist/skills/git/references/pr/validate-branch.md +8 -5
  87. package/dist/skills/git/references/publication-gate.md +6 -7
  88. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
  89. package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
  90. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
  91. package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
  92. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
  93. package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
  94. package/dist/targets/claude-code/{learning-install.js → curation-install.js} +85 -65
  95. package/dist/targets/claude-code/installer.js +15 -15
  96. package/dist/targets/claude-code/language-stamp.js +5 -5
  97. package/dist/targets/claude-code/post-install.js +2 -2
  98. package/dist/targets/claude-code/tracker-servers.js +1 -1
  99. package/package.json +3 -2
  100. package/src/assets/agents/_partials/_code.mds +11 -11
  101. package/src/assets/agents/code.mds +6 -6
  102. package/src/assets/agents/curate.md +8 -8
  103. package/src/assets/agents/debug.mds +6 -6
  104. package/src/assets/agents/design.mds +18 -18
  105. package/src/assets/agents/evaluate.md +6 -12
  106. package/src/assets/agents/fix.mds +7 -6
  107. package/src/assets/agents/git.mds +3 -3
  108. package/src/assets/agents/harden.mds +7 -7
  109. package/src/assets/agents/knowledge.mds +23 -23
  110. package/src/assets/agents/refactor.mds +2 -2
  111. package/src/assets/agents/research.mds +15 -15
  112. package/src/assets/agents/review.mds +43 -43
  113. package/src/assets/agents/skim.mds +11 -11
  114. package/src/assets/agents/triage.mds +29 -22
  115. package/src/assets/agents/validate.md +30 -50
  116. package/src/assets/commands/_partials/_decisions.mds +22 -20
  117. package/src/assets/commands/_partials/_engine.mds +8 -8
  118. package/src/assets/commands/_partials/_knowledge.mds +29 -15
  119. package/src/assets/commands/_partials/_preamble.mds +4 -4
  120. package/src/assets/commands/_partials/_settings.mds +3 -3
  121. package/src/assets/commands/_partials/_wave.mds +7 -7
  122. package/src/assets/commands/bug-analysis.mds +37 -48
  123. package/src/assets/commands/code-review.mds +53 -57
  124. package/src/assets/commands/debug.mds +14 -14
  125. package/src/assets/commands/dynamic-build.mds +38 -37
  126. package/src/assets/commands/dynamic-plan.mds +22 -22
  127. package/src/assets/commands/dynamic-profile.mds +2 -2
  128. package/src/assets/commands/dynamic-tickets.mds +8 -8
  129. package/src/assets/commands/explore.mds +20 -20
  130. package/src/assets/commands/implement.mds +77 -98
  131. package/src/assets/commands/plan.mds +27 -27
  132. package/src/assets/commands/release.mds +12 -12
  133. package/src/assets/commands/research.mds +19 -19
  134. package/src/assets/commands/resolve.mds +66 -60
  135. package/src/assets/commands/self-review.mds +20 -20
  136. package/src/assets/mds/git/_ops.mds +9 -2
  137. package/src/assets/mds/git/_ops_pr.mds +21 -11
  138. package/src/assets/mds/git/_ops_steps.mds +20 -4
  139. package/src/assets/mds/git/_pr.mds +21 -18
  140. package/src/assets/mds/git/_references.mds +6 -7
  141. package/src/assets/mds/tracker/_github.mds +1 -4
  142. package/src/assets/mds/tracker/_jira.mds +1 -4
  143. package/src/assets/mds/tracker/_linear.mds +1 -4
  144. package/src/assets/rules/quality.md +1 -0
  145. package/src/assets/scripts/ci-wait.cjs +389 -92
  146. package/src/assets/scripts/hooks/background-memory-update +81 -3
  147. package/src/assets/scripts/hooks/capture-prompt +12 -12
  148. package/src/assets/scripts/hooks/capture-question +11 -11
  149. package/src/assets/scripts/hooks/capture-turn +12 -12
  150. package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
  151. package/src/assets/scripts/hooks/curation-move +293 -0
  152. package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
  153. package/src/assets/scripts/hooks/ensure-proxy +4 -4
  154. package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
  155. package/src/assets/scripts/hooks/git-marker +81 -0
  156. package/src/assets/scripts/hooks/json-helper.cjs +176 -51
  157. package/src/assets/scripts/hooks/json-parse +1 -1
  158. package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
  159. package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +308 -122
  160. package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
  161. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
  162. package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
  163. package/src/assets/scripts/hooks/lib/render-decisions.cjs +42 -22
  164. package/src/assets/scripts/hooks/lib/tracked-areas.cjs +452 -0
  165. package/src/assets/scripts/hooks/memory-permission +10 -2
  166. package/src/assets/scripts/hooks/memory-worker +12 -1
  167. package/src/assets/scripts/hooks/pre-compact-memory +17 -16
  168. package/src/assets/scripts/hooks/queue-append +444 -102
  169. package/src/assets/scripts/hooks/resolve-project-root +2 -2
  170. package/src/assets/scripts/hooks/session-start-context +198 -72
  171. package/src/assets/scripts/hooks/session-start-memory +42 -43
  172. package/src/assets/scripts/lib/project-config.cjs +12 -5
  173. package/src/assets/scripts/pr-evidence.cjs +5 -2
  174. package/src/assets/scripts/resolve-base.cjs +1043 -0
  175. package/src/assets/scripts/resolve-settings.cjs +283 -45
  176. package/src/assets/scripts/verify-evidence.cjs +307 -75
  177. package/src/assets/skills/agent-references/SKILL.md +11 -2
  178. package/src/assets/skills/agent-references/references/commands/implement-levels.md +29 -0
  179. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
  180. package/src/assets/skills/agent-references/references/commands/resolve-levels.md +25 -0
  181. package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
  182. package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
  183. package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -0
  184. package/src/assets/skills/apply-decisions/SKILL.md +1 -1
  185. package/src/assets/skills/docs-framework/SKILL.md +7 -7
  186. package/src/assets/skills/docs-framework/references/patterns.md +3 -4
  187. package/src/assets/skills/quality-gates/SKILL.md +1 -1
  188. package/src/assets/skills/worktree-support/references/roots.md +2 -2
  189. package/templates/lint-pack/.lint-pack.conf +86 -0
  190. package/templates/lint-pack/README.md +190 -0
  191. package/templates/lint-pack/lint-pack.sh +905 -0
  192. package/dist/cli/commands/learning.js +0 -554
  193. package/dist/core/learning-queue-cleanup.js +0 -27
  194. package/dist/core/learning-store.js +0 -61
  195. /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
  196. /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
  197. /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
  198. /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
  199. /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
  200. /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
  201. /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
  202. /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
  203. /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
  204. /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
  205. /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
@@ -3,7 +3,7 @@ description: Proactive bug finding with static and semantic analysis — hunts r
3
3
  ---
4
4
  # Bug Analysis Command
5
5
 
6
- Run a proactive bug analysis on the current branch by combining static analysis tools with parallel semantic analyzers, then synthesizing results into an actionable bug report. Supports incremental analysis, timestamped report directories, and `/resolve` compatibility.
6
+ Run a proactive bug analysis on the current branch by combining static analysis tools with parallel semantic analyzers, then synthesizing results into an actionable bug report.
7
7
 
8
8
  ## Usage
9
9
 
@@ -55,16 +55,16 @@ Agent(subagent_type="Git", run_in_background=false):
55
55
  PR_TEST_PLAN_BLOCK: {PR_TEST_PLAN_BLOCK verbatim, or (none)}
56
56
  APPLY_CONVENTIONS: {APPLY_CONVENTIONS}
57
57
  Validate branch, commit if needed, push, create PR if needed.
58
- Return: branch, base_branch, branch-slug, PR#"
58
+ Return: branch, base_branch, base_ref, branch-slug, PR#"
59
59
  ```
60
60
 
61
- **Extract from response:** `branch`, `base_branch`, `branch_slug`, `pr_number`.
61
+ **Extract from response:** `branch`, `base_branch`, `base_ref`, `branch_slug`, `pr_number`. Stop with cause and fix when the Base line reads (rejected) or `branch_slug` does not match the slug pattern above; name the Git agent's fix, never your own, and for the slug the allowed characters, never its value.
62
62
 
63
63
  **Fetch PR body** (after extracting `pr_number`):
64
64
  ```bash
65
65
  PR_DESCRIPTION=$(gh pr view {pr_number} --json body --jq '.body' 2>/dev/null || echo "(none)")
66
66
  ```
67
- If `pr_number` is absent or the command fails, set `PR_DESCRIPTION` to `(none)`.
67
+ If `pr_number` is absent, set `PR_DESCRIPTION` to `(none)`.
68
68
 
69
69
  ### Phase 2: Static Analysis
70
70
 
@@ -75,14 +75,15 @@ If `pr_number` is absent or the command fails, set `PR_DESCRIPTION` to `(none)`.
75
75
  **Produces:** DIFF_RANGE, ANALYSIS_DIR
76
76
  **Requires:** BRANCH_INFO
77
77
 
78
- 1. Check `{worktree}/.devflow/docs/bug-analysis/{branch-slug}/.last-analysis-head`:
78
+ 1. Check `{gd}/devflow/bug-analysis/{branch-slug}/.last-analysis-head`, `{gd}` being what `git -C "{worktree}" rev-parse --absolute-git-dir` prints:
79
79
  - **If exists AND `--full` NOT set:**
80
- - Read the SHA from the file
81
- - Verify reachable: `git cat-file -t {sha}` — if exit code non-zero (rebase invalidated SHA), fall through to full
80
+ - The stored analysis head is checked as a commit ID: use it only when it is exactly 40 or 64 lowercase hex, else take the full-range path
81
+ - Verify reachable: `git -C "{worktree}" cat-file -t {sha}` must print `commit`; if not (rebase invalidated SHA), take the full-range path
82
82
  - If SHA == current HEAD → "No new commits since last analysis. Use --full for a full re-analysis." Stop.
83
83
  - Set `DIFF_RANGE` to `{sha}...HEAD`
84
- - **If not exists, unreachable SHA, or `--full`:**
85
- - Set `DIFF_RANGE` to `{base_branch}...HEAD`
84
+ - **Full-range path (no marker, or `--full`):**
85
+ - Stop as for a (rejected) Base line unless `base_ref` is 40 or 64 hex.
86
+ - Set `DIFF_RANGE` to `{base_ref}...HEAD`
86
87
  2. Generate timestamp: `YYYY-MM-DD_HHMM`. If directory already exists (same-minute collision), append seconds (`YYYY-MM-DD_HHMMSS`).
87
88
  3. Create timestamped analysis directory: `mkdir -p "{worktree}/.devflow/docs/bug-analysis/{branch-slug}/{timestamp}/"`
88
89
  4. Set `ANALYSIS_DIR` to that path.
@@ -95,7 +96,7 @@ If `pr_number` is absent or the command fails, set `PR_DESCRIPTION` to `(none)`.
95
96
  CHANGED_FILES=$(git diff --name-only {DIFF_RANGE})
96
97
  ```
97
98
 
98
- Store result as `CHANGED_FILES` — used throughout Steps 2d and Phase 4 to avoid repeated git invocations and ensure consistency.
99
+ Store as `CHANGED_FILES`; Steps 2d and Phase 4 read it. A non-zero diff exit stops.
99
100
 
100
101
  If output is empty → "No changes to analyze." Stop.
101
102
 
@@ -124,7 +125,7 @@ Skip if `--no-static` flag provided or all tools unavailable.
124
125
 
125
126
  Run available tools on the changed files (from `CHANGED_FILES` computed in Step 2b).
126
127
 
127
- **Semgrep and Snyk run in parallel** — launch both in the background, then wait for both before proceeding to CodeQL. CodeQL is conditional and sequential (it needs Semgrep/Snyk results to decide whether to run).
128
+ **Semgrep and Snyk run in parallel** — launch both in the background, then wait for both before proceeding to CodeQL. CodeQL is conditional and sequential.
128
129
 
129
130
  **Semgrep** (if available):
130
131
  ```bash
@@ -135,11 +136,9 @@ Parse SARIF output → extract findings.
135
136
 
136
137
  **Snyk Code** (if available):
137
138
  ```bash
138
- # Run a single project-level scan; filter SARIF results programmatically to CHANGED_FILES before LLM processing.
139
- # Per-file invocation via xargs would invoke snyk O(n) times and --file is for dependency scanning, not source code.
139
+ # One project-level scan (a per-file xargs run would invoke snyk O(n) times; --file is for dependency scans).
140
140
  SNYK_SARIF=$(timeout 300 snyk code test --sarif 2>/dev/null)
141
- # Programmatic filter: keep only results whose file path appears in CHANGED_FILES.
142
- # This is defense-in-depth — filtering at the data layer before the LLM agent sees any output.
141
+ # Programmatic filter: keep only results whose file path is in CHANGED_FILES — defense-in-depth, before the LLM agent sees any output.
143
142
  SNYK_FILTERED=$(echo "$SNYK_SARIF" | jq --argjson files "$(echo "$CHANGED_FILES" | jq -R . | jq -s .)" \
144
143
  '.runs[].results |= map(select(.locations[].physicalLocation.artifactLocation.uri as $uri | $files | index($uri) != null))' \
145
144
  2>/dev/null || echo "$SNYK_SARIF")
@@ -165,7 +164,7 @@ trap - EXIT INT TERM
165
164
  ```
166
165
  Parse `CODEQL_SARIF` → extract findings. If database creation fails, `CODEQL_EXIT` is non-zero — skip CodeQL findings and note it. The `trap` guarantees cleanup on SIGTERM/SIGINT/EXIT so orphaned temp directories do not accumulate across interrupted sessions.
167
166
 
168
- **Normalize** all findings to unified table, cap at top 50 by severity. Truncate each Description entry to 200 characters maximum to bound the serialized size of `STATIC_FINDINGS`:
167
+ **Normalize** all findings to unified table, cap at top 50 by severity. Truncate each Description entry to 200 characters maximum:
169
168
 
170
169
  | Tool | File:Line | CWE | Severity | Title | Description |
171
170
  |------|-----------|-----|----------|-------|-------------|
@@ -306,13 +305,14 @@ Output: {ANALYSIS_DIR}/bug-analysis-summary.md"
306
305
 
307
306
  **Requires:** BRANCH_INFO, ANALYSIS_DIR
308
307
 
309
- 1. Write current HEAD SHA to `{worktree}/.devflow/docs/bug-analysis/{branch-slug}/.last-analysis-head`
308
+ 1. In one Bash call run `M="$(git -C "{worktree}" rev-parse --absolute-git-dir)/devflow/bug-analysis/{branch-slug}" && mkdir -p "$M" && git -C "{worktree}" rev-parse HEAD > "$M/.last-analysis-head"`. A failed or refused write never stops the run and is not retried another way.
310
309
  2. Report to user:
311
310
 
312
311
  ```
313
312
  ## Bug Analysis Complete
314
313
 
315
314
  **Branch**: {branch} -> {base_branch}
315
+ {any base `TRACEABILITY: DEGRADED` line from Phase 1, as returned}
316
316
  **Analysis**: {ANALYSIS_DIR}
317
317
 
318
318
  ### Risk Assessment: {risk_level}
@@ -348,11 +348,7 @@ Run `/resolve` to process and fix these findings.
348
348
  │ └─ Git agent (ensure-pr-ready)
349
349
  │
350
350
  ├─ Phase 2: Static Analysis
351
- │ ├─ Step 2a: Incremental detection + timestamp setup
352
- │ ├─ Step 2b: Check changed files (stop if none)
353
- │ ├─ Step 2c: Tool availability check
354
- │ └─ Step 2d: Tiered static analysis (semgrep → snyk → codeql)
355
- │ Write static-findings.md
351
+ │ └─ 2a incremental range, 2b changed files (stop if none), 2c tools, 2d semgrep → snyk → codeql
356
352
  │
357
353
  ├─ Phase 3: Context Loading
358
354
  │ ├─ Feature knowledge load → FEATURE_KNOWLEDGE
@@ -362,10 +358,7 @@ Run `/resolve` to process and fix these findings.
362
358
  │ └─ Detect active focuses (security + functional always; integration + usability conditional)
363
359
  │
364
360
  ├─ Phase 5: Bug Analysis (PARALLEL)
365
- │ ├─ Debug agent: security (+ STATIC_FINDINGS)
366
- │ ├─ Debug agent: functional
367
- │ ├─ Debug agent: integration (conditional)
368
- │ └─ Debug agent: usability (conditional)
361
+ │ └─ Debug agent per active focus (security also reads STATIC_FINDINGS)
369
362
  │
370
363
  ├─ Phase 6: Synthesis
371
364
  │ └─ Synthesize agent (mode: bug-analysis)
@@ -381,16 +374,12 @@ Run `/resolve` to process and fix these findings.
381
374
  |------|----------|
382
375
  | No new commits since last analysis | Stop: "No new commits since last analysis. Use --full for a full re-analysis." |
383
376
  | Rebase invalidates `.last-analysis-head` SHA | `git cat-file -t` check fails → fallback to full diff |
384
- | Zero changed files in DIFF_RANGE | Stop: "No changes to analyze." |
377
+ | Zero changed files in DIFF_RANGE with exit 0 | Stop: "No changes to analyze." |
385
378
  | Same-minute analysis collision | `mkdir` with seconds suffix (`YYYY-MM-DD_HHMMSS`) |
386
379
  | All static tools unavailable | Warn, proceed with semantic-only analysis |
387
380
  | `--no-static` flag | Skip Phase 2c and 2d entirely; `STATIC_FINDINGS=(none)` |
388
381
  | `--full` flag | Bypass incremental detection (Step 2a), run full diff from base |
389
382
  | CodeQL database creation fails | Skip CodeQL, note in output, continue with other tools |
390
- | Static tool produces no findings | `STATIC_FINDINGS=(none)` — normal, proceed with semantic analysis |
391
- | No plan artifact found | `PLAN_CONTEXT=(none)`, `ACCEPTANCE_RULES=(none)` — proceed without acceptance criteria |
392
- | `integration` focus skipped | Not spawned when only 1 directory changed |
393
- | `usability` focus skipped | Not spawned when no UI files changed |
394
383
 
395
384
  ## Resolve Compatibility
396
385
 
@@ -3,7 +3,7 @@ description: Comprehensive branch review using specialized sub-agents for PR rea
3
3
  ---
4
4
  # Code Review Command
5
5
 
6
- Run a comprehensive code review of the current branch by spawning parallel review agents, then synthesizing results into PR comments. Supports incremental reviews, timestamped report directories, and multi-worktree auto-discovery.
6
+ Run a comprehensive code review of the current branch by spawning parallel review agents, then synthesizing results into PR comments.
7
7
 
8
8
  ## Usage
9
9
 
@@ -27,21 +27,21 @@ Run a comprehensive code review of the current branch by spawning parallel revie
27
27
  - **More than one listed:** Invoke the `devflow:worktree-support` skill, then Read its `references/discovery.md` from the skill's base directory for the full 7-step algorithm; the canonical protected branch list stays in the skill. Parse the list, filter (skip protected/detached/mid-rebase), dedup by branch, sort by recent commit
28
28
  - **Exactly one listed:** load neither the skill nor the reference — that worktree is the only candidate, and Step 0c's Git agent validates its branch
29
29
  2. **If `--path` flag provided:** use only that worktree, skip discovery
30
- **`--path` validation**: Before proceeding, verify the path exists as a directory and appears in `git worktree list` output. If not: report error and stop.
31
- 3. **If only 1 reviewable worktree**: proceed as single-worktree flow — zero behavior change
30
+ **`--path` validation**: the path must be a directory in `git worktree list` output; if not, report an error and stop.
31
+ 3. **If only 1 reviewable worktree**: proceed as single-worktree flow
32
32
  4. **If multiple reviewable worktrees:** report "Found N worktrees with reviewable branches: {list with paths and branches}" and proceed with multi-worktree flow
33
33
 
34
34
  #### Step 0b: Resolve the compliance lens and gate level
35
35
 
36
36
  **Produces:** COMPLIANCE_ACTIVE, COMPLIANCE_FRAMEWORKS, GATE_LEVEL
37
37
 
38
- **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
38
+ **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root in a one-worktree run:
39
39
 
40
40
  ```bash
41
41
  node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
42
42
  ```
43
43
 
44
- Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> LEARNING=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on LEARNING=on KNOWLEDGE=off GATE_LEVEL=full` instead.
44
+ Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> CURATION=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=off CURATION=off KNOWLEDGE=off GATE_LEVEL=full` instead.
45
45
 
46
46
  The accepted line is the only source of these values.
47
47
 
@@ -97,20 +97,20 @@ PR_DESCRIPTION_GUIDANCE: {pr_description_guidance}
97
97
  PR_TEST_PLAN_BLOCK: {PR_TEST_PLAN_BLOCK verbatim, or (none)}
98
98
  APPLY_CONVENTIONS: {APPLY_CONVENTIONS}
99
99
  Validate branch, commit if needed, push, create PR if needed.
100
- Return: branch, base_branch, branch-slug, PR#"
100
+ Return: branch, base_branch, base_ref, branch-slug, PR#"
101
101
  ```
102
102
 
103
103
  In multi-worktree mode, spawn all pre-flight agents **in a single message**.
104
104
 
105
105
  **If BLOCKED:** In single-worktree mode, stop and report. In multi-worktree mode, report the failure but continue with other worktrees.
106
106
 
107
- **Extract from response:** `branch`, `base_branch`, `branch_slug`, `pr_number` per worktree.
107
+ **Extract from response:** `branch`, `base_branch`, `base_ref`, `branch_slug`, `pr_number` per worktree. Stop with cause and fix when the Base line reads (rejected) or `branch_slug` does not match the slug pattern above; name the Git agent's fix, never your own, and for the slug the allowed characters, never its value. In multi-worktree mode this blocks only that worktree.
108
108
 
109
109
  **Fetch PR body** (after extracting `pr_number`):
110
110
  ```bash
111
111
  PR_DESCRIPTION=$(gh pr view {pr_number} --json body --jq '.body' 2>/dev/null || echo "(none)")
112
112
  ```
113
- If `pr_number` is absent or the command fails, set `PR_DESCRIPTION` to `(none)`.
113
+ If `pr_number` is absent, set `PR_DESCRIPTION` to `(none)`.
114
114
 
115
115
  #### Step 0d: Incremental Detection & Timestamp Setup
116
116
 
@@ -120,26 +120,27 @@ If `pr_number` is absent or the command fails, set `PR_DESCRIPTION` to `(none)`.
120
120
  For each worktree:
121
121
 
122
122
  1. Generate timestamp: `YYYY-MM-DD_HHMM`. If directory already exists (same-minute collision), append seconds (`YYYY-MM-DD_HHMMSS`).
123
- 2. Create timestamped review directory: `mkdir -p {worktree}/.devflow/docs/reviews/{branch-slug}/{timestamp}/`
124
- 3. Check if `{worktree}/.devflow/docs/reviews/{branch-slug}/.last-review-head` exists:
123
+ 2. Check if `{gd}/devflow/reviews/{branch-slug}/.last-review-head` exists, `{gd}` being what `git -C "{worktree}" rev-parse --absolute-git-dir` prints:
125
124
  - **If yes AND `--full` NOT set:**
126
- - Read the SHA from the file
127
- - Verify reachable: `git -C {worktree} cat-file -t {sha}` (handles rebases — if unreachable, fallback to full)
125
+ - The stored review head is checked as a commit ID: use it only when it is exactly 40 or 64 lowercase hex, else take the full-range path
126
+ - Verify reachable: `git -C "{worktree}" cat-file -t {sha}` must print `commit` (else take the full-range path)
128
127
  - Check if SHA == current HEAD → if so, skip review: "No new commits since last review. Use --full for a full re-review."
129
128
  - Set `DIFF_RANGE` to `{last-review-sha}...HEAD`
130
- - **If no (first review), or `--full`:**
131
- - Set `DIFF_RANGE` to `{base_branch}...HEAD`
129
+ - **Full-range path (no marker, or `--full`):**
130
+ - Stop as for a (rejected) Base line unless `base_ref` is 40 or 64 hex.
131
+ - Set `DIFF_RANGE` to `{base_ref}...HEAD`
132
+ 3. Create timestamped review directory: `mkdir -p {worktree}/.devflow/docs/reviews/{branch-slug}/{timestamp}/`
132
133
 
133
134
  #### Step 0e-i: Load Prior Resolution and Count Cycles
134
135
 
135
136
  **Produces:** PRIOR_RESOLUTIONS, CYCLE_NUMBER
136
137
  **Requires:** BRANCH_INFO
137
138
 
138
- For each worktree, perform a single pass over timestamped directories:
139
- 1. List timestamped directories in `{worktree}/.devflow/docs/reviews/{branch-slug}/` sorted descending: `ls -1d {worktree}/.devflow/docs/reviews/{branch-slug}/20* 2>/dev/null | sort -r`
140
- 2. Iterate once: accumulate CYCLE_NUMBER count for each directory containing `resolution-summary.md`; capture the first (most-recent) such directory as PRIOR_DIR.
139
+ For each worktree:
140
+ 1. List the timestamped directories: `ls -1d {worktree}/.devflow/docs/reviews/{branch-slug}/20* 2>/dev/null | sort -r`
141
+ 2. Iterate once: count each directory containing `resolution-summary.md` as CYCLE_NUMBER; capture the first (newest) as PRIOR_DIR.
141
142
  3. If CYCLE_NUMBER = 0: set PRIOR_RESOLUTIONS=(none), CYCLE_NUMBER=1, proceed.
142
- 4. Otherwise: set CYCLE_NUMBER = count + 1. Read `{PRIOR_DIR}/resolution-summary.md` as PRIOR_RESOLUTIONS.
143
+ 4. Otherwise: set CYCLE_NUMBER = count + 1. Read `{PRIOR_DIR}/resolution-summary.md` as PRIOR_RESOLUTIONS minus each section from a heading line (fenced or not) with the word `Private` (any case) not followed by a letter or digit, to the next unfenced heading with as many `#` as its start or fewer, else at end of file. Close any fence it leaves open. When unsure, cut more; if the cut fails, PRIOR_RESOLUTIONS=(none).
143
144
  5. If `--full`: still load PRIOR_RESOLUTIONS.
144
145
 
145
146
  #### Step 0e-ii: Convergence Assessment
@@ -157,7 +158,7 @@ MAX_REVIEW_CYCLES = 10
157
158
  - `deferred_count` = the Deferred row value (= FIX_SEPARATE + TECH_DEBT); By Design and Escalated are excluded from the fp_ratio denominator
158
159
  - fp_ratio = fp_count / (fp_count + fixed_count + deferred_count)
159
160
  - If denominator = 0: fp_ratio = 0, skip warning
160
- - If parsing fails: fp_ratio = 0, skip warning; note in output: "Warning: Could not parse Statistics table from prior resolution. FP ratio unavailable — convergence tracking degraded."
161
+ - If parsing fails: fp_ratio = 0, skip warning; note in output: "Warning: Could not parse Statistics table. Convergence tracking degraded."
161
162
  3. If fp_ratio > 0.7 AND CYCLE_NUMBER >= 3:
162
163
  Warn in output: "⚠️ Convergence: {ratio}% false positives in cycle {N-1}. Consider merging or manual inspection."
163
164
  Continue with review.
@@ -199,10 +200,11 @@ List the changed paths into `{REVIEW_DIR}/paths.txt` and print only the line cou
199
200
 
200
201
  ```bash
201
202
  git -C "{WORKTREE_PATH}" diff --name-only --no-renames {DIFF_RANGE} > "{REVIEW_DIR}/paths.txt"
203
+ echo "exit=$?"
202
204
  wc -l < "{REVIEW_DIR}/paths.txt"
203
205
  ```
204
206
 
205
- Read `paths.txt` once when the count is about 40 or fewer, and in ranges of about 40 lines (Read with offset and limit) when it is more.
207
+ The `exit=` line is the diff's. A non-zero diff exit stops. Read `paths.txt` once when the count is about 40 or fewer, and in ranges of about 40 lines (Read with offset and limit) when it is more.
206
208
 
207
209
  Set `DIFF_CLASS` by these rules, in order; the first that decides wins:
208
210
 
@@ -276,7 +278,7 @@ Write `diff.patch` once per worktree, by redirect:
276
278
  git -C "{WORKTREE_PATH}" diff --no-color --no-ext-diff --no-textconv {DIFF_RANGE} > "{REVIEW_DIR}/diff.patch"
277
279
  ```
278
280
 
279
- It holds the whole diff of `DIFF_RANGE`. On a `code` diff, add one patch for each language focus in REVIEW_FOCUS_LIST, by the same command with quoted pathspecs from this table:
281
+ It holds the whole diff of `DIFF_RANGE`. A non-zero exit of any patch write stops. On a `code` diff, add one patch for each language focus in REVIEW_FOCUS_LIST, by the same command with quoted pathspecs from this table:
280
282
 
281
283
  ```bash
282
284
  git -C "{WORKTREE_PATH}" diff --no-color --no-ext-diff --no-textconv {DIFF_RANGE} -- '<pathspec>' ... > "{REVIEW_DIR}/diff-{focus}.patch"
@@ -388,7 +390,7 @@ Spawn Review agents **in a single message**, one per focus in REVIEW_FOCUS_LIST
388
390
  | documentation | conditional | devflow:documentation | diff.patch |
389
391
  | compliance | diff-driven | devflow:compliance | diff.patch |
390
392
 
391
- Review agent count: at `full`, `code` runs 8 core +1-12; `standard` runs up to 2 fewer; lean `code` runs 3-4; a reduced class or a mix runs 2-6.
393
+ Review agent count: `code` runs 8 core +1-12 at `full`, up to 2 fewer at `standard`, 3-4 at lean; a reduced class or a mix runs 2-6.
392
394
 
393
395
  **Set the compliance focus directive** from `COMPLIANCE_FRAMEWORKS`. `COMPLIANCE_FOCUS_DIRECTIVE` is this one line, with `<ids>` replaced by the value of `COMPLIANCE_FRAMEWORKS`; only the compliance focus's spawn carries it:
394
396
 
@@ -412,7 +414,7 @@ Follow devflow:apply-feature-knowledge for FEATURE_KNOWLEDGE — feature-specifi
412
414
  IMPORTANT: Write report to {worktree_path}/.devflow/docs/reviews/{branch-slug}/{timestamp}/{focus}.md using Write tool"
413
415
  ```
414
416
 
415
- In multi-worktree mode, process worktrees **sequentially**: complete Phases 1-4 for one worktree before starting the next, so Review spawns never pile up.
417
+ In multi-worktree mode, process worktrees **sequentially**: Phases 1-4 for each.
416
418
 
417
419
  ### Phase 3: Synthesis → Review Comment (Sequential)
418
420
 
@@ -447,7 +449,7 @@ CYCLE_NUMBER: {cycle_number}
447
449
  REVIEW_TIMESTAMP: {timestamp}
448
450
  REVIEW_PUBLICATION: {REVIEW_PUBLICATION}
449
451
  WORKTREE_PATH: {worktree_path} (omit if cwd)
450
- Posts the consolidated review-summary comment (marker-deduped by the operation on the cycle+timestamp pair)."
452
+ Posts the consolidated review-summary comment (marker-deduped on the cycle+timestamp pair)."
451
453
  ```
452
454
 
453
455
  ### Phase 4: Write Review Head Marker & Report
@@ -455,34 +457,29 @@ Posts the consolidated review-summary comment (marker-deduped by the operation o
455
457
  **Requires:** BRANCH_INFO, REVIEW_DIR
456
458
 
457
459
  Per worktree, after successful completion:
458
- 1. Write current HEAD SHA to `{worktree_path}/.devflow/docs/reviews/{branch-slug}/.last-review-head`
460
+ 1. In one Bash call run `M="$(git -C "{worktree_path}" rev-parse --absolute-git-dir)/devflow/reviews/{branch-slug}" && mkdir -p "$M" && git -C "{worktree_path}" rev-parse HEAD > "$M/.last-review-head"`. A failed or refused write never stops the run and is not retried another way.
459
461
  2. Display results from all agents:
460
462
  - Merge recommendation (from Synthesize agent)
461
463
  - Issue counts by category (🔴 blocking / ⚠️ should-fix / ℹ️ pre-existing)
462
464
  - Review comment status: POSTED / POSTED+TRUNCATED (body exceeded 60k after redaction) / SKIPPED (D7 dedup) / DEGRADED (from Git)
463
465
  - Publication status: FULL (private repo) | FULL (config override) | STUB (public repository) | OFF (publication disabled by config) | STUB (visibility undeterminable) | STUB (evidence policy) (from Git agent)
466
+ - Any base `TRACEABILITY: DEGRADED` line from Step 0c, as returned
464
467
  - Artifact paths
465
468
 
466
469
  ## Architecture
467
470
 
468
471
  ```
469
- /code-review (orchestrator - spawns agents only)
472
+ /code-review
470
473
  │
471
- ├─ Phase 0: Worktree Discovery & Pre-flight
472
- │ ├─ Step 0a: git worktree list → filter reviewable
474
+ ├─ Phase 0: Worktree Discovery & Pre-flight, Steps 0a-0f (Git agent per worktree, parallel)
473
475
  │ ├─ Step 0b: Resolve the compliance lens and gate level
474
- │ ├─ Step 0c: Git agent (ensure-pr-ready) per worktree [parallel]
475
- │ ├─ Step 0d: Incremental detection + timestamp setup per worktree
476
- │ ├─ Step 0e-i: Load prior resolution-summary.md
477
- │ ├─ Step 0e-ii: Convergence assessment (warn only)
478
- │ └─ Step 0f: Resolve REVIEW_PUBLICATION per worktree (publication gate)
479
476
  │
480
- ├─ Per worktree (SEQUENTIAL — one worktree at a time):
477
+ ├─ Per worktree (SEQUENTIAL):
481
478
  │ │
482
479
  │ ├─ Phase 1: Analyze changed files
483
480
  │ │ ├─ Classify the diff (paths.txt → DIFF_CLASS)
484
- │ │ ├─ Choose the focuses (code: file types + the language stamp; all: the gate level)
485
- │ │ └─ Write diff.patch and diff-{focus}.patch, by redirect
481
+ │ │ ├─ Choose the focuses
482
+ │ │ └─ Write diff.patch and diff-{focus}.patch
486
483
  │ │
487
484
  │ ├─ Phase 2: Reviews (PARALLEL within worktree), one per REVIEW_FOCUS_LIST focus
488
485
  │ │ ├─ code: core (diff.patch), language (diff-{focus}.patch), conditional
@@ -500,7 +497,7 @@ Per worktree, after successful completion:
500
497
  | Case | Handling |
501
498
  |------|----------|
502
499
  | `--full` in multi-worktree mode | Applies to all worktrees (global modifier). It sets the diff range, never the gate level |
503
- | Review comment already posted | Git agent matches its own marker on the `{cycle, timestamp}` pair → skip silently (D7); a re-review in the same cycle (different timestamp) posts its own comment. The marker's format belongs to the operation; this caller passes the pair and never restates the literal |
500
+ | Review comment already posted | Git agent matches its own marker on the `{cycle, timestamp}` pair → skip silently (D7) |
504
501
  | Docs-only diff | Its Step 1.1 set, narrowed by level |
505
502
  | Tests-only diff | Its Step 1.1 set, narrowed by level |
506
503
  | Lockfile-only diff | Its Step 1.1 set, narrowed by level |
@@ -509,8 +506,7 @@ Per worktree, after successful completion:
509
506
  | A file moved across classes | `--no-renames` lists both paths, so a code path makes the diff `code` |
510
507
  | Path listing above about 40 lines | Read in ranges (Step 1.1) |
511
508
  | Language files changed, plugin not installed | The focus is absent from the stamped line: not spawned |
512
- | Public repo (`auto` mode) | Git agent probes visibility, posts counts-only STUB comment; full report stays local |
513
- | `reviewPublication: off` | Git agent skips posting the review comment (OFF) — except that, only when `EVIDENCE_POLICY` is `required`, Step 0f passes `stub` and a counts-only STUB comment posts |
509
+ | `reviewPublication: off` | Git agent skips posting the review comment (OFF) — except that, only when `EVIDENCE_POLICY` is `required`, Step 0f passes `stub` and a counts-only STUB comment posts unless no public text remains |
514
510
 
515
511
  ## Backwards Compatibility
516
512
 
@@ -518,8 +514,8 @@ Per worktree, after successful completion:
518
514
 
519
515
  ## Principles
520
516
 
521
- 1. **Orchestration only** - Command spawns agents, doesn't do git/review work itself. The exceptions are the read-only Phase 0 probes (`git worktree list`, `git -C ... branch --show-current`, `git -C ... cat-file -t`), the bounded Phase 1 path listing (a redirect into `paths.txt`, a `wc -l` count and ranged reads) and the redirect-only patch writes (`diff.patch`, `diff-{focus}.patch`). Redirect-only writes print nothing, so no diff content reaches this thread
517
+ 1. **Orchestration only** - Command spawns agents, doesn't do git/review work itself. The exceptions are the read-only Phase 0 probes (`git worktree list`, `git -C ... branch --show-current`, `git -C ... cat-file -t`, `git -C ... rev-parse`), the bounded Phase 1 path listing and the writes (`diff.patch`, `diff-{focus}.patch`, the marker and its `mkdir -p`). These writes print nothing
522
518
  2. **Parallel, not background** - Multiple agents in one message, but `run_in_background=false` so phases complete before proceeding
523
- 3. **Git agent for git work** - All git operations go through Git agent, except the exceptions named in Principle 1; redirect-only writes print nothing
519
+ 3. **Git agent for git work** - All git operations go through Git agent, except the exceptions named in Principle 1; its writes print nothing
524
520
  4. **Honest reporting** - Display agent outputs directly
525
521
  5. **Incremental by default** - Only review new changes unless `--full` specified
@@ -30,13 +30,13 @@ $ARGUMENTS
30
30
 
31
31
  ### Phase 1: Resolve Settings
32
32
 
33
- **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
33
+ **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root in a one-worktree run:
34
34
 
35
35
  ```bash
36
36
  node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
37
37
  ```
38
38
 
39
- Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> LEARNING=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on LEARNING=on KNOWLEDGE=off GATE_LEVEL=full` instead.
39
+ Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> CURATION=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=off CURATION=off KNOWLEDGE=off GATE_LEVEL=full` instead.
40
40
 
41
41
  The accepted line is the only source of these values.
42
42
 
@@ -77,13 +77,13 @@ When a ticket requires multiple sequential Code agent phases, each Code agent ap
77
77
 
78
78
  ### Settings line
79
79
 
80
- **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
80
+ **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root in a one-worktree run:
81
81
 
82
82
  ```bash
83
83
  node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
84
84
  ```
85
85
 
86
- Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> LEARNING=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on LEARNING=on KNOWLEDGE=off GATE_LEVEL=full` instead.
86
+ Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> CURATION=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=off CURATION=off KNOWLEDGE=off GATE_LEVEL=full` instead.
87
87
 
88
88
  The accepted line is the only source of these values.
89
89
 
@@ -278,7 +278,7 @@ TASK_DESCRIPTION: ${TICKET}
278
278
  ISSUE_REQUIRED: ${ISSUE_REQUIRED}
279
279
  APPLY_CONVENTIONS: ${APPLY_CONVENTIONS}
280
280
  ${ISSUE_INPUT !== "(none)" ? "ISSUE_INPUT: " + ISSUE_INPUT : ""}
281
- Return: {"branch": "<the - **Branch name**: value under your ### Branch, or (none)>", "issueId": "<the - **Issue ID**: value under your ### Handoff Values, or (none)>", "prLinkLine": "<the - **PR link line**: value, or (none)>"}`, { agentType: "Git" })
281
+ Return: {"branch": "<the - **Branch name**: value under your ### Branch, or (none)>", "issueId": "<the - **Issue ID**: value under your ### Handoff Values, or (none)>", "prLinkLine": "<the - **PR link line**: value, or (none)>", "baseSha": "<the 40 or 64 hex git rev-parse HEAD prints before step 4>"}`, { agentType: "Git" })
282
282
  );
283
283
 
284
284
  // The ticket's own Handoff Values, read from setup-task's Output — never derived from ISSUE_INPUT.
@@ -288,6 +288,7 @@ const ISSUE_NUMBER = ISSUE_ID_SHAPE.test(String(setup?.issueId ?? "")) ? setup.i
288
288
  const ISSUE_PR_LINK = setup?.prLinkLine || "(none)"; // "(none)" ⇒ the wave PR carries no closing reference for this ticket
289
289
  // The branch setup-task created, as it reported it: every later phase and the merge use it. The engine never names one.
290
290
  const BRANCH = typeof setup?.branch === "string" && setup.branch.trim() !== "" && setup.branch.trim() !== "(none)" ? setup.branch : "(none)";
291
+ const BASE_SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(setup?.baseSha ?? "")) ? setup.baseSha : "(none)"; // the commit the ticket branch was forked from
291
292
 
292
293
  // Ticket-link stop, before implement. The workflow cannot ask, so it never records an exception: it stops.
293
294
  if (ISSUE_REQUIRED === "true" && ISSUE_NUMBER === "(none)") {
@@ -299,6 +300,11 @@ if (BRANCH === "(none)") {
299
300
  return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: "branch-missing", description: "setup-task reported no branch name — check its Output, then re-run" }] };
300
301
  }
301
302
 
303
+ // Base stop: every review range starts at the commit the ticket branch was forked from.
304
+ if (BASE_SHA === "(none)") {
305
+ return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: "base-missing", description: "setup-task reported no base commit — check its Output, then re-run" }] };
306
+ }
307
+
302
308
  // Phase 2: Implement
303
309
  await phase("implement", () =>
304
310
  agent(`OPERATION: implement
@@ -412,10 +418,9 @@ Self-verify your fix compiles and the scenarios pass, following your Running com
412
418
 
413
419
  // Phase 5: Review → verify → fix (single pass)
414
420
  const reviewResult = await phase("review", async () => {
415
- // Review scope: the entire branch diff from the base branch to HEAD. No base SHA is tracked —
416
- // Review agents diff the branch against its merge-base with the default branch.
421
+ // Review scope: the entire branch diff, from the commit the ticket branch was forked from to HEAD.
417
422
  // The pass runs exactly ONCE.
418
- const reviewScope = `Review the full branch diff on branch ${BRANCH}. Diff the branch against its merge-base with the default branch (do not guess a base SHA — compute the merge-base from git).`;
423
+ const reviewScope = `Review the full branch diff on branch ${BRANCH}.\nDIFF_RANGE: ${BASE_SHA}...HEAD\n`;
419
424
 
420
425
  // Review agent focus list (order = index mapping for dead-reviewer detection)
421
426
  const reviewFocuses = ["security", "architecture", "performance", "complexity", "consistency", "regression", "testing", "reliability"];
@@ -459,13 +464,12 @@ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "fil
459
464
  return { survivingFindings: [], fixedFindings: [], coverageGaps };
460
465
  }
461
466
 
462
- // Adversarial verification — bounded 2-3 agent panel, majority-survives
463
- // Panel asks perspective-diverse questions over the WHOLE finding set (not one agent per finding).
464
- // A finding survives if >50% of panel agents CONFIRM it.
467
+ // Adversarial panel over the WHOLE finding set (not one agent per finding): >50% CONFIRMED survives
465
468
  const VERIFY_PANEL = 3;
466
469
  const panels = await parallel(
467
470
  Array.from({ length: VERIFY_PANEL }, (_, lens) => () =>
468
471
  agent(`Adversarially verify all ${allFindings.length} findings on branch ${BRANCH}.
472
+ DIFF_RANGE: ${BASE_SHA}...HEAD
469
473
  Lens ${lens + 1} of ${VERIFY_PANEL}:
470
474
  ${["Does each finding actually reproduce in the current code? Hunt for findings that describe patterns not present in the diff.",
471
475
  "Is each finding a real issue or a false positive in context? Consider intent, surrounding code, and project conventions.",
@@ -483,16 +487,13 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
483
487
  panels.filter(p => (p.verdicts || []).find(v => v.index === i)?.verdict === "CONFIRMED").length > VERIFY_PANEL / 2
484
488
  );
485
489
 
486
- // No confirmed findings — coverageGaps carry forward and block PASS downstream; also eliminates the empty-batch parallel() path
490
+ // No confirmed findings: coverageGaps carry forward and block PASS downstream
487
491
  if (confirmedFindings.length === 0) {
488
492
  return { survivingFindings: [], fixedFindings: [], coverageGaps };
489
493
  }
490
494
 
491
- // Batched fix Code agents — one file per set of sub-batches; max 5 findings per sub-batch.
492
- // Sub-batches for the SAME file run sequentially (never two Code agents editing the same file concurrently —
493
- // concurrent same-file edits cause index contention and lost fixes).
494
- // Sub-batches for DISTINCT files run in parallel (different code areas — safe per concurrency doctrine).
495
- // A finding with no file field gets its own singleton group (unknown scope — never mix with known files).
495
+ // Sub-batches (max 5 findings): same file in sequence, distinct files in parallel.
496
+ // A finding with no file is a singleton group.
496
497
  const MAX_BATCH = 5; // max 5 findings per sub-batch
497
498
  const fileGroups = {};
498
499
  for (let fi = 0; fi < confirmedFindings.length; fi++) {
@@ -502,9 +503,7 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
502
503
  fileGroups[key].push(f);
503
504
  }
504
505
 
505
- // One entry per distinct file; each entry runs its sub-batches sequentially.
506
- // Distinct-file groups are paced in chunks of FIX_CHUNK — same pacing bar as the Review spawn
507
- // path — to avoid launching all Code agents at once on a large branch (reliability: explicit bound).
506
+ // One entry per file; FIX_CHUNK chunks bound concurrent Code agents
508
507
  const FIX_CHUNK = 5;
509
508
  // issue-fix SCOPE per finding: CRITICAL or HIGH takes the Careful protocol (failing regression test first); anything else is Standard.
510
509
  const CAREFUL_SEVERITIES = new Set(["critical", "high"]);
@@ -785,7 +784,7 @@ The review pass runs exactly ONCE per ticket. One full-branch review → one adv
785
784
 
786
785
  **Step 1 — Spawn Review agents in parallel (one agent() per focus)**
787
786
 
788
- **Review scope:** the entire branch diff from the base branch to HEAD (no base SHA is tracked — the reviewing agent diffs the branch against its merge-base with the default branch).
787
+ **Review scope:** the entire branch diff, `DIFF_RANGE: {base commit}...HEAD`; the base commit is the 40 or 64 hex commit that the ticket branch was forked from, as the setup step reported it; a ticket with none stops `base-missing`.
789
788
 
790
789
  Core Review agents (always, 8 total): security, architecture, performance, complexity, consistency, regression, testing, reliability
791
790
 
@@ -868,7 +867,7 @@ Each ticket engine run returns a structured result. The Synthesize agent or the
868
867
  },
869
868
  "escalations": [
870
869
  {
871
- "type": "merge-conflict | gate2-fail | validation-exhausted | harden-blocked | ambiguous-resolution | review-coverage-incomplete | dependency-blocked | engine-crash | ticket-link-missing | branch-missing",
870
+ "type": "merge-conflict | gate2-fail | validation-exhausted | harden-blocked | ambiguous-resolution | review-coverage-incomplete | dependency-blocked | engine-crash | ticket-link-missing | branch-missing | base-missing",
872
871
  "description": "string"
873
872
  }
874
873
  ],
@@ -1010,7 +1009,7 @@ const results = {}; // ticketId → its row of the workflow's return
1010
1009
  const rowOf = (ticketId, r, merged, treeEqual = null) => ({ ticket: ticketId, ran: true, verdict: r?.verdict || r?.overallVerdict || null, merged, treeEqual, issuePrLink: r?.issuePrLink || "(none)", evaluateVerdict: r?.gate2?.evaluateVerdict, testVerdict: r?.gate2?.testVerdict, surviving: r?.survivingFindings?.length, coverageComplete: r?.reviewCoverage?.complete });
1011
1010
  const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
1012
1011
  const MERGE_SHA = /^[0-9a-f]{40}$/; // a SHA as git prints it; with the checked branch, the only Git-reported values that reach a merge, Validate or undo prompt
1013
- const BRANCH_NAME = /^(?![-#]|(?:@|(?:FETCH_|ORIG_|MERGE_)?HEAD)$)(?!.*\.\.)[A-Za-z0-9._/+@,#-]{1,200}$/; // implement's ref allowlist; no leading - or #, no revision name, no ..: the name enters git lines
1012
+ const BRANCH_NAME = /^(?![-#]|(?:@|(?:FETCH_|ORIG_|MERGE_)?HEAD)$)(?!.*\.\.)[A-Za-z0-9._/+@,#-]{1,200}$/; // STRICT_NAME_RE's deliberate variant, less its origin/ refusal; the name enters git lines
1014
1013
  // One ticket's merge, post-merge Validate and undo (D-WAVE-MERGE-VALIDATE: Git validates nothing).
1015
1014
  const mergeValidateUndo = async (ticketId, engineResult) => {
1016
1015
  if (typeof engineResult.branch !== "string" || !BRANCH_NAME.test(engineResult.branch)) return { kept: false, reason: "ticket branch name refused: not a plain git ref name", halted: null, treeEqual: null };
@@ -1179,12 +1178,14 @@ Do NOT ask questions mid-workflow — that is impossible (F4). The workflow only
1179
1178
 
1180
1179
  7. **Wave PR evidence** — only when step 6 reported the wave PR, after it; it never blocks, and every outcome below goes into the run summary. Take `{n}` from step 6's `- **PR**: #{n}` line when `{n}` matches `^[1-9][0-9]{0,9}$` as a whole; no such line ⇒ record `TRACEABILITY: DEGRADED (wave PR number not captured)` and skip this step. `PR_TEST_PLAN_BLOCK` `(none)` ⇒ record `Wave evidence: skipped (no wave test plan)` and skip it.
1181
1180
 
1181
+ **Changed files.** Take `{base_ref}` from step 6's `- **Base ref**:` line; only when it is 40 or 64 lowercase hex, run `git -C "{integration worktree root}" diff --name-only --diff-filter=d {base_ref}...HEAD; echo "exit=$?"`. Unless it ran and printed `exit=0`, record `TRACEABILITY: DEGRADED (wave base unusable)` and skip this step.
1182
+
1182
1183
  **(a) Test the wave.** Spawn one Test agent on the integration worktree with the wave test plan — the TP lines of the `## Test Plan` section step 3(b) wrote to `{integration worktree root}/.devflow/docs/evidence-wave-{slug}.md`:
1183
1184
 
1184
1185
  ```
1185
1186
  Agent(subagent_type="Test"):
1186
1187
  "ORIGINAL_REQUEST: the merged tickets of wave/{slug}, as the wave test plan names them
1187
- FILES_CHANGED: {the files wave/{slug} changes against the - **Base**: branch step 6 reported}
1188
+ FILES_CHANGED: {the file list that diff printed}
1188
1189
  TEST_PLAN: {the TP lines of the wave evidence file's ## Test Plan section}
1189
1190
  WORKTREE_PATH: {integration worktree root}
1190
1191
  Cover every TEST_PLAN line on the integration branch as it stands. Report PASS or FAIL with evidence."
@@ -30,13 +30,13 @@ $ARGUMENTS
30
30
 
31
31
  ### Phase 1: Resolve Settings
32
32
 
33
- **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
33
+ **Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root in a one-worktree run:
34
34
 
35
35
  ```bash
36
36
  node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
37
37
  ```
38
38
 
39
- Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> LEARNING=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on LEARNING=on KNOWLEDGE=off GATE_LEVEL=full` instead.
39
+ Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> CURATION=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=off CURATION=off KNOWLEDGE=off GATE_LEVEL=full` instead.
40
40
 
41
41
  The accepted line is the only source of these values.
42
42