session-orchestrator 5.1.0 → 5.3.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 (484) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +6 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +7 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +8 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
  9. package/.agents/skills/close/SKILL.md +21 -0
  10. package/.agents/skills/close/agents/openai.yaml +5 -0
  11. package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
  12. package/.agents/skills/debug/SKILL.md +7 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +7 -2
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +7 -1
  17. package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
  18. package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
  19. package/.agents/skills/ecosystem-health/SKILL.md +3 -1
  20. package/.agents/skills/eli5/SKILL.md +7 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +7 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +8 -3
  25. package/.agents/skills/evolve/agents/openai.yaml +5 -0
  26. package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
  27. package/.agents/skills/gitlab-ops/SKILL.md +3 -1
  28. package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
  29. package/.agents/skills/go/SKILL.md +22 -0
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +7 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +20 -0
  34. package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
  35. package/.agents/skills/hook-development/SKILL.md +3 -1
  36. package/.agents/skills/mcp-builder/SKILL.md +3 -1
  37. package/.agents/skills/memory-cleanup/SKILL.md +6 -1
  38. package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
  39. package/.agents/skills/mode-selector/SKILL.md +3 -1
  40. package/.agents/skills/npm-publish/SKILL.md +4 -2
  41. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  42. package/.agents/skills/persona-panel/SKILL.md +6 -1
  43. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  44. package/.agents/skills/plan/SKILL.md +8 -2
  45. package/.agents/skills/plan/agents/openai.yaml +5 -0
  46. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  47. package/.agents/skills/portfolio/SKILL.md +21 -0
  48. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  49. package/.agents/skills/quality-gates/SKILL.md +3 -1
  50. package/.agents/skills/reconcile/SKILL.md +6 -1
  51. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  52. package/.agents/skills/release/SKILL.md +22 -0
  53. package/.agents/skills/release/agents/openai.yaml +5 -0
  54. package/.agents/skills/remote-offload/SKILL.md +3 -1
  55. package/.agents/skills/repo-audit/SKILL.md +6 -1
  56. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  57. package/.agents/skills/session/SKILL.md +21 -0
  58. package/.agents/skills/session/agents/openai.yaml +5 -0
  59. package/.agents/skills/session-end/SKILL.md +3 -1
  60. package/.agents/skills/session-plan/SKILL.md +3 -1
  61. package/.agents/skills/session-start/SKILL.md +3 -1
  62. package/.agents/skills/spinout/SKILL.md +6 -1
  63. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  64. package/.agents/skills/sunset-review/SKILL.md +7 -1
  65. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  66. package/.agents/skills/templates-ack/SKILL.md +21 -0
  67. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  68. package/.agents/skills/test/SKILL.md +21 -0
  69. package/.agents/skills/test/agents/openai.yaml +5 -0
  70. package/.agents/skills/test-runner/SKILL.md +3 -1
  71. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  72. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  73. package/.agents/skills/ux-grill/SKILL.md +7 -1
  74. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  75. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  76. package/.agents/skills/vault-sync/SKILL.md +3 -1
  77. package/.agents/skills/wave-executor/SKILL.md +3 -1
  78. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  79. package/.claude-plugin/marketplace.json +1 -1
  80. package/.claude-plugin/plugin.json +1 -1
  81. package/.codex-plugin/plugin.json +4 -4
  82. package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
  83. package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
  84. package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
  85. package/.codex-plugin/skills/close/SKILL.md +3 -3
  86. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
  87. package/.codex-plugin/skills/debug/SKILL.md +11 -4
  88. package/.codex-plugin/skills/discovery/SKILL.md +8 -4
  89. package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
  90. package/.codex-plugin/skills/eli5/SKILL.md +9 -4
  91. package/.codex-plugin/skills/eval/SKILL.md +9 -4
  92. package/.codex-plugin/skills/evolve/SKILL.md +9 -4
  93. package/.codex-plugin/skills/go/SKILL.md +3 -3
  94. package/.codex-plugin/skills/grill/SKILL.md +11 -4
  95. package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
  96. package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
  97. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
  98. package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
  99. package/.codex-plugin/skills/plan/SKILL.md +8 -4
  100. package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
  101. package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
  102. package/.codex-plugin/skills/release/SKILL.md +3 -3
  103. package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
  104. package/.codex-plugin/skills/session/SKILL.md +1 -1
  105. package/.codex-plugin/skills/spinout/SKILL.md +4 -4
  106. package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
  107. package/.codex-plugin/skills/test/SKILL.md +3 -3
  108. package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
  109. package/.cursor/commands/autopilot.md +4 -4
  110. package/.cursor/commands/bootstrap.md +5 -4
  111. package/.cursor/commands/brainstorm.md +5 -4
  112. package/.cursor/commands/close.md +4 -3
  113. package/.cursor/commands/debug.md +4 -4
  114. package/.cursor/commands/discovery.md +4 -4
  115. package/.cursor/commands/dispatcher.md +4 -4
  116. package/.cursor/commands/eli5.md +4 -4
  117. package/.cursor/commands/eval.md +4 -4
  118. package/.cursor/commands/evolve.md +4 -4
  119. package/.cursor/commands/go.md +4 -3
  120. package/.cursor/commands/grill.md +4 -4
  121. package/.cursor/commands/harness-audit.md +3 -3
  122. package/.cursor/commands/memory-cleanup.md +4 -4
  123. package/.cursor/commands/persona-panel.md +4 -4
  124. package/.cursor/commands/plan.md +5 -4
  125. package/.cursor/commands/portfolio.md +3 -3
  126. package/.cursor/commands/reconcile.md +4 -4
  127. package/.cursor/commands/release.md +4 -3
  128. package/.cursor/commands/repo-audit.md +4 -4
  129. package/.cursor/commands/session.md +1 -1
  130. package/.cursor/commands/spinout.md +4 -4
  131. package/.cursor/commands/sunset-review.md +4 -4
  132. package/.cursor/commands/test.md +3 -3
  133. package/.cursor/commands/ux-grill.md +4 -4
  134. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  135. package/.cursor/rules/010-session-workflow.mdc +2 -2
  136. package/.cursor/rules/050-plan.mdc +1 -1
  137. package/.cursor/skills/bootstrap/SKILL.md +1 -0
  138. package/.cursor/skills/close/SKILL.md +13 -0
  139. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  140. package/.cursor/skills/debug/SKILL.md +0 -1
  141. package/.cursor/skills/discovery/SKILL.md +0 -1
  142. package/.cursor/skills/dispatcher/SKILL.md +0 -1
  143. package/.cursor/skills/eli5/SKILL.md +0 -1
  144. package/.cursor/skills/eval/SKILL.md +1 -1
  145. package/.cursor/skills/evolve/SKILL.md +0 -1
  146. package/.cursor/skills/go/SKILL.md +13 -0
  147. package/.cursor/skills/grill/SKILL.md +0 -1
  148. package/.cursor/skills/harness-audit/SKILL.md +12 -0
  149. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  150. package/.cursor/skills/portfolio/SKILL.md +12 -0
  151. package/.cursor/skills/release/SKILL.md +13 -0
  152. package/.cursor/skills/repo-audit/SKILL.md +0 -1
  153. package/.cursor/skills/sunset-review/SKILL.md +0 -1
  154. package/.cursor/skills/test/SKILL.md +12 -0
  155. package/.cursor/skills/ux-grill/SKILL.md +0 -1
  156. package/.cursor-plugin/plugin.json +1 -1
  157. package/.orchestrator/policy/blocked-commands.json +13 -4
  158. package/AGENTS.md +3 -2
  159. package/CHANGELOG.md +197 -0
  160. package/README.md +11 -9
  161. package/SECURITY.md +12 -0
  162. package/agents/dialectic-deriver.md +13 -10
  163. package/agents/eval-judge.md +67 -45
  164. package/agents/skill-applied-judge.md +34 -19
  165. package/commands/session.md +17 -3
  166. package/docs/baseline.md +12 -6
  167. package/docs/ci-setup.md +53 -0
  168. package/docs/codex-setup.md +15 -3
  169. package/docs/components.md +13 -6
  170. package/docs/events-schema.md +59 -9
  171. package/docs/install.md +16 -0
  172. package/docs/persona-panel.md +1 -1
  173. package/docs/pi-setup.md +1 -1
  174. package/docs/rule-authoring.md +135 -14
  175. package/docs/scope-collision-guard.md +2 -0
  176. package/docs/session-config-reference.md +106 -11
  177. package/docs/session-config-template.md +31 -2
  178. package/docs/telemetry.md +2 -0
  179. package/hooks/_lib/hook-import-set.json +125 -8
  180. package/hooks/_lib/subagent-paths.mjs +15 -0
  181. package/hooks/_lib/subagent-transcript.mjs +582 -31
  182. package/hooks/_lib/vcs-create-matcher.mjs +217 -62
  183. package/hooks/config-protection.mjs +11 -3
  184. package/hooks/cwd-change-restore.mjs +11 -3
  185. package/hooks/enforce-commands.mjs +70 -23
  186. package/hooks/enforce-scope.mjs +143 -33
  187. package/hooks/hooks-codex.json +1 -1
  188. package/hooks/hooks.json +1 -1
  189. package/hooks/loop-guard.mjs +11 -3
  190. package/hooks/on-session-end.mjs +72 -25
  191. package/hooks/on-session-start.mjs +48 -11
  192. package/hooks/on-stop.mjs +211 -23
  193. package/hooks/operator-steer.mjs +11 -3
  194. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  195. package/hooks/post-bash-write-verify.mjs +6 -2
  196. package/hooks/post-edit-import-probe.mjs +17 -9
  197. package/hooks/post-edit-validate.mjs +13 -5
  198. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  199. package/hooks/post-tool-batch-wave-signal.mjs +200 -38
  200. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  201. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  202. package/hooks/pre-auq-clarity.mjs +18 -2
  203. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  204. package/hooks/pre-bash-issue-budget.mjs +119 -28
  205. package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
  206. package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
  207. package/hooks/pre-bash-staging-fence.mjs +335 -31
  208. package/hooks/pre-bash-templates-first.mjs +19 -14
  209. package/hooks/pre-task-scope-disjoint.mjs +385 -5
  210. package/hooks/skill-invocation-telemetry.mjs +2 -1
  211. package/hooks/subagent-telemetry.mjs +15 -19
  212. package/hooks/wave-scope-commit-guard.mjs +197 -100
  213. package/monitors/monitors.json +1 -1
  214. package/output-styles/wave-summary.md +1 -1
  215. package/package.json +2 -1
  216. package/pi/prompts/autopilot.md +3 -3
  217. package/pi/prompts/bootstrap.md +3 -3
  218. package/pi/prompts/brainstorm.md +3 -3
  219. package/pi/prompts/close.md +2 -2
  220. package/pi/prompts/debug.md +3 -3
  221. package/pi/prompts/discovery.md +3 -3
  222. package/pi/prompts/dispatcher.md +3 -3
  223. package/pi/prompts/eli5.md +3 -3
  224. package/pi/prompts/eval.md +3 -3
  225. package/pi/prompts/evolve.md +3 -3
  226. package/pi/prompts/go.md +2 -2
  227. package/pi/prompts/grill.md +3 -3
  228. package/pi/prompts/harness-audit.md +2 -3
  229. package/pi/prompts/memory-cleanup.md +3 -3
  230. package/pi/prompts/persona-panel.md +3 -3
  231. package/pi/prompts/plan.md +3 -3
  232. package/pi/prompts/portfolio.md +2 -2
  233. package/pi/prompts/reconcile.md +3 -3
  234. package/pi/prompts/release.md +3 -3
  235. package/pi/prompts/repo-audit.md +3 -4
  236. package/pi/prompts/session.md +2 -2
  237. package/pi/prompts/spinout.md +3 -3
  238. package/pi/prompts/sunset-review.md +3 -3
  239. package/pi/prompts/templates-ack.md +1 -1
  240. package/pi/prompts/test.md +3 -3
  241. package/pi/prompts/ux-grill.md +3 -3
  242. package/rules/README.md +1 -1
  243. package/rules/opt-in-domain/prompt-caching.md +1 -1
  244. package/rules/opt-in-stack/backend-data.md +1 -1
  245. package/rules/opt-in-stack/backend.md +3 -3
  246. package/rules/opt-in-stack/frontend.md +1 -1
  247. package/rules/opt-in-stack/security-web.md +3 -3
  248. package/rules/opt-in-stack/swift.md +1 -1
  249. package/scripts/archive-closed-prds.mjs +2 -2
  250. package/scripts/auq-audit.mjs +2 -3
  251. package/scripts/autopilot.mjs +23 -2
  252. package/scripts/backfill-abandoned-sessions.mjs +171 -15
  253. package/scripts/backfill-evidence-digest.mjs +2 -1
  254. package/scripts/backfill-learnings-from-vault.mjs +2 -2
  255. package/scripts/check-package-manager.mjs +2 -2
  256. package/scripts/check-sessions-integrity.mjs +300 -0
  257. package/scripts/ci/assert-vitest-green.mjs +2 -1
  258. package/scripts/dialectic-deriver.mjs +50 -13
  259. package/scripts/emit-session.mjs +77 -32
  260. package/scripts/eval-session.mjs +65 -3
  261. package/scripts/export-hw-learnings.mjs +2 -1
  262. package/scripts/express-path.mjs +1 -1
  263. package/scripts/gc-stale-worktrees.mjs +2 -1
  264. package/scripts/generate-agents-skills.mjs +102 -29
  265. package/scripts/generate-codex-skills.mjs +48 -4
  266. package/scripts/generate-cursor-adapter.mjs +220 -11
  267. package/scripts/generate-hook-import-set.mjs +12 -27
  268. package/scripts/generate-pi-prompts.mjs +183 -13
  269. package/scripts/github-protection-audit.mjs +2 -3
  270. package/scripts/lib/agent-frontmatter.mjs +23 -1
  271. package/scripts/lib/agent-status.mjs +2 -31
  272. package/scripts/lib/auq/clarity.mjs +10 -2
  273. package/scripts/lib/auq/parse.mjs +12 -31
  274. package/scripts/lib/auq/schema.mjs +56 -41
  275. package/scripts/lib/auto-dialectic.mjs +304 -15
  276. package/scripts/lib/autopilot/flags.mjs +12 -1
  277. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  278. package/scripts/lib/autopilot/loop.mjs +14 -1
  279. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  280. package/scripts/lib/ci-status-banner.mjs +376 -16
  281. package/scripts/lib/claude-md-budget-lint.mjs +2 -5
  282. package/scripts/lib/command-blocker.mjs +408 -33
  283. package/scripts/lib/config/dialectic.mjs +12 -3
  284. package/scripts/lib/config/drift-check.mjs +19 -0
  285. package/scripts/lib/config/gate.mjs +74 -0
  286. package/scripts/lib/config/reaper.mjs +162 -0
  287. package/scripts/lib/config.mjs +14 -0
  288. package/scripts/lib/convergence-monitor.mjs +76 -13
  289. package/scripts/lib/cursor-hook-bridge.mjs +2 -2
  290. package/scripts/lib/description-surface.mjs +2 -5
  291. package/scripts/lib/dispatcher/cli.mjs +2 -1
  292. package/scripts/lib/ecosystem-health.mjs +11 -0
  293. package/scripts/lib/ecosystem-wizard.mjs +2 -1
  294. package/scripts/lib/eval/engine.mjs +421 -53
  295. package/scripts/lib/eval/judge.mjs +463 -40
  296. package/scripts/lib/eval/schema.mjs +10 -1
  297. package/scripts/lib/events-rotation.mjs +221 -25
  298. package/scripts/lib/events-schema.mjs +114 -0
  299. package/scripts/lib/events.mjs +524 -5
  300. package/scripts/lib/fetch-baseline.mjs +3 -8
  301. package/scripts/lib/frontmatter-guard.mjs +21 -10
  302. package/scripts/lib/gates/gate-baseline.mjs +27 -2
  303. package/scripts/lib/gates/gate-full.mjs +28 -3
  304. package/scripts/lib/gates/gate-helpers.mjs +243 -21
  305. package/scripts/lib/gates/gate-incremental.mjs +28 -3
  306. package/scripts/lib/gates/gate-per-file.mjs +27 -2
  307. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
  308. package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
  309. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  310. package/scripts/lib/instruction-budget-guard.mjs +332 -50
  311. package/scripts/lib/io.mjs +42 -8
  312. package/scripts/lib/is-main-module.mjs +82 -0
  313. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  314. package/scripts/lib/js-mask.mjs +197 -0
  315. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  316. package/scripts/lib/locks/index.mjs +32 -25
  317. package/scripts/lib/maintenance-due-banner.mjs +122 -91
  318. package/scripts/lib/orphan-reaper.mjs +1588 -0
  319. package/scripts/lib/peer-cards/merger.mjs +48 -10
  320. package/scripts/lib/peer-cards/reader.mjs +78 -2
  321. package/scripts/lib/peer-discovery.mjs +2 -5
  322. package/scripts/lib/playwright-driver/runner.mjs +2 -1
  323. package/scripts/lib/process-group.mjs +899 -0
  324. package/scripts/lib/quality-gate.mjs +107 -28
  325. package/scripts/lib/reconcile/backlog.mjs +368 -0
  326. package/scripts/lib/reconcile/engine.mjs +55 -188
  327. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
  328. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  329. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  330. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  331. package/scripts/lib/rule-loader.mjs +41 -12
  332. package/scripts/lib/rules-sync.mjs +2 -5
  333. package/scripts/lib/scope-echo.mjs +429 -7
  334. package/scripts/lib/scope-gate.mjs +605 -1
  335. package/scripts/lib/session-close-backfill.mjs +91 -12
  336. package/scripts/lib/session-id.mjs +9 -20
  337. package/scripts/lib/session-invocation.mjs +20 -0
  338. package/scripts/lib/session-schema/constants.mjs +30 -2
  339. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  340. package/scripts/lib/session-schema.mjs +8 -3
  341. package/scripts/lib/session-start-probes.mjs +95 -10
  342. package/scripts/lib/sessions-canonical.mjs +23 -0
  343. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  344. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  345. package/scripts/lib/skill-evidence-window.mjs +891 -0
  346. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  347. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  348. package/scripts/lib/skill-judge.mjs +45 -3
  349. package/scripts/lib/state-md.mjs +84 -3
  350. package/scripts/lib/sunset/walker.mjs +31 -4
  351. package/scripts/lib/tail-window.mjs +56 -0
  352. package/scripts/lib/telemetry/schema.mjs +30 -0
  353. package/scripts/lib/telemetry/sync.mjs +61 -6
  354. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  355. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  356. package/scripts/lib/tests-src-ratio.mjs +2 -6
  357. package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
  358. package/scripts/lib/user-invocable-skills.mjs +205 -0
  359. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  360. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  361. package/scripts/lib/validate/check-banner-parity.mjs +2 -2
  362. package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
  363. package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
  364. package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
  365. package/scripts/lib/validate/check-entry-guard.mjs +329 -0
  366. package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
  367. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  368. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
  369. package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
  370. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  371. package/scripts/lib/validate/check-rules.mjs +7 -5
  372. package/scripts/lib/validate/check-skill-links.mjs +35 -6
  373. package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
  374. package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
  375. package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
  376. package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
  377. package/scripts/lib/validate/check-unwired-features.mjs +220 -33
  378. package/scripts/lib/validate/check-validator-registration.mjs +36 -12
  379. package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
  380. package/scripts/lib/validate/confidential-names.mjs +10 -0
  381. package/scripts/lib/validate-vendored-rules.mjs +39 -12
  382. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  383. package/scripts/lib/vault-mirror/process.mjs +10 -3
  384. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  385. package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
  386. package/scripts/lib/vault-yaml.mjs +118 -0
  387. package/scripts/lib/wave-transcript-tail.mjs +2 -2
  388. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  389. package/scripts/lock-reaper.mjs +2 -1
  390. package/scripts/materialize-wave-scope.mjs +87 -4
  391. package/scripts/migrate-sessions-jsonl.mjs +2 -1
  392. package/scripts/migrate-vault-paths.mjs +2 -3
  393. package/scripts/release-session-lock.mjs +305 -0
  394. package/scripts/release.mjs +109 -39
  395. package/scripts/relocate-vault-corpus.mjs +2 -3
  396. package/scripts/repair-invalid-sessions.mjs +2 -2
  397. package/scripts/resolve-session-invocation.mjs +59 -0
  398. package/scripts/run-quality-gate.mjs +156 -17
  399. package/scripts/session-shape.mjs +2 -2
  400. package/scripts/site-numbers.mjs +35 -11
  401. package/scripts/sweep-expired-rules.mjs +227 -0
  402. package/scripts/validate-plugin.mjs +21 -0
  403. package/scripts/validate-wave-scope.mjs +32 -105
  404. package/scripts/vault-consolidate.mjs +2 -2
  405. package/scripts/vault-mirror.mjs +11 -4
  406. package/scripts/wave-scope-binding.mjs +2 -3
  407. package/skills/_shared/bootstrap-gate.md +1 -1
  408. package/skills/_shared/monitor-patterns.md +1 -1
  409. package/skills/_shared/platform-tools.md +23 -11
  410. package/skills/_shared/research-evidence.md +53 -0
  411. package/skills/_shared/state-ownership.md +3 -0
  412. package/skills/autopilot/SKILL.md +80 -11
  413. package/skills/bootstrap/SKILL.md +51 -1
  414. package/skills/brainstorm/SKILL.md +16 -0
  415. package/skills/claude-md-drift-check/SKILL.md +1 -1
  416. package/skills/claude-md-drift-check/checker.mjs +49 -11
  417. package/{commands/close.md → skills/close/SKILL.md} +9 -3
  418. package/skills/convergence-monitoring/README.md +8 -1
  419. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  420. package/skills/convergence-monitoring/SKILL.md +15 -6
  421. package/skills/debug/SKILL.md +10 -0
  422. package/skills/discovery/SKILL.md +24 -1
  423. package/skills/discovery/probes-session.md +2 -2
  424. package/skills/dispatcher/SKILL.md +38 -7
  425. package/skills/eli5/SKILL.md +11 -0
  426. package/skills/eval/SKILL.md +52 -23
  427. package/skills/eval/rubric-v1.md +1 -0
  428. package/skills/eval/rubric-v2.md +457 -0
  429. package/skills/evolve/SKILL.md +9 -2
  430. package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
  431. package/skills/gitlab-ops/SKILL.md +3 -2
  432. package/{commands/go.md → skills/go/SKILL.md} +9 -1
  433. package/skills/grill/SKILL.md +19 -0
  434. package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
  435. package/skills/hook-development/SKILL.md +46 -41
  436. package/skills/memory-cleanup/SKILL.md +7 -0
  437. package/skills/npm-publish/SKILL.md +2 -2
  438. package/skills/persona-panel/SKILL.md +56 -1
  439. package/skills/persona-panel/persona-format.md +1 -1
  440. package/skills/plan/SKILL.md +28 -1
  441. package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
  442. package/skills/reconcile/SKILL.md +21 -0
  443. package/{commands/release.md → skills/release/SKILL.md} +16 -2
  444. package/skills/repo-audit/SKILL.md +7 -0
  445. package/skills/session-end/SKILL.md +13 -16
  446. package/skills/session-end/discovery-scan.md +1 -1
  447. package/skills/session-end/phase-3-6-tail.md +55 -9
  448. package/skills/session-end/plan-verification.md +2 -2
  449. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  450. package/skills/session-end/session-metrics-write.md +10 -0
  451. package/skills/session-plan/SKILL.md +18 -6
  452. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  453. package/skills/session-start/SKILL.md +5 -4
  454. package/skills/session-start/phase-8-5-express-path.md +6 -6
  455. package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
  456. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
  457. package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
  458. package/skills/spinout/SKILL.md +12 -1
  459. package/skills/sunset-review/SKILL.md +13 -0
  460. package/{commands/test.md → skills/test/SKILL.md} +10 -4
  461. package/skills/ux-grill/SKILL.md +20 -2
  462. package/skills/wave-executor/SKILL.md +14 -7
  463. package/skills/wave-executor/circuit-breaker.md +2 -0
  464. package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
  465. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  466. package/skills/wave-executor/references/wave-loop-review.md +17 -1
  467. package/commands/autopilot.md +0 -80
  468. package/commands/bootstrap.md +0 -56
  469. package/commands/brainstorm.md +0 -48
  470. package/commands/debug.md +0 -36
  471. package/commands/discovery.md +0 -32
  472. package/commands/dispatcher.md +0 -59
  473. package/commands/eli5.md +0 -33
  474. package/commands/eval.md +0 -28
  475. package/commands/evolve.md +0 -10
  476. package/commands/grill.md +0 -45
  477. package/commands/memory-cleanup.md +0 -26
  478. package/commands/persona-panel.md +0 -121
  479. package/commands/plan.md +0 -15
  480. package/commands/reconcile.md +0 -23
  481. package/commands/repo-audit.md +0 -24
  482. package/commands/spinout.md +0 -15
  483. package/commands/sunset-review.md +0 -27
  484. package/commands/ux-grill.md +0 -51
@@ -21,6 +21,13 @@
21
21
  * when the platform exposes a stable prompt-assembly boundary; at that point the
22
22
  * digest can be computed against the real assembled prompt instead of echoed.
23
23
  *
24
+ * CALLERS (#1298): the CLI modes run only from prose — `--instruction` in
25
+ * `skills/wave-executor/references/wave-loop-dispatch.md`, `--emit` and `--verify`
26
+ * in `wave-loop-review.md`. Ceiling: `check-unwired-features` S4 cannot notice both
27
+ * dropping that citation — it judges modules, not CLI modes, and reads this one wired
28
+ * as a CLI entrypoint AND via its hook importer (`scopeDigest`). Revisit on a third
29
+ * prose caller, or when the census learns to track prose-invoked CLI entrypoints.
30
+ *
24
31
  * Pure + stdlib only. Nothing here throws on malformed input — a broken echo
25
32
  * check must never change a wave's outcome.
26
33
  *
@@ -34,14 +41,16 @@
34
41
  * node scripts/lib/scope-echo.mjs --scope-file <path> --instruction
35
42
  * node scripts/lib/scope-echo.mjs --scope-file <path> --report-file <path> \
36
43
  * [--wave N --agent-id ID --emit]
44
+ * node scripts/lib/scope-echo.mjs --verify --wave <N> --state-dir <dir> \
45
+ * [--session <id> --events <path> --json --emit]
37
46
  * node scripts/lib/scope-echo.mjs --help
38
47
  */
39
48
 
40
- import { readFileSync } from 'node:fs';
41
- import { resolve } from 'node:path';
42
- import { fileURLToPath } from 'node:url';
49
+ import { readFileSync, readdirSync } from 'node:fs';
50
+ import { join, resolve } from 'node:path';
43
51
 
44
52
  import { digestSha256Short } from './crypto-digest-utils.mjs';
53
+ import { isMainModule } from './is-main-module.mjs';
45
54
 
46
55
  /** Marker the agent must emit. Case-sensitive by design — a lowercase lookalike is not an echo. */
47
56
  export const SCOPE_ECHO_MARKER = 'SCOPE-DIGEST:';
@@ -49,6 +58,15 @@ export const SCOPE_ECHO_MARKER = 'SCOPE-DIGEST:';
49
58
  /** Event name for the post-wave verdict. */
50
59
  export const SCOPE_ECHO_EVENT = 'orchestrator.wave_dispatch.scope_echo_checked';
51
60
 
61
+ /** The SEND-side record `hooks/pre-task-scope-disjoint.mjs` writes per dispatch. */
62
+ export const SCOPE_CHECKED_EVENT = 'orchestrator.wave_dispatch.scope_checked';
63
+
64
+ /** The DEGRADATION record `scripts/materialize-wave-scope.mjs` writes per wave. */
65
+ export const SCOPE_MATERIALIZED_EVENT = 'orchestrator.wave_dispatch.scope_materialized';
66
+
67
+ /** The per-WAVE join verdict `--verify` emits. */
68
+ export const SCOPE_VERIFIED_EVENT = 'orchestrator.wave_dispatch.scope_verified';
69
+
52
70
  /** Max characters retained for `agent_id` in the payload (same clamp as the send-side hook). */
53
71
  const AGENT_ID_MAX = 120;
54
72
 
@@ -212,6 +230,330 @@ export function scopeEchoPayload(verdict, meta = {}) {
212
230
  };
213
231
  }
214
232
 
233
+ // ---------------------------------------------------------------------------
234
+ // --verify — join the two halves of one wave ON THE DIGEST (#1092)
235
+ // ---------------------------------------------------------------------------
236
+
237
+ /**
238
+ * Closed verdict enum. Order is PRECEDENCE, not preference — the first matching
239
+ * row wins, and every (file, dispatch, echo) triple lands on exactly one row:
240
+ *
241
+ * duplicate-claim ≥2 DISTINCT agent ids claimed one digest at dispatch.
242
+ * That is agent A's scope reported for agent B, and it is
243
+ * a defect whatever the other two bits say — so it is
244
+ * checked first.
245
+ * echoed-not-injected an echo names a digest NO dispatch claimed.
246
+ * digest-unknown no scope FILE on disk carries this digest: the record
247
+ * exists but the artefact that would ground it does not
248
+ * (a scope file rewritten or reconciled away after the
249
+ * dispatch).
250
+ * matched file ∧ dispatch ∧ echo.
251
+ * injected-not-echoed file ∧ dispatch, no echo — the normal state DURING a
252
+ * wave, before the reports land.
253
+ * injection-missing file ∧ neither — a scope file no dispatch claimed and
254
+ * no agent echoed. This is AC-2: the omitted injection.
255
+ *
256
+ * Every member is kebab-case. `echo-only` is NOT one of the six: it is the
257
+ * DEGRADED value every verdict collapses to when the wave's transport is
258
+ * unobservable (see {@link verifyWaveScope}), so it is never produced by the
259
+ * precedence chain above and never belongs in this array.
260
+ *
261
+ * `injection-missing` was spelled `injection_missing` until 2026-09-16 — the
262
+ * one snake_case member of an otherwise kebab-case enum. Ledger records written
263
+ * before that date may still carry the old spelling in `by_verdict`; there is no
264
+ * dual-emit, so a consumer reading historical rows must accept both keys.
265
+ */
266
+ export const SCOPE_VERDICTS = Object.freeze([
267
+ 'duplicate-claim',
268
+ 'echoed-not-injected',
269
+ 'digest-unknown',
270
+ 'matched',
271
+ 'injected-not-echoed',
272
+ 'injection-missing',
273
+ ]);
274
+
275
+ /** A well-formed digest. Anything else is ignored rather than joined on. */
276
+ const DIGEST_RE = /^[0-9a-f]{8}$/;
277
+
278
+ /**
279
+ * Parse a JSONL ledger leniently: a malformed line is skipped, never thrown on
280
+ * — and COUNTED, which is the load-bearing half.
281
+ *
282
+ * A crashed writer leaves a truncated final line (`{"event":"…","wave":4` with
283
+ * no closing brace) — a measured shape in this repo's own ledger. Skipping it
284
+ * silently truncates the join, and a truncated join reports
285
+ * `dispatches N / injected N / echoed N` with every digest `matched`: a CLEAN
286
+ * wave, from the instrument built to detect silent failure. The count is what
287
+ * separates "nothing was wrong" from "I could not read part of the evidence".
288
+ *
289
+ * A malformed line is any NON-BLANK line that does not yield a plain object:
290
+ * one that does not start with `{`, one `JSON.parse` rejects, and one that
291
+ * parses to an array or a scalar. Blank lines are not malformed — a trailing
292
+ * newline is the normal end of a JSONL file.
293
+ *
294
+ * @param {string} raw
295
+ * @returns {{records: Array<Record<string, unknown>>, malformed: number}}
296
+ */
297
+ function parseJsonl(raw) {
298
+ /** @type {Array<Record<string, unknown>>} */
299
+ const records = [];
300
+ let malformed = 0;
301
+ if (typeof raw !== 'string') return { records, malformed };
302
+ for (const line of raw.split('\n')) {
303
+ const trimmed = line.trim();
304
+ if (trimmed.length === 0) continue;
305
+ if (trimmed[0] !== '{') {
306
+ malformed += 1;
307
+ continue;
308
+ }
309
+ try {
310
+ const rec = JSON.parse(trimmed);
311
+ if (rec !== null && typeof rec === 'object' && !Array.isArray(rec)) records.push(rec);
312
+ else malformed += 1;
313
+ } catch {
314
+ malformed += 1; // a half-written line is not a verdict — but it is not nothing either
315
+ }
316
+ }
317
+ return { records, malformed };
318
+ }
319
+
320
+ /**
321
+ * Digest every per-agent scope file of one wave — shape (a) of the two scope
322
+ * shapes (CLAUDE.md / AGENTS.md § allowedPaths).
323
+ *
324
+ * A file that cannot be read or parsed is the FILE-side counterpart of a
325
+ * truncated ledger line: skipping it silently makes a wave whose scope files are
326
+ * corrupt indistinguishable from one that was never injected (`digest-unknown` /
327
+ * `injection-missing`). The count therefore rides back on the returned Map as
328
+ * `malformedFiles` — an additive own property, so every existing consumer that
329
+ * only iterates the entries is unaffected. (A file that parses to a non-array or
330
+ * to an empty scope stays a silent skip: an empty scope is legitimate.)
331
+ *
332
+ * @param {string} stateDir
333
+ * @param {number} wave
334
+ * @param {{readDir?: typeof readdirSync, readFile?: typeof readFileSync}} [io]
335
+ * @returns {Map<string, string[]> & {malformedFiles: number}} digest → agent ids (file basenames)
336
+ */
337
+ export function digestScopeFiles(stateDir, wave, { readDir = readdirSync, readFile = readFileSync } = {}) {
338
+ const out = new Map();
339
+ out.malformedFiles = 0;
340
+ const dir = resolve(String(stateDir), 'filescopes', `wave-${wave}`);
341
+ let names;
342
+ try {
343
+ names = readDir(dir);
344
+ } catch {
345
+ return out; // no directory yet is not a failure — it is "nothing materialized"
346
+ }
347
+ for (const name of names) {
348
+ const file = typeof name === 'string' ? name : name?.name;
349
+ if (typeof file !== 'string' || !file.endsWith('.json')) continue;
350
+ let parsed;
351
+ try {
352
+ parsed = JSON.parse(readFile(join(dir, file), 'utf8'));
353
+ } catch {
354
+ out.malformedFiles += 1; // unreadable or unparseable — not nothing
355
+ continue;
356
+ }
357
+ if (!Array.isArray(parsed) || normalizeScopePaths(parsed).length === 0) continue;
358
+ const digest = scopeDigest(parsed);
359
+ const ids = out.get(digest) ?? [];
360
+ ids.push(file.slice(0, -'.json'.length));
361
+ out.set(digest, ids);
362
+ }
363
+ return out;
364
+ }
365
+
366
+ /**
367
+ * Verify ONE wave's FILE-SCOPE injection end to end.
368
+ *
369
+ * ## Why the join key is the DIGEST and never `agent_id`
370
+ *
371
+ * Measured 2026-09-16 over this host's `.orchestrator/metrics/events.jsonl`
372
+ * corpus: 609 `scope_checked` records against 51 `scope_echo_checked`, and the
373
+ * two agent-id sets OVERLAP IN ZERO ELEMENTS — the send side records the
374
+ * dispatch tool's `description` plus `subagent_type`
375
+ * (`"i-3 #1353 refund event (session-orchestrator:code-implementer)"`), the
376
+ * receive side the coordinator's short handle (`"i-3"`). One session showed 23
377
+ * echoes against 18 injections and the halves could not be joined at all. The
378
+ * digest is the one value both halves derive from the SAME artefact, so it joins
379
+ * them by construction.
380
+ *
381
+ * ## Observability, not enforcement
382
+ *
383
+ * Every verdict exits 0. This tool reports; the wave-executor turns
384
+ * `injection-missing` / `duplicate-claim` into a STATE.md deviation
385
+ * (`wave-loop-review.md` step 3d-bis), never into a block.
386
+ *
387
+ * ## A partial read is never a clean wave
388
+ *
389
+ * `malformed_lines` counts the ledger lines that could not be parsed (a crashed
390
+ * writer's truncated append is the measured shape). It is ALWAYS present,
391
+ * including as `0`: the ledger read either happened or the file was absent, and
392
+ * both are measurements. A non-zero count means the join ran on INCOMPLETE
393
+ * evidence — the counts and verdicts below are a floor, not a census — and the
394
+ * human table says so beside them.
395
+ *
396
+ * `malformed_scope_files` is the same measurement on the FILE side (#1379 P2):
397
+ * a scope file that cannot be read or parsed was silently skipped, which made a
398
+ * corrupt wave yield `digest-unknown` / `injection-missing` verdicts
399
+ * indistinguishable from a genuinely missing injection. Always present,
400
+ * including as `0`.
401
+ *
402
+ * ## Transport degradation
403
+ *
404
+ * On a platform with no `PreToolUse` `Agent` matcher (Codex, Cursor, Pi) the
405
+ * send-side record cannot exist at all, so a missing dispatch is NOT evidence of
406
+ * a missing injection. When the wave's `scope_materialized` record says
407
+ * `transport_observable: false`, `transport` reads `unobservable` and every
408
+ * verdict degrades to `echo-only` — the receive half is all the platform can
409
+ * carry, and reporting `injection-missing` there would be an accusation derived
410
+ * from an instrument that is not installed.
411
+ *
412
+ * @param {object} params
413
+ * @param {number} params.wave
414
+ * @param {string} params.stateDir
415
+ * @param {string} [params.session] semantic (or raw) session id to filter on
416
+ * @param {string} [params.eventsPath] defaults to `.orchestrator/metrics/events.jsonl`
417
+ * @param {typeof readFileSync} [params.readFile]
418
+ * @param {typeof readdirSync} [params.readDir]
419
+ * @returns {{wave: number, transport: 'observable'|'unobservable', dispatches: number,
420
+ * injected: number, echoed: number, malformed_lines: number,
421
+ * by_verdict: Record<string, number>,
422
+ * agents: Array<{agent_id: string, verdict: string, digest: string}>}}
423
+ */
424
+ export function verifyWaveScope({
425
+ wave,
426
+ stateDir,
427
+ session,
428
+ eventsPath,
429
+ readFile = readFileSync,
430
+ readDir = readdirSync,
431
+ }) {
432
+ const ledgerPath = eventsPath || join('.orchestrator', 'metrics', 'events.jsonl');
433
+ let records = [];
434
+ let malformedLines = 0;
435
+ try {
436
+ ({ records, malformed: malformedLines } = parseJsonl(readFile(ledgerPath, 'utf8')));
437
+ } catch { /* no ledger yet — every count is legitimately 0 */ }
438
+
439
+ const mine = (rec) => {
440
+ if (rec.wave !== wave) return false;
441
+ if (!session) return true;
442
+ return rec.semantic_session_id === session || rec.session_id === session;
443
+ };
444
+
445
+ /** @type {Map<string, Set<string>>} digest → distinct dispatching agent ids */
446
+ const dispatchIds = new Map();
447
+ /** @type {Map<string, string>} digest → first echoing agent id */
448
+ const echoIds = new Map();
449
+ let dispatches = 0;
450
+ let injected = 0;
451
+ let echoed = 0;
452
+ // A wave may be materialized more than once (#1103); the LAST record wins,
453
+ // because that is the state the dispatches below actually ran against.
454
+ let transportObservable = null;
455
+
456
+ for (const rec of records) {
457
+ if (rec.event === SCOPE_MATERIALIZED_EVENT && mine(rec)) {
458
+ if (typeof rec.transport_observable === 'boolean') transportObservable = rec.transport_observable;
459
+ continue;
460
+ }
461
+ if (rec.event === SCOPE_CHECKED_EVENT && mine(rec)) {
462
+ dispatches += 1;
463
+ if (rec.injected === true) injected += 1;
464
+ const digest = rec.scope_digest;
465
+ if (typeof digest === 'string' && DIGEST_RE.test(digest)) {
466
+ const ids = dispatchIds.get(digest) ?? new Set();
467
+ ids.add(typeof rec.agent_id === 'string' ? rec.agent_id : 'unnamed-agent');
468
+ dispatchIds.set(digest, ids);
469
+ }
470
+ continue;
471
+ }
472
+ if (rec.event === SCOPE_ECHO_EVENT && mine(rec)) {
473
+ if (rec.echoed === true) echoed += 1;
474
+ // The agent's OWN claim first (`actual_digest`); the coordinator-side
475
+ // expectation only as a fallback, so a mismatched echo still joins to the
476
+ // scope file it was checked against rather than vanishing from the report.
477
+ const digest = [rec.actual_digest, rec.expected_digest]
478
+ .find((d) => typeof d === 'string' && DIGEST_RE.test(d));
479
+ if (digest !== undefined && !echoIds.has(digest)) {
480
+ echoIds.set(digest, typeof rec.agent_id === 'string' ? rec.agent_id : 'unnamed-agent');
481
+ }
482
+ }
483
+ }
484
+
485
+ const fileIds = digestScopeFiles(stateDir, wave, { readDir, readFile });
486
+ const digests = [...new Set([...fileIds.keys(), ...dispatchIds.keys(), ...echoIds.keys()])].sort();
487
+
488
+ const unobservable = transportObservable === false;
489
+ /** @type {Record<string, number>} */
490
+ const byVerdict = {};
491
+ const agents = digests.map((digest) => {
492
+ const claimants = dispatchIds.get(digest);
493
+ const hasDispatch = claimants !== undefined && claimants.size > 0;
494
+ const hasEcho = echoIds.has(digest);
495
+ const hasFile = fileIds.has(digest);
496
+
497
+ let verdict;
498
+ if (hasDispatch && claimants.size > 1) verdict = 'duplicate-claim';
499
+ else if (hasEcho && !hasDispatch) verdict = 'echoed-not-injected';
500
+ else if (!hasFile) verdict = 'digest-unknown';
501
+ else if (hasDispatch && hasEcho) verdict = 'matched';
502
+ else if (hasDispatch) verdict = 'injected-not-echoed';
503
+ else verdict = 'injection-missing';
504
+ if (unobservable) verdict = 'echo-only';
505
+
506
+ byVerdict[verdict] = (byVerdict[verdict] ?? 0) + 1;
507
+ const agentId = (hasDispatch ? [...claimants].sort().join(' + ') : null)
508
+ ?? echoIds.get(digest)
509
+ ?? (fileIds.get(digest) ?? []).join(' + ')
510
+ ?? 'unknown';
511
+ return { agent_id: agentId, verdict, digest };
512
+ });
513
+
514
+ return {
515
+ wave,
516
+ transport: unobservable ? 'unobservable' : 'observable',
517
+ dispatches,
518
+ injected,
519
+ echoed,
520
+ malformed_lines: malformedLines,
521
+ malformed_scope_files: fileIds.malformedFiles ?? 0,
522
+ by_verdict: byVerdict,
523
+ agents,
524
+ };
525
+ }
526
+
527
+ /**
528
+ * Telemetry payload for a `--verify` run. Counts, closed enums and 8-hex digests
529
+ * ONLY — no agent id, no path, no prompt text: this record travels over the same
530
+ * unredacted Clank Event-Bus webhook as its two halves, and an `agent_id` is a
531
+ * free-form coordinator string that has carried private project slugs before.
532
+ *
533
+ * `malformed_lines` travels with the counts and, like them, is ALWAYS present
534
+ * including as `0` — it is the denominator's honesty check: without it a join
535
+ * that silently dropped half the ledger is indistinguishable in the record from
536
+ * a wave where nothing went wrong (`.claude/rules/host-resources.md` § HR-105).
537
+ * `malformed_scope_files` carries the same guarantee for the scope files the
538
+ * join reads (#1379 P2).
539
+ *
540
+ * @param {ReturnType<typeof verifyWaveScope>} report
541
+ * @returns {Record<string, unknown>}
542
+ */
543
+ export function scopeVerifiedPayload(report) {
544
+ return {
545
+ wave: report.wave,
546
+ transport_observable: report.transport === 'observable',
547
+ dispatches: report.dispatches,
548
+ injected: report.injected,
549
+ echoed: report.echoed,
550
+ malformed_lines: report.malformed_lines ?? 0,
551
+ malformed_scope_files: report.malformed_scope_files ?? 0,
552
+ by_verdict: report.by_verdict,
553
+ digests: report.agents.map((a) => a.digest),
554
+ };
555
+ }
556
+
215
557
  // ---------------------------------------------------------------------------
216
558
  // CLI
217
559
  // ---------------------------------------------------------------------------
@@ -229,9 +571,19 @@ const USAGE = `Usage:
229
571
  an ${SCOPE_ECHO_EVENT} row to .orchestrator/metrics/events.jsonl
230
572
  (best-effort: a failed emit never changes the verdict or the exit code).
231
573
 
574
+ scope-echo --verify --wave <N> --state-dir <dir> [--session <id>]
575
+ [--events <path>] [--json] [--emit]
576
+ Join ONE wave's send side (${SCOPE_CHECKED_EVENT}),
577
+ its scope files and its echoes ON THE DIGEST, and print the per-agent
578
+ verdict table. --emit additionally appends one ${SCOPE_VERIFIED_EVENT}
579
+ row per wave (counts and 8-hex digests only).
580
+ Verdicts: ${SCOPE_VERDICTS.join(' · ')} — plus echo-only when the wave's
581
+ transport is unobservable.
582
+
232
583
  scope-echo --help
233
584
 
234
- Exit codes: 0 for every verdict, 1 for a missing --scope-file.
585
+ Exit codes: 0 for every verdict, 1 for a missing --scope-file (or, under
586
+ --verify, a missing --wave / --state-dir).
235
587
  `;
236
588
 
237
589
  /**
@@ -259,6 +611,74 @@ function parseArgv(argv) {
259
611
  return out;
260
612
  }
261
613
 
614
+ /**
615
+ * `--verify` mode — the per-wave join (see {@link verifyWaveScope}).
616
+ *
617
+ * Exit 0 for EVERY verdict, including `injection-missing`: this is an
618
+ * observability tool, and the wave-executor decides what a verdict costs
619
+ * (`wave-loop-review.md` step 3d-bis writes a STATE.md deviation, never a
620
+ * block). Exit 1 is reserved for the caller's own mistake — a missing required
621
+ * flag — per `.claude/rules/cli-design.md`.
622
+ *
623
+ * @param {Record<string, string|boolean>} args
624
+ * @returns {Promise<number>}
625
+ */
626
+ async function mainVerify(args) {
627
+ const wave = Number(args.wave);
628
+ const stateDir = typeof args['state-dir'] === 'string' ? args['state-dir'] : '';
629
+ if (!Number.isSafeInteger(wave) || wave <= 0 || stateDir === '') {
630
+ process.stderr.write('scope-echo: --verify requires --wave <positive-int> and --state-dir <dir>\n');
631
+ process.stderr.write(USAGE);
632
+ return 1;
633
+ }
634
+
635
+ const report = verifyWaveScope({
636
+ wave,
637
+ stateDir,
638
+ session: typeof args.session === 'string' ? args.session : undefined,
639
+ eventsPath: typeof args.events === 'string' ? args.events : undefined,
640
+ });
641
+
642
+ if (args.emit) {
643
+ try {
644
+ const repoRoot = resolve(stateDir, '..');
645
+ const { emitEvent, sessionAttribution } = await import('./events.mjs');
646
+ await emitEvent(
647
+ SCOPE_VERIFIED_EVENT,
648
+ { ...scopeVerifiedPayload(report), ...sessionAttribution(repoRoot) },
649
+ { repoRoot },
650
+ );
651
+ } catch (err) {
652
+ process.stderr.write(`scope-echo: emit failed — ${err?.message ?? err}\n`);
653
+ }
654
+ }
655
+
656
+ if (args.json) {
657
+ process.stdout.write(`${JSON.stringify(report)}\n`);
658
+ return 0;
659
+ }
660
+ const lines = [
661
+ `wave ${report.wave} — transport: ${report.transport} — `
662
+ + `${report.injected}/${report.dispatches} injected, ${report.echoed} echoed`,
663
+ // A partial read must never read as a clean wave: the counts above are a
664
+ // FLOOR when lines were dropped, and this is the only place the human table
665
+ // can say so.
666
+ ...(report.malformed_lines > 0
667
+ ? [` WARNING: ${report.malformed_lines} malformed ledger line(s) skipped — `
668
+ + 'counts and verdicts below are a floor, not a census']
669
+ : []),
670
+ // Same honesty check on the FILE side: an unparseable scope file makes a
671
+ // corrupt wave look exactly like one that was never injected.
672
+ ...(report.malformed_scope_files > 0
673
+ ? [` WARNING: ${report.malformed_scope_files} malformed scope file(s) skipped — `
674
+ + 'digest-unknown / injection-missing below may be corruption, not a missing injection']
675
+ : []),
676
+ ...report.agents.map((a) => ` ${a.digest} ${a.verdict.padEnd(20)} ${a.agent_id}`),
677
+ ];
678
+ process.stdout.write(`${lines.join('\n')}\n`);
679
+ return 0;
680
+ }
681
+
262
682
  /**
263
683
  * @param {string[]} [argv]
264
684
  * @returns {Promise<number>} process exit code: 0 for every verdict (this is an
@@ -272,6 +692,10 @@ export async function main(argv = process.argv.slice(2)) {
272
692
  process.stdout.write(USAGE);
273
693
  return 0;
274
694
  }
695
+ // --verify is its own mode and takes no --scope-file: it reads EVERY scope
696
+ // file of the wave, so the check below must not reject it first.
697
+ if (args.verify) return mainVerify(args);
698
+
275
699
  const scopeFilePath = typeof args['scope-file'] === 'string' ? args['scope-file'] : '';
276
700
  if (!scopeFilePath) {
277
701
  process.stderr.write('scope-echo: --scope-file <path> is required\n');
@@ -337,9 +761,7 @@ export async function main(argv = process.argv.slice(2)) {
337
761
  return 0;
338
762
  }
339
763
 
340
- const invokedAsCli =
341
- process.argv[1] !== undefined &&
342
- resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
764
+ const invokedAsCli = isMainModule(import.meta.url);
343
765
 
344
766
  if (invokedAsCli) {
345
767
  process.exitCode = await main();