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
package/hooks/on-stop.mjs CHANGED
@@ -30,28 +30,28 @@
30
30
  */
31
31
 
32
32
  import path from 'node:path';
33
+ import { spawn } from 'node:child_process';
34
+ import { performance } from 'node:perf_hooks';
33
35
  import {
34
36
  promises as fs,
35
- closeSync,
36
37
  existsSync,
37
- fstatSync,
38
- openSync,
39
38
  readFileSync,
40
- readSync,
41
39
  statSync,
42
40
  writeFileSync,
43
41
  } from 'node:fs';
42
+ import { fileURLToPath } from 'node:url';
44
43
 
45
44
  import { shouldRunHook } from './_lib/profile-gate.mjs';
45
+ import { readTailWindow } from '../scripts/lib/tail-window.mjs';
46
46
  import { AGENT_ID_RE, resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
47
- // #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
48
- if (!shouldRunHook('on-stop')) process.exit(0);
49
47
 
50
48
  import { emitEvent } from '../scripts/lib/events.mjs';
51
49
  import { detectPlatform, getProjectDir } from '../scripts/lib/platform.mjs';
52
50
  import { parseSessionId } from '../scripts/lib/session-id.mjs';
51
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
53
52
  import { heartbeat, logSweepEvent } from '../scripts/lib/session-registry.mjs';
54
53
  import { readLock, updateHeartbeat } from '../scripts/lib/session-lock.mjs';
54
+ import { _parseReaper } from '../scripts/lib/config/reaper.mjs';
55
55
 
56
56
  // ---------------------------------------------------------------------------
57
57
  // stdin reading (inline — no io.mjs because Stop hooks exit 0 always, never deny)
@@ -551,6 +551,39 @@ async function handleSubagentStop(input) {
551
551
  } catch { /* probe failed — omit rather than assert `false` */ }
552
552
 
553
553
  try {
554
+ // `sidecar_missing` — the DENOMINATOR for every meta-derived field below
555
+ // (#1289 Befund 2). `agent_type_meta` and `tool_use_id` come from the same
556
+ // read, so they co-occur perfectly (measured 2026-09-16 over
557
+ // `.orchestrator/metrics/events.jsonl`: 15.291 `orchestrator.agent.stopped`
558
+ // records, 542 with `agent_type_meta`, 542 with `tool_use_id`,
559
+ // both/only-meta/only-tool = 542/0/0). That made the low presence rate look
560
+ // like a producer defect in the KEY, when 1000/1000 sidecars on this host
561
+ // carry `agentType`. This flag makes "no sidecar found" visible in the
562
+ // record instead of indistinguishable from "sidecar found, field absent".
563
+ //
564
+ // WHAT THIS KEY IS NOT: a sidecar-LOOKUP failure rate. Re-measured
565
+ // 2026-09-16 over 15.457 `orchestrator.agent.stopped` records (5086
566
+ // `transcript_found:false` vs 553 `true`), the false mass is the #939/#949
567
+ // PHANTOM-STOP class — no subagent ever existed, so there is nothing for
568
+ // the lookup to find:
569
+ // - 0 of 5077 distinct false `agent_id`s have a sidecar ANYWHERE on this
570
+ // host (`find ~/.claude/projects -path '*/subagents/agent-*.jsonl'` →
571
+ // 8626 ids), and 0 have a `SubagentStart` record (2176 starts in
572
+ // `.orchestrator/metrics/subagents.jsonl`);
573
+ // - 0 records carrying a valid `agent_id` got a null resolver result;
574
+ // - the two classes are perfectly bimodal on the type field:
575
+ // `false` + no `agent` 5086, `true` + typed 553, off-diagonal 0.
576
+ // `agent` (the harness `agent_type`) is therefore the ready-made
577
+ // discriminator — no further key is needed. Do NOT "repair" the derivation
578
+ // in hooks/_lib/subagent-paths.mjs to chase this rate: it already resolves
579
+ // 553/553 of the real stops, and a widened one would only start resolving
580
+ // onto a FOREIGN agent's sidecar. This misreading has now cost three
581
+ // investigations (#939, the 2026-08-11 ledger re-run, #1289 Befund 2).
582
+ //
583
+ // Present ONLY when true, like every other optional key in this payload;
584
+ // its own probe is inside this try, so a failed `existsSync` omits it and
585
+ // nothing else (a telemetry fault must never change a decision).
586
+ if (!existsSync(metaPath)) payload.sidecar_missing = true;
554
587
  // One small read, one parse, two fields. `description` is operator prose
555
588
  // and is deliberately NOT carried: this payload also travels over the
556
589
  // optional Clank webhook unredacted.
@@ -565,7 +598,16 @@ async function handleSubagentStop(input) {
565
598
  payload.tool_use_id = meta.toolUseId.trim();
566
599
  }
567
600
  // A SECOND witness for the type — never merged into `agent`, so the
568
- // empty-`agent_type` rate stays measurable.
601
+ // empty-`agent_type` rate stays measurable. NO FALLBACK MERGE, deliberately:
602
+ // `payload.agent` already carries the harness's `input.agent_type`, and
603
+ // filling one from the other would erase exactly the signal this pair
604
+ // exists to produce.
605
+ //
606
+ // REPORTING RULE: the presence rate of this key is only meaningful against
607
+ // the events whose SIDECAR WAS FOUND — "present on N of M events without
608
+ // `sidecar_missing`", never N of all `orchestrator.agent.stopped` records.
609
+ // Measured against all events the rate reads as a producer defect in this
610
+ // block; against its real denominator it is a lookup-failure rate.
569
611
  if (typeof meta?.agentType === 'string' && AGENT_TYPE_META_RE.test(meta.agentType.trim())) {
570
612
  payload.agent_type_meta = meta.agentType.trim();
571
613
  }
@@ -701,17 +743,11 @@ function firstNonEmptyString(input, keys) {
701
743
  * @returns {string|null}
702
744
  */
703
745
  function readStatusFromTranscriptTail(transcriptPath) {
704
- let fd = null;
705
746
  try {
706
- fd = openSync(transcriptPath, 'r');
707
- const size = fstatSync(fd).size;
708
- const length = Math.min(size, STATUS_TAIL_BYTES);
709
- const start = size - length;
710
- const buf = Buffer.alloc(length);
711
- const read = readSync(fd, buf, 0, length, start);
712
- let lines = buf.subarray(0, read).toString('utf8').split('\n');
747
+ const window = readTailWindow(transcriptPath, STATUS_TAIL_BYTES);
748
+ let lines = window.text.split('\n');
713
749
  // Drop the leading partial line when the window did not start at byte 0.
714
- if (start > 0) lines = lines.slice(1);
750
+ if (window.cut) lines = lines.slice(1);
715
751
 
716
752
  for (let i = lines.length - 1; i >= 0; i -= 1) {
717
753
  const line = lines[i].trim();
@@ -732,10 +768,6 @@ function readStatusFromTranscriptTail(transcriptPath) {
732
768
  return null;
733
769
  } catch {
734
770
  return null;
735
- } finally {
736
- if (fd !== null) {
737
- try { closeSync(fd); } catch { /* best-effort */ }
738
- }
739
771
  }
740
772
  }
741
773
 
@@ -743,10 +775,158 @@ function readStatusFromTranscriptTail(transcriptPath) {
743
775
  // main
744
776
  // ---------------------------------------------------------------------------
745
777
 
778
+ // ---------------------------------------------------------------------------
779
+ // orphan-reaper trigger (#1432 B4)
780
+ // ---------------------------------------------------------------------------
781
+ //
782
+ // DUPLICATED VERBATIM between the two trigger hooks —
783
+ // hooks/post-tool-batch-wave-signal.mjs <-> hooks/on-stop.mjs. Deliberate, with
784
+ // a named revisit trigger (BV-004): they are the only two trigger points the
785
+ // PRD declares, and a shared `hooks/_lib/reaper-trigger.mjs` would be a third
786
+ // file in the hook import graph. SIZE, measured 2026-09-22 by diffing the two
787
+ // blocks: 138 identical lines — this note said "~40 lines of glue" until then,
788
+ // which is what made the duplicate look cheaper than it is. Extract it the
789
+ // moment a THIRD hook needs the trigger, or the moment the copies must differ.
790
+
791
+ /**
792
+ * Filesystem path of the scan CLI, spawned as a PLAIN argv call.
793
+ *
794
+ * It used to be a `file://` URL handed to `node --input-type=module -e
795
+ * <program>`, because `scripts/lib/orphan-reaper.mjs` had no entry guard. It
796
+ * has one now (`parseReaperCliArgs` + the `isMainModule` tail), so the child's
797
+ * contract lives in that module instead of as source text duplicated here —
798
+ * and nothing this hook builds is a program any more: every variable part is
799
+ * an argv value, which cannot become code whatever the checkout path contains.
800
+ *
801
+ * `fileURLToPath`, not `new URL(...).pathname`: the latter leaves a
802
+ * percent-encoded path for any checkout directory containing a space.
803
+ */
804
+ const ORPHAN_REAPER_SCRIPT = fileURLToPath(
805
+ new URL('../scripts/lib/orphan-reaper.mjs', import.meta.url),
806
+ );
807
+
808
+ /**
809
+ * Read the `reaper:` block from the project's Session Config host file.
810
+ * Sync + inline, mirroring `hooks/loop-guard.mjs` `loadConfig()` — a hot hook
811
+ * path must not import the full config orchestrator. A missing or unreadable
812
+ * file yields the parser defaults, i.e. DISABLED.
813
+ *
814
+ * @param {string} projectDir
815
+ * @returns {ReturnType<typeof _parseReaper>}
816
+ */
817
+ function loadReaperConfig(projectDir) {
818
+ for (const name of ['CLAUDE.md', 'AGENTS.md']) {
819
+ try {
820
+ return _parseReaper(readFileSync(path.join(projectDir, name), 'utf8'));
821
+ } catch {
822
+ // missing or unreadable — try the next candidate
823
+ }
824
+ }
825
+ return _parseReaper('');
826
+ }
827
+
828
+ /**
829
+ * Trigger the orphan scan, throttled and NON-BLOCKING (PRD FA4).
830
+ *
831
+ * The hook itself does exactly two pieces of I/O — one config read and one
832
+ * `stat` of the throttle marker — and then hands the work to a DETACHED,
833
+ * unref'd child. The scan is never run inline: one `ps` round-trip out of Node
834
+ * was measured at ~47 ms over 287 KB of output, which alone would blow the
835
+ * 50 ms `reaper.max-hook-latency-ms` budget this hook has to stay inside.
836
+ *
837
+ * The marker is stamped BEFORE the spawn, so a spawn that fails still consumes
838
+ * the throttle window — otherwise a broken spawn would be retried on every
839
+ * single tool batch.
840
+ *
841
+ * `reaper.max-hook-latency-ms` is the ceiling on the work above — measured here
842
+ * with `performance.now()` and reported as ONE stderr WARN line when exceeded.
843
+ * A WARN and not an event: this fires from a `PostToolBatch`-class hook, and a
844
+ * per-fire telemetry record is exactly the always-on signal
845
+ * `.claude/rules/host-resources.md` HR-101 calls a broken instrument. The
846
+ * measurement covers preparation only — the scan itself runs detached, which is
847
+ * the whole reason the budget can be held.
848
+ *
849
+ * Never throws: any failure degrades silently (PRD FA4 "lautlos degradieren").
850
+ *
851
+ * @param {object} [opts]
852
+ * @param {string} [opts.projectDir] Repo root; defaults to `getProjectDir()`.
853
+ * @param {number} [opts.now] Injected clock (ms).
854
+ * @param {Function} [opts.spawnFn] Injected `spawn` (tests).
855
+ * @param {Function} [opts.statFn] Injected `statSync` (tests).
856
+ * @param {Function} [opts.writeFn] Injected marker writer (tests).
857
+ * @param {() => number} [opts.clockFn] Injected monotonic clock for the latency
858
+ * budget (tests); defaults to `performance.now`.
859
+ * @returns {Promise<{spawned: boolean, reason: string}>}
860
+ */
861
+ export async function maybeTriggerOrphanScan({
862
+ projectDir,
863
+ now,
864
+ spawnFn = spawn,
865
+ statFn,
866
+ writeFn,
867
+ clockFn = () => performance.now(),
868
+ } = {}) {
869
+ const startedAt = clockFn();
870
+ /** One WARN line when the preparation overran `reaper.max-hook-latency-ms`. */
871
+ const checkLatency = (budgetMs) => {
872
+ const elapsed = clockFn() - startedAt;
873
+ if (Number.isFinite(budgetMs) && budgetMs > 0 && elapsed > budgetMs) {
874
+ process.stderr.write(
875
+ `orphan-reaper trigger: hook latency ${elapsed.toFixed(1)} ms exceeded `
876
+ + `reaper.max-hook-latency-ms (${budgetMs} ms)\n`,
877
+ );
878
+ }
879
+ };
880
+ try {
881
+ const root = typeof projectDir === 'string' && projectDir ? projectDir : getProjectDir();
882
+ const cfg = loadReaperConfig(root);
883
+ // Cheapest gate first: disabled means no stat, no spawn, no module load.
884
+ if (cfg.enabled !== true) return { spawned: false, reason: 'disabled' };
885
+
886
+ const reaper = await import('../scripts/lib/orphan-reaper.mjs');
887
+ const markerPath = reaper.scanMarkerPath(root);
888
+ const nowMs = typeof now === 'number' ? now : Date.now();
889
+
890
+ if (!reaper.shouldScanNow(markerPath, nowMs, cfg['min-scan-interval-seconds'], { statFn })) {
891
+ checkLatency(cfg['max-hook-latency-ms']);
892
+ return { spawned: false, reason: 'throttled' };
893
+ }
894
+ reaper.touchScanMarker(markerPath, { writeFn });
895
+
896
+ const child = spawnFn(
897
+ process.execPath,
898
+ [
899
+ ORPHAN_REAPER_SCRIPT,
900
+ '--repo-root', root,
901
+ '--mode', cfg.mode,
902
+ '--min-age-seconds', String(cfg['min-age-seconds']),
903
+ '--kill-grace-ms', String(cfg['kill-grace-ms']),
904
+ '--verify-wait-ms', String(cfg['verify-wait-ms']),
905
+ '--false-alarm-window', String(cfg['false-alarm-window']),
906
+ ],
907
+ { detached: true, stdio: 'ignore' },
908
+ );
909
+ if (child && typeof child.unref === 'function') child.unref();
910
+ checkLatency(cfg['max-hook-latency-ms']);
911
+ return { spawned: true, reason: 'spawned' };
912
+ } catch {
913
+ return { spawned: false, reason: 'error' };
914
+ }
915
+ }
916
+
746
917
  async function main() {
747
918
  const input = await readStdinJson();
748
919
  const eventType = discriminate(input);
749
920
 
921
+ // Orphan-reaper trigger (#1432 B4) — SubagentStop is the PRD's second trigger
922
+ // point: it lands exactly when gate children have just finished and their PIDs
923
+ // are still known. Placed BEFORE the branch dispatch (unlike the PostToolBatch
924
+ // hook, which triggers last) because both branches below may throw — a
925
+ // `handleStop` failure must not silently disarm the watchdog. The cost is one
926
+ // config read plus one stat, bounded by `reaper.max-hook-latency-ms` (50 ms),
927
+ // and the scan itself runs in a detached child.
928
+ await maybeTriggerOrphanScan();
929
+
750
930
  if (eventType === 'subagent_stop') {
751
931
  const additionalContext = await handleSubagentStop(input);
752
932
  // v2.1.163+: emit hookSpecificOutput for SubagentStop path.
@@ -818,6 +998,14 @@ function buildTerminalSequenceJson(platform) {
818
998
  }
819
999
 
820
1000
  // Exit 0 always — informational hook must never block Claude.
821
- main()
822
- .catch(() => {})
823
- .finally(() => process.exit(0));
1001
+ // Entry guard (#1298 P7): run only as the node script every harness execs
1002
+ // (`sh run-node.sh <this file>`); a bare `import()` must not run the Stop
1003
+ // handler against the live repo.
1004
+ if (isMainModule(import.meta.url)) {
1005
+ // #1393: the profile gate sits INSIDE the entry guard — at module top level
1006
+ // its `process.exit(0)` exited every process that merely imported this hook.
1007
+ if (!shouldRunHook('on-stop')) process.exit(0);
1008
+ main()
1009
+ .catch(() => {})
1010
+ .finally(() => process.exit(0));
1011
+ }
@@ -34,8 +34,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
34
34
  import path from 'node:path';
35
35
 
36
36
  import { shouldRunHook } from './_lib/profile-gate.mjs';
37
- // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
38
- if (!shouldRunHook('operator-steer')) process.exit(0);
37
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
39
38
 
40
39
  import { getProjectDir } from '../scripts/lib/platform.mjs';
41
40
 
@@ -61,4 +60,13 @@ async function main() {
61
60
  }
62
61
 
63
62
  // Exit 0 always — informational hook must never block Claude.
64
- main().catch(() => {}).finally(() => process.exit(0));
63
+ // Entry guard (#1393): run only when this file IS the script node was invoked
64
+ // with — every harness path execs it (`sh run-node.sh <this file>`). A bare
65
+ // `import()` (a probe, a test, a curious agent) must neither run main() nor
66
+ // tear the importing process down. The profile gate sits INSIDE the guard for
67
+ // that second reason: at module top level its `process.exit(0)` exited every
68
+ // process that merely imported this hook.
69
+ if (isMainModule(import.meta.url)) {
70
+ if (!shouldRunHook('operator-steer')) process.exit(0);
71
+ main().catch(() => {}).finally(() => process.exit(0));
72
+ }
@@ -68,12 +68,13 @@ import {
68
68
  } from '../scripts/lib/issue-budget.mjs';
69
69
 
70
70
  import { shouldRunHook } from './_lib/profile-gate.mjs';
71
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
71
72
  import { existsSync } from 'node:fs';
72
73
  import path from 'node:path';
73
74
 
74
75
  // Opt-out per session via SO_DISABLED_HOOKS=post-bash-issue-budget-refund; the
75
- // "minimal"/"off" profiles disable it like every other non-core hook.
76
- if (!shouldRunHook('post-bash-issue-budget-refund')) process.exit(0);
76
+ // "minimal"/"off" profiles disable it like every other non-core hook — the gate
77
+ // itself runs inside the entry guard at the bottom of this file (#1393).
77
78
 
78
79
  // ---------------------------------------------------------------------------
79
80
  // Helpers
@@ -367,9 +368,18 @@ async function main() {
367
368
  }
368
369
 
369
370
  // Top-level error handler — fail open, same posture as the sibling hooks.
370
- main().catch((e) => {
371
- process.stderr.write(
372
- `⚠ post-bash-issue-budget-refund: internal error ${e?.message || e}\n`,
373
- );
374
- process.exit(0);
375
- });
371
+ // Entry guard (#1393): run only when this file IS the script node was invoked
372
+ // with — every harness path execs it (`sh run-node.sh <this file>`). A bare
373
+ // `import()` (a probe, a test, a curious agent) must neither run main() nor
374
+ // tear the importing process down. The profile gate sits INSIDE the guard for
375
+ // that second reason: at module top level its `process.exit(0)` exited every
376
+ // process that merely imported this hook.
377
+ if (isMainModule(import.meta.url)) {
378
+ if (!shouldRunHook('post-bash-issue-budget-refund')) process.exit(0);
379
+ main().catch((e) => {
380
+ process.stderr.write(
381
+ `⚠ post-bash-issue-budget-refund: internal error — ${e?.message || e}\n`,
382
+ );
383
+ process.exit(0);
384
+ });
385
+ }
@@ -143,8 +143,6 @@
143
143
  */
144
144
 
145
145
  import { shouldRunHook } from './_lib/profile-gate.mjs';
146
- // Exit 0 immediately (silent no-op) when disabled via profile/env (#211).
147
- if (!shouldRunHook('post-bash-write-verify')) process.exit(0);
148
146
 
149
147
  import path from 'node:path';
150
148
  import os from 'node:os';
@@ -1120,6 +1118,9 @@ async function main() {
1120
1118
  // string compare then reads false and silently no-ops the whole scope-detector
1121
1119
  // under a symlinked `$CLAUDE_PLUGIN_ROOT`. realpath'ing both sides also survives
1122
1120
  // `--preserve-symlinks` (where import.meta.url stays symlinked instead).
1121
+ // BV-004 ceiling: kept inline — hooks avoid importing scripts/lib on the hot
1122
+ // path; the canonical predicate is scripts/lib/is-main-module.mjs. Revisit if the
1123
+ // hook-import-set (hooks/_lib/hook-import-set.json) ever admits scripts/lib here.
1123
1124
  function invokedAsScript() {
1124
1125
  const entry = process.argv[1];
1125
1126
  if (!entry) return false;
@@ -1133,6 +1134,9 @@ function invokedAsScript() {
1133
1134
  }
1134
1135
  const isMain = invokedAsScript();
1135
1136
  if (isMain) {
1137
+ // #1393: the profile gate sits INSIDE the entry guard — at module top level
1138
+ // its `process.exit(0)` exited every process that merely imported this hook.
1139
+ if (!shouldRunHook('post-bash-write-verify')) process.exit(0);
1136
1140
  // Advisory hook: never block, never surface an error to the tool call.
1137
1141
  main().catch(() => {}).finally(() => process.exit(0));
1138
1142
  }
@@ -37,8 +37,7 @@
37
37
  */
38
38
 
39
39
  import { shouldRunHook } from './_lib/profile-gate.mjs';
40
- // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
41
- if (!shouldRunHook('post-edit-import-probe')) process.exit(0);
40
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
42
41
 
43
42
  import path from 'node:path';
44
43
  import { readFileSync, existsSync } from 'node:fs';
@@ -335,10 +334,19 @@ async function main() {
335
334
  // Exit 0 always — a hook that guards against hook breakage must never block.
336
335
  // The catch stays silent towards the TOOL CALL but not towards the operator:
337
336
  // a probe whose own breakage is invisible is a probe nobody can trust.
338
- main()
339
- .catch((err) => {
340
- try {
341
- process.stderr.write(`post-edit-import-probe: ${err?.message ?? String(err)}\n`);
342
- } catch { /* stderr gone nothing left to report to */ }
343
- })
344
- .finally(() => process.exit(0));
337
+ // Entry guard (#1393): run only when this file IS the script node was invoked
338
+ // with — every harness path execs it (`sh run-node.sh <this file>`). A bare
339
+ // `import()` (a probe, a test, a curious agent) must neither run main() nor
340
+ // tear the importing process down. The profile gate sits INSIDE the guard for
341
+ // that second reason: at module top level its `process.exit(0)` exited every
342
+ // process that merely imported this hook.
343
+ if (isMainModule(import.meta.url)) {
344
+ if (!shouldRunHook('post-edit-import-probe')) process.exit(0);
345
+ main()
346
+ .catch((err) => {
347
+ try {
348
+ process.stderr.write(`post-edit-import-probe: ${err?.message ?? String(err)}\n`);
349
+ } catch { /* stderr gone — nothing left to report to */ }
350
+ })
351
+ .finally(() => process.exit(0));
352
+ }
@@ -32,8 +32,7 @@ import { spawnSync } from 'node:child_process';
32
32
  import { performance } from 'node:perf_hooks';
33
33
 
34
34
  import { shouldRunHook } from './_lib/profile-gate.mjs';
35
- // #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
36
- if (!shouldRunHook('post-edit-validate')) process.exit(0);
35
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
37
36
 
38
37
  import { readStdin } from '../scripts/lib/io.mjs';
39
38
  import { resolveProjectDir } from '../scripts/lib/platform.mjs';
@@ -220,6 +219,15 @@ async function main() {
220
219
  }
221
220
 
222
221
  // REQ-01: top-level catch — PostToolUse must never block; always exit 0
223
- main().catch(() => {
224
- process.exit(0);
225
- });
222
+ // Entry guard (#1393): run only when this file IS the script node was invoked
223
+ // with — every harness path execs it (`sh run-node.sh <this file>`). A bare
224
+ // `import()` (a probe, a test, a curious agent) must neither run main() nor
225
+ // tear the importing process down. The profile gate sits INSIDE the guard for
226
+ // that second reason: at module top level its `process.exit(0)` exited every
227
+ // process that merely imported this hook.
228
+ if (isMainModule(import.meta.url)) {
229
+ if (!shouldRunHook('post-edit-validate')) process.exit(0);
230
+ main().catch(() => {
231
+ process.exit(0);
232
+ });
233
+ }
@@ -22,7 +22,24 @@
22
22
  * `findViolations()` — the whole matcher (claim patterns, negative-context
23
23
  * guards, evidence proximity, normalisation, dedup) lives there so it can
24
24
  * be measured against a claim corpus without spawning this hook. It
25
- * returns DEDUPLICATED `{claim, normalized, occurrences}` records.
25
+ * returns DEDUPLICATED `{claim, normalized, occurrences, kind, detail?}`
26
+ * records in THREE classes: `distributional` (#567/#908 — "4 of 4
27
+ * callers"), `gate-verdict` (w4-1 — "STATUS: done" / "alles grün" with
28
+ * no run receipt anywhere in the report), and `claim-mismatch` (#1385 R1
29
+ * — a claimed test count that NO observed vitest run carries). `kind`
30
+ * reaches both the ledger record and the WARN; its ABSENCE on a record
31
+ * means `distributional`, since no pre-w4-1 record could be anything
32
+ * else.
33
+ * 5b. The `claim-mismatch` class needs the OTHER side of the transcript, so
34
+ * `readTranscriptObservations()` reads the vitest summaries out of the
35
+ * `tool_result` blocks in the same window and passes them in. It closes
36
+ * an INVERSION: `RUN_RECEIPT_RE` is report-wide and matches
37
+ * `\d+\s+passed`, so before R1 an INVENTED number was its own receipt
38
+ * while an honest report with no number was flagged (measured
39
+ * 2026-09-19: `'STATUS: done\nTests pass: 5129 passed / 0 failed.'` → no
40
+ * violation; `'STATUS: done\nAlles grün.'` → two). No observations in
41
+ * the window ⇒ no `claim-mismatch` finding: evidence ABSENCE is
42
+ * `gate-verdict`'s job.
26
43
  * 6. Attribute the claim: `agent` + `agent_source` (`payload`|`meta`|`none`)
27
44
  * + `agent_description`, and — on `none` — the sorted stdin `payload_keys`
28
45
  * the harness DID send, so a gap is diagnosable from the ledger.
@@ -62,8 +79,7 @@
62
79
  */
63
80
 
64
81
  import { shouldRunHook } from './_lib/profile-gate.mjs';
65
- // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
66
- if (!shouldRunHook('post-subagent-discovery-validator')) process.exit(0);
82
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
67
83
 
68
84
  import { createHash } from 'node:crypto';
69
85
  import { promises as fs } from 'node:fs';
@@ -71,7 +87,11 @@ import { tmpdir } from 'node:os';
71
87
  import path from 'node:path';
72
88
 
73
89
  import { resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
74
- import { findViolations, readTranscriptTail } from './_lib/subagent-transcript.mjs';
90
+ import {
91
+ findViolations,
92
+ readTranscriptObservations,
93
+ readTranscriptTail,
94
+ } from './_lib/subagent-transcript.mjs';
75
95
  import { appendJsonl } from '../scripts/lib/common.mjs';
76
96
  import { eventsFilePath } from '../scripts/lib/events.mjs';
77
97
  import { getProjectDir } from '../scripts/lib/platform.mjs';
@@ -393,7 +413,11 @@ async function main() {
393
413
  if (sidecar === null) return;
394
414
 
395
415
  const text = await readTranscriptTail(sidecar.transcript);
396
- const { violations, undatedVerified } = findViolations(text);
416
+ // The `tool_result` side of the SAME window (#1385 R1) — what the agent
417
+ // actually RAN, against which its claimed test counts are compared. An
418
+ // empty list disables the `claim-mismatch` class; it never creates one.
419
+ const observations = await readTranscriptObservations(sidecar.transcript);
420
+ const { violations, undatedVerified } = findViolations(text, { observations });
397
421
  if (violations.length === 0) return;
398
422
 
399
423
  const attribution = await resolveAgentAttribution(input, sidecar.meta);
@@ -443,7 +467,15 @@ async function main() {
443
467
  }
444
468
  if (seen) continue;
445
469
  }
446
- await appendJsonl(filePath, {
470
+ // AWAITED **and** CAUGHT. A telemetry write added to a guard silently
471
+ // disarms it unless both hold: without the await the process can exit
472
+ // before the append lands, and without the catch one failed write aborts
473
+ // the remaining violations AND the stderr WARN below — the coordinator's
474
+ // only copy of the finding. This hook never denies, so the failure is a
475
+ // lost finding rather than a lost block; it is still a loss, and a full
476
+ // disk must not be able to silence the scanner.
477
+ try {
478
+ await appendJsonl(filePath, {
447
479
  event: 'discovery_validator_violation',
448
480
  timestamp: new Date().toISOString(),
449
481
  agent,
@@ -457,8 +489,25 @@ async function main() {
457
489
  ...(sessionId !== null ? { session_id: sessionId } : {}),
458
490
  claim_text: violation.claim,
459
491
  occurrences: violation.occurrences,
460
- });
461
- written++;
492
+ // Claim CLASS (w4-1, #1385 R1): `distributional` (the #567/#908
493
+ // patterns), `gate-verdict` (a done/green assertion with no run
494
+ // receipt), or `claim-mismatch` (a test count no observed run carries).
495
+ // A new field rather than an overloaded old one — a ledger reader
496
+ // triaging 3,000 records must be able to separate them without
497
+ // re-parsing `claim_text`, and every pre-w4-1 record is
498
+ // `distributional` by construction (the field's absence means that).
499
+ kind: violation.kind ?? 'distributional',
500
+ // `claim-mismatch` only: `{mismatch, claimed, observed, observed_n}`.
501
+ // Numbers, never the command text that produced them (precedent
502
+ // `8f15f77b` — `command_hash` instead of the raw command); `observed`
503
+ // is capped at the last 3 runs by findViolations().
504
+ ...(violation.detail ?? {}),
505
+ });
506
+ written++;
507
+ } catch {
508
+ // Write failed — keep going. The WARN below still reaches the
509
+ // coordinator, which is the channel PSA-006 actually depends on.
510
+ }
462
511
  }
463
512
 
464
513
  // Advisory only (#908 item 4) — never promoted to a violation in v1.
@@ -473,10 +522,39 @@ async function main() {
473
522
  ? ` ${violations.length - written} already recorded earlier in this session.`
474
523
  : '';
475
524
 
525
+ // THREE claim CLASSES, counted separately in the WARN (w4-1, #1385 R1):
526
+ // they fail different rules and want different corrections — a
527
+ // distributional claim needs its grep transcript, a gate verdict needs a
528
+ // run receipt, and a count mismatch needs the run repeated or the number
529
+ // corrected. One aggregate count would leave the coordinator guessing which.
530
+ const gateCount = violations.filter((v) => v.kind === 'gate-verdict').length;
531
+ const mismatchCount = violations.filter((v) => v.kind === 'claim-mismatch').length;
532
+ const distCount = violations.length - gateCount - mismatchCount;
533
+ const classNote = gateCount === 0
534
+ ? ''
535
+ : ` ${gateCount} of them ${gateCount === 1 ? 'is a' : 'are'} DONE/GATE verdict(s) with no run ` +
536
+ `receipt (no test count, no exit code) anywhere in the report; ` +
537
+ `${distCount} repo-state/distributional.`;
538
+
539
+ // The correction this class asks for is named, because it is NOT the one
540
+ // the other two ask for: no amount of added grep evidence fixes a number
541
+ // that contradicts every run in the window.
542
+ const mismatchNote = mismatchCount === 0
543
+ ? ''
544
+ : ` ${mismatchCount} ${mismatchCount === 1 ? 'is a' : 'are'} COUNT MISMATCH: ` +
545
+ violations
546
+ .filter((v) => v.kind === 'claim-mismatch')
547
+ .map((v) => {
548
+ const observed = (v.detail?.observed ?? []).map((o) => `${o.passed} passed`).join(', ');
549
+ return `claimed ${v.detail?.claimed?.passed} passed, observed ${observed || 'none'}`;
550
+ })
551
+ .join('; ') +
552
+ ` — re-run the suite or correct the number.`;
553
+
476
554
  const warnText =
477
- `⚠ PSA-006: ${violations.length} distinct repo-state/distributional claim(s) from agent ` +
478
- `"${agent}" (source: ${attribution.source}) lack an adjacent measurement transcript ` +
479
- `(grep/rg/find/git/wc/jq/ls/node/npm) (non-blocking).` +
555
+ `⚠ PSA-006: ${violations.length} distinct claim(s) from agent ` +
556
+ `"${agent}" (source: ${attribution.source}) lack measurement evidence ` +
557
+ `(grep/rg/find/git/wc/jq/ls/node/npm) (non-blocking).${classNote}${mismatchNote}` +
480
558
  `${suppressedNote}${undatedNote} ` +
481
559
  `See .claude/rules/parallel-sessions.md § PSA-006.`;
482
560
  process.stderr.write(warnText + '\n');
@@ -526,5 +604,12 @@ async function main() {
526
604
  }));
527
605
  }
528
606
 
529
- // Exit 0 always informational hook must never block Claude (#567 v1).
530
- main().catch(() => {}).finally(() => process.exit(0));
607
+ // Entry guard (#1393): run only as the node script the harness execs — a bare
608
+ // `import()` must run no handler and must not exit the importing process.
609
+ if (isMainModule(import.meta.url)) {
610
+ // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
611
+ if (!shouldRunHook('post-subagent-discovery-validator')) process.exit(0);
612
+
613
+ // Exit 0 always — informational hook must never block Claude (#567 v1).
614
+ main().catch(() => {}).finally(() => process.exit(0));
615
+ }