devflow-kit 3.2.0 → 3.4.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 (188) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/dist/agents/code.md +330 -0
  4. package/{src/assets → dist}/agents/design.md +11 -1
  5. package/{src/assets → dist}/agents/diagnose.md +17 -13
  6. package/dist/agents/git.md +29 -56
  7. package/{src/assets → dist}/agents/knowledge.md +8 -5
  8. package/{src/assets → dist}/agents/research.md +21 -2
  9. package/{src/assets → dist}/agents/review.md +18 -7
  10. package/{src/assets → dist}/agents/scrutinize.md +35 -11
  11. package/dist/agents/skim.md +148 -0
  12. package/{src/assets → dist}/agents/triage.md +8 -2
  13. package/dist/cli/commands/init.js +62 -0
  14. package/dist/cli/commands/learning.js +63 -8
  15. package/dist/cli/commands/uninstall.js +42 -1
  16. package/dist/commands/bug-analysis.md +30 -8
  17. package/dist/commands/code-review.md +142 -61
  18. package/dist/commands/debug.md +15 -13
  19. package/dist/commands/dynamic-build.md +176 -130
  20. package/dist/commands/dynamic-plan.md +31 -19
  21. package/dist/commands/dynamic-profile.md +27 -13
  22. package/dist/commands/dynamic-tickets.md +28 -14
  23. package/dist/commands/explore.md +16 -14
  24. package/dist/commands/implement.md +163 -158
  25. package/dist/commands/plan.md +37 -24
  26. package/dist/commands/release.md +69 -4
  27. package/dist/commands/research.md +33 -11
  28. package/dist/commands/resolve.md +61 -47
  29. package/dist/commands/self-review.md +50 -32
  30. package/dist/core/agent-models.js +53 -7
  31. package/dist/core/assets.js +55 -10
  32. package/dist/core/claude-md-audit.js +190 -0
  33. package/dist/core/feature-switch.js +20 -1
  34. package/dist/core/flags.js +28 -0
  35. package/dist/core/fs-atomic.js +8 -3
  36. package/dist/core/learning-tuning-config.js +8 -0
  37. package/dist/core/learning-variants.js +213 -0
  38. package/dist/core/manifest.js +62 -0
  39. package/dist/core/mds-variants.js +38 -1
  40. package/dist/core/plugins.js +78 -11
  41. package/{src/assets → dist/learning-off}/agents/code.md +77 -28
  42. package/dist/learning-off/agents/design.md +119 -0
  43. package/dist/learning-off/agents/diagnose.md +210 -0
  44. package/dist/learning-off/agents/knowledge.md +90 -0
  45. package/dist/learning-off/agents/research.md +149 -0
  46. package/dist/learning-off/agents/review.md +228 -0
  47. package/dist/learning-off/agents/scrutinize.md +117 -0
  48. package/{src/assets → dist/learning-off}/agents/skim.md +5 -10
  49. package/dist/learning-off/agents/triage.md +163 -0
  50. package/dist/learning-off/commands/bug-analysis.md +420 -0
  51. package/dist/learning-off/commands/code-review.md +525 -0
  52. package/dist/learning-off/commands/debug.md +294 -0
  53. package/dist/learning-off/commands/dynamic-build.md +1255 -0
  54. package/dist/learning-off/commands/dynamic-plan.md +424 -0
  55. package/dist/learning-off/commands/dynamic-profile.md +214 -0
  56. package/dist/learning-off/commands/dynamic-tickets.md +632 -0
  57. package/dist/learning-off/commands/explore.md +210 -0
  58. package/dist/learning-off/commands/implement.md +808 -0
  59. package/dist/learning-off/commands/plan.md +664 -0
  60. package/dist/learning-off/commands/release.md +310 -0
  61. package/dist/learning-off/commands/research.md +222 -0
  62. package/dist/learning-off/commands/resolve.md +837 -0
  63. package/dist/learning-off/commands/self-review.md +266 -0
  64. package/dist/skills/git/references/tracker/_contract.md +33 -0
  65. package/dist/skills/git/references/tracker/github/fetch-issue.md +2 -0
  66. package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +2 -0
  67. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +5 -1
  68. package/dist/skills/git/references/tracker/github/post-wave-report.md +2 -0
  69. package/dist/skills/git/references/tracker/github/setup-task.md +12 -0
  70. package/dist/skills/git/references/tracker/jira/associate-release.md +1 -1
  71. package/dist/skills/git/references/tracker/jira/fetch-issue.md +2 -0
  72. package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +2 -0
  73. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +5 -1
  74. package/dist/skills/git/references/tracker/jira/post-wave-report.md +2 -0
  75. package/dist/skills/git/references/tracker/jira/setup-task.md +14 -2
  76. package/dist/skills/git/references/tracker/linear/associate-release.md +1 -1
  77. package/dist/skills/git/references/tracker/linear/fetch-issue.md +2 -0
  78. package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +2 -0
  79. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +5 -1
  80. package/dist/skills/git/references/tracker/linear/post-wave-report.md +2 -0
  81. package/dist/skills/git/references/tracker/linear/setup-task.md +14 -2
  82. package/dist/targets/claude-code/installer.js +72 -36
  83. package/dist/targets/claude-code/language-stamp.js +185 -0
  84. package/dist/targets/claude-code/learning-install.js +489 -0
  85. package/package.json +1 -1
  86. package/src/assets/agents/code.mds +339 -0
  87. package/src/assets/agents/design.mds +149 -0
  88. package/src/assets/agents/diagnose.mds +225 -0
  89. package/src/assets/agents/evaluate.md +12 -25
  90. package/src/assets/agents/git.mds +29 -56
  91. package/src/assets/agents/knowledge.mds +125 -0
  92. package/src/assets/agents/learning.md +4 -6
  93. package/src/assets/agents/research.mds +176 -0
  94. package/src/assets/agents/review.mds +286 -0
  95. package/src/assets/agents/scrutinize.mds +132 -0
  96. package/src/assets/agents/simplify.md +20 -0
  97. package/src/assets/agents/skim.mds +161 -0
  98. package/src/assets/agents/synthesize.md +12 -0
  99. package/src/assets/agents/test.md +2 -2
  100. package/src/assets/agents/triage.mds +194 -0
  101. package/src/assets/agents/validate.md +15 -8
  102. package/src/assets/commands/_partials/_compliance.mds +5 -4
  103. package/src/assets/commands/_partials/_decisions.mds +31 -0
  104. package/src/assets/commands/_partials/_engine.mds +29 -24
  105. package/src/assets/commands/_partials/_knowledge.mds +26 -13
  106. package/src/assets/commands/_partials/_plan_contract.mds +1 -1
  107. package/src/assets/commands/_partials/_preamble.mds +33 -9
  108. package/src/assets/commands/_partials/_publication.mds +5 -4
  109. package/src/assets/commands/_partials/_settings.mds +13 -5
  110. package/src/assets/commands/_partials/_wave.mds +16 -6
  111. package/src/assets/commands/bug-analysis.mds +24 -2
  112. package/src/assets/commands/code-review.mds +148 -45
  113. package/src/assets/commands/debug.mds +17 -1
  114. package/src/assets/commands/dynamic-build.mds +144 -65
  115. package/src/assets/commands/dynamic-plan.mds +36 -6
  116. package/src/assets/commands/dynamic-profile.mds +9 -1
  117. package/src/assets/commands/dynamic-tickets.mds +16 -2
  118. package/src/assets/commands/explore.mds +27 -1
  119. package/src/assets/commands/implement.mds +171 -138
  120. package/src/assets/commands/plan.mds +47 -8
  121. package/src/assets/commands/{release.md → release.mds} +27 -24
  122. package/src/assets/commands/research.mds +28 -4
  123. package/src/assets/commands/resolve.mds +69 -17
  124. package/src/assets/commands/self-review.mds +43 -13
  125. package/src/assets/mds/tracker/_common.mds +1 -1
  126. package/src/assets/mds/tracker/_contract.mds +72 -0
  127. package/src/assets/mds/tracker/_github.mds +13 -2
  128. package/src/assets/mds/tracker/_jira.mds +17 -5
  129. package/src/assets/mds/tracker/_linear.mds +17 -5
  130. package/src/assets/mds/tracker/_mcp.mds +2 -2
  131. package/src/assets/mds/tracker/_steps.mds +97 -0
  132. package/src/assets/rules/context-economy.md +10 -0
  133. package/src/assets/rules/go.md +1 -0
  134. package/src/assets/rules/java.md +1 -0
  135. package/src/assets/rules/python.md +1 -0
  136. package/src/assets/rules/rust.md +1 -0
  137. package/src/assets/rules/typescript.md +1 -0
  138. package/src/assets/scripts/ci-wait.cjs +636 -0
  139. package/src/assets/scripts/claude-md-audit.cjs +611 -0
  140. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +2 -3
  141. package/src/assets/scripts/hooks/background-memory-update +290 -9
  142. package/src/assets/scripts/hooks/json-helper.cjs +13 -5
  143. package/src/assets/scripts/hooks/json-parse +34 -10
  144. package/src/assets/scripts/hooks/session-start-context +315 -7
  145. package/src/assets/scripts/release-trace.cjs +27 -10
  146. package/src/assets/skills/accessibility/SKILL.md +1 -1
  147. package/src/assets/skills/apply-decisions/SKILL.md +12 -82
  148. package/src/assets/skills/apply-feature-knowledge/SKILL.md +9 -43
  149. package/src/assets/skills/architecture/SKILL.md +1 -1
  150. package/src/assets/skills/boundary-validation/SKILL.md +1 -1
  151. package/src/assets/skills/complexity/SKILL.md +1 -1
  152. package/src/assets/skills/compliance/SKILL.md +1 -1
  153. package/src/assets/skills/consistency/SKILL.md +1 -1
  154. package/src/assets/skills/database/SKILL.md +1 -1
  155. package/src/assets/skills/dependencies/SKILL.md +1 -1
  156. package/src/assets/skills/dependency-research/SKILL.md +3 -6
  157. package/src/assets/skills/design-review/SKILL.md +1 -1
  158. package/src/assets/skills/docs-framework/SKILL.md +1 -1
  159. package/src/assets/skills/documentation/SKILL.md +1 -1
  160. package/src/assets/skills/feature-knowledge/SKILL.md +43 -12
  161. package/src/assets/skills/gap-analysis/SKILL.md +1 -1
  162. package/src/assets/skills/git/SKILL.md +1 -1
  163. package/src/assets/skills/go/SKILL.md +1 -1
  164. package/src/assets/skills/java/SKILL.md +1 -1
  165. package/src/assets/skills/patterns/SKILL.md +1 -1
  166. package/src/assets/skills/performance/SKILL.md +1 -1
  167. package/src/assets/skills/python/SKILL.md +1 -1
  168. package/src/assets/skills/qa/SKILL.md +1 -3
  169. package/src/assets/skills/quality-gates/SKILL.md +10 -13
  170. package/src/assets/skills/quality-gates/references/report-template.md +20 -20
  171. package/src/assets/skills/react/SKILL.md +1 -1
  172. package/src/assets/skills/regression/SKILL.md +1 -1
  173. package/src/assets/skills/reliability/SKILL.md +1 -1
  174. package/src/assets/skills/research-codebase/SKILL.md +1 -1
  175. package/src/assets/skills/research-competitor/SKILL.md +1 -1
  176. package/src/assets/skills/research-external/SKILL.md +1 -1
  177. package/src/assets/skills/research-technology/SKILL.md +1 -1
  178. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  179. package/src/assets/skills/rust/SKILL.md +1 -1
  180. package/src/assets/skills/security/SKILL.md +1 -1
  181. package/src/assets/skills/software-design/SKILL.md +1 -1
  182. package/src/assets/skills/test-driven-development/SKILL.md +9 -29
  183. package/src/assets/skills/testing/SKILL.md +1 -1
  184. package/src/assets/skills/typescript/SKILL.md +1 -1
  185. package/src/assets/skills/ui-design/SKILL.md +1 -1
  186. package/src/assets/skills/worktree-support/SKILL.md +3 -55
  187. package/src/assets/skills/worktree-support/references/discovery.md +48 -0
  188. package/src/assets/skills/worktree-support/references/roots.md +2 -2
@@ -63,8 +63,34 @@ Then run a cheap syntax gate: write the authored script to a fresh, run-unique s
63
63
 
64
64
  The `budget` global governs depth. Scale Review agent roster and verification votes to `budget`. A low-budget run uses a leaner roster and fewer verification votes; a high-budget run expands both. Never hardcode a roster size — let budget guide it.
65
65
 
66
+ ### Handoff convention for sequential Code agents within a ticket
67
+
68
+ When a ticket requires multiple sequential Code agent phases, each Code agent appends its own `## Phase {N} Implementation Summary` section, at most 8,192 bytes, to `{toplevel}/.devflow/docs/handoff-{branch_slug}.md` (branch-scoped to prevent concurrent session clobber), `{toplevel}` being `git rev-parse --show-toplevel` in the checkout the ticket's branch is in — never a subdirectory. It never rewrites an earlier section. The next Code agent reads, via HANDOFF_FILE input, only the section of the phase immediately before its own, not the whole file. PRIOR_PHASE_SUMMARY is the compact in-context form; the handoff file is the durable form that survives context compaction. Code is authoritative, summaries are supplementary.
69
+
70
+ ### IRON RULE (LLM-vs-plumbing)
71
+
72
+ **Author ZERO deterministic feature code.** No parsers, no schedulers, no topological-sort, no dependency-graph helpers, no confidence formulas. ALL issue reading, dependency reasoning, and scheduling decisions are LLM judgment at runtime, performed by the workflow's agents. The recipe is instructions. The workflow script Claude authors IS the runtime logic — keep it free of hand-coded feature algorithms.
73
+
74
+ ### SAFETY BANNER
75
+
76
+ **NEVER merge to main or master** — the workflow merges to an integration branch only. The user merges to main themselves after reviewing. This rule is absolute and must appear as an `engine_invariants()` note in every workflow that touches git.
77
+
78
+ ### Settings line
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:
81
+
82
+ ```bash
83
+ node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
84
+ ```
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>` — 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` instead.
87
+
88
+ The accepted line is the only source of these values: the script alone folds the committed `.devflow/project.json`, the personal `.devflow/config.json` and the machine manifest.
89
+
66
90
  ### DECISIONS_CONTEXT — obtain BEFORE authoring
67
91
 
92
+ When the settings line says `LEARNING=off`, set `DECISIONS_CONTEXT` to `(none)` and skip this step, locating no ledger and reading no index.
93
+
68
94
  The decisions ledger belongs to the repository, not to one checkout: in a linked worktree it lives in the main worktree, and a session started in a subdirectory reads the copy at the repository root. Locate it with ONE git call, run from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`):
69
95
 
70
96
  ```bash
@@ -81,19 +107,7 @@ This is the rule the learning hooks apply (D-LEDGER-MAIN-WORKTREE, D-PROMPT-ROOT
81
107
 
82
108
  Before you author the workflow script, read `{ledger}/.devflow/learning/index.md`. If the file is absent or empty, set `DECISIONS_CONTEXT` to `(none)`; otherwise use the file content as `DECISIONS_CONTEXT`.
83
109
 
84
- The script body cannot perform this read — you (the main model) do it before authoring. Then inject the relevant DECISIONS_CONTEXT into agent prompts using the `devflow:apply-decisions` consumption algorithm (scan index → Read relevant entries → cite verbatim IDs in agent prompts). Only agents that need architectural context (Code agent, Evaluate agent, Review agent, Scrutinize agent) need DECISIONS_CONTEXT injected; lightweight agents (Validate agent, Simplify agent) do not.
85
-
86
- ### Handoff convention for sequential Code agents within a ticket
87
-
88
- When a ticket requires multiple sequential Code agent phases, each Code agent writes `{toplevel}/.devflow/docs/handoff-{branch_slug}.md` (branch-scoped to prevent concurrent session clobber), `{toplevel}` being `git rev-parse --show-toplevel` in the checkout the ticket's branch is in — never a subdirectory. The next Code agent reads it via HANDOFF_FILE input. PRIOR_PHASE_SUMMARY is the compact in-context form; the handoff file is the durable form that survives context compaction. Always read the handoff file directly — code is authoritative, summaries are supplementary.
89
-
90
- ### IRON RULE (LLM-vs-plumbing)
91
-
92
- **Author ZERO deterministic feature code.** No parsers, no schedulers, no topological-sort, no dependency-graph helpers, no confidence formulas. ALL issue reading, dependency reasoning, and scheduling decisions are LLM judgment at runtime, performed by the workflow's agents. The recipe is instructions. The workflow script Claude authors IS the runtime logic — keep it free of hand-coded feature algorithms.
93
-
94
- ### SAFETY BANNER
95
-
96
- **NEVER merge to main or master** — the workflow merges to an integration branch only. The user merges to main themselves after reviewing. This rule is absolute and must appear as an `engine_invariants()` note in every workflow that touches git.
110
+ The script body cannot perform this read — you (the main model) do it before authoring. Then inject the relevant DECISIONS_CONTEXT into the prompts of the Code, Design, Knowledge, Review and Scrutinize agents, using the `devflow:apply-decisions` consumption algorithm (scan index → Read relevant entries → cite verbatim IDs in agent prompts). Those are the agent types above whose contract declares it; every other agent type gets no decisions context.
97
111
 
98
112
  ---
99
113
 
@@ -168,17 +182,7 @@ Author the resolved `ISSUE_REQUIRED` and `APPLY_CONVENTIONS` into the workflow s
168
182
 
169
183
  **Produces:** COMPLIANCE_FRAMEWORKS
170
184
 
171
- **Resolve the compliance lens** for each worktree root, from its settings line (every framework reference is installed on every machine, so no file check decides it):
172
-
173
- **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:
174
-
175
- ```bash
176
- node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
177
- ```
178
-
179
- 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>` — 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` instead.
180
-
181
- The accepted line is the only source of these values: the script alone folds the committed `.devflow/project.json`, the personal `.devflow/config.json` and the machine manifest.
185
+ **Resolve the compliance lens** for each worktree root, from its settings line — the line resolved above for that root, by the settings block when this run has not yet resolved it (every framework reference is installed on every machine, so no file check decides it).
182
186
 
183
187
  **Set the compliance lens** from that line: `COMPLIANCE_FRAMEWORKS` is the settings line's `COMPLIANCE` with `generic` written `none`: `off`, `none`, or the framework ids the machine and this repository declare.
184
188
 
@@ -198,7 +202,7 @@ and using its one-line output. If the command fails (outside a git repository),
198
202
 
199
203
  **1. Apply decisions context**
200
204
 
201
- Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded per the preamble above: scan the index, Read relevant entries, note the verbatim ADR/PF IDs you will inject into Code agent and Evaluate agent prompts.
205
+ Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded by the decisions step above: scan the index, Read relevant entries, note the verbatim ADR/PF IDs you will inject into Code agent prompts.
202
206
 
203
207
  **2. Read budget**
204
208
 
@@ -322,7 +326,8 @@ if (BRANCH === "(none)") {
322
326
 
323
327
  // Phase 2: Implement
324
328
  await phase("implement", () =>
325
- agent(`Implement the following ticket on branch ${BRANCH}:
329
+ agent(`OPERATION: implement
330
+ Implement the following ticket on branch ${BRANCH}:
326
331
 
327
332
  ${TICKET}
328
333
 
@@ -343,39 +348,52 @@ After implementing, commit your changes with a conventional-commit message and r
343
348
  - Any open questions or blockers`, { agentType: "Code" })
344
349
  );
345
350
 
346
- // Phase 3: Gate 1 — post-code pipeline
351
+ // Phase 3: Gate 1 #1 — post-code pipeline: Simplify agent → Scrutinize agent → Validate agent.
352
+ // Validate runs last and unconditionally, so its one full run covers every commit the pass made (see gate1_postcode()).
347
353
  const gate1 = await phase("gate1", async () => {
348
- // Gate 1 #1 — runs once, immediately after the initial implementation.
354
+ await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
355
+
356
+ const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
357
+ Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
358
+ // Anything but an explicit PASS or FIXED is a stop: a Scrutinize that returned no status, or one this skeleton does not know, has not accepted the code.
359
+ if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
360
+ return { verdict: "ESCALATED", type: "scrutiny-blocked", reason: scrutiny?.status === "BLOCKED" ? "Scrutinize agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Scrutinize agent returned no recognised status: counted as BLOCKED" };
361
+ }
362
+
349
363
  const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH}.
350
364
  Follow your Running commands block for every build and test command.
351
- Report: PASS or FAIL with details.`, { agentType: "Validate" });
365
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
352
366
 
353
- if (validation.verdict === "FAIL") {
354
- // Up to 2 fix attempts
367
+ // Anything but an explicit PASS is a failure: a Validate that returned no verdict has not validated the branch.
368
+ if (validation?.verdict !== "PASS") {
369
+ let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
370
+ // Up to 2 fix attempts, each followed by a Validate re-run
355
371
  for (let attempt = 1; attempt <= 2; attempt++) {
356
- await agent(`Fix the validation failures on branch ${BRANCH}:
357
- ${validation.details}
372
+ await agent(`OPERATION: validation-fix
373
+ Fix the validation failures on branch ${BRANCH}:
374
+ ${failureDetails}
375
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
358
376
  ISSUE_NUMBER: ${ISSUE_NUMBER}
359
377
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
360
378
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
361
379
  Commit fixes with conventional-commit message.`, { agentType: "Code" });
362
- const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH}. Report: PASS or FAIL.`, { agentType: "Validate" });
363
- if (recheck.verdict === "PASS") break;
364
- if (attempt === 2) return { verdict: "ESCALATED", reason: "Validation exhausted after 2 Code agent fix attempts" };
380
+ const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} after fix attempt ${attempt} (follow your Running commands block).
381
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
382
+ if (recheck?.verdict === "PASS") break;
383
+ failureDetails = recheck?.details || failureDetails;
384
+ if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Validation exhausted after 2 Code agent fix attempts" };
365
385
  }
366
386
  }
367
387
 
368
- await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
369
-
370
- const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
371
-
372
- if (scrutiny.codeChanged) {
373
- await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (Scrutinize agent made changes). Report: PASS or FAIL.`, { agentType: "Validate" });
374
- }
375
-
376
388
  return { verdict: "PASS" };
377
389
  });
378
390
 
391
+ // Gate 1 #1 stop, before Gate 2: an exhausted Validate or a BLOCKED Scrutinize leaves a broken or unfinished build,
392
+ // so Gate 2 and the review pass never run on it — a correctness stop, like the ticket-link and branch stops.
393
+ if (gate1.verdict === "ESCALATED") {
394
+ return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: gate1.type, description: gate1.reason }] };
395
+ }
396
+
379
397
  // Phase 4: Gate 2 — acceptance gate (once, before review pass)
380
398
  const gate2 = await phase("gate2", async () => {
381
399
  if (!PLAN && !CRITERIA && !TEST_PLAN) {
@@ -384,21 +402,20 @@ const gate2 = await phase("gate2", async () => {
384
402
 
385
403
  let evalVerdict = "SKIPPED";
386
404
  if (PLAN) {
387
- const panel = await parallel([
388
- () => agent(`Acceptance-criteria evaluator: does the implementation on branch ${BRANCH} satisfy each numbered criterion, INCLUDING negative criteria?
405
+ const evaluation = await agent(`Acceptance and scope check on branch ${BRANCH}. Answer both questions:
406
+ 1. Does the implementation satisfy each numbered criterion, INCLUDING negative criteria (what it must NOT do)?
407
+ 2. Did the Code agent introduce any unplanned changes, smuggled anti-features, or drift from the plan's intent?
389
408
  Plan: ${PLAN}
390
409
  Criteria: ${CRITERIA || "see plan"}
391
- Report: PASS or FAIL per criterion with rationale.`, { agentType: "Evaluate" }),
392
- () => agent(`Scope/intent-drift evaluator: did the Code agent on branch ${BRANCH} introduce any unplanned changes, smuggled anti-features, or drift from the plan's intent?
393
- Plan: ${PLAN}
394
- Report: PASS or FAIL with rationale.`, { agentType: "Evaluate" }),
395
- ]);
396
- evalVerdict = panel.every(p => p.verdict === "PASS") ? "PASS" : "FAIL";
410
+ Return: {"verdict": "PASS" | "FAIL", "rationale": "..."} — FAIL if either answer is no, naming the failing criterion or the drift.`, { agentType: "Evaluate" });
411
+ evalVerdict = evaluation?.verdict === "PASS" ? "PASS" : "FAIL";
397
412
  if (evalVerdict === "FAIL") {
398
413
  // fix-and-continue — no re-evaluate, no inline Gate 1. The final Gate 1 (#2)
399
414
  // after the review pass is the build gate before the branch is handed back.
400
- await agent(`Fix the alignment issues identified by the Evaluate agent panel on branch ${BRANCH}:
401
- ${panel.filter(p => p.verdict === "FAIL").map(p => p.rationale).join("\n")}
415
+ await agent(`OPERATION: alignment-fix
416
+ Fix the alignment issues identified by the Evaluate agent on branch ${BRANCH}:
417
+ ${evaluation?.rationale || "see the Evaluate agent's report"}
418
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
402
419
  ISSUE_NUMBER: ${ISSUE_NUMBER}
403
420
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
404
421
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
@@ -417,8 +434,10 @@ Cover: functionality, API contracts, performance, and cover every TEST_PLAN scen
417
434
  testVerdict = testResult.verdict;
418
435
  if (testVerdict === "FAIL") {
419
436
  // fix-and-continue — no re-test, no inline Gate 1.
420
- await agent(`Fix the failing acceptance test scenarios on branch ${BRANCH}:
437
+ await agent(`OPERATION: qa-fix
438
+ Fix the failing acceptance test scenarios on branch ${BRANCH}:
421
439
  ${testResult.failures}
440
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
422
441
  ISSUE_NUMBER: ${ISSUE_NUMBER}
423
442
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
424
443
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
@@ -444,7 +463,7 @@ const reviewResult = await phase("review", async () => {
444
463
  const chunkSize = 5;
445
464
  const reviewThunks = reviewFocuses.map(focus =>
446
465
  () => agent(`${focus.charAt(0).toUpperCase() + focus.slice(1)} review. ${reviewScope}
447
- Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" })
466
+ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "line": <number-or-null>, "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" })
448
467
  );
449
468
 
450
469
  let rawReviews = [];
@@ -461,7 +480,7 @@ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "fil
461
480
  const focus = reviewFocuses[i];
462
481
  if (!r || r.reviewed !== true) {
463
482
  // Dead Review agent: retry once sequentially
464
- const retry = await agent(`Retry ${focus} review. ${reviewScope} Focus: ${focus} discipline. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" });
483
+ const retry = await agent(`Retry ${focus} review. ${reviewScope} Focus: ${focus} discipline. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "line": <number-or-null>, "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" });
465
484
  if (!retry || retry.reviewed !== true) {
466
485
  coverageGaps.push(focus);
467
486
  log(`review coverage incomplete: ${focus}`);
@@ -526,13 +545,19 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
526
545
  // Distinct-file groups are paced in chunks of FIX_CHUNK — same pacing bar as the Review spawn
527
546
  // path — to avoid launching all Code agents at once on a large branch (reliability: explicit bound).
528
547
  const FIX_CHUNK = 5;
529
- const fixThunks = Object.entries(fileGroups).map(([fileKey, findings]) => async () => {
548
+ // issue-fix SCOPE per finding: CRITICAL or HIGH takes the Careful protocol (failing regression test first); anything else is Standard.
549
+ const CAREFUL_SEVERITIES = new Set(["critical", "high"]);
550
+ const fixThunks = Object.values(fileGroups).map(findings => async () => {
530
551
  const chunkResults = [];
531
552
  for (let i = 0; i < findings.length; i += MAX_BATCH) {
532
553
  const chunk = findings.slice(i, i + MAX_BATCH);
533
- const r = await agent(`Fix the following confirmed review findings on branch ${BRANCH}${fileKey.startsWith("__nofile") ? "" : ` in ${fileKey}`}:
534
- ${chunk.map(f => `- ${f.description} (${f.severity})`).join("\n")}
535
-
554
+ const r = await agent(`OPERATION: issue-fix
555
+ Fix the following confirmed review findings on branch ${BRANCH}:
556
+ ISSUES:
557
+ ${chunk.map((f, n) => `${n + 1}. ${f.file ? `${f.file}${f.line ? `:${f.line}` : ""} — ` : ""}${f.description} (${f.severity})`).join("\n")}
558
+ SCOPE: ${chunk.map((f, n) => `${n + 1}=${CAREFUL_SEVERITIES.has(String(f.severity).toLowerCase()) ? "Careful" : "Standard"}`).join(", ")}
559
+ PUSH: false
560
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
536
561
  ISSUE_NUMBER: ${ISSUE_NUMBER}
537
562
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
538
563
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
@@ -571,38 +596,41 @@ Return: {"status": "fixed"|"blocked", "commitShas": ["<sha>"], "unresolved": ["<
571
596
  return { survivingFindings: notAddressed, fixedFindings, coverageGaps };
572
597
  });
573
598
 
574
- // Phase 5.5: Gate 1 #2 — FINAL post-fix gate (full Validate agent → Simplify agent → Scrutinize agent).
599
+ // Phase 5.5: Gate 1 #2 — FINAL post-fix gate (Simplify agent → Scrutinize agent → full Validate agent, Validate last and unconditional).
575
600
  // Runs ONCE, after the review pass. Inside the pass the Code agent self-verified its own fixes;
576
601
  // this is the build gate before the branch is handed back. See gate1_postcode() cadence.
577
602
  const gate1Final = await phase("gate1-final", async () => {
603
+ await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
604
+
605
+ const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
606
+ Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
607
+ if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
608
+ return { verdict: "ESCALATED", type: "scrutiny-blocked", reason: scrutiny?.status === "BLOCKED" ? "Final Gate 1 Scrutinize agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Final Gate 1 Scrutinize agent returned no recognised status: counted as BLOCKED" };
609
+ }
610
+
578
611
  const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH} (final gate after all fixing).
579
612
  Follow your Running commands block for every build and test command.
580
- Report: PASS or FAIL with details.`, { agentType: "Validate" });
613
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
581
614
 
582
- if (validation.verdict === "FAIL") {
583
- let failureDetails = validation.details;
615
+ if (validation?.verdict !== "PASS") {
616
+ let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
584
617
  for (let attempt = 1; attempt <= 2; attempt++) {
585
- await agent(`Fix the final validation failures on branch ${BRANCH}:
618
+ await agent(`OPERATION: validation-fix
619
+ Fix the final validation failures on branch ${BRANCH}:
586
620
  ${failureDetails}
621
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
587
622
  ISSUE_NUMBER: ${ISSUE_NUMBER}
588
623
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
589
624
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
590
625
  Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "Code" });
591
- const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (follow your Running commands block). Report: PASS or FAIL.`, { agentType: "Validate" });
592
- if (recheck.verdict === "PASS") break;
593
- failureDetails = recheck.details || failureDetails;
594
- if (attempt === 2) return { verdict: "ESCALATED", reason: "Final Gate 1 validation exhausted after 2 Code agent fix attempts" };
626
+ const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (final gate, after fix attempt ${attempt}; follow your Running commands block).
627
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
628
+ if (recheck?.verdict === "PASS") break;
629
+ failureDetails = recheck?.details || failureDetails;
630
+ if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Final Gate 1 validation exhausted after 2 Code agent fix attempts" };
595
631
  }
596
632
  }
597
633
 
598
- await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
599
-
600
- const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
601
-
602
- if (scrutiny.codeChanged) {
603
- await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (Scrutinize agent made changes; follow your Running commands block). Report: PASS or FAIL.`, { agentType: "Validate" });
604
- }
605
-
606
634
  return { verdict: "PASS" };
607
635
  });
608
636
 
@@ -631,7 +659,7 @@ Write a concise report. Present only surviving findings as outstanding — never
631
659
  survivingFindings: reviewResult.survivingFindings || [], fixedFindings: reviewResult.fixedFindings || [],
632
660
  reviewCoverage: { failedFocuses: coverageGaps, complete: coverageGaps.length === 0 },
633
661
  escalations: [
634
- ...(gate1Final.verdict === "ESCALATED" ? [{ type: "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
662
+ ...(gate1Final.verdict === "ESCALATED" ? [{ type: gate1Final.type || "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
635
663
  ...coverageGaps.map(focus => ({ type: "review-coverage-incomplete", description: `review coverage incomplete: ${focus}` })),
636
664
  ],
637
665
  gate2, report,
@@ -679,12 +707,12 @@ Run builds, typechecks, lints and tests in the foreground, each with an explicit
679
707
  The standard implementation unit for one ticket. Run in order:
680
708
 
681
709
  ```
682
- Code(agentType:"Code", prompt: full task + plan + DECISIONS_CONTEXT + handoff if sequential)
710
+ Code(agentType:"Code", prompt: "OPERATION: implement" first line, then full task + plan + DECISIONS_CONTEXT + COMPLIANCE_FRAMEWORKS + handoff if sequential)
683
711
  → gate1_postcode()
684
712
  → gate2_acceptance() ← Gate 2 runs HERE — before the review pass, not after
685
713
  ```
686
714
 
687
- The Code agent prompt must include: task description, implementation plan (if one exists), relevant DECISIONS_CONTEXT (the index you loaded before authoring), the compliance lens (`COMPLIANCE_FRAMEWORKS` — every Code prompt carries it, fix prompts included), and any PRIOR_PHASE_SUMMARY / HANDOFF_FILE for sequential multi-phase tickets.
715
+ Every Code agent prompt opens with `OPERATION: <mode>` as its first line (`implement`, `issue-fix`, `validation-fix`, `alignment-fix` or `qa-fix`). It must include: task description, implementation plan (if one exists), relevant DECISIONS_CONTEXT (the index you loaded before authoring), the compliance lens (`COMPLIANCE_FRAMEWORKS` — every Code prompt carries it, fix prompts included), and any PRIOR_PHASE_SUMMARY / HANDOFF_FILE for sequential multi-phase tickets.
688
716
 
689
717
  Gate 2 runs at implementation acceptance — this matches devflow's deliberate placement: "evaluation is part of implementation acceptance, not post-review" (§6.1).
690
718
 
@@ -692,21 +720,21 @@ Gate 2 runs at implementation acceptance — this matches devflow's deliberate p
692
720
 
693
721
  ORDER IS LOAD-BEARING. Run exactly in this sequence:
694
722
 
695
- 1. **Validate agent** — build / typecheck / lint / test
723
+ 1. **Simplify agent** — reduce complexity, remove duplication
724
+ 2. **Scrutinize agent** — 9-pillar self-review (deep structural analysis); returns `{"status": "PASS" | "FIXED" | "BLOCKED"}`
725
+ - BLOCKED (a P0 issue cannot be fixed in scope), or a missing or unrecognised status, which counts as BLOCKED → stop the pass and escalate as `scrutiny-blocked`; Validate does not run on code Scrutinize could not accept
726
+ 3. **Validate agent** — build / typecheck / lint / test; returns `{"verdict": "PASS" | "FAIL", "details": "..."}`. It runs LAST and UNCONDITIONALLY: one full run over the HEAD that Simplify and Scrutinize left, so it covers their commits whether or not Scrutinize changed code
696
727
  - Build/test commands follow `build_execution_doctrine()`: the Validate agent's Running commands block, foreground under an explicit Bash timeout.
697
- - FAIL → Code agent fix (max 2 retries) → re-run Validate agent
698
- - If still FAIL after 2 retries → escalate (do not loop endlessly)
699
- 2. **Simplify agent** — reduce complexity, remove duplication
700
- 3. **Scrutinize agent** — 9-pillar self-review (deep structural analysis)
701
- - If Scrutinize agent changed code → re-run Validate agent (verify the Scrutinize agent's edits compile/pass)
728
+ - FAIL → Code agent fix (max 2 retries), each followed by a Validate agent re-run
729
+ - If still FAIL after 2 retries → escalate as `validation-exhausted` (do not loop endlessly)
702
730
 
703
731
  **Gate 1 contains NO Evaluate agent and NO Test agent.** Those are Gate 2 only.
704
732
 
705
733
  Depth scales to change size + budget: a trivial one-line fix warrants a lighter pass; a multi-file refactor warrants the full depth.
706
734
 
707
735
  **Cadence — Gate 1 runs at exactly TWO points per ticket:**
708
- 1. **Gate 1 #1** — immediately after the initial Code agent implementation (inside `implement_bundle()`).
709
- 2. **Gate 1 #2** — the FINAL gate, after ALL Gate-2 fixes AND the entire review pass have completed.
736
+ 1. **Gate 1 #1** — immediately after the initial Code agent implementation (inside `implement_bundle()`). An escalated result — Validate exhausted, or Scrutinize BLOCKED — returns early: the ticket's engine returns `ESCALATED` with one escalation, and Gate 2 and the review pass never run on a broken or unfinished build.
737
+ 2. **Gate 1 #2** — the FINAL gate, after ALL Gate-2 fixes AND the entire review pass have completed. Scrutinize BLOCKED here is `ESCALATED` as well.
710
738
 
711
739
  It does NOT run inside the review pass, nor after each individual Gate-2 / review / QA fix. At those points the fixing Code agent self-verifies its OWN build compiles (see `review_pass()` and the Code agent's Running commands block). The final Gate 1 #2 is the invariant that all written code passes before merge.
712
740
 
@@ -716,30 +744,27 @@ Gate 2 fires ONCE: after the implement-bundle and BEFORE the review pass. It doe
716
744
 
717
745
  Gate 2 inputs are produced by `/devflow:dynamic-plan`'s plan-challenge step — the acceptance criteria and test plan written for the Evaluate agent and Test agent.
718
746
 
719
- **Evaluate agent panel** (only if a plan exists):
720
- - Run `evaluator_panel()` — see that block for the panel composition
721
- - If any critical lens returns MISALIGNED: fix-and-continue — the demanded fixes are applied by a Code agent that self-verifies its own build (batched per the review-pass batching doctrine if numerous). The recorded verdict becomes `FAIL-FIXED` (issues found, fixes applied, not re-evaluated by design); Gate 2 then proceeds. In SINGLE mode the run reports it as `UNVERIFIED`, never PASS.
747
+ **Evaluate agent** (only if a plan exists):
748
+ - Run `evaluator_panel()` — see that block for the one spawn and the two lenses it names
749
+ - If the Evaluate agent returns FAIL (either lens failed): fix-and-continue — the demanded fixes are applied by a Code agent that self-verifies its own build (batched per the review-pass batching doctrine if numerous). The recorded verdict becomes `FAIL-FIXED` (issues found, fixes applied, not re-evaluated by design); Gate 2 then proceeds. In SINGLE mode the run reports it as `UNVERIFIED`, never PASS.
722
750
 
723
751
  **Test agent** (only if acceptance criteria or a test plan exist):
724
752
  - Scenario-based acceptance tests covering functionality, API contracts, performance
725
753
  - FAIL → fix-and-continue — a Code agent applies the demanded fixes and self-verifies its own build. The recorded verdict becomes `FAIL-FIXED`; Gate 2 then proceeds. In SINGLE mode the run reports it as `UNVERIFIED`, never PASS.
726
754
 
727
755
  **When Gate 2 inputs are absent:**
728
- - No plan → skip Evaluate agent panel silently (note in output: "Gate 2 Evaluate agent skipped — no plan available")
756
+ - No plan → skip the Evaluate agent silently (note in output: "Gate 2 Evaluate agent skipped — no plan available")
729
757
  - No acceptance criteria and no test plan → skip Test agent silently (note in output: "Gate 2 Test agent skipped — no criteria available")
730
758
  - Build proceeds Gate-1-only. Never refuse to build; never force-generate fake criteria. Trust the user.
731
759
 
732
- ### Evaluate agent panel (§12 — diverse-lens verification)
733
-
734
- Run 2–3 Evaluate agents with DISTINCT lenses. Diversity not redundancy — each agent asks a different question:
760
+ ### Evaluate agent spawn (one agent, two lenses)
735
761
 
736
- 1. **Acceptance-criteria evaluator** — "Does the implementation satisfy each numbered acceptance criterion, INCLUDING the negative criteria (what it must NOT do)?"
737
- 2. **Scope / intent-drift evaluator** — "Did the Code agent smuggle in unplanned changes, deviate from the plan's intent, or introduce anti-features?"
738
- 3. **Cross-ticket-consistency evaluator** — (wave-only; skip for single-ticket runs) "Does this ticket honor the API contracts and invariants that other wave tickets depend on?"
762
+ Run ONE Evaluate agent whose prompt names both lenses, so a ticket pays for one spawn and one read of the plan:
739
763
 
740
- Gate = **all-critical-must-pass**: a single critical FAIL from any lens blocks acceptance.
764
+ 1. **Acceptance criteria** — "Does the implementation satisfy each numbered acceptance criterion, INCLUDING the negative criteria (what it must NOT do)?"
765
+ 2. **Scope / intent drift** — "Did the Code agent smuggle in unplanned changes, deviate from the plan's intent, or introduce anti-features?"
741
766
 
742
- Keep the panel to 2–3 agents. Do not spawn redundant agents asking the same question — that is cost with no diversity benefit.
767
+ It returns `{"verdict": "PASS" | "FAIL", "rationale": "..."}`: FAIL when either lens fails, the rationale naming the failing criterion or the drift. A FAIL on either lens blocks acceptance. A wave ticket runs this same skeleton through `runSingleTicketEngine`, so it gets the same single spawn.
743
768
 
744
769
  ### Acceptance criteria + test plan contract
745
770
 
@@ -784,7 +809,7 @@ Every line of a `## Test Plan` section, or of a PR's test-plan block, follows th
784
809
 
785
810
  #### Consumption by Gate 2
786
811
 
787
- The Evaluate agent panel receives: the per-ticket plan + the numbered acceptance criteria (positive and negative).
812
+ The Evaluate agent receives: the per-ticket plan + the numbered acceptance criteria (positive and negative).
788
813
 
789
814
  The Test agent receives: the test plan's TP lines, once `check tp` has admitted them.
790
815
 
@@ -846,7 +871,7 @@ If survivors remain: batch the confirmed findings for fixing: group findings by
846
871
 
847
872
  1. **Code is written ONLY by Code agents.** No other agent type writes code — not Review agent, not Evaluate agent.
848
873
  2. **Findings are verified before any fix is written.** The adversarial verification step is not optional; unverified findings are not passed to the Code agent.
849
- 3. **All written code passes Gate 1.** No code merge, commit, or handoff before Validate agent + Simplify agent + Scrutinize agent (in that order).
874
+ 3. **All written code passes Gate 1.** No code merge, commit, or handoff before Simplify agent + Scrutinize agent + Validate agent (in that order — Validate last, so its one run covers the commits of the two before it).
850
875
  4. **Gate 2 runs once, at implementation acceptance.** It does not re-run after review-fixes.
851
876
  5. **NEVER auto-merge to main or master.** All merges target the integration branch. The user merges to main themselves.
852
877
  6. **No unauthorized tracker or remote side-effects.** Sub-agents NEVER create issues/PRs on the tracker, comment on them, or push beyond the ticket-authorized branch unless the ticket, plan, or user explicitly authorizes that exact action. This applies to whatever tracker is resolved, not to one vendor. Proposed follow-ups go in the run report.
@@ -888,7 +913,7 @@ Each ticket engine run returns a structured result. The Synthesize agent or the
888
913
  },
889
914
  "escalations": [
890
915
  {
891
- "type": "merge-conflict | gate2-fail | validation-exhausted | ambiguous-resolution | review-coverage-incomplete | dependency-blocked | engine-crash | ticket-link-missing | branch-missing",
916
+ "type": "merge-conflict | gate2-fail | validation-exhausted | scrutiny-blocked | ambiguous-resolution | review-coverage-incomplete | dependency-blocked | engine-crash | ticket-link-missing | branch-missing",
892
917
  "description": "string"
893
918
  }
894
919
  ],
@@ -947,8 +972,8 @@ For each ready ticket (sequentially by default; parallel only past the §7.1 bar
947
972
  - Branch setup: the engine's setup-task creates the ticket's branch off integration HEAD at ready-time (so it already contains merged deps); every later phase, and the merge, uses the branch setup-task created, and a setup-task that reports none stops the ticket before implementing
948
973
  - Run the single-ticket engine inside a try/catch — one ticket's crash/stall never kills the wave; catch the exception, quarantine that ticket, and continue with the remaining ready set
949
974
  - The engine gets the ticket's own reference, the one the pre-fetch printed, as its setup-task input — never the wave's tracking issue
950
- - On engine PASS or UNVERIFIED: merge to integration branch, run Validate agent (build + test)
951
- - Merge FAIL (build red after merge): quarantine ticket, mark as escalated, continue
975
+ - On engine PASS or UNVERIFIED: merge to the integration branch locally through Git (no push, no build or test), then the workflow spawns the Validate agent (build + test) over the merge. The merge counts as kept only when that Validate returns PASS
976
+ - Validate FAIL or no PASS (build red after merge): the workflow spawns Git to undo the merge, quarantines the ticket, marks it as escalated, and continues. If the undo is refused, the wave stops taking merges and rounds
952
977
  - On any other verdict (PARTIAL, FAIL, ESCALATED) or none: quarantine ticket, do not block independent siblings
953
978
 
954
979
  **Cascade quarantine:** when a ticket is quarantined for any reason (Gate-1 exhausted, engine crash/stall, build-red after merge, review coverage incomplete after retry), the quarantine cascades to its direct and transitive dependents — each is marked blocked with the named reason, naming the blocker by its `{ISSUE_REF}` (e.g. "blocked: depends on {ISSUE_REF} which failed Gate-1"). Independent siblings are never affected. The quarantined list is injected into every subsequent Design agent reader prompt so the reader never schedules dependents of failed tickets.
@@ -975,7 +1000,9 @@ MAX_ROUNDS = LLM judgment based on ticket count (heuristic: ticket_count * 2 + 5
975
1000
 
976
1001
  **Parallel independent tickets:** each gets its own `git worktree add` + durable branch managed by the Git agent. Use explicit `git worktree add` — NOT the Workflow tool's ephemeral `isolation:'worktree'`. The branch must persist across implement → review → resolve → merge stages; ephemeral worktrees are gone when the agent call ends.
977
1002
 
978
- **Post-merge validation:** after EVERY merge into the integration branch, run Validate agent (build + test). A red build immediately after merge is the cheapest possible conflict detector. Red → quarantine the merged result + escalate.
1003
+ **Post-merge validation:** after EVERY merge into the integration branch, the workflow itself spawns the Validate agent (build + test) over the integration branch; Git runs no build or test. A red build immediately after merge is the cheapest possible conflict detector. It always runs — the merge reports `treeEqual` (whether the merge commit's tree equals the ticket branch head's tree) and the wave row records it, but a true value never skips the Validate.
1004
+
1005
+ **Undo of a red merge:** on FAIL or no PASS verdict the workflow spawns Git to undo the merge. The undo runs only when no remote branch contains the merge commit and the integration HEAD still equals it, as `git reset --keep {mergeSha}^1` on the integration branch — never `--hard`, never a push. It returns `{"undone": true}` or `{"undone": false, "reason": "..."}`. Undone → the row reads `merged: false` and the ticket is quarantined ("post-merge build red; merge undone"). Not undone → the wave stops taking merges and rounds, and its report names the red integration HEAD.
979
1006
 
980
1007
  **Commit discipline:** Git agent creates atomic commits per logical change, conventional-commit format, on the ticket branch before merge.
981
1008
 
@@ -986,13 +1013,13 @@ Two parallel sibling tickets can produce real git conflicts. The resolution is *
986
1013
  **Resolution procedure:**
987
1014
 
988
1015
  1. Git agent detects the conflict and reports the conflicting files + sections
989
- 2. Spawn a Code agent with FULL intent context:
1016
+ 2. Spawn a Code agent whose prompt opens with `OPERATION: implement` and carries `COMPLIANCE_FRAMEWORKS` and `DECISIONS_CONTEXT`, with FULL intent context:
990
1017
  - Both ticket descriptions and plans
991
1018
  - The conflicting diff sections (both sides)
992
1019
  - Relevant ADRs from DECISIONS_CONTEXT (loaded by main model before authoring)
993
1020
  3. Code agent resolves to PRESERVE BOTH INTENTS — the resolution must honor what both tickets were trying to achieve
994
1021
  4. If the correct resolution is NOT UNAMBIGUOUS from the intent context: **do NOT guess** → quarantine + escalate
995
- 5. After any resolution: Validate agent (build + test) immediately
1022
+ 5. After any resolution: the post-merge Validate covers it — the workflow spawns it after the merge, and no Validate is requested from Git
996
1023
 
997
1024
  **Conservative-or-escalate is absolute.** An LLM silently guessing a wrong merge is the highest-danger failure mode in the whole design. When in doubt: quarantine + surface in report. The user re-runs (resume) with the escalated context.
998
1025
 
@@ -1007,7 +1034,7 @@ A workflow cannot pause mid-run (F4). "Escalate" means: quarantine-and-continue
1007
1034
  - Ticket engine FAIL after max retries (Gate 1 exhausted)
1008
1035
  - Gate 2 FAIL after max retries (Evaluate agent/Test agent not satisfied)
1009
1036
  - Circular dependency detected (all remaining tickets blocked on each other)
1010
- - Build red after merge (Validate agent fails post-merge)
1037
+ - Build red after merge (the post-merge Validate agent fails: the merge is undone and the ticket quarantined; a refused undo halts the wave)
1011
1038
  - Review coverage incomplete after retry (a focus area failed to produce a live Review agent result after the retry)
1012
1039
  - Ticket engine crash/stall (unrecoverable exception or watchdog kill — quarantine cascades to dependents)
1013
1040
  - No ticket link while issues are required (the engine stops before implementing)
@@ -1025,7 +1052,7 @@ A workflow cannot pause mid-run (F4). "Escalate" means: quarantine-and-continue
1025
1052
 
1026
1053
  **Wave workflow structure (author after the SINGLE engine blocks above):**
1027
1054
 
1028
- The wave workflow uses the same phases as SINGLE but wraps them in a wave loop. The integration branch is `wave/<initiative>` — the initiative slug, referenced below as `{slug}` — (or the user's current branch). Each ticket works on the branch setup-task created. The Git agent manages worktrees for parallel-eligible tickets. After every merge: Validate agent (build + test). Escalations accumulate in a list; the final report lists all of them.
1055
+ The wave workflow uses the same phases as SINGLE but wraps them in a wave loop. The integration branch is `wave/<initiative>` — the initiative slug, referenced below as `{slug}` — (or the user's current branch). Each ticket works on the branch setup-task created. The Git agent manages worktrees for parallel-eligible tickets. After every merge the workflow spawns the Validate agent (build + test) and undoes a red merge. Escalations accumulate in a list; the final report lists all of them.
1029
1056
 
1030
1057
  Wave skeleton — compact reference (see `wave_loop()` doctrine for full semantics):
1031
1058
 
@@ -1036,11 +1063,12 @@ Wave skeleton — compact reference (see `wave_loop()` doctrine for full semanti
1036
1063
  const WAVE_TICKETS = [...remainingTickets]; // the pre-fetch's refs, in input order — the wave block's row order
1037
1064
  const results = {}; // ticketId → its row of the workflow's return; a ticket with no entry never ran
1038
1065
  // A row from an engine result: the fields step 3 after the workflow renders
1039
- const rowOf = (ticketId, r, merged) => ({ ticket: ticketId, ran: true, verdict: r?.verdict || r?.overallVerdict || null, merged, issuePrLink: r?.issuePrLink || "(none)", evaluateVerdict: r?.gate2?.evaluateVerdict, testVerdict: r?.gate2?.testVerdict, surviving: r?.survivingFindings?.length, coverageComplete: r?.reviewCoverage?.complete });
1066
+ 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 });
1040
1067
  const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
1041
- const waveState = { quarantined: [], round: 0 };
1068
+ const MERGE_SHA = /^[0-9a-f]{40}$/; // a merge commit as git prints it: the only Git-reported value that reaches a later prompt
1069
+ const waveState = { quarantined: [], round: 0, halted: null };
1042
1070
  let reAskedThisDeadlock = false; // re-ask guard: re-ask once on empty ready-set, then escalate
1043
- while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS) {
1071
+ while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS && waveState.halted === null) {
1044
1072
  waveState.round++;
1045
1073
 
1046
1074
  // Design agent reader (opus) applies vacuous-truth rule: a ticket with no unmet dependencies is ALWAYS ready.
@@ -1073,10 +1101,36 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
1073
1101
  // Check both verdict (engine_output_schema) and overallVerdict (SINGLE skeleton alias).
1074
1102
  // PASS and UNVERIFIED merge; PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
1075
1103
  if (["PASS", "UNVERIFIED"].includes(engineResult.verdict || engineResult.overallVerdict)) {
1076
- const merge = await agent(`Merge ${engineResult.branch} to ${INTEGRATION_BRANCH}. Include ticket ID ${ticketId} in the merge commit message. Run Validate agent (build + test) after merge.
1077
- Return: {"merged": true} — or {"merged": false, "reason": "<why>"} when the merge or the post-merge build failed and the merge was not kept.`, { agentType: "Git" });
1078
- results[ticketId] = rowOf(ticketId, engineResult, merge?.merged === true);
1079
- if (merge?.merged !== true) waveState.quarantined.push({ ticket: ticketId, reason: merge?.reason || "merge or post-merge build failed" });
1104
+ // Git merges locally and validates nothing: the workflow owns the post-merge Validate (D-WAVE-MERGE-VALIDATE).
1105
+ const merge = await agent(`Merge ${engineResult.branch} to ${INTEGRATION_BRANCH} locally. Include ticket ID ${ticketId} in the merge commit message. Do not push, and run no build or test.
1106
+ Return: {"merged": true, "mergeSha": "<40-hex merge commit>", "treeEqual": <true when mergeSha^{tree} equals the tree of ${engineResult.branch}'s head>} — or {"merged": false, "reason": "<why>"} when no merge is kept.`, { agentType: "Git" });
1107
+ let kept = false;
1108
+ let reason = merge?.reason || "merge failed";
1109
+ if (merge?.merged === true && !MERGE_SHA.test(String(merge.mergeSha))) {
1110
+ // A merge may sit on the integration branch with no SHA to validate or undo it by: stop taking merges.
1111
+ waveState.halted = { ticket: ticketId, mergeSha: null, reason: "merge reported without a 40-hex mergeSha; the integration branch may hold an unvalidated merge" };
1112
+ reason = waveState.halted.reason;
1113
+ } else if (merge?.merged === true) {
1114
+ // A merge counts as kept only when this Validate returns PASS. It always runs: treeEqual is recorded, never a reason to skip.
1115
+ const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
1116
+ Follow your Running commands block for every build and test command.
1117
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
1118
+ if (post?.verdict === "PASS") {
1119
+ kept = true;
1120
+ } else {
1121
+ const undo = await agent(`Undo the merge ${merge.mergeSha} on ${INTEGRATION_BRANCH}, only when no remote branch contains ${merge.mergeSha} and the HEAD of ${INTEGRATION_BRANCH} still equals ${merge.mergeSha}. On the integration branch run exactly this one command and no other that changes a branch or a remote: git reset --keep ${merge.mergeSha}^1
1122
+ Return: {"undone": true} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
1123
+ if (undo?.undone === true) {
1124
+ reason = "post-merge build red; merge undone";
1125
+ } else {
1126
+ // A merge that stays red would poison every later ticket: stop taking merges and rounds, and name the red HEAD.
1127
+ reason = `post-merge build red; merge not undone (${undo?.reason || "no reason given"})`;
1128
+ waveState.halted = { ticket: ticketId, mergeSha: merge.mergeSha, reason };
1129
+ }
1130
+ }
1131
+ }
1132
+ results[ticketId] = rowOf(ticketId, engineResult, kept, merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null);
1133
+ if (!kept) waveState.quarantined.push({ ticket: ticketId, reason });
1080
1134
  } else {
1081
1135
  results[ticketId] = rowOf(ticketId, engineResult, false);
1082
1136
  waveState.quarantined.push({ ticket: ticketId, reason: engineResult.escalations?.[0]?.description || "engine fail/escalated" });
@@ -1087,11 +1141,12 @@ Return: {"merged": true} — or {"merged": false, "reason": "<why>"} when the me
1087
1141
  waveState.quarantined.push({ ticket: ticketId, reason: `engine crash: ${String(err)}` });
1088
1142
  }
1089
1143
  remainingTickets = remainingTickets.filter(t => t !== ticketId);
1144
+ if (waveState.halted !== null) break; // a red integration HEAD takes no further merge in this round
1090
1145
  }
1091
1146
  }
1092
1147
 
1093
1148
  // After the wave report is written: one row per wave ticket, in input order. No entry ⇒ never ran (cascade, deadlock, MAX_ROUNDS).
1094
- return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, issuePrLink: "(none)" }), quarantined: waveState.quarantined };
1149
+ return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, treeEqual: null, issuePrLink: "(none)" }), quarantined: waveState.quarantined, halted: waveState.halted };
1095
1150
  ```
1096
1151
 
1097
1152
  ---
@@ -1115,6 +1170,7 @@ A workflow cannot pause mid-run. After the build/wave workflow returns, you (the
1115
1170
  In WAVE mode, if no tracking-issue number was resolved in Pre-authoring step 5: state `TRACEABILITY: DEGRADED (no tracking issue for this run)` in the run summary and skip — never skip silently.
1116
1171
  3. **Compose the wave PR inputs** (WAVE mode only — skip this step entirely in SINGLE mode). The workflow returned `tickets`: one entry per wave ticket, in its input order, each `{ticket, ran, verdict, merged, issuePrLink, evaluateVerdict, testVerdict, surviving, coverageComplete}`. Every value in it is agent-reported and treated as untrusted: nothing below is repaired, and no reference is ever composed from a number.
1117
1172
  - **Branch check.** Run `git -C "{integration worktree root}" branch --show-current`. Only a name matching `^wave/[a-z0-9][a-z0-9-]{0,59}$` opens a wave PR, and its part after `wave/` is the `{slug}` below. Any other name: record `TRACEABILITY: DEGRADED (not a wave branch)` and go to step 4 with no wave PR.
1173
+ - **Halted** (the workflow returned a non-null `halted`: a post-merge build it could not undo, or a merge reported with no SHA): the integration HEAD may be red. Record `Wave PR: skipped (integration HEAD red, wave halted at <halted.ticket>)`, name `halted.mergeSha` (or `none reported`) and `halted.reason` as the red integration HEAD in the run summary, and go to step 4 with no wave PR.
1118
1174
  - **Nothing merged** (no entry has `merged: true`): record `Wave PR: skipped (nothing merged)` and go to step 4.
1119
1175
  - Otherwise compose (a) and then (b).
1120
1176
 
@@ -1210,17 +1266,7 @@ git -C "{integration worktree root}" push origin HEAD; echo "exit=$?"
1210
1266
 
1211
1267
  **(d) Refresh.** Resolve the publication value for the integration worktree:
1212
1268
 
1213
- **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:
1214
-
1215
- ```bash
1216
- node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
1217
- ```
1218
-
1219
- 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>` — 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` instead.
1220
-
1221
- The accepted line is the only source of these values: the script alone folds the committed `.devflow/project.json`, the personal `.devflow/config.json` and the machine manifest.
1222
-
1223
- **Resolve `REVIEW_PUBLICATION` per worktree:** take `REVIEW_PUBLICATION` from that worktree's settings line, with `{root}` the worktree's root — multi-worktree repos may resolve different values per worktree. The line already caps the personal choice at the team's (D-PUBLICATION-CEILING), so it is `off`, `auto` or `full`, and `off` when the line was unresolvable.
1269
+ **Resolve `REVIEW_PUBLICATION` per worktree:** take `REVIEW_PUBLICATION` from that worktree's settings line — the line resolved above for `{root}`, the worktree's root, by the settings block when this run has not yet resolved that root; multi-worktree repos may resolve different values per worktree. The line already caps the personal choice at the team's (D-PUBLICATION-CEILING), so it is `off`, `auto` or `full`, and `off` when the line was unresolvable.
1224
1270
 
1225
1271
  **Evidence stub:** only when `EVIDENCE_POLICY` is `required`, a resolved `off` becomes `stub`, so a counts-only record still reaches the PR. `stub` is never a config value: the settings line never carries it.
1226
1272