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
@@ -356,7 +356,7 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
356
356
  'templates/python-uv/tests/test_sanity.py',
357
357
  ]);
358
358
 
359
- // `commands/release.md` quotes the `npm view` OUTPUT that proves the 3.18.0 gap,
359
+ // `skills/release/SKILL.md` quotes the `npm view` OUTPUT that proves the 3.18.0 gap,
360
360
  // dated at the line. Bumping it would destroy the evidence it exists to carry —
361
361
  // the registry state on that date is the whole point of the paragraph.
362
362
  //
@@ -384,7 +384,10 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
384
384
  // September 10 campaign snapshot: inputs and receipt reproduce the dated 4.3-planned
385
385
  // render made while 4.2.0 was current. Exact files only; neighboring marketing
386
386
  // remains checked. Do not bump receipt props without a newly reviewed render.
387
- export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|commands\/release\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
387
+ // `scripts/lib/locks/index.mjs` is the one-cycle deprecation shim (5.2.0 CHANGELOG, Changed): its
388
+ // runtime warning names the version it was deprecated IN, so it legitimately carries the previous
389
+ // release literal until its 6.0.0 removal — version history by construction, not drift.
390
+ export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|scripts\/lib\/locks\/index\.mjs|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|skills\/release\/SKILL\.md|marketing\/remotion\/(?:README\.md|campaign\.json|render-receipt\.json|src\/ReleaseFilm\.tsx)$)/;
388
391
 
389
392
  /** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
390
393
  export function checkLeakage(lines) {
@@ -881,14 +884,36 @@ export function evaluateNpmAuth(whoami) {
881
884
  * never found out". Only an actual `status: 'green'` reading passes; a release
882
885
  * must never proceed on an unknown CI state.
883
886
  *
884
- * @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
887
+ * The row also carries the CAUSE the probe already knows, because the three
888
+ * ways this row goes red are indistinguishable without it (#1384 P5): a short
889
+ * `sha`, a commit the mirror has never seen, and a HEAD with no pipeline all
890
+ * printed `CI status unknown (query-failed)` / `status: unknown`. The probe's
891
+ * `detail` (degraded branch, `degradedResult`) and `details.reason` /
892
+ * `failingJobName` (read branch, `sanitizeApiText`) are clamped and
893
+ * control-byte-escaped AT THEIR GENERATION in `ci-status-banner.mjs` —
894
+ * appended here, never re-formatted. That escaping is load-bearing, not
895
+ * hygiene: the read branch interpolates a RAW API `status` value into
896
+ * `reason`, and until #1384 f-4 a status carrying `\r` + an ANSI sequence
897
+ * could repaint THIS row green while its verdict stayed `ok:false`. Do not
898
+ * append a further probe field here without checking it goes through one of
899
+ * those two. With neither present the text is byte-identical to
900
+ * before, so a plain green or a named failing job reads exactly as it did.
901
+ *
902
+ * @param {null | {status?: string, failingJobName?: string, degraded?: string,
903
+ * detail?: string, details?: {reason?: string}}} ci
885
904
  * @returns {{ok: boolean, detail: string}}
886
905
  */
887
906
  export function evaluateCiRow(ci) {
888
907
  if (ci === null || ci === undefined) return { ok: false, detail: 'CI status unavailable' };
889
- if (ci.degraded) return { ok: false, detail: `CI status unknown (${ci.degraded})` };
890
- const job = ci.failingJobName ? ` (${ci.failingJobName})` : '';
891
- return { ok: ci.status === 'green', detail: `status: ${ci.status}${job}` };
908
+ if (ci.degraded) {
909
+ const why = ci.detail ? `: ${ci.detail}` : '';
910
+ return { ok: false, detail: `CI status unknown (${ci.degraded}${why})` };
911
+ }
912
+ // A failing job NAMES the failure, so it wins over the generic reason;
913
+ // `reason` is what fills the gap when there is no job to name (`unknown`).
914
+ const reason = ci.failingJobName || ci.details?.reason || '';
915
+ const why = reason ? ` (${reason})` : '';
916
+ return { ok: ci.status === 'green', detail: `status: ${ci.status}${why}` };
892
917
  }
893
918
 
894
919
  /**
@@ -908,6 +933,11 @@ export function evaluateCiRow(ci) {
908
933
  * "we could not read the mirror" is not "the mirror is green" (the fail-closed
909
934
  * house rule at the top of this file).
910
935
  *
936
+ * This function only judges the `ci` reading it is handed — it is
937
+ * `evaluateCiPreflightRows` (the caller, see its own docblock) that fetches
938
+ * `ci` for the commit actually being released, rather than whichever HEAD the
939
+ * GitHub mirror itself reports.
940
+ *
911
941
  * @param {string|undefined} repoSpec — `resolveRepoSpec({vcs:'github'})`, undefined when no github remote resolves
912
942
  * @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
913
943
  * @returns {{ok: boolean, detail: string}}
@@ -969,6 +999,62 @@ function readPackageVersion(repoRoot) {
969
999
  return JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version;
970
1000
  }
971
1001
 
1002
+ /**
1003
+ * Both CI preflight rows (`ci-green-on-head` + `ci-green-on-head-github`) for
1004
+ * ONE commit — the commit actually about to be released — never whichever
1005
+ * HEAD the queried platform happens to report.
1006
+ *
1007
+ * Before this function existed, neither `checkCiStatus` call below passed
1008
+ * `sha`, so the GitHub branch fell back to `commits/HEAD` — the MIRROR's own
1009
+ * default-branch head, which is the release commit only once `head-pushed-github`
1010
+ * has ALREADY proven `github/main == local HEAD`. A release cut before that
1011
+ * push landed asked GitHub about a commit that was never pushed and could read
1012
+ * green for work GitHub has not seen at all (measured 2026-09-16 for
1013
+ * `3ebf0e9d`). The GitLab branch was already correct by default (`deps.sha ??
1014
+ * getHeadSha(repoRoot)`), so only the two call sites below needed the fix —
1015
+ * see `checkCiStatus`'s own docblock in `ci-status-banner.mjs` for the `sha`
1016
+ * contract (#1332).
1017
+ *
1018
+ * Factored out of `preflight()` as its own async, DI-testable unit:
1019
+ * `checkCiStatus` reaches the network by default, and `preflight()`'s
1020
+ * surrounding checks (git, npm) are not test-doubled, so this is the seam
1021
+ * through which the `sha` wiring can be unit-tested without spinning up a
1022
+ * full fixture release.
1023
+ *
1024
+ * @param {string} repoRoot
1025
+ * @param {string} head - full hex commit id of the commit being released (the
1026
+ * local HEAD `preflight()` already computed for the remote-parity rows).
1027
+ * @param {{
1028
+ * skipCi?: boolean,
1029
+ * checkCiStatus?: Function,
1030
+ * resolveRepoSpec?: (opts: { repoRoot: string, vcs: 'github' }) => string|undefined,
1031
+ * }} [deps]
1032
+ * @returns {Promise<{ gitlab: {ok: boolean, detail: string}, github: {ok: boolean, detail: string} }>}
1033
+ */
1034
+ export async function evaluateCiPreflightRows(repoRoot, head, {
1035
+ skipCi = false,
1036
+ checkCiStatus: checkCiStatusDep,
1037
+ resolveRepoSpec: resolveRepoSpecDep = resolveRepoSpec,
1038
+ } = {}) {
1039
+ if (skipCi) {
1040
+ const skipped = { ok: true, detail: 'SKIPPED via --skip-ci' };
1041
+ return { gitlab: skipped, github: skipped };
1042
+ }
1043
+ const checkCiStatusImpl = checkCiStatusDep ?? (await import('./lib/ci-status-banner.mjs')).checkCiStatus;
1044
+
1045
+ const ci = await checkCiStatusImpl({ repoRoot, timeoutMs: 15000, sha: head });
1046
+ const gitlab = evaluateCiRow(ci);
1047
+
1048
+ const githubSpec = resolveRepoSpecDep({ repoRoot, vcs: 'github' });
1049
+ let githubCi = null;
1050
+ if (githubSpec) {
1051
+ githubCi = await checkCiStatusImpl({ repoRoot, vcs: 'github', timeoutMs: 15000, sha: head });
1052
+ }
1053
+ const github = evaluateGithubCiRow(githubSpec, githubCi);
1054
+
1055
+ return { gitlab, github };
1056
+ }
1057
+
972
1058
  async function preflight(repoRoot, target, { skipCi = false } = {}) {
973
1059
  const checks = [];
974
1060
  const add = (name, ok, detail = '') => checks.push({ name, ok, detail });
@@ -1098,39 +1184,23 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
1098
1184
  add('npm-token-live', false, err.message);
1099
1185
  }
1100
1186
 
1101
- // 6. CI green on HEAD (the repo's iron session-start rule applies to
1102
- // releases doubly: local green is not evidence see .claude/rules).
1103
- // --skip-ci is refused under --publish upstream in validateFlags(); it can
1104
- // only reach this branch from --check.
1105
- if (skipCi) {
1106
- add('ci-green-on-head', true, 'SKIPPED via --skip-ci');
1107
- } else {
1108
- const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
1109
- const ci = await checkCiStatus({ repoRoot, timeoutMs: 15000 });
1110
- const row = evaluateCiRow(ci);
1111
- add('ci-green-on-head', row.ok, row.detail);
1112
- }
1113
-
1114
- // 6b. CI green on the GitHub mirror too. The row above asks the platform
1115
- // `detectVcsFamily` picks for `origin` (GitLab), whose pipeline is Linux-only;
1116
- // the macOS matrix leg lives exclusively in `.github/workflows/test.yml`.
1117
- // `vcs: 'github'` forces the probe onto the mirror without touching the
1118
- // detection order. The GitHub check-runs path reads `commits/HEAD`, i.e. the
1119
- // mirror's default-branch head — which is HEAD only because `head-pushed-github`
1120
- // above proves github/main == local HEAD; that row is this one's precondition,
1121
- // not a duplicate of it.
1122
- if (skipCi) {
1123
- add('ci-green-on-head-github', true, 'SKIPPED via --skip-ci');
1124
- } else {
1125
- const githubSpec = resolveRepoSpec({ repoRoot, vcs: 'github' });
1126
- let githubCi = null;
1127
- if (githubSpec) {
1128
- const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
1129
- githubCi = await checkCiStatus({ repoRoot, vcs: 'github', timeoutMs: 15000 });
1130
- }
1131
- const row = evaluateGithubCiRow(githubSpec, githubCi);
1132
- add('ci-green-on-head-github', row.ok, row.detail);
1133
- }
1187
+ // 6/6b. CI green on HEAD, both platforms judged for the commit actually
1188
+ // being released, never whichever HEAD the queried platform reports (see
1189
+ // evaluateCiPreflightRows's docblock for the `sha`-wiring history, #1332).
1190
+ //
1191
+ // GitLab: the repo's iron session-start rule applies to releases doubly —
1192
+ // local green is not evidence — see .claude/rules. GitHub: this second row
1193
+ // exists because `detectVcsFamily` picks `origin` (GitLab) for the row
1194
+ // above, whose pipeline is Linux-only; the macOS matrix leg lives
1195
+ // exclusively in `.github/workflows/test.yml`. `vcs: 'github'` forces the
1196
+ // probe onto the mirror without touching the detection order.
1197
+ // SELF-DISABLING: a checkout with no `github` remote has no mirror to be
1198
+ // red, so `evaluateGithubCiRow` reports `skipped` rather than red — see its
1199
+ // own docblock. --skip-ci is refused under --publish upstream in
1200
+ // validateFlags(); it can only reach this branch from --check.
1201
+ const ciRows = await evaluateCiPreflightRows(repoRoot, head, { skipCi });
1202
+ add('ci-green-on-head', ciRows.gitlab.ok, ciRows.gitlab.detail);
1203
+ add('ci-green-on-head-github', ciRows.github.ok, ciRows.github.detail);
1134
1204
 
1135
1205
  // 7. Leakage gate over the actual pack file list.
1136
1206
  // `npm_config_loglevel` is INHERITED, and `npm pack --dry-run` writes its whole
@@ -40,7 +40,6 @@
40
40
 
41
41
  import { promises as fs, existsSync } from 'node:fs';
42
42
  import { spawnSync } from 'node:child_process';
43
- import { pathToFileURL } from 'node:url';
44
43
  import path from 'node:path';
45
44
  import {
46
45
  loadVaultRelocationRules,
@@ -55,6 +54,7 @@ import {
55
54
  } from './lib/vault-relocation-rules.mjs';
56
55
  import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
57
56
  import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
57
+ import { isMainModule } from './lib/is-main-module.mjs';
58
58
 
59
59
  // ---------------------------------------------------------------------------
60
60
  // Constants
@@ -1158,8 +1158,7 @@ async function main() {
1158
1158
  // Entry guard (run only when invoked directly)
1159
1159
  // ---------------------------------------------------------------------------
1160
1160
 
1161
- const invokedDirectly =
1162
- process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
1161
+ const invokedDirectly =isMainModule(import.meta.url);
1163
1162
 
1164
1163
  if (invokedDirectly) {
1165
1164
  main().catch((err) => {
@@ -37,10 +37,10 @@
37
37
  import fs from 'node:fs';
38
38
  import { parseArgs } from 'node:util';
39
39
  import path from 'node:path';
40
- import { fileURLToPath } from 'node:url';
41
40
 
42
41
  import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
43
42
  import { getProjectDir } from './lib/platform.mjs';
43
+ import { isMainModule } from './lib/is-main-module.mjs';
44
44
 
45
45
  const USAGE =
46
46
  'Usage: node scripts/repair-invalid-sessions.mjs [--dry-run|--apply] [--json]\n' +
@@ -200,7 +200,7 @@ async function main() {
200
200
  process.exit(summary.ok === false ? 3 : 0);
201
201
  }
202
202
 
203
- const isDirectRun = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
203
+ const isDirectRun =isMainModule(import.meta.url);
204
204
  if (isDirectRun) {
205
205
  main().catch((err) => {
206
206
  process.stderr.write(`repair-invalid-sessions: unexpected error: ${err?.stack ?? err}\n`);
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /** Stdin-only boundary for commands/session.md; never executes task context. */
3
+ import { readFileSync } from 'node:fs';
4
+ import { parseArgs } from 'node:util';
5
+ import { resolveSessionInvocation } from './lib/session-invocation.mjs';
6
+
7
+ const HELP = `Usage: node scripts/resolve-session-invocation.mjs [--json] < arguments.txt
8
+
9
+ Read the complete /session argument text from UTF-8 stdin.
10
+ Resolve the leading housekeeping, feature, deep or ultradeep token; preserve
11
+ the rest as task context. Empty input defaults to deep. An invalid mode emits
12
+ a warning and resolves to deep, as the session command has always specified.
13
+
14
+ --json Emit one JSON object instead of a human-readable summary
15
+ --help Show this help
16
+ --version Show the plugin version
17
+
18
+ Example: node scripts/resolve-session-invocation.mjs --json < arguments.txt
19
+ Write arguments.txt with a file tool; do not interpolate user text into a shell.
20
+ Exit codes: 0 resolved (including fallback), 1 bad CLI arguments, 2 stdin I/O error
21
+ `;
22
+
23
+ let options;
24
+ try {
25
+ options = parseArgs({
26
+ options: { json: { type: 'boolean' }, help: { type: 'boolean' }, version: { type: 'boolean' } },
27
+ allowPositionals: false,
28
+ }).values;
29
+ } catch (error) {
30
+ process.stderr.write(`session-invocation: ${error.message}\n`);
31
+ process.exit(1);
32
+ }
33
+
34
+ if (options.help) {
35
+ process.stdout.write(HELP);
36
+ } else if (options.version) {
37
+ process.stdout.write(
38
+ `${JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version}\n`,
39
+ );
40
+ } else {
41
+ let input;
42
+ try {
43
+ input = readFileSync(0, 'utf8');
44
+ } catch {
45
+ process.stderr.write('session-invocation: could not read UTF-8 stdin\n');
46
+ process.exit(2);
47
+ }
48
+ const resolved = resolveSessionInvocation(input);
49
+ if (resolved.invalidMode !== undefined) {
50
+ process.stderr.write(
51
+ `Invalid session type '${resolved.invalidMode}'. Valid types: housekeeping, feature, deep (alias: ultradeep). Falling back to deep.\n`,
52
+ );
53
+ }
54
+ process.stdout.write(
55
+ options.json
56
+ ? `${JSON.stringify(resolved)}\n`
57
+ : `Session type: ${resolved.sessionType}${resolved.profile ? ` (profile: ${resolved.profile})` : ''}\nTask context: ${resolved.context}\n`,
58
+ );
59
+ }
@@ -23,9 +23,15 @@
23
23
  * 0 — pass (or informational; non-blocking variants always exit 0)
24
24
  * 1 — script error (bad arguments, missing dependencies)
25
25
  * 2 — gate failed (full-gate only: typecheck/test/lint errors)
26
+ * 124 — the gate sub-script exceeded the wall-clock ceiling and its whole
27
+ * process GROUP was killed (SIGTERM→SIGKILL). Same value coreutils
28
+ * `timeout(1)` uses. Applies to EVERY variant, including the otherwise
29
+ * non-blocking ones: a killed gate measured nothing, so reporting it as
30
+ * 0 would be a false green (Epic #1425 A3).
26
31
  *
27
32
  * The gate sub-scripts in scripts/lib/gates/ are NOT reimplemented here; they are
28
- * invoked via child_process.spawn('bash', [path, ...]) with the required env vars.
33
+ * invoked via spawnInGroup() — a detached, process-group-leading shell — with the
34
+ * required env vars.
29
35
  *
30
36
  * References:
31
37
  * scripts/run-quality-gate.sh — original shell orchestrator
@@ -37,12 +43,18 @@
37
43
  import { existsSync, readFileSync, statSync } from 'node:fs';
38
44
  import { join, dirname, resolve } from 'node:path';
39
45
  import { fileURLToPath } from 'node:url';
40
- import { spawnSync } from 'node:child_process';
46
+ import { spawn } from 'node:child_process';
41
47
 
42
48
  import { die, warn } from './lib/common.mjs';
43
49
  import { loadQualityGatesPolicy, resolveCommand } from './lib/quality-gates-policy.mjs';
44
50
  import { emitEvent, sessionAttribution } from './lib/events.mjs';
45
- import { admitSuiteCounts } from './lib/gates/gate-helpers.mjs';
51
+ import {
52
+ admitSuiteCounts,
53
+ publishGateOutcome,
54
+ resolveGateTimeoutMs,
55
+ } from './lib/gates/gate-helpers.mjs';
56
+ import { buildCommandSignature, spawnInGroup } from './lib/process-group.mjs';
57
+ import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
46
58
  import { findScopeFile } from './lib/scope-gate.mjs';
47
59
 
48
60
  // ---------------------------------------------------------------------------
@@ -62,6 +74,24 @@ const VALID_VARIANTS = ['baseline', 'incremental', 'full-gate', 'per-file'];
62
74
  */
63
75
  const GATE_STDOUT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
64
76
 
77
+ /**
78
+ * Head-room between the ceiling the gate's OWN commands run under
79
+ * (`resolveGateTimeoutMs()`, applied per command inside `runCheck`) and the one
80
+ * this wrapper applies to the gate sub-script as a whole.
81
+ *
82
+ * The inner cap must fire FIRST: it kills exactly the wedged command and still
83
+ * lets the gate print its JSON envelope, name the failing check on stderr and
84
+ * exit 2. This outer cap is the backstop for the case the inner one cannot
85
+ * reach — a gate wedged outside `runCheck`, or a kill ladder that left a
86
+ * survivor holding the pipe open.
87
+ *
88
+ * Named ceiling (BV-004): 60 s. It only has to cover one full SIGTERM→grace→
89
+ * SIGKILL→verify ladder (`DEFAULT_KILL_GRACE_MS` 10 s + 0.5 s verify + 1 s
90
+ * deadline slack ≈ 11.5 s) plus the gate's own JSON write. Revisit if the grace
91
+ * period is ever raised past ~45 s.
92
+ */
93
+ const GATE_OUTER_TIMEOUT_RESERVE_MS = 60_000;
94
+
65
95
  const DEFAULT_TEST_CMD = 'npm test';
66
96
  const DEFAULT_TYPECHECK_CMD = 'npm run typecheck';
67
97
  const DEFAULT_LINT_CMD = 'npm run lint';
@@ -88,7 +118,8 @@ if (argv.includes('-h') || argv.includes('--help')) {
88
118
  'Exit codes:\n' +
89
119
  ' 0 — pass (non-blocking variants always exit 0)\n' +
90
120
  ' 1 — script error (bad arguments, missing dependencies)\n' +
91
- ' 2 — gate failed (full-gate only)\n',
121
+ ' 2 — gate failed (full-gate only)\n' +
122
+ ' 124 — gate timed out; its process group was killed (SIGTERM→SIGKILL)\n',
92
123
  );
93
124
  process.exit(0);
94
125
  }
@@ -355,6 +386,11 @@ if (!existsSync(gatePath)) {
355
386
  die(`Gate script not found: ${gatePath}`);
356
387
  }
357
388
 
389
+ // Resolved BEFORE the spawn (it used to sit beside the telemetry block below):
390
+ // the gate-process ledger the spawn writes is pinned to the same root as the
391
+ // event, for the same reason — see the spawn's `repoRoot` comment.
392
+ const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
393
+
358
394
  // `npm_config_loglevel` is INHERITED by every descendant, and the pre-push hook
359
395
  // invokes this script as `npm run --silent quality-gate` — which sets it to
360
396
  // `silent`. That level then reached the gate's own children: `npm pack
@@ -368,37 +404,141 @@ if (!existsSync(gatePath)) {
368
404
  // independent of how the gate itself was invoked. `--silent` still does its real
369
405
  // job — keeping THIS process's stdout to the single JSON envelope — because the
370
406
  // children's output is captured by `runCheck`, never streamed.
407
+ /**
408
+ * Per-command wall-clock ceiling for PATH B (#1425 A3 / #1432).
409
+ *
410
+ * Precedence, highest first:
411
+ * 1. `SO_GATE_TIMEOUT_MS` — the OPERATOR override. It is inherited by every
412
+ * descendant, so a gate child that re-derives the ceiling gets the same
413
+ * answer; `resolveGateTimeoutMs()` already reads it.
414
+ * 2. Session Config `gate.timeout-path-b-ms` — the COMMITTED default.
415
+ * 3. `DEFAULT_GATE_TIMEOUT_MS` (900 000), via `resolveGateTimeoutMs()`.
416
+ *
417
+ * Published to the gate sub-script as `GATE_TIMEOUT_MS` — a DIFFERENT name from
418
+ * the operator override on purpose: writing the resolved value back into
419
+ * `SO_GATE_TIMEOUT_MS` would make a committed default indistinguishable from an
420
+ * operator decision for every process further down the tree.
421
+ */
422
+ const configuredGateTimeoutMs = (() => {
423
+ const block = configJson !== null && typeof configJson === 'object' ? configJson.gate : null;
424
+ const raw = block && typeof block === 'object' ? block['timeout-path-b-ms'] : undefined;
425
+ return Number.isFinite(raw) && raw > 0 ? raw : null;
426
+ })();
427
+ const operatorTimeoutOverride = (process.env.SO_GATE_TIMEOUT_MS || '').trim() !== '';
428
+ const commandTimeoutMs = operatorTimeoutOverride || configuredGateTimeoutMs === null
429
+ ? resolveGateTimeoutMs()
430
+ : configuredGateTimeoutMs;
431
+
432
+ // Gate-process REGISTER root — the same precedence the telemetry destination
433
+ // uses below (`--ledger-root` > project-dir env > cwd), so a sandboxed test that
434
+ // only sets CLAUDE_PROJECT_DIR never writes register lines into the checkout.
435
+ const gateLedgerRoot =
436
+ ledgerRoot ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? repoRoot;
437
+
371
438
  const env = {
372
439
  ...process.env,
373
440
  npm_config_loglevel: 'notice',
374
441
  TYPECHECK_CMD,
375
442
  TEST_CMD,
376
443
  LINT_CMD,
444
+ GATE_TIMEOUT_MS: String(commandTimeoutMs),
445
+ // Same resolution the wrapper uses for its OWN spawn (`--ledger-root` > repo
446
+ // root): the gate sub-scripts pass it to runCheck() so every register line of
447
+ // one gate run lands in ONE ledger (#1425 A4, W5 fix-pass).
448
+ GATE_LEDGER_ROOT: gateLedgerRoot,
377
449
  FILES: files,
378
450
  SESSION_START_REF: sessionStartRef,
379
451
  };
380
452
 
453
+ /**
454
+ * POSIX single-quote one argument for the shell `spawnInGroup` runs the command
455
+ * through. The gate path is derived from `import.meta.url`, so it carries
456
+ * whatever the checkout path carries — a space in it must not split the command.
457
+ *
458
+ * @param {string} value
459
+ * @returns {string}
460
+ */
461
+ function shellQuote(value) {
462
+ return `'${String(value).replace(/'/g, "'\\''")}'`;
463
+ }
464
+
465
+ // The gate sub-script runs as the LEADER OF ITS OWN PROCESS GROUP under a
466
+ // wall-clock ceiling (Epic #1425 A3). Before this, `spawnSync('node', [gatePath])`
467
+ // had NO timeout at all and no group semantics: a wedged `tsgo` or vitest worker
468
+ // two levels down was reparented to PPID 1 and kept its memory (2026-09-20: four
469
+ // such orphans, up to 8.0 GB RSS each, host at 13 % free).
470
+ //
381
471
  // stdout is PIPED (not inherited) so the suite counts the gate already computed
382
472
  // can be lifted straight off its JSON envelope into telemetry (#954) instead of
383
473
  // travelling as prose through the STATE.md header. The envelope is re-emitted
384
474
  // verbatim below, so the stdout contract is unchanged — a gate sub-script writes
385
475
  // exactly one JSON line at the very end (its own child commands are captured by
386
- // `runCheck`), so nothing streamed before and nothing streams now. stderr stays
387
- // inherited, keeping warnings live.
388
- const result = spawnSync('node', [gatePath], {
476
+ // `runCheck`), so nothing streamed before and nothing streams now.
477
+ //
478
+ // stderr stays INHERITED, and that is why `spawnFn` is overridden here rather
479
+ // than left at its default: `spawnInGroup` merges stdout and stderr into one
480
+ // capture, which would interleave the gate's failure disclosure (hundreds of
481
+ // lines, #1149) into the single JSON document every consumer parses off stdout.
482
+ // Handing it a child with `stderr: 'inherit'` leaves `child.stderr` null, the
483
+ // module's own `child.stderr?.on(…)` a no-op, and the gate's warnings live on
484
+ // the operator's terminal exactly as before.
485
+ const gateCommand = `node ${shellQuote(gatePath)}`;
486
+ // Derived from the SAME resolved per-command ceiling published as
487
+ // `GATE_TIMEOUT_MS` above — not from `resolveGateTimeoutMs()` a second time.
488
+ // The outer cap must stay strictly ABOVE the inner one so the inner kill fires
489
+ // first and the gate can still print its envelope; re-deriving here would leave
490
+ // the outer cap at the 900 000 default while a committed
491
+ // `gate.timeout-path-b-ms` above it made the inner cap the later of the two.
492
+ const gateTimeoutMs = commandTimeoutMs + GATE_OUTER_TIMEOUT_RESERVE_MS;
493
+ const result = await spawnInGroup(gateCommand, {
494
+ cwd: repoRoot,
389
495
  env,
390
- stdio: ['inherit', 'pipe', 'inherit'],
391
- encoding: 'utf8',
392
- maxBuffer: GATE_STDOUT_MAX_BUFFER_BYTES,
496
+ timeoutMs: gateTimeoutMs,
497
+ maxOutputBytes: GATE_STDOUT_MAX_BUFFER_BYTES,
498
+ // The ledger is what the orphan reaper (#1425 B) reads to tell its OWN gate
499
+ // processes from every other `node` on the host, so it is pinned to the same
500
+ // root the telemetry is — under the pre-push hook the tree the gate runs in
501
+ // is deleted seconds later, and the record with it.
502
+ repoRoot: gateLedgerRoot,
503
+ commandSignature: buildCommandSignature(gateCommand),
504
+ // The OWNER the ledger records, from the PROCESS-LOCAL witness only — the same
505
+ // rule `scripts/lib/quality-gate.mjs` follows for the gate commands it spawns
506
+ // (`.claude/rules/identity-and-locks.md`: rank witnesses, never union them).
507
+ // `.orchestrator/session.lock` is deliberately NOT consulted: it is a
508
+ // repo-GLOBAL artefact any session in this working copy may hold, so reading
509
+ // it would stamp a PEER's id onto our own gate process and point the orphan
510
+ // reaper (#1425 B) at the wrong owner. No witness → `null`, which the ledger
511
+ // records as "owner unknown" — the state every record carried before this.
512
+ sessionId: [...readProcessLocalSessionIds({ env: process.env, hookInput: null })][0] ?? null,
513
+ // stdin is IGNORED, not inherited. A gate sub-script reads no stdin, but a
514
+ // DETACHED child is in its own process group and is therefore not the
515
+ // terminal's foreground group: the first read from an inherited TTY earns it
516
+ // SIGTTIN, which stops the whole group until the outer ceiling kills it. An
517
+ // ignored stdin turns that hang into an immediate EOF.
518
+ spawnFn: (command, options) => spawn(command, { ...options, stdio: ['ignore', 'pipe', 'inherit'] }),
393
519
  });
394
520
 
395
- const gateStdout = typeof result.stdout === 'string' ? result.stdout : '';
396
- if (gateStdout) process.stdout.write(gateStdout);
397
-
398
- if (result.error && typeof result.status !== 'number') {
399
- die(`Failed to run gate script: ${result.error.message}`);
521
+ // `pid: -1` is `spawnInGroup`'s spawn-failure channel (it never rejects).
522
+ if (result.pid === -1) {
523
+ die(`Failed to run gate script: ${result.fullOutput.trim()}`);
400
524
  }
401
525
 
526
+ // On timeout the child was KILLED before it could write its envelope, so its
527
+ // capture is at best a partial JSON document. Publishing that would hand every
528
+ // stdout consumer a parse error where a named failure belongs; publishing the
529
+ // partial text AND an envelope would break the one-document contract. So the
530
+ // capture goes to stderr, where the operator can still read it, and stdout
531
+ // carries a complete `gate-timeout` envelope instead.
532
+ //
533
+ // The DECISION lives in `publishGateOutcome` (gate-helpers.mjs) and only the
534
+ // WRITES live here: this branch needs a real 16-minute gate to reach, so while
535
+ // the decision was inline it was pinned by nothing.
536
+ const outcome = publishGateOutcome({ result, variant, timeoutMs: gateTimeoutMs });
537
+ const gateStdout = result.timedOut ? '' : result.fullOutput;
538
+ if (outcome.stderr) process.stderr.write(outcome.stderr);
539
+ if (outcome.stdout) process.stdout.write(outcome.stdout);
540
+ for (const line of outcome.warnings) warn(line);
541
+
402
542
  // Quality-gate telemetry — one canonical event per gate run via emitEvent
403
543
  // (single emission path). `sessionAttribution` is the shared helper in
404
544
  // events.mjs (#941); this CLI wrapper runs against the CWD `repoRoot`, so the
@@ -433,8 +573,7 @@ if (result.error && typeof result.status !== 'number') {
433
573
  //
434
574
  // Best-effort: a telemetry failure must NEVER alter the gate's authoritative
435
575
  // exit code — which is why the counts parse also lives inside this try.
436
- const exitCode = result.status ?? 1;
437
- const ledgerRoot = resolveLedgerRoot(ledgerRootArg);
576
+ const exitCode = outcome.exitCode;
438
577
  try {
439
578
  const counts = suiteCountsFromGateStdout(gateStdout);
440
579
  // The names behind `counts.failed`. Absent, never `[]` — see
@@ -42,10 +42,10 @@
42
42
  import { existsSync, readFileSync, statSync } from 'node:fs';
43
43
  import { join, resolve } from 'node:path';
44
44
  import { parseArgs } from 'node:util';
45
- import { fileURLToPath } from 'node:url';
46
45
  import { resolveInstructionFile } from './lib/common.mjs';
47
46
  import { parseSessionConfig } from './lib/config.mjs';
48
47
  import { resolveAndRecordSessionShape } from './lib/session-shape.mjs';
48
+ import { isMainModule } from './lib/is-main-module.mjs';
49
49
 
50
50
  const USAGE = [
51
51
  'Usage: node scripts/session-shape.mjs --repo-root <path> --session-type <housekeeping|feature|deep>',
@@ -258,7 +258,7 @@ async function main() {
258
258
  }
259
259
 
260
260
  // Entrypoint guard — importing this file must not run it (check-unwired-features S3).
261
- if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
261
+ if (isMainModule(import.meta.url)) {
262
262
  main().catch((err) => {
263
263
  process.stderr.write(`session-shape: unexpected error: ${err?.stack ?? err}\n`);
264
264
  process.exit(EXIT_CONFIG_IO);
@@ -101,10 +101,11 @@
101
101
  import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs';
102
102
  import { join, resolve, relative } from 'node:path';
103
103
  import { execFileSync } from 'node:child_process';
104
- import { fileURLToPath } from 'node:url';
105
104
 
106
105
  import { writeStdoutLineSync, writeJsonAtomicSync } from './lib/io.mjs';
107
106
  import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
107
+ import { isMainModule } from './lib/is-main-module.mjs';
108
+ import { slashCommandNames } from './lib/user-invocable-skills.mjs';
108
109
 
109
110
  /** Machine-readable schema tag for the --json envelope. */
110
111
  export const SCHEMA = 'site-numbers/1';
@@ -169,11 +170,37 @@ export function countSkills(root) {
169
170
  return n;
170
171
  }
171
172
 
172
- /** `ls commands/*.md | wc -l` — same basis as checker.mjs `command-count`. */
173
+ /**
174
+ * The product's "slash commands" number: `commands/*.md` ∪ skills carrying an
175
+ * EXPLICIT `user-invocable: true`, deduplicated by name.
176
+ *
177
+ * NOT `ls commands/*.md | wc -l` any more, and the change is not cosmetic. After
178
+ * the 2026-09-16 fold (#1370) `commands/` holds two files — `session` and
179
+ * `templates-ack`, the only two names a skill cannot carry — while 26 operator-
180
+ * facing slash commands exist. A command file only ever resolved because a
181
+ * same-named skill existed, and the twin additionally listed the entry twice in
182
+ * the `/` picker; the skill frontmatter flag is what makes a slash command a
183
+ * slash command now. Counting the directory would have reported 2.
184
+ *
185
+ * Dedup by name is load-bearing: a name present on BOTH sides is the picker-
186
+ * duplicate defect (`tests/commands/headless-bare-command-availability.test.mjs`
187
+ * fails on it), so it must count once here rather than inflate the tile.
188
+ *
189
+ * null (→ the tile renders as unmeasured) only when NEITHER `commands/` nor
190
+ * `skills/` exists — i.e. this is not a plugin tree at all. A tree with one of
191
+ * the two counts what it has; a missing `commands/` is now the NORMAL state, not
192
+ * a missing measurement.
193
+ *
194
+ * NO divergence from `skills/claude-md-drift-check/checker.mjs` `command-count`:
195
+ * that checker unions the same user-invocable skills (`checker.mjs:435-446`,
196
+ * measured 2026-09-17), and both routes reach the same verdict through the same
197
+ * SSOT — `isUserInvocableValue` in `scripts/lib/user-invocable-skills.mjs`. The
198
+ * checker stays GENERIC over consumer repos only in the sense that a repo
199
+ * without `skills/` sees the union collapse to the directory count on its own.
200
+ */
173
201
  export function countCommands(root) {
174
- const dir = join(root, 'commands');
175
- if (!isDir(dir)) return null;
176
- return readdirSync(dir).filter((f) => f.endsWith('.md') && !f.startsWith('.')).length;
202
+ if (!isDir(join(root, 'commands')) && !isDir(join(root, 'skills'))) return null;
203
+ return slashCommandNames(root).length;
177
204
  }
178
205
 
179
206
  /**
@@ -400,7 +427,8 @@ export const METRIC_DEFS = Object.freeze([
400
427
  {
401
428
  id: 'commands',
402
429
  provenance: false,
403
- source: 'ls commands/*.md | wc -l',
430
+ source:
431
+ 'ls commands/*.md | wc -l + grep -l "^user-invocable: true" skills/*/SKILL.md | wc -l (union by name — see countCommands)',
404
432
  compute: (root) => fmtCount(countCommands(root)),
405
433
  },
406
434
  {
@@ -1398,11 +1426,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
1398
1426
  return exitCode;
1399
1427
  }
1400
1428
 
1401
- const isMain =
1402
- typeof process !== 'undefined' &&
1403
- process.argv[1] !== null &&
1404
- process.argv[1] !== undefined &&
1405
- resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
1429
+ const isMain =isMainModule(import.meta.url);
1406
1430
 
1407
1431
  if (isMain) {
1408
1432
  const argv = process.argv.slice(2);