session-orchestrator 5.2.0 → 5.4.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 (427) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +5 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +5 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +5 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
  9. package/.agents/skills/close/SKILL.md +5 -1
  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 +5 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +5 -1
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +5 -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 +5 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +6 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +6 -2
  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 +5 -1
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +5 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +5 -1
  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 +5 -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/navigator/SKILL.md +22 -0
  41. package/.agents/skills/navigator/agents/openai.yaml +5 -0
  42. package/.agents/skills/npm-publish/SKILL.md +4 -2
  43. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  44. package/.agents/skills/persona-panel/SKILL.md +5 -1
  45. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  46. package/.agents/skills/plan/SKILL.md +5 -1
  47. package/.agents/skills/plan/agents/openai.yaml +5 -0
  48. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  49. package/.agents/skills/portfolio/SKILL.md +5 -1
  50. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  51. package/.agents/skills/quality-gates/SKILL.md +3 -1
  52. package/.agents/skills/reconcile/SKILL.md +5 -1
  53. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  54. package/.agents/skills/release/SKILL.md +5 -1
  55. package/.agents/skills/release/agents/openai.yaml +5 -0
  56. package/.agents/skills/remote-offload/SKILL.md +3 -1
  57. package/.agents/skills/repo-audit/SKILL.md +5 -1
  58. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  59. package/.agents/skills/session/SKILL.md +21 -0
  60. package/.agents/skills/session/agents/openai.yaml +5 -0
  61. package/.agents/skills/session-end/SKILL.md +3 -1
  62. package/.agents/skills/session-plan/SKILL.md +3 -1
  63. package/.agents/skills/session-start/SKILL.md +3 -1
  64. package/.agents/skills/spinout/SKILL.md +5 -1
  65. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  66. package/.agents/skills/sunset-review/SKILL.md +5 -1
  67. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  68. package/.agents/skills/templates-ack/SKILL.md +21 -0
  69. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  70. package/.agents/skills/test/SKILL.md +5 -1
  71. package/.agents/skills/test/agents/openai.yaml +5 -0
  72. package/.agents/skills/test-audit/SKILL.md +21 -0
  73. package/.agents/skills/test-audit/agents/openai.yaml +5 -0
  74. package/.agents/skills/test-runner/SKILL.md +3 -1
  75. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  76. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  77. package/.agents/skills/ux-grill/SKILL.md +5 -1
  78. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  79. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  80. package/.agents/skills/vault-sync/SKILL.md +3 -1
  81. package/.agents/skills/wave-executor/SKILL.md +3 -1
  82. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  83. package/.claude-plugin/marketplace.json +1 -1
  84. package/.claude-plugin/plugin.json +1 -1
  85. package/.codex-plugin/plugin.json +4 -4
  86. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
  87. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
  88. package/.codex-plugin/skills/eval/SKILL.md +1 -1
  89. package/.codex-plugin/skills/evolve/SKILL.md +1 -1
  90. package/.codex-plugin/skills/navigator/SKILL.md +22 -0
  91. package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
  92. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
  93. package/.codex-plugin/skills/session/SKILL.md +1 -1
  94. package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
  95. package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
  96. package/.cursor/commands/eval.md +1 -1
  97. package/.cursor/commands/navigator.md +15 -0
  98. package/.cursor/commands/session.md +1 -1
  99. package/.cursor/commands/test-audit.md +14 -0
  100. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  101. package/.cursor/rules/050-plan.mdc +1 -1
  102. package/.cursor/rules/060-evolve.mdc +27 -12
  103. package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
  104. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  105. package/.cursor/skills/eval/SKILL.md +1 -1
  106. package/.cursor/skills/navigator/SKILL.md +13 -0
  107. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  108. package/.cursor/skills/test-audit/SKILL.md +12 -0
  109. package/.cursor-plugin/plugin.json +1 -1
  110. package/.orchestrator/policy/blocked-commands.json +12 -3
  111. package/AGENTS.md +9 -30
  112. package/CHANGELOG.md +206 -0
  113. package/README.md +13 -9
  114. package/SECURITY.md +12 -0
  115. package/agents/dialectic-deriver.md +13 -10
  116. package/agents/eval-judge.md +67 -45
  117. package/agents/skill-applied-judge.md +34 -19
  118. package/commands/session.md +7 -3
  119. package/docs/agent-authoring.md +2 -2
  120. package/docs/baseline.md +22 -7
  121. package/docs/ci-setup.md +57 -4
  122. package/docs/codex-setup.md +14 -2
  123. package/docs/components.md +12 -10
  124. package/docs/events-schema.md +65 -15
  125. package/docs/persona-panel.md +5 -4
  126. package/docs/rule-authoring.md +84 -17
  127. package/docs/scope-collision-guard.md +26 -4
  128. package/docs/session-config-reference.md +154 -12
  129. package/docs/session-config-template.md +41 -3
  130. package/docs/telemetry.md +2 -0
  131. package/hooks/_lib/hook-import-set.json +87 -6
  132. package/hooks/_lib/subagent-transcript.mjs +582 -31
  133. package/hooks/config-protection.mjs +11 -3
  134. package/hooks/cwd-change-restore.mjs +11 -3
  135. package/hooks/enforce-commands.mjs +70 -23
  136. package/hooks/enforce-scope.mjs +143 -33
  137. package/hooks/hooks-codex.json +1 -1
  138. package/hooks/hooks-cursor.json +5 -0
  139. package/hooks/hooks-pi.json +5 -0
  140. package/hooks/hooks.json +6 -1
  141. package/hooks/loop-guard.mjs +11 -3
  142. package/hooks/on-session-end.mjs +58 -23
  143. package/hooks/on-session-start.mjs +101 -11
  144. package/hooks/on-stop.mjs +170 -22
  145. package/hooks/operator-steer.mjs +11 -3
  146. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  147. package/hooks/post-bash-write-verify.mjs +3 -2
  148. package/hooks/post-edit-import-probe.mjs +17 -9
  149. package/hooks/post-edit-validate.mjs +13 -5
  150. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  151. package/hooks/post-tool-batch-wave-signal.mjs +202 -38
  152. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  153. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  154. package/hooks/pre-auq-clarity.mjs +15 -2
  155. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  156. package/hooks/pre-bash-issue-budget.mjs +16 -11
  157. package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
  158. package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
  159. package/hooks/pre-bash-staging-fence.mjs +335 -31
  160. package/hooks/pre-bash-templates-first.mjs +19 -14
  161. package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
  162. package/hooks/pre-task-scope-disjoint.mjs +792 -74
  163. package/hooks/subagent-telemetry.mjs +111 -38
  164. package/hooks/wave-scope-commit-guard.mjs +197 -100
  165. package/monitors/monitors.json +1 -1
  166. package/output-styles/wave-summary.md +1 -1
  167. package/package.json +3 -3
  168. package/pi/prompts/eval.md +1 -1
  169. package/pi/prompts/navigator.md +12 -0
  170. package/pi/prompts/session.md +1 -1
  171. package/pi/prompts/test-audit.md +12 -0
  172. package/rules/README.md +7 -2
  173. package/rules/_index.md +3 -3
  174. package/rules/always-on/bash-harness-pitfalls.md +7 -1
  175. package/rules/opt-in-domain/prompt-caching.md +6 -2
  176. package/rules/opt-in-stack/backend-data.md +10 -2
  177. package/rules/opt-in-stack/backend.md +10 -4
  178. package/rules/opt-in-stack/frontend.md +7 -2
  179. package/rules/opt-in-stack/security-web.md +10 -4
  180. package/rules/opt-in-stack/swift.md +6 -2
  181. package/scripts/apply-session-learnings.mjs +492 -0
  182. package/scripts/archive-closed-prds.mjs +2 -1
  183. package/scripts/autopilot.mjs +70 -53
  184. package/scripts/backfill-abandoned-sessions.mjs +118 -16
  185. package/scripts/baseline-archetypes.mjs +1 -1
  186. package/scripts/check-package-manager.mjs +62 -5
  187. package/scripts/check-sessions-integrity.mjs +300 -0
  188. package/scripts/cursor-install.mjs +1 -4
  189. package/scripts/dialectic-deriver.mjs +50 -13
  190. package/scripts/emit-session.mjs +371 -31
  191. package/scripts/eval-session.mjs +65 -3
  192. package/scripts/export-hw-learnings.mjs +22 -3
  193. package/scripts/fleet-checkin.mjs +147 -0
  194. package/scripts/generate-agents-skills.mjs +102 -29
  195. package/scripts/generate-cursor-adapter.mjs +61 -16
  196. package/scripts/lib/agent-frontmatter.mjs +3 -3
  197. package/scripts/lib/agent-status.mjs +2 -31
  198. package/scripts/lib/auq/clarity.mjs +10 -2
  199. package/scripts/lib/auq/parse.mjs +12 -31
  200. package/scripts/lib/auq/schema.mjs +56 -41
  201. package/scripts/lib/auto-dialectic.mjs +304 -15
  202. package/scripts/lib/auto-dream.mjs +7 -6
  203. package/scripts/lib/autopilot/flags.mjs +18 -2
  204. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  205. package/scripts/lib/autopilot/loop.mjs +19 -2
  206. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  207. package/scripts/lib/baseline-archetypes.mjs +41 -22
  208. package/scripts/lib/ci-status-banner.mjs +376 -16
  209. package/scripts/lib/command-blocker.mjs +275 -28
  210. package/scripts/lib/config/dialectic.mjs +12 -3
  211. package/scripts/lib/config/gate.mjs +74 -0
  212. package/scripts/lib/config/host-paths.mjs +79 -0
  213. package/scripts/lib/config/persona-gate-wave.mjs +3 -3
  214. package/scripts/lib/config/reaper.mjs +163 -0
  215. package/scripts/lib/config-schema.mjs +13 -0
  216. package/scripts/lib/config.mjs +85 -10
  217. package/scripts/lib/convergence-monitor.mjs +74 -11
  218. package/scripts/lib/ecosystem-health.mjs +11 -0
  219. package/scripts/lib/eval/engine.mjs +421 -53
  220. package/scripts/lib/eval/judge.mjs +463 -40
  221. package/scripts/lib/eval/schema.mjs +10 -1
  222. package/scripts/lib/events-rotation.mjs +221 -25
  223. package/scripts/lib/events-schema.mjs +114 -0
  224. package/scripts/lib/events.mjs +545 -7
  225. package/scripts/lib/fleet-protocol.mjs +270 -0
  226. package/scripts/lib/frontmatter-guard.mjs +21 -10
  227. package/scripts/lib/gates/gate-baseline.mjs +40 -5
  228. package/scripts/lib/gates/gate-full.mjs +35 -5
  229. package/scripts/lib/gates/gate-helpers.mjs +336 -23
  230. package/scripts/lib/gates/gate-incremental.mjs +49 -23
  231. package/scripts/lib/gates/gate-per-file.mjs +42 -8
  232. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  233. package/scripts/lib/instruction-budget-guard.mjs +208 -8
  234. package/scripts/lib/io.mjs +42 -8
  235. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  236. package/scripts/lib/js-mask.mjs +197 -0
  237. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  238. package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
  239. package/scripts/lib/learnings/io.mjs +154 -26
  240. package/scripts/lib/learnings/schema.mjs +163 -15
  241. package/scripts/lib/learnings/sizing-subject.mjs +2 -2
  242. package/scripts/lib/maintenance-due-banner.mjs +53 -88
  243. package/scripts/lib/memory-proposals/schema.mjs +11 -5
  244. package/scripts/lib/named-baseline-resolver.mjs +53 -28
  245. package/scripts/lib/named-vault-resolver.mjs +104 -5
  246. package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
  247. package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
  248. package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
  249. package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
  250. package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
  251. package/scripts/lib/orphan-reaper/scan.mjs +506 -0
  252. package/scripts/lib/orphan-reaper.mjs +316 -0
  253. package/scripts/lib/owner-yaml.mjs +104 -6
  254. package/scripts/lib/peer-cards/merger.mjs +48 -10
  255. package/scripts/lib/peer-cards/reader.mjs +78 -2
  256. package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
  257. package/scripts/lib/process-group.mjs +950 -0
  258. package/scripts/lib/project-hygiene.mjs +21 -1
  259. package/scripts/lib/quality-gate.mjs +152 -33
  260. package/scripts/lib/reconcile/backlog.mjs +368 -0
  261. package/scripts/lib/reconcile/engine.mjs +55 -188
  262. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
  263. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  264. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  265. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  266. package/scripts/lib/resource-probe.mjs +1 -1
  267. package/scripts/lib/rule-loader.mjs +141 -74
  268. package/scripts/lib/rules-sync.mjs +27 -12
  269. package/scripts/lib/scope-baseline.mjs +3 -4
  270. package/scripts/lib/scope-echo.mjs +39 -2
  271. package/scripts/lib/scope-gate.mjs +605 -1
  272. package/scripts/lib/session-close-backfill.mjs +33 -6
  273. package/scripts/lib/session-id.mjs +9 -20
  274. package/scripts/lib/session-invocation.mjs +20 -0
  275. package/scripts/lib/session-record-repair.mjs +183 -26
  276. package/scripts/lib/session-registry.mjs +27 -2
  277. package/scripts/lib/session-schema/constants.mjs +56 -2
  278. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  279. package/scripts/lib/session-schema/validator.mjs +28 -1
  280. package/scripts/lib/session-schema.mjs +8 -3
  281. package/scripts/lib/session-start-probes.mjs +95 -10
  282. package/scripts/lib/session-token-rollup.mjs +245 -52
  283. package/scripts/lib/sessions-canonical.mjs +118 -4
  284. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  285. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  286. package/scripts/lib/sh-quote.mjs +29 -0
  287. package/scripts/lib/skill-evidence-window.mjs +891 -0
  288. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  289. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  290. package/scripts/lib/skill-judge.mjs +45 -3
  291. package/scripts/lib/subagents-schema.mjs +30 -3
  292. package/scripts/lib/tail-window.mjs +56 -0
  293. package/scripts/lib/telemetry/pricing.mjs +118 -71
  294. package/scripts/lib/telemetry/schema.mjs +30 -0
  295. package/scripts/lib/telemetry/sync.mjs +61 -6
  296. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  297. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  298. package/scripts/lib/tests-src-ratio.mjs +140 -23
  299. package/scripts/lib/tmux-layout/layouts.mjs +1 -11
  300. package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
  301. package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
  302. package/scripts/lib/user-invocable-skills.mjs +23 -3
  303. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  304. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  305. package/scripts/lib/validate/check-agents.mjs +1 -1
  306. package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
  307. package/scripts/lib/validate/check-entry-guard.mjs +13 -50
  308. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  309. package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
  310. package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
  311. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  312. package/scripts/lib/validate/check-rules.mjs +7 -5
  313. package/scripts/lib/validate/check-skill-links.mjs +9 -1
  314. package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
  315. package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
  316. package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
  317. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  318. package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
  319. package/scripts/lib/validate/check-unwired-features.mjs +130 -27
  320. package/scripts/lib/validate/check-validator-registration.mjs +34 -10
  321. package/scripts/lib/validate/confidential-names.mjs +10 -0
  322. package/scripts/lib/validate-vendored-rules.mjs +210 -84
  323. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  324. package/scripts/lib/vault-mirror/process.mjs +10 -3
  325. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  326. package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
  327. package/scripts/lib/vault-status/board-writer.mjs +48 -11
  328. package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
  329. package/scripts/lib/vault-yaml.mjs +118 -0
  330. package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
  331. package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
  332. package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
  333. package/scripts/lib/wave-resource-gate.mjs +34 -8
  334. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  335. package/scripts/memory-propose.mjs +18 -5
  336. package/scripts/release-session-lock.mjs +306 -0
  337. package/scripts/release.mjs +40 -8
  338. package/scripts/repair-invalid-sessions.mjs +37 -4
  339. package/scripts/resolve-session-invocation.mjs +59 -0
  340. package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
  341. package/scripts/run-quality-gate.mjs +357 -197
  342. package/scripts/site-numbers.mjs +163 -5
  343. package/scripts/sweep-expired-learnings.mjs +56 -5
  344. package/scripts/sweep-expired-rules.mjs +16 -4
  345. package/scripts/validate-plugin.mjs +19 -0
  346. package/scripts/validate-wave-scope.mjs +32 -105
  347. package/scripts/vault-integration-watcher.mjs +2 -1
  348. package/scripts/vault-mirror.mjs +81 -47
  349. package/scripts/wave-scope-binding.mjs +2 -1
  350. package/skills/_shared/config-reading.md +3 -3
  351. package/skills/_shared/fleet-protocol.md +131 -0
  352. package/skills/_shared/platform-tools.md +23 -11
  353. package/skills/_shared/state-ownership.md +5 -0
  354. package/skills/autopilot/SKILL.md +47 -18
  355. package/skills/bootstrap/SKILL.md +1 -1
  356. package/skills/bootstrap/_shared-template.md +1 -1
  357. package/skills/bootstrap/fast-template.md +1 -1
  358. package/skills/bootstrap/private-contract.md +2 -1
  359. package/skills/bootstrap/public-fallback.md +6 -3
  360. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
  361. package/skills/claude-md-drift-check/SKILL.md +10 -10
  362. package/skills/claude-md-drift-check/checker.mjs +26 -18
  363. package/skills/convergence-monitoring/README.md +8 -1
  364. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  365. package/skills/convergence-monitoring/SKILL.md +15 -6
  366. package/skills/eval/SKILL.md +39 -24
  367. package/skills/eval/rubric-v1.md +1 -0
  368. package/skills/eval/rubric-v2.md +457 -0
  369. package/skills/evolve/SKILL.md +3 -3
  370. package/skills/evolve/references/evolve-analyze-mode.md +10 -6
  371. package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
  372. package/skills/gitlab-ops/SKILL.md +3 -2
  373. package/skills/memory-cleanup/SKILL.md +1 -1
  374. package/skills/navigator/SKILL.md +193 -0
  375. package/skills/navigator/references/account-check.sh +87 -0
  376. package/skills/navigator/references/fleet-decisions.mjs +231 -0
  377. package/skills/navigator/references/slot-check.sh +114 -0
  378. package/skills/npm-publish/SKILL.md +1 -1
  379. package/skills/persona-panel/SKILL.md +2 -2
  380. package/skills/persona-panel/persona-format.md +2 -2
  381. package/skills/persona-panel/presets/designer-lens.md +1 -1
  382. package/skills/persona-panel/presets/engineer-lens.md +1 -1
  383. package/skills/persona-panel/presets/pm-lens.md +1 -1
  384. package/skills/plan/mode-retro.md +19 -4
  385. package/skills/quality-gates/SKILL.md +6 -4
  386. package/skills/reconcile/SKILL.md +11 -0
  387. package/skills/remote-offload/SKILL.md +4 -2
  388. package/skills/session-end/SKILL.md +13 -16
  389. package/skills/session-end/discovery-scan.md +1 -1
  390. package/skills/session-end/learning-patterns.md +22 -12
  391. package/skills/session-end/metrics-collection.md +5 -6
  392. package/skills/session-end/phase-3-6-tail.md +55 -9
  393. package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
  394. package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
  395. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  396. package/skills/session-end/references/session-summary-template.md +1 -1
  397. package/skills/session-end/session-metrics-write.md +32 -60
  398. package/skills/session-plan/SKILL.md +17 -8
  399. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  400. package/skills/session-start/SKILL.md +6 -0
  401. package/skills/session-start/references/operations-contract.md +4 -0
  402. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  403. package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
  404. package/skills/test-audit/SKILL.md +152 -0
  405. package/skills/test-audit/references/campaign.md +111 -0
  406. package/skills/test-audit/references/mutate.sh +50 -0
  407. package/skills/ux-grill/SKILL.md +1 -1
  408. package/skills/wave-executor/SKILL.md +9 -5
  409. package/skills/wave-executor/circuit-breaker.md +2 -0
  410. package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
  411. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  412. package/skills/wave-executor/references/wave-loop-review.md +3 -5
  413. package/templates/personas/README.md +5 -5
  414. package/templates/personas/accounting-compliance.v1.md +1 -1
  415. package/templates/personas/accounting-tax-advisor.v1.md +1 -1
  416. package/templates/personas/buyer-p1-cto.v1.md +1 -1
  417. package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
  418. package/templates/personas/buyer-p3-build.v1.md +1 -1
  419. package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
  420. package/templates/personas/buyer-p5-solo.v1.md +1 -1
  421. package/templates/personas/buyer-p6-ld.v1.md +1 -1
  422. package/templates/personas/klima-ai-expert.v1.md +1 -1
  423. package/templates/personas/klima-physicist.v1.md +1 -1
  424. package/.cursor/commands/convergence-monitoring.md +0 -13
  425. package/.cursor/commands/npm-publish.md +0 -13
  426. package/pi/prompts/convergence-monitoring.md +0 -11
  427. package/pi/prompts/npm-publish.md +0 -11
@@ -14,6 +14,10 @@
14
14
  * --max-sessions=N Max iterations (1..50, default 5).
15
15
  * --max-hours=H Max wall-clock hours (0.5..24.0, default 4.0).
16
16
  * --confidence-threshold=0.X Mode confidence gate (0.0..1.0, default 0.85).
17
+ * --max-tokens=N Cumulative token budget (0..10000000, default 0 = off). Counts
18
+ * SUBAGENT output tokens only (sessions.jsonl total_token_output,
19
+ * else total_tokens — both subagent-only; coordinator tokens are
20
+ * not counted). One deep session measured ~450k.
17
21
  * --dry-run Emit a single record without spawning sessions.
18
22
  * --verbose Pipe child process stdio (instead of inherit).
19
23
  *
@@ -32,9 +36,10 @@
32
36
  * "/autopilot Loop Command (Phase C)" (#277/#271; archived in the private Meta-Vault)
33
37
  */
34
38
 
35
- import { readFileSync, existsSync } from 'node:fs';
39
+ import { randomUUID } from 'node:crypto';
36
40
  import { spawnSync, spawn } from 'node:child_process';
37
41
  import { resolve } from 'node:path';
42
+ import { fileURLToPath } from 'node:url';
38
43
 
39
44
  import {
40
45
  runLoop,
@@ -47,7 +52,7 @@ import { selectMode } from './lib/mode-selector.mjs';
47
52
  import { probe, evaluate, DEFAULT_RESOURCE_THRESHOLDS } from './lib/resource-probe.mjs';
48
53
  import { detectPeers } from './lib/session-registry.mjs';
49
54
  import { normalizeSession } from './lib/session-schema.mjs';
50
- import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
55
+ import { isBackfillStub, readCanonicalSessions } from './lib/sessions-canonical.mjs';
51
56
 
52
57
  // ---------------------------------------------------------------------------
53
58
  // CLI-level flag extraction
@@ -55,6 +60,14 @@ import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
55
60
 
56
61
  const argv = process.argv.slice(2);
57
62
 
63
+ /**
64
+ * Plugin root, resolved from THIS file's own location (`<root>/scripts/autopilot.mjs`
65
+ * → `<root>`) and never from an env var. The driver is spawned unattended from an
66
+ * arbitrary cwd, so `--plugin-dir` must name the plugin that owns this very file;
67
+ * an env-var read would point wherever the operator's shell last happened to point.
68
+ */
69
+ const PLUGIN_ROOT = resolve(fileURLToPath(new URL('.', import.meta.url)), '..');
70
+
58
71
  const hasHeadless = argv.includes('--headless');
59
72
  const hasVerbose = argv.includes('--verbose');
60
73
 
@@ -69,7 +82,7 @@ const flagsForParse = argv.filter(
69
82
  (a) => a !== '--headless' && a !== '--verbose'
70
83
  );
71
84
 
72
- const { maxSessions, maxHours, confidenceThreshold, dryRun } = parseFlags(flagsForParse);
85
+ const { maxSessions, maxHours, confidenceThreshold, maxTokens, dryRun } = parseFlags(flagsForParse);
73
86
 
74
87
  // ---------------------------------------------------------------------------
75
88
  // Session Config (parsed once, shared by thresholds + decay readers)
@@ -84,10 +97,7 @@ const { maxSessions, maxHours, confidenceThreshold, dryRun } = parseFlags(flagsF
84
97
  */
85
98
  function loadConfig() {
86
99
  try {
87
- const scriptPath = resolve(
88
- new URL('.', import.meta.url).pathname,
89
- 'parse-config.mjs'
90
- );
100
+ const scriptPath = fileURLToPath(new URL('parse-config.mjs', import.meta.url));
91
101
  const result = spawnSync(
92
102
  process.execPath,
93
103
  [scriptPath],
@@ -175,50 +185,49 @@ const branch = detectBranch();
175
185
  const SESSIONS_JSONL_PATH = resolve('.orchestrator/metrics/sessions.jsonl');
176
186
 
177
187
  /**
178
- * Set of the DISTINCT session identities currently recorded in sessions.jsonl.
179
- * Empty set when the file is missing or unreadable.
188
+ * Read the child's canonical record by its raw harness UUID, independent of
189
+ * semantic session IDs and peer append order. Zero or multiple matches throw;
190
+ * only the unique record supplies normalized fields and finite token usage.
180
191
  *
181
- * Identities, not lines (#1167): the ledger is append-only, so one physical
182
- * session can occupy two lines (an abandoned stub plus the authoritative
183
- * record that supersedes it, or the systemic double-stub pair). The
184
- * pre/post comparison below asks "did the session I just spawned record
185
- * itself?", and a raw line count answers that question wrong in BOTH
186
- * directions — a duplicate pair looks like two sessions, while a supersede
187
- * append (+1 line, −1 stub) leaves a canonical COUNT unchanged. Comparing the
188
- * id SETS is immune to both.
189
- *
190
- * @returns {Set<string>}
192
+ * A unique record that is a BACKFILL STUB throws too (#1457 F2): a stub is a
193
+ * placeholder some backfill wrote because the child never closed — no waves,
194
+ * agents or token totals. Its usage is UNKNOWN, not 0, and its empty
195
+ * agent_summary/effectiveness would blind the post-session kill-switches as
196
+ * well, so it fails closed like the zero-match case above: the loop records
197
+ * kill_switch=failed-wave with a detail naming the stub, instead of counting a
198
+ * healthy 0-token iteration that silently undercounts --max-tokens.
199
+ * @returns {{session_id: string, agent_summary?: object, effectiveness?: object,
200
+ * usage?: {output_tokens?: number, total_tokens?: number}}}
191
201
  */
192
- function readSessionIds() {
193
- if (!existsSync(SESSIONS_JSONL_PATH)) return new Set();
194
- const ids = new Set();
195
- for (const rec of readCanonicalSessions({ filePath: SESSIONS_JSONL_PATH })) {
196
- if (typeof rec.session_id === 'string' && rec.session_id.length > 0) {
197
- ids.add(rec.session_id);
198
- }
202
+ function readOwnSession(childSessionId) {
203
+ const own = readCanonicalSessions({ filePath: SESSIONS_JSONL_PATH }).filter((r) => r.raw_session_id === childSessionId);
204
+ if (own.length === 0) {
205
+ throw new Error(
206
+ `no session record carries raw_session_id=${childSessionId} (the child wrote none, or none stamped ` +
207
+ 'with its raw id) — refusing to fall back to the ledger tail (#1457)'
208
+ );
199
209
  }
200
- return ids;
201
- }
202
-
203
- /**
204
- * Read the last non-empty line from sessions.jsonl, parse it, run it through
205
- * normalizeSession, and project to the sessionRunner return shape.
206
- *
207
- * @returns {{session_id: string, agent_summary?: object, effectiveness?: object}}
208
- */
209
- function readTailSession() {
210
- const raw = readFileSync(SESSIONS_JSONL_PATH, 'utf8');
211
- const lines = raw.split('\n').filter((l) => l.trim().length > 0);
212
- if (lines.length === 0) {
213
- throw new Error('sessions.jsonl is empty after session completed');
210
+ if (own.length > 1) {
211
+ throw new Error(`ambiguous: ${own.length} canonical records carry raw_session_id=${childSessionId} (#1457)`);
214
212
  }
215
- const parsed = JSON.parse(lines[lines.length - 1]);
216
- const normalized = normalizeSession(parsed);
217
- return {
213
+ if (isBackfillStub(own[0])) {
214
+ throw new Error(
215
+ `the only record with raw_session_id=${childSessionId} is a backfill stub ` +
216
+ `(_backfill_source=${own[0]._backfill_source}, no waves/agents/token totals) — token usage unknown, ` +
217
+ 'refusing to count it as a 0-token iteration (#1457 F2)'
218
+ );
219
+ }
220
+ const normalized = normalizeSession(own[0]);
221
+ const result = {
218
222
  session_id: normalized.session_id,
219
223
  agent_summary: normalized.agent_summary,
220
224
  effectiveness: normalized.effectiveness,
221
225
  };
226
+ const usage = {};
227
+ if (Number.isFinite(normalized.total_token_output)) usage.output_tokens = normalized.total_token_output;
228
+ if (Number.isFinite(normalized.total_tokens)) usage.total_tokens = normalized.total_tokens;
229
+ if (Object.keys(usage).length > 0) result.usage = usage;
230
+ return result;
222
231
  }
223
232
 
224
233
  // ---------------------------------------------------------------------------
@@ -256,22 +265,30 @@ async function modeSelector() {
256
265
 
257
266
  /**
258
267
  * sessionRunner — spawns `claude` with the recommended mode and awaits exit.
259
- * Verifies that a new sessions.jsonl record was appended before returning.
268
+ * Sets a fresh raw harness UUID so a peer's later ledger append is never read
269
+ * as the child's record (emit-session stamps that UUID as `raw_session_id`).
270
+ * After exit 0, returns its unique canonical record; zero or multiple matches
271
+ * throw into the loop's failed-wave channel.
260
272
  *
261
273
  * @param {{mode: string, autopilotRunId: string}} args
262
274
  * @returns {Promise<{session_id: string, agent_summary?: object, effectiveness?: object}>}
263
275
  */
264
276
  async function sessionRunner({ mode, autopilotRunId }) {
265
- const preIds = readSessionIds();
277
+ const childSessionId = randomUUID();
266
278
 
267
279
  await new Promise((res, rej) => {
268
280
  const childStdio = hasVerbose
269
281
  ? ['ignore', 'pipe', 'pipe']
270
282
  : ['ignore', 'inherit', 'inherit'];
271
283
 
284
+ // `session` is a RESERVED terminal-only built-in name under `claude -p`: the
285
+ // bare `/session <mode>` form answers "/session isn't available in this
286
+ // environment." and the child exits having done nothing (measured 2026-09-16,
287
+ // claude 2.1.273 — `commands/session.md` § Headless). Only the namespaced form
288
+ // resolves, and only when the plugin that defines it is on `--plugin-dir`.
272
289
  const child = spawn(
273
290
  'claude',
274
- ['-p', `/session ${mode}`],
291
+ ['-p', `/session-orchestrator:session ${mode}`, '--plugin-dir', PLUGIN_ROOT, '--session-id', childSessionId],
275
292
  {
276
293
  env: { ...process.env, AUTOPILOT_RUN_ID: autopilotRunId },
277
294
  stdio: childStdio,
@@ -294,13 +311,7 @@ async function sessionRunner({ mode, autopilotRunId }) {
294
311
  });
295
312
  });
296
313
 
297
- const postIds = readSessionIds();
298
- const appeared = [...postIds].some((id) => !preIds.has(id));
299
- if (!appeared) {
300
- throw new Error('no session record appended');
301
- }
302
-
303
- return readTailSession();
314
+ return readOwnSession(childSessionId);
304
315
  }
305
316
 
306
317
  /**
@@ -372,12 +383,18 @@ async function main() {
372
383
  maxSessions,
373
384
  maxHours,
374
385
  confidenceThreshold,
386
+ maxTokens,
375
387
  dryRun,
376
388
  modeSelector,
377
389
  sessionRunner,
378
390
  resourceEvaluator,
379
391
  peerCounter,
380
392
  abortSignal: controller.signal,
393
+ // STALL_TIMEOUT progress marker. `autopilot.jsonl` is written ONCE per
394
+ // invocation (after the loop), so its mtime belongs to the PREVIOUS run and
395
+ // fired the kill-switch after iteration 1; the session.lock heartbeat is
396
+ // refreshed while a session is alive. See stall-sampler.mjs § PRECEDENCE.
397
+ sessionLockPath: resolve('.orchestrator/session.lock'),
381
398
  runId: autopilotRunId,
382
399
  branch: branch ?? undefined,
383
400
  });
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * backfill-abandoned-sessions.mjs — one-time historical migration CLI (#724 C1).
4
4
  *
5
- * Scans `.orchestrator/metrics/events.jsonl` for every distinct
5
+ * Scans `.orchestrator/metrics/events.jsonl` — and every rotated archive
6
+ * beside it (#1414) — for every distinct
6
7
  * `orchestrator.session.started` UUID that has no counterpart in
7
8
  * `.orchestrator/metrics/sessions.jsonl`, bridges each to its semantic id via
8
9
  * `orchestrator.session.lock.acquired` where available, and synthesizes a
@@ -41,7 +42,7 @@ import path from 'node:path';
41
42
  import { parseArgs } from 'node:util';
42
43
 
43
44
  import { backfillAbandonedSession, isUuid } from './lib/session-close-backfill.mjs';
44
- import { emitEvent } from './lib/events.mjs';
45
+ import { SCAN_CHUNK_BYTES, emitEvent, listEventSourcesNewestFirst } from './lib/events.mjs';
45
46
  import { getProjectDir } from './lib/platform.mjs';
46
47
  import { isMainModule } from './lib/is-main-module.mjs';
47
48
  import { isLockLive, readLock } from './lib/session-lock.mjs';
@@ -125,7 +126,7 @@ function readJsonl(filePath) {
125
126
  * @returns {Array<{ sessionId: string, semanticSessionId: string|null }>}
126
127
  */
127
128
  export function planSessions({ repoRoot }) {
128
- const events = readJsonl(path.join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl'));
129
+ const eventsPath = path.join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl');
129
130
 
130
131
  // Two independent UUID -> semantic bridges (#1167). `lock.acquired` is the
131
132
  // original one, but a session that LOST the lock-acquire race never emits it;
@@ -137,29 +138,130 @@ export function planSessions({ repoRoot }) {
137
138
  // lock.acquired keeps precedence — it is the older, mode-carrying attestation.
138
139
  const semanticFromLock = new Map();
139
140
  const semanticFromEnded = new Map();
140
- for (const ev of events) {
141
- if (typeof ev.session_id !== 'string' || typeof ev.semantic_session_id !== 'string') continue;
142
- if (ev.event === LOCK_ACQUIRED) semanticFromLock.set(ev.session_id, ev.semantic_session_id);
143
- else if (ev.event === SESSION_ENDED) semanticFromEnded.set(ev.session_id, ev.semantic_session_id);
141
+ const seen = new Set();
142
+ /** Distinct `session.started` ids in FIRST-SEEN (chronological) order. */
143
+ const startedIds = [];
144
+
145
+ // #1414 — the whole history, OLDEST SOURCE FIRST, streamed line by line. The
146
+ // previous `readJsonl` of the active file alone missed every session whose
147
+ // `session.started` had been rotated into `_archive/`; reading the same
148
+ // sources through `readEventsWithRotations` instead would have loaded up to
149
+ // ~60 MB into memory on a path that runs at SESSION START
150
+ // (`hooks/on-session-start.mjs` → `backfillOnSessionStart`). Reversing the
151
+ // newest-first source list keeps first-seen order chronological, exactly as
152
+ // the single-file read produced it.
153
+ for (const source of [...listEventSourcesNewestFirst({ filePath: eventsPath })].reverse()) {
154
+ forEachJsonlRecord(source.path, (ev) => {
155
+ if (!ev || typeof ev !== 'object') return;
156
+ if (typeof ev.session_id === 'string' && typeof ev.semantic_session_id === 'string') {
157
+ if (ev.event === LOCK_ACQUIRED) semanticFromLock.set(ev.session_id, ev.semantic_session_id);
158
+ else if (ev.event === SESSION_ENDED) {
159
+ semanticFromEnded.set(ev.session_id, ev.semantic_session_id);
160
+ }
161
+ }
162
+ if (ev.event !== SESSION_STARTED || typeof ev.session_id !== 'string') return;
163
+ if (seen.has(ev.session_id)) return;
164
+ seen.add(ev.session_id);
165
+ startedIds.push(ev.session_id);
166
+ });
144
167
  }
168
+
169
+ // Resolved AFTER the pass, never during it: a `lock.acquired` / `session.ended`
170
+ // bridge may sit later in the stream (or in a later source) than the
171
+ // `session.started` it names, and deciding mid-stream would mint a synthetic
172
+ // id for a session whose semantic id was two lines further on.
145
173
  const semanticByUuid = new Map([...semanticFromEnded, ...semanticFromLock]);
146
174
 
147
175
  const runningIds = readRunningSessionIds(repoRoot);
148
176
 
149
- const seen = new Set();
150
177
  const plan = [];
151
- for (const ev of events) {
152
- if (ev.event !== SESSION_STARTED || typeof ev.session_id !== 'string') continue;
153
- if (seen.has(ev.session_id)) continue;
154
- seen.add(ev.session_id);
155
- const semanticSessionId = semanticByUuid.get(ev.session_id) ?? null;
156
- if (runningIds.has(ev.session_id)
178
+ for (const sessionId of startedIds) {
179
+ const semanticSessionId = semanticByUuid.get(sessionId) ?? null;
180
+ if (runningIds.has(sessionId)
157
181
  || (semanticSessionId !== null && runningIds.has(semanticSessionId))) continue;
158
- plan.push({ sessionId: ev.session_id, semanticSessionId });
182
+ plan.push({ sessionId, semanticSessionId });
159
183
  }
160
184
  return plan;
161
185
  }
162
186
 
187
+ /**
188
+ * Stream one JSONL file, handing every PARSED record to `onRecord` in file
189
+ * order — without ever holding the file in memory (#1414).
190
+ *
191
+ * MISSING (ENOENT) → silent no-op; UNREADABLE → a stderr WARN, same split as
192
+ * {@link readJsonl} (#1188: a missing ledger is the fresh-repo case, an
193
+ * unreadable one used to read as "no records" and made every count below
194
+ * silently wrong). Malformed lines are skipped, as in the full-read path.
195
+ *
196
+ * A line split across a chunk boundary is CARRIED as a BUFFER, so neither a
197
+ * record nor a multibyte character is ever cut in half — the 0x0A byte cannot
198
+ * occur inside a UTF-8 continuation sequence.
199
+ *
200
+ * @param {string} filePath
201
+ * @param {(record: object) => void} onRecord
202
+ */
203
+ function forEachJsonlRecord(filePath, onRecord) {
204
+ const emit = (buf) => {
205
+ const text = buf.toString('utf8').trim();
206
+ if (!text) return;
207
+ try {
208
+ onRecord(JSON.parse(text));
209
+ } catch {
210
+ /* skip malformed */
211
+ }
212
+ };
213
+
214
+ let fd;
215
+ try {
216
+ fd = fs.openSync(filePath, 'r');
217
+ } catch (err) {
218
+ if (!err || err.code !== 'ENOENT') {
219
+ process.stderr.write(
220
+ `⚠ backfill-abandoned-sessions: cannot read ${filePath} ` +
221
+ `(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
222
+ 'treating as EMPTY, counts below are floors\n',
223
+ );
224
+ }
225
+ return;
226
+ }
227
+ try {
228
+ const size = fs.fstatSync(fd).size;
229
+ const buf = Buffer.alloc(SCAN_CHUNK_BYTES);
230
+ let pos = 0;
231
+ let carry = Buffer.alloc(0);
232
+ while (pos < size) {
233
+ const n = fs.readSync(fd, buf, 0, SCAN_CHUNK_BYTES, pos);
234
+ if (n <= 0) break;
235
+ pos += n;
236
+ const block =
237
+ carry.length > 0
238
+ ? Buffer.concat([carry, buf.subarray(0, n)])
239
+ : Buffer.from(buf.subarray(0, n));
240
+ let start = 0;
241
+ let idx = block.indexOf(0x0a, start);
242
+ while (idx !== -1) {
243
+ emit(block.subarray(start, idx));
244
+ start = idx + 1;
245
+ idx = block.indexOf(0x0a, start);
246
+ }
247
+ carry = block.subarray(start);
248
+ }
249
+ if (carry.length > 0) emit(carry);
250
+ } catch (err) {
251
+ process.stderr.write(
252
+ `⚠ backfill-abandoned-sessions: cannot read ${filePath} ` +
253
+ `(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
254
+ 'treating as EMPTY, counts below are floors\n',
255
+ );
256
+ } finally {
257
+ try {
258
+ fs.closeSync(fd);
259
+ } catch {
260
+ /* best-effort */
261
+ }
262
+ }
263
+ }
264
+
163
265
  /**
164
266
  * The id(s) naming the session THIS process is, when that session holds the
165
267
  * live lock — else the empty set (#1376). Never throws: an unreadable lock,
@@ -551,7 +653,7 @@ async function main() {
551
653
 
552
654
  // --apply is an explicit opt-in; absent it (or with --dry-run) we never write.
553
655
  const apply = values.apply === true && values['dry-run'] !== true;
554
- const repoRoot = values['repo-root'] || getProjectDir();
656
+ const repoRoot = path.resolve(values['repo-root'] || getProjectDir());
555
657
 
556
658
  let assumeDeadBeforeMs = null;
557
659
  if (typeof values['assume-dead-before'] === 'string' && values['assume-dead-before'].length > 0) {
@@ -10,7 +10,7 @@ export async function main(argv = process.argv.slice(2)) {
10
10
  for (let index = 0; index < argv.length; index++) {
11
11
  const arg = argv[index];
12
12
  if (arg === '--help' || arg === '-h') {
13
- process.stdout.write('Usage: node scripts/baseline-archetypes.mjs [--repo PATH] [--archetype ID]\nOffline, read-only JSON lookup. Exit 0: public/private; exit 2: invalid configuration or contract.\n');
13
+ process.stdout.write('Usage: node scripts/baseline-archetypes.mjs [--repo PATH] [--archetype ID]\nOffline, read-only JSON lookup. Exit 0: public/private; exit 2: invalid configuration or contract.\nExit 2 reason producer-missing: the baseline directory exists but has no scripts/archetype-manifest.mjs exporter.\n');
14
14
  return 0;
15
15
  }
16
16
  if (!['--repo', '--archetype'].includes(arg) || !argv[index + 1] || argv[index + 1].startsWith('--')) {
@@ -25,6 +25,11 @@
25
25
  // (warn locally; upgraded to a hard failure under CI)
26
26
  // c1 pnpm-lock.yaml must NOT be tracked in git
27
27
  // c2 package-lock.json MUST be tracked in git
28
+ // 3. Husky activation (#1458, advisory only — never affects `ok`/exit code):
29
+ // h1 `.husky/` exists but git core.hooksPath is not `.husky/_` — the
30
+ // `prepare: husky` script never ran because ignore-scripts=true,
31
+ // so commit-msg/pre-commit/pre-push are inactive. Warns locally
32
+ // and recommends `npx husky`; silent under CI.
28
33
  //
29
34
  // SCOPE GUARD:
30
35
  // This script only guards the REPOSITORY ROOT package.json. If invoked
@@ -64,8 +69,8 @@
64
69
  // on any dependency install).
65
70
 
66
71
  import { existsSync, lstatSync, readFileSync } from 'node:fs';
67
- import { dirname, join, resolve } from 'node:path';
68
- import { spawnSync } from 'node:child_process';
72
+ import { dirname, isAbsolute, join, posix, resolve } from 'node:path';
73
+ import { execFileSync, spawnSync } from 'node:child_process';
69
74
  import { fileURLToPath } from 'node:url';
70
75
  import { isMainModule } from './lib/is-main-module.mjs';
71
76
 
@@ -266,11 +271,53 @@ export function parseCiEnv(value) {
266
271
  return !['', '0', 'false', 'no'].includes(normalized);
267
272
  }
268
273
 
274
+ /** One-line stderr WARN for an inactive Husky install (#1458). */
275
+ export const HUSKY_INACTIVE_WARNING =
276
+ '⚠ check-package-manager: git core.hooksPath is not set to .husky/_ — Husky hooks (commit-msg, pre-commit, pre-push) are INACTIVE in this clone because .npmrc ignore-scripts=true skips the prepare script (#1458). Run: npx husky';
277
+
278
+ /**
279
+ * Advisory check (#1458): `.npmrc` `ignore-scripts=true` (SEC-020) means the
280
+ * `prepare: husky` script never runs on `npm ci`, so a fresh clone has no
281
+ * `core.hooksPath` and every Husky hook is silently inactive. This only
282
+ * REPORTS the state — it never runs `npx husky`, never writes git config and
283
+ * never affects the guard's `ok`/exit code. Silent under CI.
284
+ * @param {string} repoRoot
285
+ * @param {{ env?: NodeJS.ProcessEnv, ci?: boolean }} [opts] — `env` is also
286
+ * passed to the git subprocess (lets tests isolate from global git config).
287
+ * @returns {{ hooksPath: string, active: boolean, skipped: 'ci'|'no-husky-dir'|null }}
288
+ */
289
+ export function checkHuskyHooksPath(repoRoot, { env = process.env, ci } = {}) {
290
+ if (ci ?? parseCiEnv(env.CI)) return { hooksPath: '', active: false, skipped: 'ci' };
291
+ if (!existsSync(join(repoRoot, '.husky'))) {
292
+ return { hooksPath: '', active: false, skipped: 'no-husky-dir' };
293
+ }
294
+
295
+ let hooksPath;
296
+ try {
297
+ hooksPath = execFileSync('git', ['-C', repoRoot, 'config', '--get', 'core.hooksPath'], {
298
+ encoding: 'utf8',
299
+ env,
300
+ stdio: ['ignore', 'pipe', 'ignore'],
301
+ }).trim();
302
+ } catch {
303
+ // Non-zero exit (key unset) or git unavailable — treat as unset.
304
+ hooksPath = '';
305
+ }
306
+
307
+ const norm = hooksPath.replace(/\\/g, '/').replace(/\/+$/, '');
308
+ const active =
309
+ norm !== '' &&
310
+ (isAbsolute(hooksPath) ? norm.endsWith('/.husky/_') : posix.normalize(norm) === '.husky/_');
311
+
312
+ return { hooksPath, active, skipped: null };
313
+ }
314
+
269
315
  /**
270
316
  * Runs the full guard and returns a plain-object result. Pure(ish) — the
271
317
  * only side effects are read-only fs/git calls. Never throws or exits.
272
- * @param {{ cwd?: string, ci?: boolean, allowMissingNodeModules?: boolean, repoRoot?: string }} [opts]
273
- * @returns {{ ok: boolean, ci: boolean, skipped: boolean, repoRoot: string, findings: Array<{code: string, severity: string, effectiveSeverity: string, message: string}> }}
318
+ * `hooks` carries the advisory Husky check (#1458); it never affects `ok`.
319
+ * @param {{ cwd?: string, ci?: boolean, allowMissingNodeModules?: boolean, repoRoot?: string, env?: NodeJS.ProcessEnv }} [opts]
320
+ * @returns {{ ok: boolean, ci: boolean, skipped: boolean, repoRoot: string, findings: Array<{code: string, severity: string, effectiveSeverity: string, message: string}>, hooks?: { hooksPath: string, active: boolean, skipped: 'ci'|'no-husky-dir'|null } }}
274
321
  */
275
322
  export function runPackageManagerGuard(opts = {}) {
276
323
  const cwd = opts.cwd ?? process.cwd();
@@ -302,7 +349,9 @@ export function runPackageManagerGuard(opts = {}) {
302
349
 
303
350
  const ok = !findings.some((f) => f.effectiveSeverity === 'error');
304
351
 
305
- return { ok, ci, skipped: false, repoRoot, findings };
352
+ const hooks = checkHuskyHooksPath(repoRoot, { env: opts.env ?? process.env, ci });
353
+
354
+ return { ok, ci, skipped: false, repoRoot, findings, hooks };
306
355
  }
307
356
 
308
357
  // ---------------------------------------------------------------------------
@@ -332,6 +381,9 @@ CHECKS
332
381
  (warn locally; upgraded to a hard failure under CI, fail-closed)
333
382
  c1 pnpm-lock.yaml must NOT be tracked in git
334
383
  c2 package-lock.json MUST be tracked in git
384
+ Husky activation (advisory, never changes the exit code; silent under CI):
385
+ h1 .husky/ exists but git core.hooksPath is not .husky/_ (#1458) —
386
+ run \`npx husky\` once (ignore-scripts=true skips \`prepare\`)
335
387
 
336
388
  OPTIONS
337
389
  --allow-missing-node-modules Document that node_modules may not exist yet
@@ -412,6 +464,11 @@ if (isMain) {
412
464
 
413
465
  const result = runPackageManagerGuard({ allowMissingNodeModules: opts.allowMissingNodeModules });
414
466
 
467
+ // Advisory only (#1458) — never changes the exit code.
468
+ if (result.hooks && !result.hooks.active && result.hooks.skipped === null) {
469
+ console.error(HUSKY_INACTIVE_WARNING);
470
+ }
471
+
415
472
  if (opts.json) {
416
473
  console.log(JSON.stringify(result));
417
474
  process.exit(result.ok ? 0 : 1);