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
@@ -743,6 +743,31 @@ export { splitSegments as splitChainSegments };
743
743
 
744
744
  const ENV_ASSIGN_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
745
745
 
746
+ /**
747
+ * Verbs that name a WRITE TARGET as a bare operand — no redirect operator, no
748
+ * wrapper flag (#1366). A sibling of WRAPPER_UNWRAP's `fileArgFlags`: that table
749
+ * answers "does this WRAPPER flag's operand name a file the wrapper writes?",
750
+ * this one "does this VERB's operand name a file the verb writes?".
751
+ *
752
+ * Spec fields:
753
+ * - prefix: the operand's literal prefix (`of=`), matched on a token AFTER
754
+ * the resolved verb.
755
+ * - mode: the default redirect mode the write is equivalent to.
756
+ * - appendRe: operands that turn the write into an APPEND. `dd` truncates by
757
+ * default (`conv=notrunc` / `oflag=append` do not), so reporting
758
+ * `truncate` unconditionally would over-report a rule class that
759
+ * deliberately permits `>>`.
760
+ *
761
+ * Deliberately narrow — one entry, measured. `tee`/`sponge` and in-process
762
+ * writers (`node -e fs.writeFileSync`) are NOT here: they are not redirect-shaped
763
+ * and belong to a different rule class (see the `redirect-harness-memory`
764
+ * rationale in .orchestrator/policy/blocked-commands.json). REVISIT TRIGGER: a
765
+ * second `<verb> <prefix>file` writer showing up in a transcript census.
766
+ */
767
+ const VERB_FILE_OPERANDS = new Map([
768
+ ['dd', { prefix: 'of=', mode: 'truncate', appendRe: /^(?:conv=.*\bnotrunc\b|oflag=.*\bappend\b)/ }],
769
+ ]);
770
+
746
771
  /**
747
772
  * Transparent process-wrapper table for verb resolution (#982), keyed by
748
773
  * basename. Each spec describes how to skip a wrapper's own options so the
@@ -946,15 +971,37 @@ function resolveCore(segment, unknownFlagsTakeValue) {
946
971
  if (spec.splitString
947
972
  && (text === '-S' || text === '--split-string'
948
973
  || text.startsWith('--split-string=') || text.startsWith('-S'))) {
974
+ // env(1) APPENDS the remaining argv to the split string: `env -S bash
975
+ // -c '…'` runs `bash -c '…'`, not `bash` (#1366). Collecting only the
976
+ // operand made the trailing `-c '…'` execute unseen — the segment then
977
+ // resolved to `verb: 'echo x > CLAUDE.md'` (the quoted payload read as
978
+ // the verb) and neither the match surface nor the redirect surface ever
979
+ // saw it. So the payload is the operand PLUS every remaining token, and
980
+ // the wrapper CONSUMES the segment: nothing is left to resolve as a
981
+ // verb, which is why this now yields `verb: null, index: -1`.
982
+ let operand = null;
949
983
  if (text.startsWith('--split-string=')) {
950
- payloads.push(text.slice('--split-string='.length));
984
+ operand = text.slice('--split-string='.length);
985
+ i++;
951
986
  } else if (text !== '-S' && text !== '--split-string') {
952
- payloads.push(text.slice(2)); // attached form: -S'string'
953
- } else if (i + 1 < segment.length) {
954
- payloads.push(segment[i + 1].text);
987
+ operand = text.slice(2); // attached form: -S'string'
955
988
  i++;
989
+ } else if (i + 1 < segment.length) {
990
+ operand = segment[i + 1].text;
991
+ i += 2;
992
+ } else {
993
+ i++; // `env -S` with no operand at all — nothing to collect
994
+ }
995
+ if (operand !== null) {
996
+ // Raw token texts, joined by one space. Quoting is NOT re-applied:
997
+ // the tokenizer already stripped it, and re-inventing it would be a
998
+ // second quoting grammar. The direction of that loss is safe — an
999
+ // over-wide payload can only make MORE text visible to the matcher
1000
+ // (fail-closed), never hide a redirect operator.
1001
+ const rest = segment.slice(i).map((t) => t.text);
1002
+ payloads.push(rest.length > 0 ? [operand, ...rest].join(' ') : operand);
1003
+ i = segment.length;
956
1004
  }
957
- i++;
958
1005
  continue;
959
1006
  }
960
1007
  if (spec.shellFlags && spec.shellFlags.has(text)) { sawShellFlag = true; i++; continue; }
@@ -1189,15 +1236,65 @@ function dedupedSegmentPayloads(segment, resolved) {
1189
1236
  if (resolved.verb && DASH_C_SHELLS.has(resolved.verb)) {
1190
1237
  for (const p of dashCPayloads(segment, resolved.index)) payloadSet.add(p);
1191
1238
  }
1239
+ addArgJoinedPayload(segment, resolved.verb, resolved.index, payloadSet);
1192
1240
  if (resolved.alt) {
1193
1241
  for (const p of resolved.alt.payloads) payloadSet.add(p);
1194
1242
  if (resolved.alt.verb && DASH_C_SHELLS.has(resolved.alt.verb)) {
1195
1243
  for (const p of dashCPayloads(segment, resolved.alt.index)) payloadSet.add(p);
1196
1244
  }
1245
+ addArgJoinedPayload(segment, resolved.alt.verb, resolved.alt.index, payloadSet);
1197
1246
  }
1198
1247
  return [...payloadSet];
1199
1248
  }
1200
1249
 
1250
+ /**
1251
+ * Interpreters that JOIN their remaining arguments with a space and execute the
1252
+ * result — no `-c` flag, no wrapper option to key on (#1366).
1253
+ *
1254
+ * `eval` is the whole set today. It was already in SHELL_EXEC_INTERPRETERS, but
1255
+ * that set only powers the QUOTED-TOKEN match in matchSegments — it never fed
1256
+ * the payload traversal, so `eval 'echo x > CLAUDE.md'` carried a redirect no
1257
+ * redirect rule ever saw (`resolveSegmentVerb` reports `payloads: []` for it).
1258
+ *
1259
+ * NAMED CEILING (BV-004): only a LITERAL payload is reachable. `eval "$CMD"` and
1260
+ * `eval $(…)` do not exist as command text at hook time and stay in the existing
1261
+ * `unresolved` class (#641). REVISIT TRIGGER: a second arg-joining interpreter
1262
+ * showing up in a transcript census — extend the set, never special-case `eval`.
1263
+ */
1264
+ const ARG_JOINING_INTERPRETERS = new Set(['eval']);
1265
+
1266
+ /**
1267
+ * Add the joined post-verb argument text as a recursion payload when `verb` is
1268
+ * an arg-joining interpreter. No-op otherwise — every other caller shape keeps
1269
+ * the pre-#1366 payload set byte-identical.
1270
+ *
1271
+ * Quoting is not re-applied for the same reason as the `env -S` tail (see
1272
+ * resolveCore): the tokenizer stripped it, and a wider payload can only make
1273
+ * more text visible to the matcher, never hide an operator.
1274
+ *
1275
+ * @param {Array<{ text: string, quoted: boolean }>} segment
1276
+ * @param {string|null} verb
1277
+ * @param {number} index — the verb's token index (`-1` when unresolved)
1278
+ * @param {Set<string>} payloadSet — mutated in place
1279
+ */
1280
+ function addArgJoinedPayload(segment, verb, index, payloadSet) {
1281
+ if (!verb || index < 0 || !ARG_JOINING_INTERPRETERS.has(verb)) return;
1282
+ const words = [];
1283
+ for (let i = index + 1; i < segment.length; i++) {
1284
+ if (segment[i].redirect) {
1285
+ // A redirect belongs to the SEGMENT (`eval 'x' > f` redirects eval's own
1286
+ // stdout), never to the joined argv — the segment scan already reported
1287
+ // it, and re-reporting it through the payload would duplicate the entry.
1288
+ const end = redirectSpanEnd(segment, i);
1289
+ if (end > i) i = end;
1290
+ continue;
1291
+ }
1292
+ words.push(segment[i].text);
1293
+ }
1294
+ const joined = words.join(' ').trim();
1295
+ if (joined.length > 0) payloadSet.add(joined);
1296
+ }
1297
+
1201
1298
  /**
1202
1299
  * Match a blocked-pattern regex against tokenized chain segments — the shared
1203
1300
  * core of commandMatchesBlocked, recursion-capable for `-c` payloads (#982).
@@ -1347,6 +1444,37 @@ function collectRedirectTargets(segments, out, depth, budget) {
1347
1444
  out.push({ target: wa.value, mode: 'truncate', fd: null });
1348
1445
  }
1349
1446
 
1447
+ // A VERB can name its write target as a bare operand, with no redirect
1448
+ // operator and no wrapper flag: `dd of=CLAUDE.md` truncates CLAUDE.md while
1449
+ // `> CLAUDE.md` is denied (measured ALLOW pre-#1366 against the real
1450
+ // policy). Keyed on the RESOLVED verb, so `sudo dd of=X` is reached through
1451
+ // the existing wrapper unwrap; both readings contribute, as above.
1452
+ const seenVerbOperands = new Set();
1453
+ for (const reading of resolved.alt ? [resolved, resolved.alt] : [resolved]) {
1454
+ const spec = reading.verb ? VERB_FILE_OPERANDS.get(reading.verb) : null;
1455
+ if (!spec || reading.index < 0) continue;
1456
+ let operand = null;
1457
+ let append = false;
1458
+ for (let k = reading.index + 1; k < segment.length; k++) {
1459
+ const text = segment[k].text;
1460
+ if (segment[k].redirect) continue;
1461
+ if (text.startsWith(spec.prefix)) operand = text.slice(spec.prefix.length);
1462
+ else if (spec.appendRe?.test(text)) append = true;
1463
+ }
1464
+ if (operand === null || operand.length === 0) continue;
1465
+ const mode = append ? 'append' : spec.mode;
1466
+ const key = `${operand}:${mode}`;
1467
+ if (seenVerbOperands.has(key)) continue;
1468
+ seenVerbOperands.add(key);
1469
+ if (/[$`]/.test(operand)) {
1470
+ // Same fail-visible rule as a redirect operand (#983) — never guess at a
1471
+ // variable or a command substitution, never silently drop it either.
1472
+ out.push({ target: null, mode, fd: null, unresolved: true });
1473
+ continue;
1474
+ }
1475
+ out.push({ target: operand, mode, fd: null });
1476
+ }
1477
+
1350
1478
  // Both readings of an ambiguous unknown flag contribute payloads (#1000),
1351
1479
  // UNIONED and de-duped (HIGH-2). Walking parse A only let
1352
1480
  // `env -Q x bash -c 'echo pwned > CLAUDE.md'` — which resolves to the
@@ -1684,6 +1812,32 @@ export function redirectRuleMatches(rule, command, opts = {}) {
1684
1812
  // an existing repo-relative rule, and a repo-relative entry still cannot
1685
1813
  // escape the root. An unexpandable `~` (no usable `home`) matches NOTHING —
1686
1814
  // fail-closed for the new class, since an unexpanded `~` names no file.
1815
+ const compiled = compileTargetDenylist(denylist, home);
1816
+ if (compiled.relRegexes.length === 0 && compiled.absRegexes.length === 0) return false;
1817
+
1818
+ for (const entry of extractRedirectTargets(command)) {
1819
+ if (entry.unresolved) continue;
1820
+ if (!modes.has(entry.mode)) continue;
1821
+ if (targetMatchesDenylist(entry.target, compiled, repoRoot, home)) return true;
1822
+ }
1823
+ return false;
1824
+ }
1825
+
1826
+ /**
1827
+ * Compile a rule's `target-denylist` into the two glob classes described in
1828
+ * `redirectRuleMatches` (#1362): repo-relative entries and home-anchored (`~/…`)
1829
+ * entries. Extracted so a SECOND rule class can reuse the exact same compilation
1830
+ * instead of growing a third copy of the glob logic (#1401) — the file already
1831
+ * carries that warning at `findMatchedRedirectEntry` in the consuming hook.
1832
+ *
1833
+ * An unexpandable `~` (no usable `home`) contributes NOTHING — fail-closed for
1834
+ * the home-anchored class, since an unexpanded `~` names no file.
1835
+ *
1836
+ * @param {string[]} denylist
1837
+ * @param {string|undefined} home
1838
+ * @returns {{ relRegexes: RegExp[], absRegexes: RegExp[] }}
1839
+ */
1840
+ function compileTargetDenylist(denylist, home) {
1687
1841
  const relRegexes = [];
1688
1842
  const absRegexes = [];
1689
1843
  for (const pattern of denylist) {
@@ -1696,38 +1850,48 @@ export function redirectRuleMatches(rule, command, opts = {}) {
1696
1850
  relRegexes.push(redirectGlobToRegExp(text));
1697
1851
  }
1698
1852
  }
1699
- if (relRegexes.length === 0 && absRegexes.length === 0) return false;
1700
-
1701
- for (const entry of extractRedirectTargets(command)) {
1702
- if (entry.unresolved) continue;
1703
- if (!modes.has(entry.mode)) continue;
1853
+ return { relRegexes, absRegexes };
1854
+ }
1704
1855
 
1705
- if (absRegexes.length > 0) {
1706
- // A RELATIVE target is repo-root-relative here, exactly as the repo-relative
1707
- // class already treats it (#994 R1) — otherwise `> ../../.claude/projects/
1708
- // <enc>/memory/MEMORY.md` from the repo root names the very file the `~`
1709
- // spelling blocks, and only the spelling would be denied (measured
1710
- // 2026-09-13: relative `>`/`>>` ALLOW vs tilde/absolute BLOCK on one and
1711
- // the same resolved path). This resolves TARGETS, never ENTRIES: a
1712
- // repo-relative denylist entry keeps going through
1713
- // `repoRelativeRedirectTarget` below and still cannot reach out of the root.
1714
- const expandedTarget = expandLeadingHome(entry.target, home);
1715
- const absTarget = path.isAbsolute(expandedTarget)
1716
- ? expandedTarget
1717
- : repoRoot && path.isAbsolute(repoRoot)
1718
- ? path.resolve(repoRoot, expandedTarget)
1719
- : null;
1720
- if (absTarget !== null) {
1721
- const folded = foldAbsoluteTarget(absTarget);
1722
- if (absRegexes.some((re) => re.test(folded))) return true;
1723
- }
1856
+ /**
1857
+ * Test ONE resolved target text against a compiled denylist.
1858
+ *
1859
+ * A RELATIVE target is repo-root-relative in BOTH classes, exactly as the
1860
+ * repo-relative class already treats it (#994 R1) — otherwise
1861
+ * `../../.claude/projects/<enc>/memory/MEMORY.md` from the repo root names the
1862
+ * very file the `~` spelling blocks, and only the spelling would be denied
1863
+ * (measured 2026-09-13: relative `>`/`>>` ALLOW vs tilde/absolute BLOCK on one
1864
+ * and the same resolved path). This resolves TARGETS, never ENTRIES: a
1865
+ * repo-relative denylist entry still goes through `repoRelativeRedirectTarget`
1866
+ * and still cannot reach out of the root.
1867
+ *
1868
+ * @param {string} rawTarget — resolved target text (quotes already stripped)
1869
+ * @param {{ relRegexes: RegExp[], absRegexes: RegExp[] }} compiled
1870
+ * @param {string|null} repoRoot
1871
+ * @param {string|undefined} home
1872
+ * @returns {boolean}
1873
+ */
1874
+ function targetMatchesDenylist(rawTarget, compiled, repoRoot, home) {
1875
+ const { relRegexes, absRegexes } = compiled;
1876
+
1877
+ if (absRegexes.length > 0) {
1878
+ const expandedTarget = expandLeadingHome(rawTarget, home);
1879
+ const absTarget = path.isAbsolute(expandedTarget)
1880
+ ? expandedTarget
1881
+ : repoRoot && path.isAbsolute(repoRoot)
1882
+ ? path.resolve(repoRoot, expandedTarget)
1883
+ : null;
1884
+ if (absTarget !== null) {
1885
+ const folded = foldAbsoluteTarget(absTarget);
1886
+ if (absRegexes.some((re) => re.test(folded))) return true;
1724
1887
  }
1888
+ }
1725
1889
 
1726
- if (relRegexes.length > 0) {
1727
- const target = repoRelativeRedirectTarget(entry.target, repoRoot, home);
1728
- if (target !== null && relRegexes.some((re) => re.test(target))) return true;
1729
- }
1890
+ if (relRegexes.length > 0) {
1891
+ const target = repoRelativeRedirectTarget(rawTarget, repoRoot, home);
1892
+ if (target !== null && relRegexes.some((re) => re.test(target))) return true;
1730
1893
  }
1894
+
1731
1895
  return false;
1732
1896
  }
1733
1897
 
@@ -1747,6 +1911,217 @@ function foldAbsoluteTarget(abs) {
1747
1911
  );
1748
1912
  }
1749
1913
 
1914
+ /**
1915
+ * Verbs that REMOVE or RELOCATE a file named as a bare operand (#1401).
1916
+ *
1917
+ * `mv` is here for BOTH of its operand roles: the SOURCE disappears from its
1918
+ * path, and the DESTINATION is overwritten when it already exists — either one
1919
+ * loses the file that was there. So every operand of these verbs is a candidate,
1920
+ * with no source/destination distinction to get wrong.
1921
+ *
1922
+ * NAMED CEILING (BV-004) — measured 2026-09-19 against `splitChainSegments` +
1923
+ * `resolveSegmentVerb`. COVERED, because the verb resolves through the existing
1924
+ * machinery: bare `rm`/`mv`/`unlink`; `env X=1 rm …`, `sudo rm …`, `timeout 5 rm
1925
+ * …`, `nohup`/`nice`/`stdbuf`/`command`/`exec` (WRAPPER_UNWRAP); every chain
1926
+ * position (`;`, `&&`, `||`, `|`, `&`, newline) and compound-statement head
1927
+ * (`do rm …`, `{ rm …; }` — COMMAND_POSITION_KEYWORDS); and the literal payload
1928
+ * of `bash -c '…'` / `env -S '…'` / `eval '…'` via the shared payload recursion.
1929
+ *
1930
+ * NOT covered, deliberately, because the deleted path is not lexically an
1931
+ * operand of a resolvable verb:
1932
+ * - `find … -delete` / `find … -exec rm {} \;` — the verb is `find`; its path
1933
+ * operand is a SEARCH ROOT, and treating a search root as a delete target
1934
+ * would also flag a pure `find … -print` over the same directory.
1935
+ * - `… | xargs rm` — the targets arrive on stdin and do not exist as command
1936
+ * text at hook time. `xargs` is deliberately NOT in WRAPPER_UNWRAP (see
1937
+ * there: unwrapping it could only LOOSEN the guard).
1938
+ * - `git rm <path>` — the verb is `git`; a git-subverb grammar is a separate
1939
+ * table. PSA-007 already bans `git rm` for dispatched subagents.
1940
+ * - in-process deletion (`node -e 'fs.unlinkSync(…)'`) — not redirect- or
1941
+ * operand-shaped; same class boundary the `redirect-harness-memory`
1942
+ * rationale draws for `tee -a` and `fs.appendFileSync`.
1943
+ * REVISIT TRIGGER: any of those four shapes showing up in a real deletion
1944
+ * incident or a transcript census — extend this set or add the sibling grammar,
1945
+ * never special-case one command.
1946
+ */
1947
+ const PATH_DESTRUCTIVE_VERBS = new Set(['rm', 'mv', 'unlink']);
1948
+
1949
+ /**
1950
+ * Recursive collector behind {@link extractDeleteTargets}. Mirrors
1951
+ * collectRedirectTargets' structure — same segment walk, same dual-reading
1952
+ * union, same depth/budget caps and the same fail-VISIBLE cut-off markers.
1953
+ *
1954
+ * @param {Array<Array<{ text: string, quoted: boolean, redirect?: object }>>} segments
1955
+ * @param {Array<object>} out — accumulator
1956
+ * @param {number} depth
1957
+ * @param {{ remaining: number }} budget
1958
+ */
1959
+ function collectDeleteTargets(segments, out, depth, budget) {
1960
+ for (const segment of segments) {
1961
+ const resolved = resolveSegmentVerb(segment);
1962
+
1963
+ // BOTH readings of an ambiguous unknown WRAPPER flag contribute (#1000),
1964
+ // unioned and never replaced: `env -Q x rm .orchestrator/metrics/events.jsonl`
1965
+ // resolves to the non-destructive verb `x` in parse A and to `rm` in parse B,
1966
+ // while `sudo -n rm <file>` is the mirror case (parse B swallows `rm` as
1967
+ // `-n`'s operand). Picking either reading alone drops one of the two.
1968
+ const readings = resolved.alt ? [resolved, resolved.alt] : [resolved];
1969
+ const seen = new Set();
1970
+
1971
+ for (const reading of readings) {
1972
+ if (!reading.verb || reading.index < 0) continue;
1973
+ if (!PATH_DESTRUCTIVE_VERBS.has(reading.verb)) continue;
1974
+ // A verb that only exists inside a quoted token is inert literal text for
1975
+ // this segment — same rule commandHasRecursiveForceRm applies. An
1976
+ // interpreter's quoted payload is reached through the recursion below
1977
+ // instead, where it is re-tokenized as the command it really is.
1978
+ if (segment[reading.index].quoted) continue;
1979
+
1980
+ let seenDashDash = false;
1981
+ for (let i = reading.index + 1; i < segment.length; i++) {
1982
+ const tok = segment[i];
1983
+ // A redirect operand is a filename the SHELL consumes, not an operand of
1984
+ // the verb — `rm -f x > out.log` deletes `x`, it does not delete
1985
+ // `out.log` (the redirect surface judges that one).
1986
+ if (tok.redirect) { i = redirectSpanEnd(segment, i); continue; }
1987
+ if (!seenDashDash && tok.text === '--') { seenDashDash = true; continue; }
1988
+ // FLAG SKIPPING, and why this is the UNION of both flag readings rather
1989
+ // than a pick (#1000 applied to the VERB's own options): an option table
1990
+ // cannot enumerate every flag `rm`/`mv` may grow, and the two possible
1991
+ // readings of an unknown one are "boolean" (the next word is an operand)
1992
+ // and "value-taking" (the next word is the flag's value). The
1993
+ // value-taking reading is a strict SUBSET of the boolean one here — it
1994
+ // can only HIDE a word, never reveal one — so collecting under the
1995
+ // boolean reading yields exactly the union of both. That is also the
1996
+ // fail-closed direction: `mv -t .orchestrator/metrics/ x` reports the
1997
+ // `-t` operand as a candidate (it IS a destination that can overwrite),
1998
+ // where a value-taking reading would have swallowed it silently.
1999
+ if (!seenDashDash && !tok.quoted && tok.text.startsWith('-') && tok.text !== '-') continue;
2000
+
2001
+ const key = `${reading.verb}\u0000${tok.text}`;
2002
+ if (seen.has(key)) continue;
2003
+ seen.add(key);
2004
+
2005
+ if (/[$`]/.test(tok.text)) {
2006
+ // Variable indirection (`rm -f "$LEDGER"`) or command substitution:
2007
+ // fail-visible, never guess (#983 rule, same direction).
2008
+ out.push({
2009
+ target: null, verb: reading.verb, unresolved: true, reason: 'variable/substitution',
2010
+ });
2011
+ continue;
2012
+ }
2013
+ out.push({ target: tok.text, verb: reading.verb });
2014
+ }
2015
+ }
2016
+
2017
+ // Literal `-c` / `env -S` / `eval` payload recursion — the same deduped
2018
+ // union helper and the same shared budget the match and redirect surfaces
2019
+ // use, so this traversal cannot be starved any differently than those.
2020
+ const payloads = dedupedSegmentPayloads(segment, resolved);
2021
+ if (payloads.length === 0) continue;
2022
+
2023
+ // A cap that drops payloads SILENTLY is a bypass, not a cap (#988 T2) —
2024
+ // emit a marker so the consuming hook can surface it.
2025
+ if (depth >= MAX_PAYLOAD_DEPTH) {
2026
+ out.push({ target: null, verb: null, unresolved: true, reason: 'depth-exceeded' });
2027
+ continue;
2028
+ }
2029
+ for (const payload of payloads) {
2030
+ if (budget.remaining <= 0) {
2031
+ out.push({ target: null, verb: null, unresolved: true, reason: 'budget-exhausted' });
2032
+ break;
2033
+ }
2034
+ budget.remaining -= 1;
2035
+ const subTokens = tokenizeCommand(
2036
+ normalizeShellWhitespaceExpansions(payload, { expandSingleQuoted: true }),
2037
+ );
2038
+ collectDeleteTargets(splitSegments(subTokens), out, depth + 1, budget);
2039
+ }
2040
+ }
2041
+ }
2042
+
2043
+ /**
2044
+ * Extract every path a command names as an operand of a file-destroying verb
2045
+ * (`rm`, `mv`, `unlink` — see {@link PATH_DESTRUCTIVE_VERBS} for the covered and
2046
+ * uncovered shapes), across all chain segments and literal shell payloads (#1401).
2047
+ *
2048
+ * This is the DELETE/RENAME sibling of `extractRedirectTargets`: that one answers
2049
+ * "which file does this command WRITE", this one "which file does it REMOVE or
2050
+ * MOVE". They are separate surfaces because a redirect target and a verb operand
2051
+ * are different grammar positions — `rm -f x > out.log` names one of each, and
2052
+ * the guard's 53,896-line telemetry loss (#1401) happened because only the write
2053
+ * surface was ever consulted.
2054
+ *
2055
+ * Entry shapes:
2056
+ * - `{ target: string, verb: 'rm'|'mv'|'unlink' }` — resolved operand, quotes
2057
+ * stripped by the tokenizer.
2058
+ * - `{ target: null, verb, unresolved: true, reason: 'variable/substitution' }`
2059
+ * — the operand carries a variable or a command substitution. Reported
2060
+ * fail-visible; never a match candidate (#641 FP class).
2061
+ * - `{ target: null, verb: null, unresolved: true,
2062
+ * reason: 'budget-exhausted'|'depth-exceeded' }` — a payload subtree was NOT
2063
+ * traversed because a recursion cap cut it off (#988 T2).
2064
+ *
2065
+ * @param {string} command
2066
+ * @returns {Array<{ target: string|null, verb: string|null, unresolved?: boolean, reason?: string }>}
2067
+ */
2068
+ export function extractDeleteTargets(command) {
2069
+ if (typeof command !== 'string' || command.length === 0) return [];
2070
+ const out = [];
2071
+ const segments = splitSegments(tokenizeCommand(normalizeShellWhitespaceExpansions(command)));
2072
+ collectDeleteTargets(segments, out, 0, { remaining: MAX_PAYLOAD_EVALUATIONS });
2073
+ return out;
2074
+ }
2075
+
2076
+ /**
2077
+ * Return the FIRST collected delete/rename target a `path-delete` policy rule's
2078
+ * `target-denylist` matches, or `null` when none does (#1401).
2079
+ *
2080
+ * Returns the ENTRY, not a boolean, so the consuming hook can name the verb and
2081
+ * the target in its deny reason without re-probing through a boolean matcher —
2082
+ * the workaround `findMatchedRedirectEntry` needs, and which its own docblock
2083
+ * flags as one copy too many.
2084
+ *
2085
+ * Takes pre-collected `entries` rather than the raw command so the hook
2086
+ * tokenizes ONCE (it needs the same array for the unresolved-marker notices).
2087
+ *
2088
+ * PER-TARGET, never per-command: every entry is judged on its own, so a sibling
2089
+ * operand that is NOT on the denylist can never lift the verdict for one that
2090
+ * is. `rm -f .orchestrator/tmp/ok .orchestrator/metrics/events.jsonl` matches on
2091
+ * the second operand — the exemption is the ABSENCE of a denylist entry for
2092
+ * `.orchestrator/tmp/**`, never a whole-command escape clause. That shape is the
2093
+ * #1106 hole (`guard-design.md` § "Widening a matcher without narrowing its
2094
+ * bypass"), where an appended exempt statement lifted the verdict for the whole
2095
+ * command; it is structurally unreachable here.
2096
+ *
2097
+ * Denylist semantics (globs, the two classes, `~` handling, case-folding,
2098
+ * repo-root relativization) are IDENTICAL to `redirectRuleMatches` — both call
2099
+ * `compileTargetDenylist` / `targetMatchesDenylist`, so the two rule classes can
2100
+ * never drift apart on what a pattern means.
2101
+ *
2102
+ * @param {{ 'target-denylist'?: string[] }} rule
2103
+ * @param {Array<{ target: string|null, verb: string|null, unresolved?: boolean }>} entries
2104
+ * @param {{ repoRoot?: string|null, home?: string|undefined }} [opts]
2105
+ * @returns {{ target: string, verb: string }|null}
2106
+ */
2107
+ export function firstDeniedDeleteTarget(rule, entries, opts = {}) {
2108
+ const { repoRoot = null, home = process.env.HOME } = opts;
2109
+ if (!rule || !Array.isArray(entries) || entries.length === 0) return null;
2110
+ const denylist = Array.isArray(rule['target-denylist']) ? rule['target-denylist'] : [];
2111
+ if (denylist.length === 0) return null;
2112
+
2113
+ const compiled = compileTargetDenylist(denylist, home);
2114
+ if (compiled.relRegexes.length === 0 && compiled.absRegexes.length === 0) return null;
2115
+
2116
+ for (const entry of entries) {
2117
+ if (entry.unresolved || typeof entry.target !== 'string') continue;
2118
+ if (targetMatchesDenylist(entry.target, compiled, repoRoot, home)) {
2119
+ return { target: entry.target, verb: entry.verb };
2120
+ }
2121
+ }
2122
+ return null;
2123
+ }
2124
+
1750
2125
  /**
1751
2126
  * Test whether a command string contains a blocked pattern with shell-aware
1752
2127
  * boundaries AND a quoted-payload guard (#641).
@@ -14,6 +14,15 @@ import { preprocessBlockLines } from './block-preprocess.mjs';
14
14
 
15
15
  const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
16
16
 
17
+ /**
18
+ * Default `dialectic.budget-tokens` — the INPUT ceiling of one derivation call
19
+ * (SSOT; `scripts/dialectic-deriver.mjs` `DEFAULT_BUDGET.input` imports it).
20
+ * It is an upper bound the pre-dispatch estimate aborts on, not a spend: card
21
+ * bodies + steering alone estimated ~12k tokens here and ~30k in a consumer
22
+ * repo, so the former default of 8000 aborted real runs (#1380).
23
+ */
24
+ export const DEFAULT_BUDGET_TOKENS = 32000;
25
+
17
26
  /**
18
27
  * Parse the top-level `dialectic:` YAML block from markdown content.
19
28
  * Independent of the `## Session Config` section boundary.
@@ -21,7 +30,7 @@ const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
21
30
  * Defaults:
22
31
  * cadence: 5 (integer ≥ 0 — session count between auto-runs; 0 = disabled)
23
32
  * model: 'haiku'
24
- * budget-tokens: 8000 (integer ≥ 0)
33
+ * budget-tokens: DEFAULT_BUDGET_TOKENS (32000, integer ≥ 0)
25
34
  *
26
35
  * Throws if `model` is present but not one of haiku|sonnet|opus.
27
36
  *
@@ -29,7 +38,7 @@ const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
29
38
  * @returns {{ cadence: number, model: string, "budget-tokens": number }}
30
39
  */
31
40
  export function _parseDialectic(content) {
32
- const defaults = { cadence: 5, model: 'haiku', 'budget-tokens': 8000 };
41
+ const defaults = { cadence: 5, model: 'haiku', 'budget-tokens': DEFAULT_BUDGET_TOKENS };
33
42
 
34
43
  const lines = preprocessBlockLines(content);
35
44
  let inBlock = false;
@@ -49,7 +58,7 @@ export function _parseDialectic(content) {
49
58
 
50
59
  let cadence = 5;
51
60
  let model = 'haiku';
52
- let budgetTokens = 8000;
61
+ let budgetTokens = DEFAULT_BUDGET_TOKENS;
53
62
 
54
63
  for (const rawLine of blockLines) {
55
64
  const clean = rawLine.replace(/\s*#.*$/, '').replace(/\s+$/, '');
@@ -5,6 +5,25 @@ import { preprocessBlockLines } from './block-preprocess.mjs';
5
5
  * drift-check.mjs — Parser for the top-level `drift-check:` YAML block.
6
6
  *
7
7
  * Ported from config-yaml-parser.sh (v2).
8
+ *
9
+ * SCOPE NOTE — this file parses FLAGS ONLY; it implements no check. The
10
+ * `check-command-count` flag below toggles the `command-count` surface in
11
+ * `skills/claude-md-drift-check/checker.mjs`, which is where the counting lives.
12
+ *
13
+ * `command-count` STAYS on `ls commands/*.md | wc -l` on purpose. The checker is
14
+ * GENERIC over any consumer repo, where a slash command still IS a
15
+ * `commands/*.md` file. The 2026-09-16 command→skill fold (#1370) is a fact
16
+ * about THIS plugin — 24 command files folded into their same-named
17
+ * `skills/<name>/SKILL.md` behind an explicit `user-invocable: true` — so
18
+ * teaching the shared checker that union would mis-measure every repo that never
19
+ * folded. `scripts/site-numbers.mjs` `countCommands()` carries the union for this
20
+ * plugin's own tile, and names the divergence in its docblock.
21
+ *
22
+ * CONSEQUENCE for THIS repo, left deliberately visible rather than papered over:
23
+ * a prose claim of the shape "N commands" / "N slash commands" in a
24
+ * drift-check-scanned doc is compared against the `commands/` listing (2 after
25
+ * the fold), so such a claim must either be dropped or reworded off the
26
+ * `\b(\d+)\s+(?:\/)?commands?\b` pattern the checker matches.
8
27
  */
9
28
 
10
29
  /**
@@ -0,0 +1,74 @@
1
+ import { matchBlockHeader } from './block-header.mjs';
2
+ import { preprocessBlockLines } from './block-preprocess.mjs';
3
+
4
+ /**
5
+ * gate.mjs — Parser for the top-level `gate:` YAML block
6
+ * (PRD `docs/prd/2026-09-20-prozessgruppen-kill-und-waisen-waechter.md` §4 A3,
7
+ * Epic #1425 / issue #1432).
8
+ *
9
+ * Holds the wall-clock ceiling for GATE PATH B — the
10
+ * `scripts/run-quality-gate.mjs` → `scripts/lib/gates/gate-*.mjs` route, which
11
+ * had NO timeout at all before #1425 A3. Path A (`scripts/lib/quality-gate.mjs`)
12
+ * keeps its own `SO_GATE_TIMEOUT_MS` operator override; this block sets the
13
+ * COMMITTED default for path B so both paths may run equally long.
14
+ *
15
+ * Precedence in `run-quality-gate.mjs`: `SO_GATE_TIMEOUT_MS` (operator, wins) >
16
+ * this committed value > `DEFAULT_GATE_TIMEOUT_MS` (900 000).
17
+ *
18
+ * Tolerant parser: malformed values silently fall back to the default.
19
+ *
20
+ * Returns `{ 'timeout-path-b-ms': number }`.
21
+ */
22
+
23
+ /** 900 000 ms (15 min) — same ceiling as `GATE_TIMEOUT_MS` on path A (PRD §4). */
24
+ const DEFAULT_TIMEOUT_PATH_B_MS = 900_000;
25
+
26
+ /**
27
+ * Parse the top-level `gate:` YAML block from markdown content.
28
+ * Independent of the `## Session Config` section boundary.
29
+ *
30
+ * @param {string} content — full file contents
31
+ * @returns {{'timeout-path-b-ms': number}}
32
+ */
33
+ export function _parseGate(content) {
34
+ let timeoutPathBMs = DEFAULT_TIMEOUT_PATH_B_MS;
35
+
36
+ const lines = preprocessBlockLines(content);
37
+ let inBlock = false;
38
+ const blockLines = [];
39
+
40
+ for (const rawLine of lines) {
41
+ const line = rawLine.replace(/\r$/, '');
42
+ if (!inBlock) {
43
+ if (matchBlockHeader(line, 'gate')) inBlock = true;
44
+ continue;
45
+ }
46
+ if (line.length > 0 && !/^\s/.test(line)) break;
47
+ blockLines.push(line);
48
+ }
49
+
50
+ for (const rawLine of blockLines) {
51
+ const clean = rawLine.replace(/\s*#.*$/, '').replace(/\s+$/, '');
52
+ if (!clean.trim()) continue;
53
+
54
+ const kvMatch = clean.match(/^\s+([a-zA-Z_-]+):\s*(.*)/);
55
+ if (!kvMatch) continue;
56
+
57
+ const k = kvMatch[1];
58
+ let v = kvMatch[2].trim();
59
+ if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) v = v.slice(1, -1);
60
+ else if (v.startsWith("'") && v.endsWith("'") && v.length >= 2) v = v.slice(1, -1);
61
+
62
+ if (k === 'timeout-path-b-ms') {
63
+ // A zero or negative ceiling would kill every command instantly — the
64
+ // failure mode is worse than the uncapped state this key exists to fix,
65
+ // so it falls back to the default rather than being honoured.
66
+ if (/^-?\d+$/.test(v)) {
67
+ const n = Number.parseInt(v, 10);
68
+ if (Number.isFinite(n) && n > 0) timeoutPathBMs = n;
69
+ }
70
+ }
71
+ }
72
+
73
+ return { 'timeout-path-b-ms': timeoutPathBMs };
74
+ }