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
@@ -66,18 +66,21 @@
66
66
  * hooks.json wiring is managed separately (W3-C4 scope).
67
67
  */
68
68
 
69
- import { execFileSync } from 'node:child_process';
69
+ import { execFileSync, spawn } from 'node:child_process';
70
+ import { readFileSync } from 'node:fs';
70
71
  import { readFile } from 'node:fs/promises';
71
72
  import path from 'node:path';
73
+ import { performance } from 'node:perf_hooks';
74
+ import { fileURLToPath } from 'node:url';
72
75
 
73
76
  import { shouldRunHook } from './_lib/profile-gate.mjs';
74
- // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
75
- if (!shouldRunHook('post-tool-batch-wave-signal')) process.exit(0);
77
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
76
78
 
77
79
  import { getProjectDir } from '../scripts/lib/platform.mjs';
78
80
  import { emitEvent } from '../scripts/lib/events.mjs';
79
81
  import { findScopeFile } from '../scripts/lib/scope-gate.mjs';
80
82
  import { atomicMutateJson } from './_lib/atomic-json.mjs';
83
+ import { _parseReaper } from '../scripts/lib/config/reaper.mjs';
81
84
 
82
85
  // ---------------------------------------------------------------------------
83
86
  // Helpers
@@ -205,7 +208,7 @@ async function ownsSessionFile(input, sessionFile) {
205
208
  * previous hard-coded `.claude/` path made this hook's whole wave-lifecycle
206
209
  * fallback structurally dead on Codex CLI, Cursor and pi: the manifest exists,
207
210
  * it just is not under `.claude/`, so every batch read 0 and no
208
- * `orchestrator.wave.started`/`completed` event was ever emitted there.
211
+ * wave-lifecycle event was ever emitted there.
209
212
  *
210
213
  * @param {string} projectDir
211
214
  * @returns {Promise<number>}
@@ -305,6 +308,145 @@ function countFilesChangedSince(projectDir, sha) {
305
308
  }
306
309
  }
307
310
 
311
+ // ---------------------------------------------------------------------------
312
+ // orphan-reaper trigger (#1432 B4)
313
+ // ---------------------------------------------------------------------------
314
+ //
315
+ // DUPLICATED VERBATIM between the two trigger hooks —
316
+ // hooks/post-tool-batch-wave-signal.mjs <-> hooks/on-stop.mjs. Deliberate, with
317
+ // a named revisit trigger (BV-004): they are the only two trigger points the
318
+ // PRD declares, and a shared `hooks/_lib/reaper-trigger.mjs` would be a third
319
+ // file in the hook import graph. SIZE, measured 2026-09-22 by diffing the two
320
+ // blocks: 138 identical lines — this note said "~40 lines of glue" until then,
321
+ // which is what made the duplicate look cheaper than it is. Extract it the
322
+ // moment a THIRD hook needs the trigger, or the moment the copies must differ.
323
+
324
+ /**
325
+ * Filesystem path of the scan CLI, spawned as a PLAIN argv call.
326
+ *
327
+ * It used to be a `file://` URL handed to `node --input-type=module -e
328
+ * <program>`, because `scripts/lib/orphan-reaper.mjs` had no entry guard. It
329
+ * has one now (`parseReaperCliArgs` + the `isMainModule` tail), so the child's
330
+ * contract lives in that module instead of as source text duplicated here —
331
+ * and nothing this hook builds is a program any more: every variable part is
332
+ * an argv value, which cannot become code whatever the checkout path contains.
333
+ *
334
+ * `fileURLToPath`, not `new URL(...).pathname`: the latter leaves a
335
+ * percent-encoded path for any checkout directory containing a space.
336
+ */
337
+ const ORPHAN_REAPER_SCRIPT = fileURLToPath(
338
+ new URL('../scripts/lib/orphan-reaper.mjs', import.meta.url),
339
+ );
340
+
341
+ /**
342
+ * Read the `reaper:` block from the project's Session Config host file.
343
+ * Sync + inline, mirroring `hooks/loop-guard.mjs` `loadConfig()` — a hot hook
344
+ * path must not import the full config orchestrator. A missing or unreadable
345
+ * file yields the parser defaults, i.e. DISABLED.
346
+ *
347
+ * @param {string} projectDir
348
+ * @returns {ReturnType<typeof _parseReaper>}
349
+ */
350
+ function loadReaperConfig(projectDir) {
351
+ for (const name of ['CLAUDE.md', 'AGENTS.md']) {
352
+ try {
353
+ return _parseReaper(readFileSync(path.join(projectDir, name), 'utf8'));
354
+ } catch {
355
+ // missing or unreadable — try the next candidate
356
+ }
357
+ }
358
+ return _parseReaper('');
359
+ }
360
+
361
+ /**
362
+ * Trigger the orphan scan, throttled and NON-BLOCKING (PRD FA4).
363
+ *
364
+ * The hook itself does exactly two pieces of I/O — one config read and one
365
+ * `stat` of the throttle marker — and then hands the work to a DETACHED,
366
+ * unref'd child. The scan is never run inline: one `ps` round-trip out of Node
367
+ * was measured at ~47 ms over 287 KB of output, which alone would blow the
368
+ * 50 ms `reaper.max-hook-latency-ms` budget this hook has to stay inside.
369
+ *
370
+ * The marker is stamped BEFORE the spawn, so a spawn that fails still consumes
371
+ * the throttle window — otherwise a broken spawn would be retried on every
372
+ * single tool batch.
373
+ *
374
+ * `reaper.max-hook-latency-ms` is the ceiling on the work above — measured here
375
+ * with `performance.now()` and reported as ONE stderr WARN line when exceeded.
376
+ * A WARN and not an event: this fires from a `PostToolBatch`-class hook, and a
377
+ * per-fire telemetry record is exactly the always-on signal
378
+ * `.claude/rules/host-resources.md` HR-101 calls a broken instrument. The
379
+ * measurement covers preparation only — the scan itself runs detached, which is
380
+ * the whole reason the budget can be held.
381
+ *
382
+ * Never throws: any failure degrades silently (PRD FA4 "lautlos degradieren").
383
+ *
384
+ * @param {object} [opts]
385
+ * @param {string} [opts.projectDir] Repo root; defaults to `getProjectDir()`.
386
+ * @param {number} [opts.now] Injected clock (ms).
387
+ * @param {Function} [opts.spawnFn] Injected `spawn` (tests).
388
+ * @param {Function} [opts.statFn] Injected `statSync` (tests).
389
+ * @param {Function} [opts.writeFn] Injected marker writer (tests).
390
+ * @param {() => number} [opts.clockFn] Injected monotonic clock for the latency
391
+ * budget (tests); defaults to `performance.now`.
392
+ * @returns {Promise<{spawned: boolean, reason: string}>}
393
+ */
394
+ export async function maybeTriggerOrphanScan({
395
+ projectDir,
396
+ now,
397
+ spawnFn = spawn,
398
+ statFn,
399
+ writeFn,
400
+ clockFn = () => performance.now(),
401
+ } = {}) {
402
+ const startedAt = clockFn();
403
+ /** One WARN line when the preparation overran `reaper.max-hook-latency-ms`. */
404
+ const checkLatency = (budgetMs) => {
405
+ const elapsed = clockFn() - startedAt;
406
+ if (Number.isFinite(budgetMs) && budgetMs > 0 && elapsed > budgetMs) {
407
+ process.stderr.write(
408
+ `orphan-reaper trigger: hook latency ${elapsed.toFixed(1)} ms exceeded `
409
+ + `reaper.max-hook-latency-ms (${budgetMs} ms)\n`,
410
+ );
411
+ }
412
+ };
413
+ try {
414
+ const root = typeof projectDir === 'string' && projectDir ? projectDir : getProjectDir();
415
+ const cfg = loadReaperConfig(root);
416
+ // Cheapest gate first: disabled means no stat, no spawn, no module load.
417
+ if (cfg.enabled !== true) return { spawned: false, reason: 'disabled' };
418
+
419
+ const reaper = await import('../scripts/lib/orphan-reaper.mjs');
420
+ const markerPath = reaper.scanMarkerPath(root);
421
+ const nowMs = typeof now === 'number' ? now : Date.now();
422
+
423
+ if (!reaper.shouldScanNow(markerPath, nowMs, cfg['min-scan-interval-seconds'], { statFn })) {
424
+ checkLatency(cfg['max-hook-latency-ms']);
425
+ return { spawned: false, reason: 'throttled' };
426
+ }
427
+ reaper.touchScanMarker(markerPath, { writeFn });
428
+
429
+ const child = spawnFn(
430
+ process.execPath,
431
+ [
432
+ ORPHAN_REAPER_SCRIPT,
433
+ '--repo-root', root,
434
+ '--mode', cfg.mode,
435
+ '--min-age-seconds', String(cfg['min-age-seconds']),
436
+ '--kill-grace-ms', String(cfg['kill-grace-ms']),
437
+ '--verify-wait-ms', String(cfg['verify-wait-ms']),
438
+ '--false-alarm-window', String(cfg['false-alarm-window']),
439
+ ],
440
+ { detached: true, stdio: 'ignore' },
441
+ );
442
+ if (child && typeof child.unref === 'function') child.unref();
443
+ checkLatency(cfg['max-hook-latency-ms']);
444
+ return { spawned: true, reason: 'spawned' };
445
+ } catch {
446
+ return { spawned: false, reason: 'error' };
447
+ }
448
+ }
449
+
308
450
  // ---------------------------------------------------------------------------
309
451
  // Main
310
452
  // ---------------------------------------------------------------------------
@@ -378,9 +520,13 @@ async function main() {
378
520
  }
379
521
  } catch { /* best effort — hook must remain non-blocking */ }
380
522
 
381
- // Emit wave-lifecycle events via the canonical stream when the orchestrator
382
- // populates wave_signal ('wave-start' | 'wave-complete'). Mechanical seam —
383
- // see docs/events-schema.md. Best-effort; never blocks the hook.
523
+ // Emit the wave-completion event via the canonical stream when the
524
+ // orchestrator populates wave_signal 'wave-complete'. Mechanical seam — see
525
+ // docs/events-schema.md. Best-effort; never blocks the hook. A 'wave-start'
526
+ // signal is still ACCEPTED — it keeps suppressing the fallback below, as
527
+ // before — but emits nothing: the `started` sibling event was removed
528
+ // 2026-09-19, having no reader once the convergence monitor stopped
529
+ // admitting it.
384
530
  //
385
531
  // Live path today: the `wave_signal === null` branch below provides the
386
532
  // mechanical wave-lifecycle fallback (#612). It fires live by diffing
@@ -388,19 +534,14 @@ async function main() {
388
534
  // payload injection required. This explicit-signal branch remains as the
389
535
  // preferred path for whenever the harness DOES inject `wave_signal` into the
390
536
  // batch payload (it takes precedence over the fallback when present).
391
- if (waveSignal === 'wave-start' || waveSignal === 'wave-complete') {
537
+ if (waveSignal === 'wave-complete') {
392
538
  try {
393
- await emitEvent(
394
- waveSignal === 'wave-start'
395
- ? 'orchestrator.wave.started'
396
- : 'orchestrator.wave.completed',
397
- {
398
- ...(waveNumber !== null ? { wave_number: waveNumber } : {}),
399
- ...(nextWaveRole !== null ? { next_wave_role: nextWaveRole } : {}),
400
- ...(batchId !== null ? { batch_id: batchId } : {}),
401
- ...(batchSize !== null ? { batch_size: batchSize } : {}),
402
- },
403
- );
539
+ await emitEvent('orchestrator.wave.completed', {
540
+ ...(waveNumber !== null ? { wave_number: waveNumber } : {}),
541
+ ...(nextWaveRole !== null ? { next_wave_role: nextWaveRole } : {}),
542
+ ...(batchId !== null ? { batch_id: batchId } : {}),
543
+ ...(batchSize !== null ? { batch_size: batchSize } : {}),
544
+ });
404
545
  // #1193 review F2 — this branch is a SECOND emitter of
405
546
  // `wave.completed`, so it must feed the same high-water mark the other
406
547
  // two read; otherwise SessionEnd (and the fallback below) would close the
@@ -413,8 +554,7 @@ async function main() {
413
554
  // and walked the marker BACKWARDS to 4. Both keys go through maxWave().
414
555
  // W4c Q1-MED — and neither is written into a PEER's record.
415
556
  if (
416
- waveSignal === 'wave-complete'
417
- && typeof waveNumber === 'number'
557
+ typeof waveNumber === 'number'
418
558
  && waveNumber > 0
419
559
  && await ownsSessionFile(input, sessionFile)
420
560
  ) {
@@ -498,24 +638,31 @@ async function main() {
498
638
  ...(batchSize !== null ? { batch_size: batchSize } : {}),
499
639
  });
500
640
  }
501
- // Open the new wave.
502
- await emitEvent('orchestrator.wave.started', {
503
- wave_number: wave,
504
- ...(nextWaveRole !== null ? { next_wave_role: nextWaveRole } : {}),
505
- ...(batchId !== null ? { batch_id: batchId } : {}),
506
- ...(batchSize !== null ? { batch_size: batchSize } : {}),
507
- });
508
- // Persist the high-water marks so the next batch does not re-emit,
509
- // and so SessionEnd can tell whether the prior wave was already
510
- // closed here (#1193). `last_wave_completed` is only advanced when a
511
- // `completed` was actually emitted above (lastWave > 0).
641
+ // Open the new wave. There is no event for this (the `started`
642
+ // sibling was removed 2026-09-19); opening IS the high-water marks
643
+ // persisted below, so the next batch does not re-close the prior
644
+ // wave, and SessionEnd can tell whether it was already closed here
645
+ // (#1193). `last_wave_completed` is only advanced when a `completed`
646
+ // was actually emitted above (lastWave > 0).
512
647
  // W4c Q1-MED — never stamp these into a PEER session's record. The
513
- // events above are still emitted (they carry THIS session's
648
+ // event above is still emitted (it carries THIS session's
514
649
  // attribution via emitEvent); only the shared-file claim is withheld.
515
650
  // NAMED CEILING: for a non-owning session the marks therefore never
516
- // advance, so this branch re-emits started{wave} once per batch until
517
- // that session's own SessionStart takes over the file. Revisit if the
518
- // ledger shows repeated started{N} for one wave in a shared checkout.
651
+ // advance, so each of its batches re-emits completed{lastWave} while
652
+ // the file's `last_wave` is ahead of its `last_wave_completed`, until
653
+ // that session's own SessionStart takes over the file. The revisit
654
+ // trigger (repeated emissions for one wave) HAS FIRED — measured
655
+ // 2026-09-19 @ 8f6ac022 over this repo's
656
+ // `.orchestrator/metrics/events.jsonl{.1,}`, timestamps >= 2026-09-12,
657
+ // grouped by (session_id, wave_number) with jq:
658
+ // started — 1936 emissions / 48 groups, 19 duplicated, max 274
659
+ // completed — 650 emissions / 28 groups, 2 duplicated, max 579
660
+ // The `started` half is GONE (#1202 §11): its only reader,
661
+ // `scripts/lib/convergence-monitor.mjs`, no longer admits it — it
662
+ // carried no measurement and, sharing a tail tick with completed{N},
663
+ // masked the (N-1, N) shrinking_diff pair. The `completed` half is
664
+ // OPEN: that event has readers, so the fix is a mark a non-owning
665
+ // session can keep for itself, not a removal.
519
666
  if (await ownsSessionFile(input, sessionFile)) {
520
667
  // #980 — the OPEN half: stamp the sha this new wave starts from.
521
668
  // Written as null (not omitted) when git is unreadable, so the
@@ -559,7 +706,22 @@ async function main() {
559
706
  },
560
707
  }));
561
708
  }
709
+
710
+ // Orphan-reaper trigger (#1432 B4). LAST, after this hook's own contract is
711
+ // fulfilled — the stdout envelope above must never wait on the watchdog.
712
+ // Awaited (not fired and forgotten) because the entry guard calls
713
+ // `process.exit(0)` in a `finally`: an un-awaited spawn would race the exit.
714
+ // The await is bounded by design — one config read, one stat, one detached
715
+ // spawn, all measured under `reaper.max-hook-latency-ms` (50 ms).
716
+ await maybeTriggerOrphanScan();
562
717
  }
563
718
 
564
- // Exit 0 always informational hook must never block Claude.
565
- main().catch(() => {}).finally(() => process.exit(0));
719
+ // Entry guard (#1393): run only as the node script the harness execs — a bare
720
+ // `import()` must run no handler and must not exit the importing process.
721
+ if (isMainModule(import.meta.url)) {
722
+ // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
723
+ if (!shouldRunHook('post-tool-batch-wave-signal')) process.exit(0);
724
+
725
+ // Exit 0 always — informational hook must never block Claude.
726
+ main().catch(() => {}).finally(() => process.exit(0));
727
+ }
@@ -27,8 +27,7 @@
27
27
  import path from 'node:path';
28
28
 
29
29
  import { shouldRunHook } from './_lib/profile-gate.mjs';
30
- // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
31
- if (!shouldRunHook('post-tool-failure-corrective-context')) process.exit(0);
30
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
32
31
 
33
32
  import { getProjectDir } from '../scripts/lib/platform.mjs';
34
33
  import { atomicMutateJson } from './_lib/atomic-json.mjs';
@@ -206,7 +205,7 @@ async function main() {
206
205
  // Strip control chars (newlines, ANSI escapes) before forwarding to additionalContext —
207
206
  // SEC-016 (Log Injection): tool errors may contain attacker-controlled bytes.
208
207
  const safeSummary = errorSummary
209
- ? errorSummary.replace(/[\r\n]/g, ' ').split('').join(' ').slice(0, 120)
208
+ ? errorSummary.replace(/[\r\n]/g, ' ').split('\x1b').join(' ').slice(0, 120)
210
209
  : '';
211
210
  const summaryLabel = safeSummary ? ` Error: ${safeSummary}` : '';
212
211
  const additionalContext =
@@ -221,5 +220,12 @@ async function main() {
221
220
  }));
222
221
  }
223
222
 
224
- // Exit 0 always informational hook must never block Claude.
225
- main().catch(() => {}).finally(() => process.exit(0));
223
+ // Entry guard (#1393): run only as the node script the harness execs — a bare
224
+ // `import()` must run no handler and must not exit the importing process.
225
+ if (isMainModule(import.meta.url)) {
226
+ // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
227
+ if (!shouldRunHook('post-tool-failure-corrective-context')) process.exit(0);
228
+
229
+ // Exit 0 always — informational hook must never block Claude.
230
+ main().catch(() => {}).finally(() => process.exit(0));
231
+ }
@@ -24,8 +24,7 @@
24
24
  */
25
25
 
26
26
  import { shouldRunHook } from './_lib/profile-gate.mjs';
27
- // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
28
- if (!shouldRunHook('frontend-slop-hook')) process.exit(0);
27
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
29
28
 
30
29
  import path from 'node:path';
31
30
 
@@ -180,5 +179,12 @@ async function main() {
180
179
  }));
181
180
  }
182
181
 
183
- // Exit 0 always informational hook must never block Claude (#684).
184
- main().catch(() => {}).finally(() => process.exit(0));
182
+ // Entry guard (#1393): run only as the node script the harness execs — a bare
183
+ // `import()` must run no handler and must not exit the importing process.
184
+ if (isMainModule(import.meta.url)) {
185
+ // Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
186
+ if (!shouldRunHook('frontend-slop-hook')) process.exit(0);
187
+
188
+ // Exit 0 always — informational hook must never block Claude (#684).
189
+ main().catch(() => {}).finally(() => process.exit(0));
190
+ }
@@ -659,6 +659,20 @@ export function decide(input, lib) {
659
659
  }
660
660
  const tail = omitted > 0 ? `\n(+${omitted} weitere Befund(e) nicht gezeigt)` : '';
661
661
 
662
+ // Die Optionsgrenze wird aus der Schwelle GERENDERT, nie danebengeschrieben.
663
+ // Ausgeschriebene Zahlwörter ("zwei bis vier") standen hier bis 2026-09-18
664
+ // und waren für jeden Zahlen-grep unsichtbar: verschiebt sich
665
+ // `THRESHOLDS.K6.optionsMax`, blockt der Hook nach der neuen Zahl und nennt
666
+ // dem Operator die alte. Fehlt die Schwelle (defektes Schema-Modul), entfällt
667
+ // der Satz — eine Lücke ist besser als eine erfundene Zahl.
668
+ const k6Limits = lib?.schema?.THRESHOLDS?.K6;
669
+ const limitSentence =
670
+ Number.isInteger(k6Limits?.optionsMin) && Number.isInteger(k6Limits?.optionsMax)
671
+ ? `Das ist keine Stilfrage: das Tool selbst nimmt nur ${k6Limits.optionsMin} bis ` +
672
+ `${k6Limits.optionsMax} Optionen je Frage an, und mehr als ${k6Limits.optionsMax} kann ` +
673
+ 'niemand gegeneinander abwägen.\n\n'
674
+ : '';
675
+
662
676
  // The FIRST LINE names the concrete break, because `emitDeny` derives the
663
677
  // operator-visible headline from it. A general preamble first would put a
664
678
  // sentence that is identical for every deny in front of the human, and the
@@ -666,8 +680,7 @@ export function decide(input, lib) {
666
680
  const reason =
667
681
  `AskUserQuestion blockiert: harte Grenze ${groups.map(([id]) => id).join(' + ')} gerissen — ` +
668
682
  'so erreicht die Frage den Operator nicht.\n\n' +
669
- 'Das ist keine Stilfrage: das Tool selbst nimmt nur zwei bis vier Optionen je Frage an, ' +
670
- 'und mehr als vier kann niemand gegeneinander abwägen.\n\n' +
683
+ limitSentence +
671
684
  `${sections.join('\n\n')}${tail}`;
672
685
 
673
686
  const suggestion =
@@ -778,6 +791,9 @@ async function main() {
778
791
  // under a symlinked plugin install) while `import.meta.url` is realpath-resolved
779
792
  // by node's default loader, so BOTH sides are realpath'd.
780
793
  // ---------------------------------------------------------------------------
794
+ // BV-004 ceiling: kept inline — hooks avoid importing scripts/lib on the hot
795
+ // path; the canonical predicate is scripts/lib/is-main-module.mjs. Revisit if the
796
+ // hook-import-set (hooks/_lib/hook-import-set.json) ever admits scripts/lib here.
781
797
  function invokedAsScript() {
782
798
  const entry = process.argv[1];
783
799
  if (!entry) return false;
@@ -29,6 +29,12 @@
29
29
  * generic pattern path — its `pattern: ">"` would FP-match nearly
30
30
  * every redirect. Unresolved targets (variable/substitution) warn
31
31
  * on stderr (fail-visible) and never block.
32
+ * rule.type === 'path-delete' (#1401): decided by the DELETE/RENAME
33
+ * operand via firstDeniedDeleteTarget (same target-denylist globs,
34
+ * same compiler), likewise shadowing the generic pattern path. The
35
+ * redirect class answers "what does this command WRITE"; nothing
36
+ * answered "what does it REMOVE", so `rm -f <ledger>` was allowed
37
+ * while `> <ledger>` was denied — 53,896 lines of telemetry lost.
32
38
  * G6 no match → exit 0
33
39
  *
34
40
  * Telemetry (Epic #803 process-safety dimension): best-effort
@@ -46,8 +52,7 @@ import crypto from 'node:crypto';
46
52
  import { pathToFileURL } from 'node:url';
47
53
 
48
54
  import { shouldRunHook } from './_lib/profile-gate.mjs';
49
- // #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
50
- if (!shouldRunHook('pre-bash-destructive-guard')) process.exit(0);
55
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
51
56
 
52
57
  // ---------------------------------------------------------------------------
53
58
  // #992 — late-bound repo dependencies
@@ -179,6 +184,8 @@ async function bootstrap() {
179
184
  'redirectSpanEnd',
180
185
  'resolveSegmentVerb',
181
186
  'splitChainSegments',
187
+ 'extractDeleteTargets',
188
+ 'firstDeniedDeleteTarget',
182
189
  ],
183
190
  },
184
191
  },
@@ -877,6 +884,61 @@ async function main() {
877
884
  continue; // unreachable (blockCommand never returns) — kept for clarity
878
885
  }
879
886
 
887
+ // #1401 — path-delete rules are decided by the DELETE/RENAME operand via
888
+ // firstDeniedDeleteTarget, never by the generic pattern path below (same
889
+ // full-shadow contract as redirect-truncate above: this rule's `pattern` is
890
+ // a human-readable label, and routing it through commandMatchesBlocked
891
+ // would match either nothing or every `rm`).
892
+ //
893
+ // Why a second surface at all: the redirect rule answers "what does this
894
+ // command WRITE". Nothing answered "what does it REMOVE" — so `rm -f
895
+ // .orchestrator/metrics/events.jsonl.1` was ALLOWED while `> ` on the same
896
+ // file was DENIED (measured 2026-09-19, #1401: 53,896 ledger lines lost).
897
+ if (rule.type === 'path-delete') {
898
+ const collected = blocker.extractDeleteTargets(command);
899
+ const cutOff = collected.filter(
900
+ (e) => e.unresolved && (e.reason === 'budget-exhausted' || e.reason === 'depth-exceeded')
901
+ );
902
+ if (cutOff.length > 0) {
903
+ // A recursion cap that drops payloads silently is a bypass, not a cap
904
+ // (#988 T2) — surface it on the VISIBLE channel (#995).
905
+ //
906
+ // Deliberately NARROWER than the redirect branch above, which also
907
+ // surfaces `variable/substitution` operands: `rm -rf "$WORK"` is the
908
+ // ORDINARY spelling of a temp cleanup, so notifying on it would fire on
909
+ // a large share of benign commands and train the operator to ignore the
910
+ // channel (host-resources.md § HR-101 — a signal may only warn if it is
911
+ // rare). A cut-off marker, by contrast, needs 32+ payload segments or
912
+ // 3 levels of nesting: pathological by construction. Unresolved
913
+ // operands stay fail-visible on stderr below; neither class ever blocks.
914
+ const reasons = [...new Set(cutOff.map((e) => e.reason))].join(', ');
915
+ const msg = `pre-bash-destructive-guard: delete-target traversal cut off (${reasons}) — not matched (fail-visible)`;
916
+ process.stderr.write(`⚠ ${msg}\n`);
917
+ notices.push(msg);
918
+ }
919
+ const unresolvedOperands = collected.filter(
920
+ (e) => e.unresolved && e.reason === 'variable/substitution'
921
+ );
922
+ if (unresolvedOperands.length > 0) {
923
+ process.stderr.write(
924
+ `ℹ pre-bash-destructive-guard: ${unresolvedOperands.length} delete target(s) carry a variable/substitution — not matched (rule: ${id})\n`
925
+ );
926
+ }
927
+ const hit = blocker.firstDeniedDeleteTarget(rule, collected, { repoRoot: projectDir });
928
+ if (!hit) continue;
929
+ if (severity !== 'block') {
930
+ const msg = `pre-bash-destructive-guard: protected delete target matched (rule: ${id}) — ${rationale}`;
931
+ process.stderr.write(`⚠ ${msg}\n`);
932
+ notices.push(msg);
933
+ continue;
934
+ }
935
+ // Reason stays short (stdout-budget): verb + target, never the command.
936
+ // The policy `rationale` is long by design (it carries the measurement
937
+ // trail); blockCommand puts only its first line in the systemMessage.
938
+ await blockCommand(`${hit.verb} ${hit.target}`, id, rationale, command, sessionId);
939
+ continue; // unreachable (blockCommand never returns) — kept for clarity
940
+ }
941
+
880
942
  // The rm-rf-destructive rule also fires for recursive+force rm flag variants
881
943
  // the literal "rm -rf" pattern misses (`rm -r -f`, `rm -fr`) — #641 gap closure.
882
944
  const matched = id === 'rm-rf-destructive'
@@ -984,10 +1046,19 @@ try {
984
1046
  process.exit(0); // fail-open, but no longer fail-silent
985
1047
  }
986
1048
 
987
- // Top-level error handler never let exit 1 leak
988
- main().catch((e) => {
989
- process.stderr.write(
990
- `⚠ pre-bash-destructive-guard: internal error ${e?.message || e}\n`
991
- );
992
- process.exit(0); // fail-open on internal errors to avoid blocking legitimate work
993
- });
1049
+ // Entry guard (#1393): run only as the node script the harness execs — a bare
1050
+ // `import()` must run no handler and must not exit the importing process.
1051
+ // Placed strictly AFTER all decision logic: it gates whether main() is invoked,
1052
+ // never what main() decides, so no deny path can be short-circuited by it.
1053
+ if (isMainModule(import.meta.url)) {
1054
+ // #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
1055
+ if (!shouldRunHook('pre-bash-destructive-guard')) process.exit(0);
1056
+
1057
+ // Top-level error handler — never let exit 1 leak
1058
+ main().catch((e) => {
1059
+ process.stderr.write(
1060
+ `⚠ pre-bash-destructive-guard: internal error — ${e?.message || e}\n`
1061
+ );
1062
+ process.exit(0); // fail-open on internal errors to avoid blocking legitimate work
1063
+ });
1064
+ }