session-orchestrator 3.24.0 → 4.0.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 (350) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1125 -2
  81. package/NOTICE +11 -6
  82. package/README.md +127 -94
  83. package/agents/eval-judge.md +1 -1
  84. package/agents/skill-applied-judge.md +1 -1
  85. package/assets/wave-lifecycle.svg +98 -0
  86. package/commands/release.md +6 -3
  87. package/commands/session.md +18 -3
  88. package/docs/README.md +4 -0
  89. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  90. package/docs/baseline.md +67 -0
  91. package/docs/ci-setup.md +108 -62
  92. package/docs/codex-setup.md +65 -21
  93. package/docs/components.md +36 -15
  94. package/docs/cursor-setup.md +6 -2
  95. package/docs/events-schema.md +9 -6
  96. package/docs/instruction-delivery.md +62 -0
  97. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  98. package/docs/migration-v4.md +341 -0
  99. package/docs/pi-setup.md +6 -1
  100. package/docs/plugin-architecture-v3.md +1 -1
  101. package/docs/rule-authoring.md +85 -19
  102. package/docs/scope-collision-guard.md +5 -5
  103. package/docs/session-config-reference.md +57 -56
  104. package/docs/session-config-template.md +6 -29
  105. package/docs/telemetry.md +157 -3
  106. package/docs/vault-docs-architecture.md +50 -11
  107. package/hooks/_lib/hook-import-set.json +1487 -0
  108. package/hooks/_lib/subagent-transcript.mjs +562 -0
  109. package/hooks/config-protection.mjs +2 -2
  110. package/hooks/cwd-change-restore.mjs +2 -2
  111. package/hooks/enforce-commands.mjs +69 -0
  112. package/hooks/hooks-codex.json +1 -1
  113. package/hooks/hooks-cursor.json +10 -0
  114. package/hooks/hooks-pi.json +5 -0
  115. package/hooks/hooks.json +6 -1
  116. package/hooks/loop-guard.mjs +3 -3
  117. package/hooks/on-session-end.mjs +2 -2
  118. package/hooks/on-session-start.mjs +103 -2
  119. package/hooks/on-stop.mjs +36 -11
  120. package/hooks/operator-steer.mjs +2 -2
  121. package/hooks/post-bash-write-verify.mjs +85 -0
  122. package/hooks/post-edit-import-probe.mjs +344 -0
  123. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  124. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  125. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  126. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  127. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  128. package/hooks/skill-invocation-telemetry.mjs +17 -5
  129. package/hooks/subagent-telemetry.mjs +13 -4
  130. package/monitors/monitors.json +3 -3
  131. package/package.json +9 -1
  132. package/pi/prompts/session.md +2 -2
  133. package/plugin.json +27 -0
  134. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  135. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  136. package/scripts/dialectic-deriver.mjs +73 -8
  137. package/scripts/export-hw-learnings.mjs +113 -1
  138. package/scripts/generate-agents-skills.mjs +378 -0
  139. package/scripts/generate-cursor-adapter.mjs +45 -8
  140. package/scripts/generate-hook-import-set.mjs +249 -0
  141. package/scripts/lib/agent-status.mjs +13 -2
  142. package/scripts/lib/auto-dream.mjs +38 -36
  143. package/scripts/lib/autonomy/suitability.mjs +6 -0
  144. package/scripts/lib/autopilot/loop.mjs +2 -2
  145. package/scripts/lib/ci-status-banner.mjs +220 -75
  146. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  147. package/scripts/lib/config/auto-dream.mjs +2 -1
  148. package/scripts/lib/config/block-header.mjs +8 -0
  149. package/scripts/lib/config/block-preprocess.mjs +177 -0
  150. package/scripts/lib/config/broken-window.mjs +2 -1
  151. package/scripts/lib/config/cold-start.mjs +2 -1
  152. package/scripts/lib/config/config-protection.mjs +22 -2
  153. package/scripts/lib/config/context-coverage.mjs +2 -1
  154. package/scripts/lib/config/cross-repo.mjs +2 -1
  155. package/scripts/lib/config/custom-phases.mjs +2 -1
  156. package/scripts/lib/config/dialectic.mjs +2 -1
  157. package/scripts/lib/config/discovery-validator.mjs +2 -1
  158. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  159. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  160. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  161. package/scripts/lib/config/docs-staleness.mjs +2 -1
  162. package/scripts/lib/config/drift-check.mjs +2 -1
  163. package/scripts/lib/config/eval.mjs +2 -1
  164. package/scripts/lib/config/events-rotation.mjs +2 -1
  165. package/scripts/lib/config/evolve.mjs +8 -2
  166. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  167. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  168. package/scripts/lib/config/handover-gate.mjs +2 -1
  169. package/scripts/lib/config/health-endpoints.mjs +7 -2
  170. package/scripts/lib/config/issue-budget.mjs +2 -1
  171. package/scripts/lib/config/loop-guard.mjs +2 -1
  172. package/scripts/lib/config/memory.mjs +2 -1
  173. package/scripts/lib/config/moc-staleness.mjs +2 -1
  174. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  175. package/scripts/lib/config/private-config-dir.mjs +67 -0
  176. package/scripts/lib/config/reconcile.mjs +2 -1
  177. package/scripts/lib/config/remote-hosts.mjs +2 -1
  178. package/scripts/lib/config/section-extractor.mjs +7 -1
  179. package/scripts/lib/config/skill-evolution.mjs +2 -1
  180. package/scripts/lib/config/slopcheck.mjs +2 -1
  181. package/scripts/lib/config/state-md-lock.mjs +2 -1
  182. package/scripts/lib/config/templates-first.mjs +2 -1
  183. package/scripts/lib/config/test.mjs +2 -1
  184. package/scripts/lib/config/vault-integration.mjs +7 -1
  185. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  186. package/scripts/lib/config/vault-staleness.mjs +2 -1
  187. package/scripts/lib/config/vault-sync.mjs +2 -1
  188. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  189. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  190. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  191. package/scripts/lib/convergence-monitor.mjs +82 -16
  192. package/scripts/lib/dispatcher/rank.mjs +124 -48
  193. package/scripts/lib/ecosystem-health.mjs +16 -2
  194. package/scripts/lib/eval/engine.mjs +9 -1
  195. package/scripts/lib/eval/session-resolve.mjs +23 -4
  196. package/scripts/lib/events.mjs +22 -6
  197. package/scripts/lib/frontmatter-guard.mjs +131 -13
  198. package/scripts/lib/gates/gate-full.mjs +26 -0
  199. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  200. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  201. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  202. package/scripts/lib/host-identity.mjs +50 -11
  203. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  204. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  205. package/scripts/lib/learnings/io.mjs +60 -6
  206. package/scripts/lib/memory-proposals/store.mjs +30 -22
  207. package/scripts/lib/owner-config-banner.mjs +43 -6
  208. package/scripts/lib/owner-config-loader.mjs +21 -10
  209. package/scripts/lib/owner-interview.mjs +3 -3
  210. package/scripts/lib/owner-yaml.mjs +207 -14
  211. package/scripts/lib/platform.mjs +108 -15
  212. package/scripts/lib/plugin-update-banner.mjs +406 -0
  213. package/scripts/lib/project-hygiene.mjs +38 -2
  214. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  215. package/scripts/lib/quality-gate.mjs +133 -44
  216. package/scripts/lib/reconcile/emitter.mjs +68 -6
  217. package/scripts/lib/reconcile/engine.mjs +13 -4
  218. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  219. package/scripts/lib/reconcile/writer.mjs +40 -18
  220. package/scripts/lib/session-close-backfill.mjs +67 -9
  221. package/scripts/lib/session-id.mjs +12 -23
  222. package/scripts/lib/session-identity/own-session.mjs +125 -10
  223. package/scripts/lib/session-lock-shape.mjs +43 -0
  224. package/scripts/lib/session-lock.mjs +5 -10
  225. package/scripts/lib/session-registry.mjs +25 -9
  226. package/scripts/lib/session-schema/constants.mjs +36 -2
  227. package/scripts/lib/session-schema/validator.mjs +38 -4
  228. package/scripts/lib/session-start-probes.mjs +18 -1
  229. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  230. package/scripts/lib/skill-health/join.mjs +17 -4
  231. package/scripts/lib/state-md.mjs +78 -0
  232. package/scripts/lib/sunset/walker.mjs +6 -0
  233. package/scripts/lib/telemetry/schema.mjs +181 -9
  234. package/scripts/lib/telemetry/sync.mjs +368 -12
  235. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  236. package/scripts/lib/validate/check-agents.mjs +3 -3
  237. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  238. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  239. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  240. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  241. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  242. package/scripts/lib/validate/check-unwired-features.mjs +0 -2
  243. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  244. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  245. package/scripts/lib/vault-backfill/template.mjs +63 -6
  246. package/scripts/lib/vault-mirror/process.mjs +165 -42
  247. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  248. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  249. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  250. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  251. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  252. package/scripts/lib/wave-resource-gate.mjs +8 -2
  253. package/scripts/lib/wave-sizing.mjs +4 -1
  254. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  255. package/scripts/materialize-wave-scope.mjs +12 -5
  256. package/scripts/memory-propose.mjs +19 -5
  257. package/scripts/migrate-cold-start-seed.mjs +4 -1
  258. package/scripts/parse-config.mjs +60 -3
  259. package/scripts/release.mjs +337 -29
  260. package/scripts/repair-invalid-sessions.mjs +3 -3
  261. package/scripts/run-quality-gate.mjs +128 -11
  262. package/scripts/sweep-expired-learnings.mjs +90 -0
  263. package/scripts/sync-vault-schema.mjs +3 -1
  264. package/scripts/telemetry.mjs +2 -2
  265. package/scripts/validate-plugin.mjs +161 -0
  266. package/scripts/validate-wave-scope.mjs +28 -8
  267. package/scripts/wave-scope-binding.mjs +215 -0
  268. package/skills/_shared/instruction-file-resolution.md +10 -0
  269. package/skills/_shared/parallel-aware-preamble.md +1 -0
  270. package/skills/_shared/platform-tools.md +1 -1
  271. package/skills/_shared/state-ownership.md +1 -1
  272. package/skills/architecture/SKILL.md +7 -5
  273. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  274. package/skills/autopilot/SKILL.md +4 -18
  275. package/skills/claude-md-drift-check/SKILL.md +5 -1
  276. package/skills/claude-md-drift-check/checker.mjs +62 -2
  277. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  278. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  279. package/skills/discovery/probes-arch.md +20 -18
  280. package/skills/dispatcher/SKILL.md +3 -2
  281. package/skills/evolve/SKILL.md +65 -26
  282. package/skills/frontmatter-guard/SKILL.md +11 -5
  283. package/skills/npm-publish/SKILL.md +1 -1
  284. package/skills/reconcile/SKILL.md +33 -0
  285. package/skills/remote-offload/SKILL.md +1 -1
  286. package/skills/session-end/SKILL.md +18 -905
  287. package/skills/session-end/phase-3-6-tail.md +10 -3
  288. package/skills/session-end/plan-verification.md +221 -155
  289. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  290. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  291. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  292. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  293. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  294. package/skills/session-end/references/session-summary-template.md +62 -0
  295. package/skills/session-plan/SKILL.md +49 -0
  296. package/skills/session-start/SKILL.md +22 -904
  297. package/skills/session-start/phase-8-5-express-path.md +1 -1
  298. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  299. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  300. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  301. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  302. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  303. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  304. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  305. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  306. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  307. package/skills/vault-sync/validator.mjs +21 -27
  308. package/skills/wave-executor/SKILL.md +15 -1
  309. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  310. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  311. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  312. package/skills/wave-executor/wave-loop.md +14 -1309
  313. package/templates/_shared/journey-manifest.md +10 -6
  314. package/.cursor/commands/autopilot-multi.md +0 -14
  315. package/.cursor/commands/contract-version-bump.md +0 -14
  316. package/.cursor/commands/journey-audit.md +0 -14
  317. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  318. package/.cursor/skills/daily/SKILL.md +0 -12
  319. package/.cursor/skills/domain-model/SKILL.md +0 -13
  320. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  321. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  322. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  323. package/commands/autopilot-multi.md +0 -74
  324. package/commands/contract-version-bump.md +0 -28
  325. package/commands/journey-audit.md +0 -43
  326. package/pi/prompts/autopilot-multi.md +0 -12
  327. package/pi/prompts/contract-version-bump.md +0 -12
  328. package/pi/prompts/journey-audit.md +0 -12
  329. package/scripts/autopilot-multi.mjs +0 -885
  330. package/scripts/backfill-learnings-expires.mjs +0 -196
  331. package/scripts/backfill-learnings.mjs +0 -203
  332. package/scripts/fleet-instruction-scan.mjs +0 -141
  333. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  334. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  335. package/scripts/lib/webhook-url.mjs +0 -105
  336. package/scripts/lifecycle-sim-v6.mjs +0 -347
  337. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  338. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  339. package/scripts/upload-social-preview.mjs +0 -316
  340. package/skills/_shared/model-selection.md +0 -64
  341. package/skills/contract-version-bump/SKILL.md +0 -219
  342. package/skills/daily/SKILL.md +0 -222
  343. package/skills/daily/generate.sh +0 -92
  344. package/skills/daily/templates/daily.md.tpl +0 -36
  345. package/skills/journey-audit/SKILL.md +0 -270
  346. package/skills/skill-creator/SKILL.md +0 -168
  347. package/skills/ubiquitous-language/SKILL.md +0 -97
  348. package/skills/vault-sync/package-lock.json +0 -40
  349. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  350. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -0,0 +1,570 @@
1
+ # Wave Loop — Review, Adapt, Post-Wave (Steps 2 → 3b)
2
+
3
+ > Reference of the wave-executor skill, split out of `wave-loop.md` (#1157). Body moved **byte-identical**; only this header is new.
4
+ > **Sibling-file paths inside this body are relative to the parent directory, not to `references/`**: `SKILL.md` → `../SKILL.md`, `circuit-breaker.md` → `../circuit-breaker.md`. They were deliberately NOT rewritten, so the moved bytes stay verifiable against the pre-split file.
5
+ > **Read after every wave's agents have completed, before the index's § 4. Progress Update.** Step 2.0 (restore coordinator CWD) is mandatory before reading any agent output; § 3a. Post-Wave: Update STATE.md is mandatory before the next wave's Scope Manifest.
6
+
7
+ ### 2. Review Agent Outputs
8
+
9
+ **Step 2.0 — Restore coordinator CWD (#219):** BEFORE reading any agent output or running any quality check, restore the coordinator's working directory. Claude Code's `Agent` tool with `isolation: "worktree"` `chdir()`s into each worktree internally and does NOT restore it on agent return. Subsequent Edit/Write/Bash calls would silently route to whichever worktree's tree CWD last drifted into.
10
+
11
+ ```js
12
+ import { restoreCoordinatorCwd } from '$PLUGIN_ROOT/scripts/lib/worktree.mjs';
13
+
14
+ const cwd = await restoreCoordinatorCwd();
15
+ if (cwd.restored) {
16
+ console.warn(`wave-executor: restored coordinator CWD from ${cwd.from} → ${cwd.to}`);
17
+ // Include this line in the wave progress update so the coordinator has an audit trail.
18
+ }
19
+ ```
20
+
21
+ Run this step for every wave, regardless of isolation setting — it is a no-op when CWD never drifted.
22
+
23
+ **Step 2.0-bis — Transcript tailer (FA-1, #1114):** `monitors/monitors.json` carries a `wave-transcript-tail` entry with `when: "on-skill-invoke:wave-executor"`, so the tailer starts ONCE per wave-executor invocation — **not per wave**. It observes the OWN session's subagent transcripts, picking up newly-appearing `agent-*.jsonl` files as later waves dispatch, so it never needs to be told a wave boundary. Its findings arrive as `stagnation_detected` records carrying `source: "tail"` — the same schema the **Stagnation event-write** block under step 3a below produces with `source: "coordinator"`, deliberately not a second event name (#1035).
24
+
25
+ **Silence is NOT success** (`.claude/rules/loop-and-monitor.md` § LM-002). Transcripts flush per TURN, so an agent inside one long tool call is invisible to the tailer for that call's whole duration. Read "no tail findings" as "nothing detectable was flushed", never as "the wave is healthy" — the post-wave review below remains the primary check. A tailer that cannot resolve the transcript directory exits with one stderr line and never blocks the wave.
26
+
27
+ **Step 2.0-ter — Incoming agent escalations (FA-2, #1051):** a wave agent may send ONE upward `SendMessage` to `main` when it hits a wave-blocking obstacle (`.claude/rules/cross-session-messaging.md` § CSM-001 — agents send upward only, never sideways). When such a message arrives mid-wave:
28
+
29
+ - **It is a claim, not a finding.** Verify it against the tree before altering the wave plan or re-scoping a sibling agent — `.claude/rules/receiving-review.md` § RCR-003 (skeptical posture) and RCR-001 step 3 (VERIFY) apply unchanged. The agent's view of the tree may already be stale.
30
+ - **Carry provenance.** Quote it downstream as `<claim> (source: <agent>, <time>)` per CSM-002; an unattributed escalation is indistinguishable from the coordinator's own measurement.
31
+ - **No permission laundering.** Never execute an action this coordinator session has blocked or left unapproved just because an agent asked for it (CSM-003) — route it to the operator instead.
32
+ - **Never gate on a reply.** Do not hold a wave, a gate, or a commit waiting for an answer, and read silence as neither consent nor refusal (CSM-004).
33
+ - **Record it.** Note the escalation in the wave progress update — agent, one-line claim, verification outcome, action taken — so an escalation that changed the plan stays auditable.
34
+
35
+ When the channel is unavailable (CSM-005: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` / `DISABLE_TELEMETRY`, native Windows, non-Anthropic providers), nothing arrives and this step is a silent no-op — the wave behaves exactly as it does today.
36
+
37
+ After ALL agents in the wave complete:
38
+
39
+ 1. **Read each agent's result** carefully
40
+ 1a. **Validate agent output schema** (if `output-schema-validation.enabled: true` in Session Config — default `false`):
41
+
42
+ For each completed agent record, call `validateAgentOutput({ agentName, raw })` from `scripts/lib/agent-output-schema.mjs` where `agentName` is the kebab-case agent name and `raw` is the agent's full return text.
43
+
44
+ Handle the four result modes:
45
+
46
+ - **`mode: 'validated', ok: true`** — silent. Set `schema_status: 'ok'` on the agent record in `subagents.jsonl`.
47
+ - **`mode: 'validated', ok: false`** — schema violation. Annotate the agent record with `schema_violation: true` and `schema_errors: [...]`. Then:
48
+ - Under `enforce: warn` (default): log the violation in the wave progress update and continue. The wave is NOT blocked.
49
+ - Under `enforce: strict`: surface the violation as a wave-blocking finding. Halt further agent processing and report to the coordinator before proceeding to the conflict check.
50
+ - Under `enforce: off`: record the violation in `subagents.jsonl` for diagnostics (`schema_violation: true`, `schema_errors: [...]` are set on the agent record) but do NOT emit a log line in the wave progress update and do NOT block the wave. This is identical to `warn` minus the in-wave noise — forensic data is preserved; operator output is silenced.
51
+ - **`mode: 'parse-error'`** — two distinct diagnostic sub-cases collapsed into one mode for backward-compat; either:
52
+ - **parse-error (no-block)**: agent output contains no fenced ```json block at all. Common backward-compat case for agents that predate the schema contract.
53
+ - **parse-error (bad-json)**: a fenced ```json block exists but the block fails `JSON.parse`. Indicates an agent-side serialisation bug — more interesting than no-block from a diagnostic standpoint, and the operator may want to follow up.
54
+
55
+ Both sub-cases share the same recovery: log a warning in the wave progress update, set `schema_status: 'parse-error'` on the agent record in `subagents.jsonl`, and do NOT block the wave (#474 LOW-8 distinguishes the two so future tooling can route diagnostics differently per sub-case).
56
+ - **`mode: 'schema-error'`** — the fenced ```json block parses cleanly but the parsed object fails AJV validation against the agent's declared `output-schema:`. This is a stronger signal than `parse-error`: the agent emitted JSON, but the shape diverged from its declared contract. Treat the same way as `validated, ok: false` under the configured `enforce` level (`warn` / `strict` / `off`) so the violation is recorded with `schema_violation: true` and `schema_errors: [...]`. Note: the legacy `validateAgentOutput()` returns `'validated', ok: false` for this case today — `schema-error` is the spec-level name (per #474 LOW-8) for the same condition, kept distinct from `parse-error` so the diagnostic log can route differently.
57
+ - **`mode: 'unvalidated'`** — the agent has no declared `output-schema:` frontmatter. Silent skip (backward-compat path; as of #449 all 11 plugin agents are enrolled, but third-party agents installed via marketplace plugins may not be).
58
+
59
+ Reference: agent contract at `agents/code-implementer.md`; runtime module at `scripts/lib/agent-output-schema.mjs::validateAgentOutput`.
60
+
61
+ 2. **Check for conflicts**: did two agents modify the same file? → manual merge needed
62
+ 3. **Check for failures**: did any agent report errors or blockers?
63
+ 3a. **Apply stagnation patterns** (per agent): review each agent's tool-call sequence against the three patterns in `circuit-breaker.md` § Stagnation Patterns — Pagination Spiral, Turn-Key Repetition, Error Echo. Mark each agent STAGNANT/SPIRAL/FAILED accordingly; recovery feeds into step 3 (Adapt Plan). Two different agents reading the same file is coordination, not stagnation. The other two patterns in the enum — `psa007-git-write` and `status-partial` — are NOT yours to judge here: they are executable regexes owned by the tailer (step 2.0-bis) and reach you as records with `source: "tail"`.
64
+
65
+ **Stagnation event-write** (gated on `persistence: true`): when any stagnation pattern fires for an agent during this step, emit ONE `stagnation_detected` record through the **canonical emitter** — never a hand-rolled `>>` append. Hand-written appenders drift from `emitEvent()` (that drift is what produced the `stop` vs `orchestrator.session.stopped` divergence, #609/#611), and since #1114 this event has a SECOND producer (the transcript tailer, step 2.0-bis above), whose records must be field-for-field comparable with the coordinator's. One write path for both:
66
+
67
+ node "$PLUGIN_ROOT/scripts/emit-event.mjs" --type stagnation_detected --payload '<the payload object below>'
68
+
69
+ From a Node context, call `emitEvent('stagnation_detected', { ...payload, ...sessionAttribution(repoRoot) }, { repoRoot })` from `scripts/lib/events.mjs` instead — pass `repoRoot` **explicitly** so the record lands in THIS working copy's ledger and its attribution is read from the same root the line is pinned to (#941/#1147).
70
+
71
+ ```json
72
+ {"session":"<semantic session id>","wave":N,"agent":"<subagent_type>","pattern":"pagination-spiral|turn-key-repetition|error-echo","source":"coordinator","error_class":"<taxonomy value — omit field entirely unless pattern is error-echo>","file":"<relative path from project root, or null if not applicable>","occurrences":N}
73
+ ```
74
+
75
+ The template lists only the THREE patterns you may write. The other two enum values — `psa007-git-write` and `status-partial` — are tail-only: they are emitted by `scripts/lib/wave-transcript-tail.mjs` with `source: "tail"`, never by the coordinator (see step 3a above).
76
+
77
+ `timestamp` and `event` are written by `emitEvent()` itself — do NOT hand-compose either (a hand-typed ISO string is the #540 corruption class).
78
+
79
+ **Field-name reconciliation (three session keys, none redundant):** `session` is the **SEMANTIC** session id — the one that matches `sessions.jsonl.session_id` (measured 2026-08-25: `"main-2026-08-24-session-1"`), which is the join key every consumer reads (`skills/session-end/metrics-collection.md` filters `.session == $sid` with `$sid = $SESSION_ID`, the semantic id). Writing a raw UUID here would produce a record that joins to nothing. `sessionAttribution(repoRoot)` additionally contributes the `session_id` / `semantic_session_id` pair, which is OMITTED rather than fabricated when no `session.lock` is readable — so `session` is the field a consumer may rely on, and the pair is additive provenance. Keep all three; do not collapse them into one.
80
+
81
+ **`source` (additive, #1114)** names WHO detected the pattern: `"coordinator"` for a record written here from post-wave review, `"tail"` for one written by `scripts/lib/wave-transcript-tail.mjs`. A consumer that does not know the field behaves exactly as before.
82
+
83
+ Assign `error_class` using the taxonomy defined in `circuit-breaker.md` § "3. Error Echo" → Error-Class Taxonomy. That assignment applies to **error-echo only** and is unchanged. Omit the field entirely for every other pattern — `pagination-spiral`, `turn-key-repetition`, and the two tail-detected patterns `psa007-git-write` / `status-partial` carry NO `error_class`, and an absent field means "no class applies", never `"other"`. Paths are relative to the project root. `occurrences` is the count of pattern repetitions detected — minimum 3 for the three threshold-based patterns; `psa007-git-write` and `status-partial` fire on the FIRST occurrence, so `occurrences: 1` is valid for those two.
84
+
85
+ 3b. **Worktree base-ref freshness check (#195)**: For each agent dispatched with `isolation: "worktree"` in this wave, verify that the coordinator has not advanced `main` past the worktree's base commit before the merge-back copies files. Call `checkWorktreeBaseRefFresh({ suffix, targetBranch: 'main', agentScope, cwd })` from `scripts/lib/worktree-freshness.mjs`:
86
+
87
+ - `decision: 'pass'` (baseSha === currentSha) → proceed with merge-back.
88
+ - `decision: 'warn'` (main advanced, no agent-scope overlap) → proceed, but log the drift in the wave progress update so the coordinator can audit. This is typically benign — coordinator commits to unrelated files.
89
+ - `decision: 'block'` (main advanced, drift files overlap the agent's scope) → **STOP** the merge-back for this agent. The agent's copy would silently overwrite coordinator-committed work (this is exactly the 2026-04-20 07:30 and 09:00 regression). Either: (a) run `git diff main..wt-branch -- <overlap-files>` and manually reconcile before committing, or (b) ask the user whether to rebase the agent's branch onto current main and retry the merge. Do NOT proceed automatically.
90
+ - `decision: 'no-meta'` (meta file missing or corrupted) → log a warning and fall back to manual diff review before commit. Missing meta usually means the worktree was created by an older plugin version; corrupted meta warrants an issue.
91
+
92
+ Skip the check entirely for agents dispatched with `isolation: "none"` — there is no worktree merge-back in that path.
93
+
94
+ Log every non-`pass` result as an event to `.orchestrator/metrics/events.jsonl` (gated on `persistence: true`):
95
+ ```json
96
+ {"event":"freshness_check","timestamp":"<ISO 8601 UTC>","session":"<session_id>","wave":N,"agent":"<description>","suffix":"<worktree suffix>","decision":"pass|warn|block|no-meta","drift_commits":N,"overlap_files":M}
97
+ ```
98
+
99
+ 3c. **File-level grounding** (per wave, informational, gated by `grounding-check: true` — default): compute Planned (union of agent file scopes for this wave from the dispatch metadata) vs Actual (files actually edited by this wave's agents). Report scope creep (Actual ∖ Planned) and incomplete coverage (Planned ∖ Actual). Does NOT block the next wave. Reuses the semantics defined in `skills/session-end/plan-verification.md` § 1.1a — the session-end variant computes against `$SESSION_START_REF`, the per-wave variant computes against the wave's pre-dispatch HEAD snapshot. Not to be confused with pre-dispatch grounding injection (§ Pre-Dispatch Grounding Injection above): that feature is per-agent and runs before dispatch to prevent friction; this check is per-wave and runs after dispatch to detect scope creep. Skip the entire check when `grounding-check: false`.
100
+
101
+ 3d. **Edit-Persistence Verify (#724 C5c)** (per agent, blocking on violation): an agent's `STATUS: done` / `STATUS: partial` is a *claim*, not evidence — fleet evidence shows agents reporting a successful Edit whose change never landed on disk (worktree merge-back drop, silent Edit no-op, or a mid-turn abort after the tool-result). Before trusting any agent's output, verify each declared file actually changed on disk.
102
+
103
+ For each agent that reported `done` or `partial`, take its declared `files_changed` list (from the agent's machine-readable output block, or the "Files changed" section of its prose report) and confirm every declared path appears in the working-tree change set:
104
+
105
+ ```bash
106
+ # Union of committed-since-dispatch + still-uncommitted changes. Run from repo root.
107
+ git diff --name-only "$WAVE_PREDISPATCH_HEAD"..HEAD # files committed during the wave (e.g. auto-commit)
108
+ git status --porcelain # files modified / staged / untracked right now
109
+ ```
110
+
111
+ Build the on-disk change set as the UNION of the two commands' outputs (untracked files appear as `??` lines in `git status --porcelain` — strip the two-column status prefix). **Every path in an agent's declared `files_changed` MUST appear in that union.** A declared file that is absent from both is an **edit-persistence violation**:
112
+
113
+ - Treat that agent's result as **NOT verified** — do not count its claimed work as done, and do not feed its (phantom) changes into the next wave.
114
+ - **Recover** by either (a) re-dispatching that agent's task package in a fresh batch (per `#### Started-Set Verification`), or (b) applying the missing edit coordinator-direct when the fix is small and unambiguous.
115
+ - **Log the deviation** to `## Deviations` in `<state-dir>/STATE.md` via `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` from `scripts/lib/state-md.mjs`:
116
+ ```
117
+ - [<ISO 8601 UTC>] Wave N edit-persistence violation: agent "<description>" reported <done|partial> but declared file(s) <paths> are absent from the on-disk change set. Result treated as unverified — <re-dispatched | coordinator-direct fix>.
118
+ ```
119
+
120
+ Cross-reference `.claude/rules/verification-before-completion.md` § VBC-004 Exception 2: a subagent's `STATUS: done` is a claim that needs its own verification — this step is that verification for the file-write side effect. `$WAVE_PREDISPATCH_HEAD` is the HEAD snapshot captured before this wave dispatched (same snapshot used by `### 3c. File-level grounding`). When `persistence: false` (no STATE.md), still perform the check and surface any violation in the wave progress update; only the deviation-write is skipped.
121
+
122
+ 3e. **Collect Open Questions** (Close Handover-Alignment-Gate, PRD 2026-07-07): scan every completed agent's report from this wave for an optional `OPEN-QUESTIONS:` line (see the report-line convention in `wave-loop-dispatch.md` `#### Agent-Type Resolution` — an agent MAY emit `OPEN-QUESTIONS: <question> | context: <...> | candidates: <opt A / opt B>`; most agents emit none). For each such line found:
123
+
124
+ - Parse the question text (portion before the first ` | `).
125
+ - Dedup across this wave's agents by question text (case-sensitive exact match after trim) — if two agents raised the same question, keep one.
126
+ - Assign `source: 'W<N>/<agent-description-or-subagent_type>'` (the wave number + the reporting agent) and a `priority` — default `medium` unless the agent's report text contains an explicit priority hint ("high priority" / "blocking" → `high`; "low priority" / "nice to know" → `low`).
127
+
128
+ The resulting deduped list feeds `### 3a. Post-Wave: Update STATE.md` step 6 (`## Open Questions`), which does the actual lock-guarded `appendOpenQuestionOnDisk` write. This step (3e) only collects and dedups in-memory — it performs no STATE.md I/O itself, the same division of labor as steps 2/3 above (detect here, write in the Post-Wave STATE.md update). Skip entirely when no agent in the wave emitted an `OPEN-QUESTIONS:` line.
129
+ 4. **Run incremental verification** (per the quality-gates skill, based on the wave's role):
130
+
131
+ **Shared-lib touch auto-promotion (#555 FL-3)** — before selecting the role-based gate variant below, check whether this wave touched files under `scripts/lib/`, `hooks/`, or `.husky/`. If so, auto-promote the inter-wave gate from Quality-Lite (Incremental) to Full Gate (typecheck + test + lint). Rationale: an Impl wave that touches shared code has a wider blast radius than the agent can predict — deep-1647 inter-wave 3→4 caught 2 such regressions only because the Lite step happened to run the full test suite. Auto-promotion makes that coverage deterministic without imposing per-session cost on waves that don't touch shared code (W1-D5 chose Option B over the always-full Option A on this exact tradeoff).
132
+
133
+ ```js
134
+ import { detectSharedLibTouch } from '$PLUGIN_ROOT/scripts/lib/quality-gate.mjs';
135
+
136
+ const touchResult = detectSharedLibTouch({
137
+ repoRoot: process.cwd(),
138
+ sinceRef: SESSION_START_REF,
139
+ promoteWhenTouched: ['scripts/lib/', 'hooks/', '.husky/'],
140
+ });
141
+
142
+ if (touchResult.touched && (waveRole === 'Impl-Core' || waveRole === 'Impl-Polish')) {
143
+ console.log(
144
+ `ℹ Quality-Lite auto-promoted to Full Gate — wave touched shared code: ` +
145
+ `${touchResult.paths.join(', ')} (#555 FL-3)`,
146
+ );
147
+ // Run Full Gate (typecheck + test + lint) instead of the role-default Incremental.
148
+ } else {
149
+ // Existing role-based selection (Discovery: none, Impl-*: Incremental, Quality: Full, Finalization: git status).
150
+ }
151
+ ```
152
+
153
+ `detectSharedLibTouch` never throws — on any git failure (invalid sinceRef, detached HEAD, missing repo) it returns `{ touched: false, paths: [] }`, so a probe failure silently falls back to the role-default Incremental rather than blocking the wave. When `waveRole === 'Quality'`, the gate is **already Full** — no further promotion possible, no double-promotion. When `waveRole === 'Discovery'` or `'Finalization'`, this check is skipped entirely (the role's verification semantics don't include a test gate to promote).
154
+
155
+ **Baseline cache check (#258, #724)** — before running Incremental quality checks for this wave, consult the session-start Baseline cache. If the cache is still valid and the diff since `$SESSION_START_REF` is narrow (<50 files), skip Incremental for this wave and note the skip in the wave progress update. **The Quality wave is exempt from the skip**: pass the current wave's `waveRole` so `shouldSkipIncremental` hard-returns `skip: false` (reason `quality-wave-full-gate-mandate`) BEFORE any cache/diff logic runs — the Quality-wave Full Gate is mechanically un-skippable (#724 C6).
156
+
157
+ ```js
158
+ // import at the top of the wave-executor runtime
159
+ import { shouldSkipIncremental } from '$PLUGIN_ROOT/scripts/lib/quality-gates-cache.mjs';
160
+
161
+ // waveRole is this wave's role: Discovery | Impl-Core | Impl-Polish | Quality | Finalization.
162
+ // When waveRole === 'Quality', shouldSkipIncremental hard-returns skip=false so the Full Gate
163
+ // ALWAYS runs — the cache short-circuit applies only to the Impl waves.
164
+ const skip = shouldSkipIncremental({ repoRoot: process.cwd(), sessionStartRef: SESSION_START_REF, waveRole });
165
+ if (skip.skip) {
166
+ console.log(`ℹ Incremental quality check skipped — ${skip.reason} (${skip.changedFileCount} files changed).`);
167
+ // proceed to next wave without running Incremental
168
+ } else {
169
+ // run the role-specific quality check as before (per role-specific rules below).
170
+ // For the Quality wave, skip.reason === 'quality-wave-full-gate-mandate' and the Full Gate runs.
171
+ }
172
+ ```
173
+
174
+ `shouldSkipIncremental` never throws — on any error (git failure, unreadable cache) it returns `skip: false` so Incremental runs. Full Gate at session-end is NEVER skipped, and after the Quality wave is likewise NEVER skipped — as of #724 the Quality-wave mandate is enforced MECHANICALLY via the `waveRole` parameter (not prose): see the close-safety invariant in `skills/quality-gates/SKILL.md § Baseline Cache (#258)`.
175
+
176
+ - After **Discovery**: no verification needed (read-only)
177
+ - After **Impl-Core**: Incremental quality checks per quality-gates (test changed files, typecheck)
178
+ - After **Impl-Polish**: Incremental quality checks + integration verification
179
+ - **Simplification pass** (at the start of the Quality wave, before test/review agents):
180
+ 1. Identify all files changed in this session: `git diff --name-only $SESSION_START_REF..HEAD`
181
+ 2. Partition the list into **production files** (exclude `*.test.*`, `*.spec.*`, `__tests__/`) and **test files** (exactly that excluded set). Both branches below are independent: skip a branch when its partition is empty; skip the pass entirely only when BOTH partitions are empty — then proceed directly to test/review agents.
182
+ 3. Dispatch 1-2 simplification agents with:
183
+ - Changed file list (production files only — exclude `*.test.*`, `*.spec.*`, `__tests__/`)
184
+ - Reference: `slop-patterns.md` from the discovery skill directory — include the actual patterns in the agent prompt
185
+ To include the patterns: read `skills/discovery/slop-patterns.md` and paste the full content into the agent prompt under a "## Slop Patterns Reference" heading. Do NOT ask the agent to read the file itself — include it inline so the agent has zero-dependency context.
186
+ - Reference: project's CLAUDE.md (or AGENTS.md on Codex CLI) conventions
187
+ - Instruction: "Review each changed file for AI-generated code patterns. Apply targeted simplifications: remove unnecessary try-catch around non-throwing operations, delete over-documentation (params that repeat the name, returns that say 'the result'), replace re-implemented stdlib functions with standard alternatives, simplify redundant boolean logic (if/else returning true/false, double negation, explicit boolean comparisons). Do NOT change functionality. Do NOT touch files you weren't given. Do NOT commit."
188
+ - Tools: Read, Edit, Grep, Glob
189
+ - Model: sonnet
190
+ 4. **Test-consolidation branch** — in the SAME dispatch round as step 3, dispatch exactly 1 test-consolidation agent with:
191
+ - File list: the test partition from step 2 (this session's changed test files) plus their immediate neighbours (sibling test files covering the same module — resolve via the production file's basename, e.g. `foo.mjs` → `tests/**/foo*.test.mjs`)
192
+ - Instruction: "Consolidate this test corpus. (a) Merge duplicated tests that differ only in input/expected values into ONE parameterized test (table-driven / `it.each`). (b) DELETE any test that fails the falsification check — ask for each test: *would this test go RED if a real bug were introduced in the code it claims to cover?* If no, it catches nothing; remove it. (c) DELETE getter/setter tests, framework-behaviour tests, and prose-presence tests (assertions that a doc/skill file merely CONTAINS a phrase) — see `.claude/rules/testing.md` § 'Test Quality — False-Positive Prevention' and § 'When NOT to Write Tests'. Do NOT touch production files. Do NOT commit."
193
+ - **Contract**: the set of bugs the suite catches may only stay the same or GROW. Never delete a test that is the sole falsifier of a real behaviour — when in doubt, keep and report it. Deletions are a SUCCESS outcome, not a regression: a net-negative test LOC with an unchanged bug-catch set is the intended result of this branch.
194
+ - **Report**: the agent MUST emit `test_delta: {added, removed, consolidated, net_loc}` in its report so the coordinator can record the pass's effect.
195
+ - Tools: Read, Edit, Grep, Glob
196
+ - Model: sonnet
197
+ 5. After the simplification and test-consolidation agents complete, proceed to Quality test/review agents
198
+ - **Review panel = primary bug-catch mechanism (Quality wave)**: the Quality wave's central verification instrument is a multi-persona review panel — `security-reviewer`, `qa-strategist`, `architect-reviewer` — dispatched read-only (Read/Grep/Glob, no Edit/Write) and scoped to the FULL session diff `$SESSION_START_REF..HEAD`, not to a single wave's file scope. Test-writing in this wave is need-gated, not default (see `SKILL.md` § "Agent Prompt Best Practices" point 5): an agent writes a test only for a bug it can name.
199
+ Rationale — 2026-07 evidence: the HIGH/MED product bugs actually caught in this repo's sessions came from panel review (argument injection in a base-branch value, a fail-open config gate, a never-wired max-proposals cap, a glob-metacharacter bypass), not from growth of the test corpus. Panel breadth over the full diff also catches coordinator-written code, which per-wave agent scopes never cover.
200
+ - After **Quality**: Full Gate quality checks per quality-gates (typecheck + test + lint, must all pass)
201
+ (Full Gate is NEVER skipped regardless of cache state — this is the close-safety invariant. As of #724 this mandate is MECHANICAL, not prose-only: the Baseline cache check above passes `waveRole: 'Quality'`, so `shouldSkipIncremental` hard-returns `skip: false` before any cache/diff logic. A targeted/incremental pass is necessary but NOT sufficient — the Quality-wave completion requires the full typecheck + test + lint run.)
202
+ - After **Finalization**: final git status check
203
+
204
+ #### Auto-Fix Protocol (#521)
205
+
206
+ When `verification-auto-fix.enabled: true`, the inter-wave Quality-Gate uses
207
+ `runQualityGateWithRetry()` to dispatch up to `max-retries` (default 2)
208
+ fixer-agent attempts before aborting.
209
+
210
+ Per attempt:
211
+ 1. Run quality-gate (lint, typecheck, test in order).
212
+ 2. On failure, collect: failure output, corrective_context from
213
+ `.orchestrator/current-session.json`, changed files since last green SHA.
214
+ 3. Dispatch code-implementer fixer-subagent with the bundle.
215
+ 4. Re-run quality-gate.
216
+ 5. After max-retries → write `.orchestrator/metrics/verification-failures/<ts>.json`
217
+ diagnostics bundle and abort the wave.
218
+
219
+ See `SKILL.md` § "Inter-Wave Quality-Gate (with Auto-Fix Loop — #521)" for
220
+ the full invocation pattern.
221
+
222
+ ##### STATE.md Deviation — Auto-Fix Result
223
+
224
+ After `runQualityGateWithRetry()` returns:
225
+
226
+ - **If `result.ok === true`:** No deviation entry — quality gate passed, wave proceeds normally.
227
+ - **If `result.attempts > 1` and `result.ok === true`:** Append ONE entry to `## Deviations` in `<state-dir>/STATE.md`:
228
+ ```
229
+ - [<ISO 8601 UTC>] Wave N auto-fix succeeded after N attempts (max-retries config: M). Failed gate(s): <gate-names>. Final pass on attempt N.
230
+ ```
231
+ - **If `result.ok === false`:** Append ONE entry to `## Deviations` in `<state-dir>/STATE.md`:
232
+ ```
233
+ - [<ISO 8601 UTC>] Wave N auto-fix exhausted retries after N attempts (max-retries config: M). Failed gate: <gate-name>. Diagnostics bundle: <bundlePath>. Coordinator to review bundle and decide: fix manually, disable auto-fix and retry, or abort wave.
234
+ ```
235
+
236
+ Use `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` from `scripts/lib/state-md.mjs`.
237
+ This is a **coordinator-only** write — fixer-subagents do not write STATE.md. The lock library
238
+ ensures atomicity if multiple coordinator-level deviations land in the same wave.
239
+
240
+ #### Auto-Commit Checkpoint (Optional, Opt-In)
241
+
242
+ > Gate conditions — ALL of the following must be true for this step to run:
243
+ > 1. `$CONFIG["auto-commit-per-wave"] === true`
244
+ > 2. `$CONFIG.persistence === true`
245
+ > 3. The Incremental quality check in step 4 returned **PASS** (skip or fail → do not commit)
246
+ > 4. Worktree base-ref freshness check (step 3b) returned **pass** or **warn** for all agents (not **block**)
247
+ > 5. No unresolved merge conflicts in the working tree (`git status --short` shows no `UU`/`AA`/`DD` lines)
248
+ >
249
+ > When any condition is false, skip this step silently. Log "auto-commit-per-wave skipped" in the wave progress update if the gate condition was `auto-commit-per-wave: true` but another condition failed — so the operator knows the flag is set but the checkpoint did not fire.
250
+
251
+ **Commit message format:**
252
+
253
+ ```
254
+ chore(wave-N): auto-checkpoint — <Role> wave complete
255
+
256
+ Quality-Lite: PASS | Wave: N / <total-waves> | Session: <session_id>
257
+ Agents: <done>/<total> done, <partial> partial, <failed> failed
258
+ ```
259
+
260
+ **Env-var bypass:** `SO_SKIP_AUTO_COMMIT=1` disables the commit for the current shell invocation regardless of config — useful for CI environments or when a human is reviewing changes mid-session.
261
+
262
+ **STATE.md deviation logging:** after a successful commit, append one entry to `## Deviations` using `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` from `scripts/lib/state-md.mjs` (acquires the lock automatically):
263
+
264
+ **Wrapper choice:** the canonical on-disk wrapper is `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` — it acquires the STATE.md lock automatically before reading + writing. Callers in `.mjs` modules MUST prefer the on-disk wrapper; callers that pre-read STATE.md contents may use `appendDeviation(stateContents, isoTimestamp, message)` directly but MUST then route the write through `writeStateMd()`. Never use `readFileSync(STATE) → transform → writeFileSync(STATE)` — the race window allows STATE.md corruption under parallel waves (PSA-005).
265
+
266
+ ```
267
+ - [<ISO 8601 UTC>] Wave N auto-commit: <sha> (<Role>, Quality-Lite PASS, <N> files staged)
268
+ ```
269
+
270
+ If the commit itself fails (e.g., nothing to commit, pre-commit hook rejects), do NOT append the deviation. Instead, log the failure in the wave progress update as a WARN and continue to the next step without blocking.
271
+
272
+ **Mission-status transition:** after a successful auto-commit, transition the mission status for all tasks in this wave from `in-dev` → `testing` using `setMissionStatus(stateContent, taskId, 'testing')` from `scripts/lib/state-md.mjs`. This matches the coordinator-level rule in `SKILL.md § Mission-Status Updates`: "in-dev → testing: Quality wave begins and this item's implementation wave completed without failure." The auto-commit checkpoint fires at the same logical moment — after implementation completes and Quality-Lite passes.
273
+
274
+ **Implementation deferred:** This subsection documents the contract. The procedural body (git add/commit sequence + error handling) will land in a future release as `scripts/lib/auto-commit.mjs` — not yet implemented as of v3.10.0, and tracked in #1219. Until then, this section is a no-op stub when `auto-commit-per-wave: true` is set; the coordinator MUST warn the user at session-start that auto-commits are not yet active (emit: "auto-commit-per-wave is set but the implementation (scripts/lib/auto-commit.mjs) is not yet available — commits will occur at session-end via /close as normal"). <!-- path-check: planned #1219 -->
275
+
276
+ ---
277
+
278
+ 5a. **Persona-reviewer dispatch** (opt-in, gated by `wave-reviewers` config):
279
+ - Read `wave-reviewers` from Session Config. If the key is absent or the array is empty → skip this step entirely (no-op).
280
+ - Applicable waves: **Impl-Core** and **Impl-Polish** only. Skip for Discovery, Quality, and Finalization waves.
281
+ - For each reviewer name in the array, dispatch in parallel with read-only scope. Example:
282
+ ```
283
+ // Dispatch all configured reviewers in parallel (Promise.all semantics)
284
+ Agent({
285
+ description: "Persona review — <reviewer-name> — Wave N",
286
+ prompt: "<include: wave scope, changed files list, relevant plan section>",
287
+ subagent_type: "session-orchestrator:<reviewer-name>",
288
+ run_in_background: false // deliberately blocking — see below
289
+ })
290
+ ```
291
+ - **`run_in_background: false` here is deliberate, not an oversight.** Reviewers are dispatched AFTER the quality gate and are NOT in the session plan's agent list, so `wave-loop-dispatch.md` § Started-Set Verification has no expected-set to check them against and the launch ack — the only thing a background dispatch returns — is explicitly not countable. There is also no consumer for early results: the next step needs ALL reviewer verdicts before it can act (`skills/persona-panel/SKILL.md` § "background dispatch would add turn-juggling with no consumer for early results"; `skills/session-start/SKILL.md` names persona-panel among the keep-false skills).
292
+ - Each reviewer writes its findings to `.orchestrator/audits/wave-reviewer-<wave>-<reviewer-name>.md`. The coordinator does NOT need to create this file — the reviewer agent writes it directly.
293
+ - **Findings are ADVISORY**: reviewer output never blocks the subsequent wave. After all dispatched reviewers complete:
294
+ - If any reviewer reports **WARN**: surface the findings to the user in the wave progress summary. Feed actionable items into the next wave's agent assignments (step 3 — Adapt Plan). If a WARN/FAIL finding is surfaced but NOT converted into a fix task for the next wave, append ONE line to `## Deviations` via `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` (#730/H5): `- [<ISO 8601 UTC>] Wave N reviewer finding overridden (not actioned): <one-line finding>.` — session-end Phase 2.6 (Broken-Window Budget) walks these entries at close.
295
+ - If any reviewer reports **FAIL**: surface the findings prominently in the wave progress summary with a `[REVIEWER FAIL]` prefix. Still proceed to step 5 (session-reviewer) — do not halt wave execution.
296
+ - If all reviewers report **PASS** or produce no findings: log a one-line note and continue.
297
+ - **Default behaviour unchanged**: when `wave-reviewers` is absent or `[]`, this step is a no-op and the wave loop proceeds exactly as before.
298
+ - Supported reviewer names (plugin-provided): `architect-reviewer`, `qa-strategist`, `analyst`. Custom reviewer agents in `agents/` are also valid if their `name` frontmatter matches.
299
+
300
+ 5. **Session-reviewer dispatch** (after Impl-Core, Impl-Polish, and Quality waves only):
301
+ - When integrating reviewer findings, follow the receiving-review protocol — see `.claude/rules/receiving-review.md` for the 6-step pattern (READ → UNDERSTAND → VERIFY → EVALUATE → RESPOND → IMPLEMENT) and the forbidden-phrase list.
302
+ - After **Impl-Core** and **Impl-Polish** waves, dispatch the session-reviewer agent to verify wave output:
303
+ ```
304
+ Agent({
305
+ description: "Review wave N output",
306
+ prompt: "<include: session plan, wave results, changed files list, acceptance criteria>",
307
+ subagent_type: "session-orchestrator:session-reviewer",
308
+ run_in_background: false // deliberately blocking — same reason as step 5a
309
+ })
310
+ ```
311
+ - **`run_in_background: false` here is deliberate**, for the same reason as step 5a: the session-reviewer runs after the quality gate, is not in the session plan's agent list (so Started-Set Verification has no signal for it), and its verdict is needed whole before step 3 (Adapt Plan) can consume it.
312
+ - The session-reviewer checks changed files against the plan and reports PASS/WARN/FAIL per category (implementation, tests, TypeScript, security, silent failures, test depth, type design, issues).
313
+ - If the session-reviewer reports **WARN or FAIL** findings: add fix tasks to the next wave's agent assignments (feed into step 3 — Adapt Plan). If a WARN/FAIL finding is surfaced but NOT converted into a fix task for the next wave, append ONE line to `## Deviations` via `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` (#730/H5): `- [<ISO 8601 UTC>] Wave N reviewer finding overridden (not actioned): <one-line finding>.` — session-end Phase 2.6 (Broken-Window Budget) walks these entries at close.
314
+ - After the **Quality** wave: dispatch the session-reviewer with **full session scope** (all files changed since session start, not just the current wave). Use `git diff --name-only $SESSION_START_REF..HEAD` to provide the complete changed files list.
315
+ - Include `SESSION_START_REF` (captured in Pre-Wave 1) in the session-reviewer prompt so it can compute the full changed files list independently.
316
+ - **Relationship to session-end Phase 1.8:** Wave-level session-reviewer runs provide incremental feedback during execution. Session-end Phase 1.8 runs a final comprehensive review of ALL changes. Both are complementary — wave reviews catch issues early, session-end review is the final quality gate.
317
+ - **Discovery** and **Finalization** waves: skip session-reviewer dispatch — Discovery is read-only and Finalization is a final git status check only.
318
+ - This is complementary to the incremental verification in step 4 — the session-reviewer provides deeper analysis (security, silent failures, test depth, type design) that automated checks do not cover.
319
+ 6. **Pencil design review** (after Impl-Core and Impl-Polish roles only, if `pencil` configured in Session Config):
320
+ a. Check Pencil editor state: `get_editor_state({ include_schema: false })`. If no editor active, open the configured `.pen` file via `open_document({ filePathOrTemplate: "<pencil-path>" })`. If that also fails → skip with note "Pencil review skipped — .pen file unavailable."
321
+ b. Get design structure: `batch_get({ filePath: "<pencil-path>", patterns: [{ type: "frame" }], readDepth: 2, searchDepth: 2 })` — find frames relevant to this wave's UI work.
322
+ c. Screenshot relevant frames: `get_screenshot({ filePath: "<pencil-path>", nodeId: "<frame-id>" })` for each frame matching the wave's UI tasks.
323
+ d. Read the actual UI files changed in this wave (from agent outputs).
324
+ e. **Compare**: layout structure, component hierarchy, visual elements (headings, buttons, inputs, cards), responsive behavior.
325
+ f. **Report** in wave progress:
326
+ `- Design: [ALIGNED / MINOR DRIFT / MAJOR MISMATCH] — [specific findings]`
327
+ g. **Act on results**:
328
+ - ALIGNED → proceed to next wave
329
+ - MINOR DRIFT → add fix tasks to next wave (no pause)
330
+ - MAJOR MISMATCH → **PAUSE wave execution**:
331
+ 1. Report specific mismatches to user
332
+ 2. AskUserQuestion: "Continue as-is", "Revise plan for remaining waves", "Abort session"
333
+ > If AskUserQuestion is unavailable (Codex CLI), present as numbered list.
334
+ 3. If "Revise" → re-run session-plan for remaining waves only
335
+ 4. If "Abort" → mark remaining waves as DEFERRED, proceed to session-end
336
+
337
+ Always use the `filePath` parameter on Pencil MCP calls. Only review frames relevant to the current wave, not the entire file.
338
+
339
+ 7. **Capture wave metrics**: If `persistence` is enabled in Session Config, record for this wave after all agents complete and quality checks run. If `persistence` is `false`, skip metrics capture entirely — do not accumulate in-memory metrics. Record:
340
+ - `wave_number`, `role`, `started_at` (when agents were dispatched), `completed_at` (when all finished)
341
+ - `agent_count`: number of agents dispatched
342
+ - `agent_count_planned`: agents named in the session plan for this wave (Started-Set Verification, #724)
343
+ - `agent_count_started`: distinct agents whose `agent-<id>.meta.json` sidecar is present, after any silent-drop re-dispatch (Started-Set Verification, #724/#1115). NOT "produced a tool-result" — under background dispatch the launch ack is a result and would count an agent that never ran. A gap `agent_count_planned > agent_count_started` after re-dispatch signals a persistent silent drop.
344
+ - `agent_count_completed`: distinct agents whose task-notification (`<status>completed</status>`) arrived (#1115). A gap `agent_count_started > agent_count_completed` at wave end is an agent that started and never returned — the started-but-never-returned state, not a silent drop.
345
+ - Per-agent results: `{description, status: done|partial|failed, files_changed_count}`
346
+ - `files_changed`: total unique files changed this wave (from `git diff --stat --name-only`)
347
+ - `planned_files_count`: size of this wave's Planned set (union of agent file scopes) as computed in step 3c File-level grounding above. Reuse that value — do not recompute.
348
+ - `over_delivery_ratio`: files_changed / max(planned_files_count, 1), rounded to 2 decimals. > 1 = agents touched more files than briefed (under-sizing signal, #730/H4). Omit both fields when `grounding-check: false`.
349
+ - `quality_check`: incremental check result (pass/fail/skipped)
350
+ - `suite_passed` / `suite_failed` (+ optional `suite_platform`): the full-suite counts feeding the § 3a Wave History header `— suite <passed>/<failed> on <platform>`. `quality_check` is a traffic light; these are the number the light was derived from, and unlike STATE.md (gitignored, demoted to `## Previous Session` and then overwritten) the metrics record survives the session.
351
+ **Copy the two counts off the gate's own event — do not re-read them from the terminal (#966 step 3).** `scripts/run-quality-gate.mjs`, the wrapper that fires between waves, emits `orchestrator.quality_gate.{passed,failed}` carrying a machine-measured `counts: {passed, failed, total}` (admitted by `admitSuiteCounts()`) plus the `wave_number` it resolved from `wave-scope.json`. Payload fields are flat at the record's top level:
352
+
353
+ ```bash
354
+ jq -c --argjson w <wave_number> --arg s "<semantic_session_id>" '
355
+ select(.event | startswith("orchestrator.quality_gate."))
356
+ | select(.semantic_session_id == $s and .wave_number == $w and .counts != null)
357
+ | .counts' .orchestrator/metrics/events.jsonl | tail -1
358
+ ```
359
+
360
+ The session filter is not optional — `events.jsonl` accumulates across sessions and every past session also had a wave with this number.
361
+ **OMIT all three when that selector returns nothing** — absent = "not measured", `suite_failed: 0` = "measured, zero failures". Never write `0` for a suite that did not run. The event enforces the same distinction at the producer: `counts` is omitted, never zero-filled, when the run fail-fast'd before the test gate or its output carried no parseable count.
362
+ > **What is NOT on the event, and stays hand-written:** `suite_platform` — the payload has no platform field, so keep writing it from the § 3a header as before. Likewise, the auto-fix-loop producer (`scripts/lib/quality-gate.mjs`, active only under `verification-auto-fix.enabled: true`) emits `counts` WITHOUT `wave_number`, so its retry records correctly never match the selector above; they are mid-wave attempts, not the wave's verdict. If the wave's gate ran outside `run-quality-gate.mjs` entirely, no event exists — fall back to the gate output you read, and say so in the progress update. The reader side (`skills/session-end/metrics-collection.md` § 1.7) reads the event first and this hand-written trio second, so keep writing the trio: it is the compatibility path for those two cases and for sessions already in flight.
363
+ Append this wave record to the session metrics `waves` array.
364
+
365
+ 7a. **Scope drift tripwire (S2 — #896, warn-only)**: distinct from `over_delivery_ratio` above — that metric is per-wave and unfiltered; this one is session-cumulative (since `session-start-ref`) and filtered through `DRIFT_EXCLUDE_PATTERNS`, so the two numbers are NOT expected to agree. Call `computeDrift()` from the same `scripts/lib/scope-baseline.mjs` module as `wave-loop-dispatch.md` § 0a Scope Baseline Freeze. Never blocks — exit code stays 0 and the next wave is dispatched regardless of the result.
366
+
367
+ ```js
368
+ import { computeDrift } from '$PLUGIN_ROOT/scripts/lib/scope-baseline.mjs';
369
+
370
+ const drift = computeDrift({ repoRoot: process.cwd(), threshold: 2.0 });
371
+ if (drift.skipped === false && drift.breached) {
372
+ console.warn(
373
+ `⚠ Scope drift: filesRatio ${drift.filesRatio} (${drift.actualFiles} actual / ${drift.plannedFiles} planned files) ` +
374
+ `>= threshold ${drift.threshold} — session has grown beyond its frozen scope baseline.`
375
+ );
376
+ }
377
+ ```
378
+
379
+ Include the WARN line verbatim in the wave progress update when `breached` is true — name `filesRatio`, `plannedFiles`, `actualFiles`, and the configured `threshold`, not merely the word "drift". `drift.skipped === true` (`no-state-md`, `unreadable-state-md`, `no-baseline`, `stale-baseline`, or `unresolvable-ref` — see `computeDrift()`'s JSDoc for the precedence order) is silent: no WARN, no progress-update line. `persistence: false` implies `no-state-md`, so this step degrades to a silent no-op in that mode without a separate gate check.
380
+
381
+ ### 3. Adapt Plan (if needed)
382
+
383
+ After reviewing wave results, decide:
384
+
385
+ - **On track**: proceed to next wave as planned
386
+ - **Minor issues**: add fix tasks to next wave's agent assignments
387
+ - **Major blocker**: propose a revised plan for the remaining waves and present the choice to the user via `AskUserQuestion` (proceed / revise / abort). See `.claude/rules/ask-via-tool.md` — never surface this as an inline prose question.
388
+ - **Agent failed**: re-dispatch with corrected instructions in next wave
389
+ - **Scope change**: document why, adjust remaining waves, present scope deltas to the user via `AskUserQuestion` (accept / reject / modify).
390
+
391
+ **Deviation protocol**: ALWAYS document WHY you deviated from the plan. Log it in a brief note that session-end can reference.
392
+
393
+ **User interaction protocol**: Any decision surfaced to the user from this loop — plan revisions, scope changes, recovery-path choice, pause/continue prompts — goes through `AskUserQuestion`. Inline markdown-list choices are a bug; see `.claude/rules/ask-via-tool.md`.
394
+
395
+ #### Dynamic Scaling
396
+
397
+ After reviewing wave results, adjust the next wave's agent count based on performance signals:
398
+
399
+ | Signal | Action | Example |
400
+ |--------|--------|---------|
401
+ | All agents completed in under 3 minutes wall-clock, no issues | Reduce next wave by 1-2 agents | 6 agents all done in <3m → next wave uses 4 |
402
+ | Agent failures or broken code | Add fix agents to next wave (+1-2) | 2 agents failed → next wave gets 2 extra |
403
+ | Scope expansion discovered | Scale up next wave | New module found → add agents for it |
404
+ | Quality regressions found | Add targeted fix agents | 3 test failures → 3 fix agents next wave |
405
+
406
+ **Scaling constraints:**
407
+ - Never exceed `agents-per-wave` from Session Config
408
+ - Never go below 1 agent per wave
409
+ - Log all scaling decisions in the wave progress update
410
+ - Record actual vs. planned agent count in wave metrics
411
+
412
+ ### 3a. Post-Wave: Update STATE.md
413
+
414
+ > Skip if `persistence: false`.
415
+
416
+ After each wave completes and before the progress update, update `<state-dir>/STATE.md`:
417
+
418
+ 1. **Frontmatter**: set `current-wave` to the just-completed wave number; set `status` to `active` (or `paused` if waiting on user input). Readers that need the RUNNING wave (e.g. `scripts/memory-propose.mjs`) read `<state-dir>/wave-scope.json` `wave` (only when the manifest is bound to this session via `semantic_session_id`; an unbound manifest is ignored — it may be a peer's, #1123) and fall back to `current-wave + 1` (#1166) — do not change this field's meaning.
419
+ 2. **`## Current Wave`**: replace contents with next wave info — wave number, role, agents to dispatch and count
420
+ 3. **`## Wave History`**: append an entry for the completed wave (the `(planned … → actual …, over-delivery …)` parenthetical is omitted when `grounding-check: false`, since the counts are unavailable):
421
+ > **Record the SUITE COUNT, not just "gates green" — and name the platform (#944).** The wave line MUST carry the full-suite pass/fail count from the gate that just ran (`<passed>/<failed>`), not merely that typecheck and lint were clean. A deep session on 2026-07-30 logged typecheck/lint/validate-plugin for every wave and no suite count; a test that had been vacuous for its entire life sat red on HEAD through three waves and was found only by the review panel — in a session whose own premise was turning CI from red to green.
422
+ >
423
+ > **A green gate on one platform is not evidence for another.** That same session's local gate reported 541/541 three times on a tree CI could not build: two tests encoded macOS assumptions (a `TMPDIR` that carries a trailing slash; an `ARG_MAX` that tolerates a 200 KB argv entry). Both passed locally and failed on the Linux runner. When the wave touched anything platform-sensitive — spawn/argv shapes, `os.tmpdir()`, path separators, file modes, `$PATH` lookups of external binaries — say so in the wave line, and treat CI, not the local run, as the verdict.
424
+
425
+ ```
426
+ ### Wave N — <Role> (planned <P> files → actual <A>, over-delivery <R>) — suite <passed>/<failed> on <platform>
427
+ - Agent "<description>": <done|partial|failed> — <files changed> — <1-line note>
428
+ - Agent "<description>": <done|partial|failed> — <files changed> — <1-line note>
429
+ ```
430
+ 4. **`## Deviations`**: if the plan was adapted in step 3, append a timestamped entry:
431
+ ```
432
+ - [<ISO timestamp>] Wave N: <what changed and why>
433
+ ```
434
+
435
+ 5. **Heartbeat refresh (#590-3)** — after the STATE.md write, refresh the session-lock heartbeat so long-running deep sessions do not let the 4h TTL lapse between waves. Best-effort: a failure must NOT block the wave.
436
+
437
+ ```js
438
+ // Per-wave heartbeat refresh (#590-3) — keeps session.lock fresh during long deep sessions.
439
+ // sessionId = the session identifier established by session-start Phase 1.2 acquire()
440
+ // and stored in .orchestrator/session.lock (session_id field); matches the
441
+ // STATE.md frontmatter `session:` field written during Pre-Wave 1b initialization.
442
+ import { updateHeartbeat } from '../../scripts/lib/session-lock.mjs';
443
+ updateHeartbeat({ sessionId, repoRoot: process.cwd() });
444
+ ```
445
+
446
+ Skip silently if `persistence: false` in Session Config (no session.lock exists in that mode).
447
+
448
+ 6. **`## Open Questions`** (Close Handover-Alignment-Gate, PRD 2026-07-07): append the wave's deduped open questions collected earlier in `3e. Collect Open Questions`, via `appendOpenQuestionOnDisk` — the same lock-guarded on-disk pattern used by `appendDeviationOnDisk` above:
449
+
450
+ ```js
451
+ import { appendOpenQuestionOnDisk } from '../../scripts/lib/state-md.mjs';
452
+ for (const q of dedupedOpenQuestions) {
453
+ await appendOpenQuestionOnDisk(repoRoot, { question: q.question, source: q.source, priority: q.priority });
454
+ }
455
+ ```
456
+
457
+ Skip silently when the wave produced no `OPEN-QUESTIONS:` lines (see `3e. Collect Open Questions`) and when `persistence: false`.
458
+
459
+ ### 3a-bis. Agent-Status Telemetry (#565)
460
+
461
+ > Optional operator-side observability — NOT load-bearing. Best-effort, fire-and-forget telemetry that a tmux `--with-status-pane` (see `skills/tmux-layout/SKILL.md`) renders as a live side-channel per ADR-0007. A status push must NEVER block or fail a wave — mirror the §3a heartbeat-refresh framing exactly.
462
+
463
+ **Gate:** `persistence: true` in Session Config. When `persistence: false`, skip every push below — there is no runtime side-channel to feed.
464
+
465
+ The helper is `scripts/lib/agent-status.mjs`. Its exports (`setStatus`, `setProgress`, `readCurrentStatus`) are all no-throw and return `{ ok: true } | { ok: false, reason }`; the coordinator ignores the return value (best-effort). Push at **three anchors** in the wave loop:
466
+
467
+ 1. **dispatch** — in `### 1. Dispatch Agents`, as each agent is dispatched, push its status. Use `setProgress` when the wave's per-agent ordinal is meaningful, else `setStatus`:
468
+
469
+ ```js
470
+ import { setStatus, setProgress } from '../../scripts/lib/agent-status.mjs';
471
+
472
+ // For each agent dispatched in this wave (i = 0-based position, total = wave agent count):
473
+ await setStatus(agentId, `dispatched — ${subagentType}`); // free-text variant
474
+ // — or —
475
+ await setProgress(agentId, { step: i + 1, total, label: subagentType }); // progress variant
476
+ ```
477
+
478
+ `agentId` is a stable per-agent key (e.g. `wave${waveN}-${i}-${subagentType}`). There is **no separate "agent-start" hook distinct from dispatch** — wave agents are in-process `Agent()` calls with no PID/TTY (see `skills/tmux-layout/SKILL.md § When NOT to Use`), so dispatch IS the start signal. Do not invent one.
479
+
480
+ 2. **agent-end** — in `### 2. Review Agent Outputs` step 1 (Read each agent's result), as each agent's terminal status is determined, push it:
481
+
482
+ ```js
483
+ // status ∈ {'done','partial','failed'} from the agent's STATUS: line
484
+ await setStatus(agentId, status);
485
+ ```
486
+
487
+ 3. **wave-end rollup** — in `### 3a. Post-Wave: Update STATE.md`, beside the `updateHeartbeat` call (step 5), push one wave-level rollup using a wave-scoped key:
488
+
489
+ ```js
490
+ // e.g. agentId = `wave${waveN}` ; counts from the wave's per-agent results
491
+ await setStatus(`wave${waveN}`, `wave ${waveN} complete — ${done} done, ${partial} partial, ${failed} failed`);
492
+ ```
493
+
494
+ A push failure (timeout, fs-error, invalid-input) is logged to the wave progress update at most as a one-line WARN — never block, never retry, never surface to the user. If `agent-status.mjs` is absent (older plugin checkout), wrap the import defensively and no-op, exactly as `layouts.mjs` does for its telemetry import.
495
+
496
+ ### 3b. Persona-Gate Hook (#458)
497
+
498
+ > Opt-in mid-wave hook that fans out a `/persona-panel`-style review after a configured wave completes. Distinct from `### 5a. Persona-reviewer dispatch` (which uses the `wave-reviewers` Session Config key and dispatches code-oriented `architect-reviewer` / `qa-strategist` / `analyst` agents). This hook uses the `persona-gate-wave` Session Config key and dispatches catalog personas (domain-experts, buyer-personas, auditors) from `.claude/personas/`. The two keys are independent and may both be configured on the same project.
499
+
500
+ **Gate conditions** — ALL must be true for the hook to fire:
501
+
502
+ 1. `persona-gate-wave.enabled: true` in Session Config (default: `false`).
503
+ 2. The just-completed wave matches `persona-gate-wave.after` — one of `'quality'` or `'impl-polish'`. The hook runs AFTER step 3a (STATE.md updated) and BEFORE step 4 (progress update), so the dispatch context already reflects the completed wave's results.
504
+ 3. `persona-gate-wave.mode !== 'off'` (when `mode: 'off'` the hook is a silent no-op even when `enabled: true`).
505
+
506
+ When any gate condition is false, skip this step entirely — proceed to `### 4. Progress Update`.
507
+
508
+ **Dispatch sequence:**
509
+
510
+ ```js
511
+ import { loadCatalog } from '$PLUGIN_ROOT/scripts/lib/persona-panel/catalog-loader.mjs';
512
+ import { buildPersonaPrompt, validatePersonaOutput } from '$PLUGIN_ROOT/scripts/lib/persona-panel/persona-runner.mjs';
513
+ import { consolidate } from '$PLUGIN_ROOT/scripts/lib/persona-panel/consolidator.mjs';
514
+ import { writeJsonAtomic } from '$PLUGIN_ROOT/scripts/lib/io.mjs';
515
+ import { appendDeviationOnDisk } from '$PLUGIN_ROOT/scripts/lib/state-md.mjs';
516
+
517
+ const cfg = $CONFIG['persona-gate-wave']; // already normalised by parseSessionConfig
518
+ const catalog = await loadCatalog(); // throws if .claude/personas/ missing or invalid
519
+ const rosterNames = cfg.personas.length > 0
520
+ ? cfg.personas
521
+ : [...catalog.keys()]; // empty list → all catalog personas
522
+ const personas = rosterNames.map((n) => catalog.get(n)).filter(Boolean);
523
+ ```
524
+
525
+ Dispatch each persona in parallel via the Agent tool, using `cfg['dispatch-model']` as the model and `Read, Grep, Glob` tools only (panel personas are read-only by contract). Each dispatch wraps the wave's scope summary + changed-files list in `buildPersonaPrompt(persona.persona, target, targetContent)`.
526
+
527
+ After all agents return, collect their outputs and validate each via `validatePersonaOutput(persona.persona, agentText)`. Compose the panel verdict via `consolidate(outputs, 'hard-gate-threshold', { threshold: cfg.threshold_parsed })`.
528
+ <!-- threshold_parsed is pre-computed by _normalizePersonaGateWave in persona-gate-wave.mjs; no re-parse needed here -->
529
+
530
+ **Behaviour by mode:**
531
+
532
+ | `mode` | Action on consolidator result |
533
+ |--------|--------------------------------|
534
+ | `off` | No dispatch (gate condition above). |
535
+ | `warn` | Log findings to the wave progress update under a `Persona-gate:` bullet. Continue to step 4 regardless of `final_verdict`. |
536
+ | `strict` | If `final_verdict === 'PROCEED'`: log to progress, continue. Otherwise pause and surface an `AskUserQuestion` with three options:<br>1. **proceed-as-is** — log Deviation, continue (Recommended only after operator inspects sidecar)<br>2. **revise-remaining-waves** — return `{ verdict: 'FIX_REQUIRED', revision_context: { dissenting_personas, recommendations } }` to the wave-executor caller<br>3. **abort-session** — return `{ verdict: 'BLOCKED' }` to the caller |
537
+
538
+ **Sidecar write:** before reporting any verdict, validate the panel result against `agents/schemas/persona-panel-sidecar.schema.json` (via `validateAgentOutput` or a direct AJV compile) and then write atomically via `writeJsonAtomic(path, value, { schemaPath })`:
539
+
540
+ ```
541
+ .orchestrator/persona-panel/<iso-timestamp>-<runId>.json
542
+ ```
543
+
544
+ The sidecar carries `personas_invoked`, per-persona `outputs`, and the full `consolidation` block — operators consult it from the AskUserQuestion prompt before deciding `strict`-mode follow-up.
545
+
546
+ **STATE.md deviation contract:** on `warn` (with at least one dissenting persona) or any `strict`-mode non-PROCEED verdict, append one timestamped entry to `## Deviations` via `appendDeviationOnDisk(repoRoot, iso, message)` from `scripts/lib/state-md.mjs` (acquires the STATE.md lock):
547
+
548
+ ```
549
+ - [<ISO 8601 UTC>] Wave N persona-gate <warn|strict-proceed|strict-revise|strict-abort>: dissenting=[<persona-1>, <persona-2>], threshold=<cfg.threshold>, mode=<cfg.mode>. Sidecar: <relative-path>.
550
+ ```
551
+
552
+ On a clean `PROCEED` no deviation is written — the sidecar alone is sufficient evidence.
553
+
554
+ **Wave metrics extension:** when persistence is enabled, extend the wave metrics record (step 7 of `### 2. Review Agent Outputs`) with a `persona_gate` block:
555
+
556
+ ```json
557
+ "persona_gate": {
558
+ "triggered": true,
559
+ "threshold": "<cfg.threshold>",
560
+ "personas_pass": <N>,
561
+ "personas_fail": <M>,
562
+ "mode_used": "<cfg.mode>",
563
+ "final_verdict": "<PROCEED|PROCEED_WITH_FOLLOWUPS|BLOCKED|REQUIRES_COORDINATOR>",
564
+ "sidecar_path": ".orchestrator/persona-panel/<...>.json"
565
+ }
566
+ ```
567
+
568
+ When the hook is skipped (gate condition false), omit the `persona_gate` field entirely — never write `triggered: false` for skipped runs, so a downstream consumer can distinguish "hook did not fire" from "hook fired but found no dissent".
569
+
570
+ **Motivating example:** a flagship product's W5 Buyer-Panel pattern (six buyer personas at `hard-gate-threshold` `6-of-6`, `mode: 'strict'`, `after: 'quality'`) — UI work is gate-checked against every persona before commit, abort on any dissent. See `docs/session-config-reference.md § Persona-Gate Wave (#458)` and `commands/persona-panel.md` for the standalone CLI equivalent.