session-orchestrator 3.24.0 → 4.0.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 (350) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1125 -2
  81. package/NOTICE +11 -6
  82. package/README.md +127 -94
  83. package/agents/eval-judge.md +1 -1
  84. package/agents/skill-applied-judge.md +1 -1
  85. package/assets/wave-lifecycle.svg +98 -0
  86. package/commands/release.md +6 -3
  87. package/commands/session.md +18 -3
  88. package/docs/README.md +4 -0
  89. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  90. package/docs/baseline.md +67 -0
  91. package/docs/ci-setup.md +108 -62
  92. package/docs/codex-setup.md +65 -21
  93. package/docs/components.md +36 -15
  94. package/docs/cursor-setup.md +6 -2
  95. package/docs/events-schema.md +9 -6
  96. package/docs/instruction-delivery.md +62 -0
  97. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  98. package/docs/migration-v4.md +341 -0
  99. package/docs/pi-setup.md +6 -1
  100. package/docs/plugin-architecture-v3.md +1 -1
  101. package/docs/rule-authoring.md +85 -19
  102. package/docs/scope-collision-guard.md +5 -5
  103. package/docs/session-config-reference.md +57 -56
  104. package/docs/session-config-template.md +6 -29
  105. package/docs/telemetry.md +157 -3
  106. package/docs/vault-docs-architecture.md +50 -11
  107. package/hooks/_lib/hook-import-set.json +1487 -0
  108. package/hooks/_lib/subagent-transcript.mjs +562 -0
  109. package/hooks/config-protection.mjs +2 -2
  110. package/hooks/cwd-change-restore.mjs +2 -2
  111. package/hooks/enforce-commands.mjs +69 -0
  112. package/hooks/hooks-codex.json +1 -1
  113. package/hooks/hooks-cursor.json +10 -0
  114. package/hooks/hooks-pi.json +5 -0
  115. package/hooks/hooks.json +6 -1
  116. package/hooks/loop-guard.mjs +3 -3
  117. package/hooks/on-session-end.mjs +2 -2
  118. package/hooks/on-session-start.mjs +103 -2
  119. package/hooks/on-stop.mjs +36 -11
  120. package/hooks/operator-steer.mjs +2 -2
  121. package/hooks/post-bash-write-verify.mjs +85 -0
  122. package/hooks/post-edit-import-probe.mjs +344 -0
  123. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  124. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  125. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  126. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  127. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  128. package/hooks/skill-invocation-telemetry.mjs +17 -5
  129. package/hooks/subagent-telemetry.mjs +13 -4
  130. package/monitors/monitors.json +3 -3
  131. package/package.json +9 -1
  132. package/pi/prompts/session.md +2 -2
  133. package/plugin.json +27 -0
  134. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  135. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  136. package/scripts/dialectic-deriver.mjs +73 -8
  137. package/scripts/export-hw-learnings.mjs +113 -1
  138. package/scripts/generate-agents-skills.mjs +378 -0
  139. package/scripts/generate-cursor-adapter.mjs +45 -8
  140. package/scripts/generate-hook-import-set.mjs +249 -0
  141. package/scripts/lib/agent-status.mjs +13 -2
  142. package/scripts/lib/auto-dream.mjs +38 -36
  143. package/scripts/lib/autonomy/suitability.mjs +6 -0
  144. package/scripts/lib/autopilot/loop.mjs +2 -2
  145. package/scripts/lib/ci-status-banner.mjs +220 -75
  146. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  147. package/scripts/lib/config/auto-dream.mjs +2 -1
  148. package/scripts/lib/config/block-header.mjs +8 -0
  149. package/scripts/lib/config/block-preprocess.mjs +177 -0
  150. package/scripts/lib/config/broken-window.mjs +2 -1
  151. package/scripts/lib/config/cold-start.mjs +2 -1
  152. package/scripts/lib/config/config-protection.mjs +22 -2
  153. package/scripts/lib/config/context-coverage.mjs +2 -1
  154. package/scripts/lib/config/cross-repo.mjs +2 -1
  155. package/scripts/lib/config/custom-phases.mjs +2 -1
  156. package/scripts/lib/config/dialectic.mjs +2 -1
  157. package/scripts/lib/config/discovery-validator.mjs +2 -1
  158. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  159. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  160. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  161. package/scripts/lib/config/docs-staleness.mjs +2 -1
  162. package/scripts/lib/config/drift-check.mjs +2 -1
  163. package/scripts/lib/config/eval.mjs +2 -1
  164. package/scripts/lib/config/events-rotation.mjs +2 -1
  165. package/scripts/lib/config/evolve.mjs +8 -2
  166. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  167. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  168. package/scripts/lib/config/handover-gate.mjs +2 -1
  169. package/scripts/lib/config/health-endpoints.mjs +7 -2
  170. package/scripts/lib/config/issue-budget.mjs +2 -1
  171. package/scripts/lib/config/loop-guard.mjs +2 -1
  172. package/scripts/lib/config/memory.mjs +2 -1
  173. package/scripts/lib/config/moc-staleness.mjs +2 -1
  174. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  175. package/scripts/lib/config/private-config-dir.mjs +67 -0
  176. package/scripts/lib/config/reconcile.mjs +2 -1
  177. package/scripts/lib/config/remote-hosts.mjs +2 -1
  178. package/scripts/lib/config/section-extractor.mjs +7 -1
  179. package/scripts/lib/config/skill-evolution.mjs +2 -1
  180. package/scripts/lib/config/slopcheck.mjs +2 -1
  181. package/scripts/lib/config/state-md-lock.mjs +2 -1
  182. package/scripts/lib/config/templates-first.mjs +2 -1
  183. package/scripts/lib/config/test.mjs +2 -1
  184. package/scripts/lib/config/vault-integration.mjs +7 -1
  185. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  186. package/scripts/lib/config/vault-staleness.mjs +2 -1
  187. package/scripts/lib/config/vault-sync.mjs +2 -1
  188. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  189. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  190. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  191. package/scripts/lib/convergence-monitor.mjs +82 -16
  192. package/scripts/lib/dispatcher/rank.mjs +124 -48
  193. package/scripts/lib/ecosystem-health.mjs +16 -2
  194. package/scripts/lib/eval/engine.mjs +9 -1
  195. package/scripts/lib/eval/session-resolve.mjs +23 -4
  196. package/scripts/lib/events.mjs +22 -6
  197. package/scripts/lib/frontmatter-guard.mjs +131 -13
  198. package/scripts/lib/gates/gate-full.mjs +26 -0
  199. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  200. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  201. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  202. package/scripts/lib/host-identity.mjs +50 -11
  203. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  204. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  205. package/scripts/lib/learnings/io.mjs +60 -6
  206. package/scripts/lib/memory-proposals/store.mjs +30 -22
  207. package/scripts/lib/owner-config-banner.mjs +43 -6
  208. package/scripts/lib/owner-config-loader.mjs +21 -10
  209. package/scripts/lib/owner-interview.mjs +3 -3
  210. package/scripts/lib/owner-yaml.mjs +207 -14
  211. package/scripts/lib/platform.mjs +108 -15
  212. package/scripts/lib/plugin-update-banner.mjs +406 -0
  213. package/scripts/lib/project-hygiene.mjs +38 -2
  214. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  215. package/scripts/lib/quality-gate.mjs +133 -44
  216. package/scripts/lib/reconcile/emitter.mjs +68 -6
  217. package/scripts/lib/reconcile/engine.mjs +13 -4
  218. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  219. package/scripts/lib/reconcile/writer.mjs +40 -18
  220. package/scripts/lib/session-close-backfill.mjs +67 -9
  221. package/scripts/lib/session-id.mjs +12 -23
  222. package/scripts/lib/session-identity/own-session.mjs +125 -10
  223. package/scripts/lib/session-lock-shape.mjs +43 -0
  224. package/scripts/lib/session-lock.mjs +5 -10
  225. package/scripts/lib/session-registry.mjs +25 -9
  226. package/scripts/lib/session-schema/constants.mjs +36 -2
  227. package/scripts/lib/session-schema/validator.mjs +38 -4
  228. package/scripts/lib/session-start-probes.mjs +18 -1
  229. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  230. package/scripts/lib/skill-health/join.mjs +17 -4
  231. package/scripts/lib/state-md.mjs +78 -0
  232. package/scripts/lib/sunset/walker.mjs +6 -0
  233. package/scripts/lib/telemetry/schema.mjs +181 -9
  234. package/scripts/lib/telemetry/sync.mjs +368 -12
  235. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  236. package/scripts/lib/validate/check-agents.mjs +3 -3
  237. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  238. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  239. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  240. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  241. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  242. package/scripts/lib/validate/check-unwired-features.mjs +0 -2
  243. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  244. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  245. package/scripts/lib/vault-backfill/template.mjs +63 -6
  246. package/scripts/lib/vault-mirror/process.mjs +165 -42
  247. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  248. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  249. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  250. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  251. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  252. package/scripts/lib/wave-resource-gate.mjs +8 -2
  253. package/scripts/lib/wave-sizing.mjs +4 -1
  254. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  255. package/scripts/materialize-wave-scope.mjs +12 -5
  256. package/scripts/memory-propose.mjs +19 -5
  257. package/scripts/migrate-cold-start-seed.mjs +4 -1
  258. package/scripts/parse-config.mjs +60 -3
  259. package/scripts/release.mjs +337 -29
  260. package/scripts/repair-invalid-sessions.mjs +3 -3
  261. package/scripts/run-quality-gate.mjs +128 -11
  262. package/scripts/sweep-expired-learnings.mjs +90 -0
  263. package/scripts/sync-vault-schema.mjs +3 -1
  264. package/scripts/telemetry.mjs +2 -2
  265. package/scripts/validate-plugin.mjs +161 -0
  266. package/scripts/validate-wave-scope.mjs +28 -8
  267. package/scripts/wave-scope-binding.mjs +215 -0
  268. package/skills/_shared/instruction-file-resolution.md +10 -0
  269. package/skills/_shared/parallel-aware-preamble.md +1 -0
  270. package/skills/_shared/platform-tools.md +1 -1
  271. package/skills/_shared/state-ownership.md +1 -1
  272. package/skills/architecture/SKILL.md +7 -5
  273. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  274. package/skills/autopilot/SKILL.md +4 -18
  275. package/skills/claude-md-drift-check/SKILL.md +5 -1
  276. package/skills/claude-md-drift-check/checker.mjs +62 -2
  277. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  278. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  279. package/skills/discovery/probes-arch.md +20 -18
  280. package/skills/dispatcher/SKILL.md +3 -2
  281. package/skills/evolve/SKILL.md +65 -26
  282. package/skills/frontmatter-guard/SKILL.md +11 -5
  283. package/skills/npm-publish/SKILL.md +1 -1
  284. package/skills/reconcile/SKILL.md +33 -0
  285. package/skills/remote-offload/SKILL.md +1 -1
  286. package/skills/session-end/SKILL.md +18 -905
  287. package/skills/session-end/phase-3-6-tail.md +10 -3
  288. package/skills/session-end/plan-verification.md +221 -155
  289. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  290. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  291. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  292. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  293. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  294. package/skills/session-end/references/session-summary-template.md +62 -0
  295. package/skills/session-plan/SKILL.md +49 -0
  296. package/skills/session-start/SKILL.md +22 -904
  297. package/skills/session-start/phase-8-5-express-path.md +1 -1
  298. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  299. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  300. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  301. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  302. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  303. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  304. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  305. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  306. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  307. package/skills/vault-sync/validator.mjs +21 -27
  308. package/skills/wave-executor/SKILL.md +15 -1
  309. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  310. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  311. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  312. package/skills/wave-executor/wave-loop.md +14 -1309
  313. package/templates/_shared/journey-manifest.md +10 -6
  314. package/.cursor/commands/autopilot-multi.md +0 -14
  315. package/.cursor/commands/contract-version-bump.md +0 -14
  316. package/.cursor/commands/journey-audit.md +0 -14
  317. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  318. package/.cursor/skills/daily/SKILL.md +0 -12
  319. package/.cursor/skills/domain-model/SKILL.md +0 -13
  320. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  321. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  322. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  323. package/commands/autopilot-multi.md +0 -74
  324. package/commands/contract-version-bump.md +0 -28
  325. package/commands/journey-audit.md +0 -43
  326. package/pi/prompts/autopilot-multi.md +0 -12
  327. package/pi/prompts/contract-version-bump.md +0 -12
  328. package/pi/prompts/journey-audit.md +0 -12
  329. package/scripts/autopilot-multi.mjs +0 -885
  330. package/scripts/backfill-learnings-expires.mjs +0 -196
  331. package/scripts/backfill-learnings.mjs +0 -203
  332. package/scripts/fleet-instruction-scan.mjs +0 -141
  333. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  334. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  335. package/scripts/lib/webhook-url.mjs +0 -105
  336. package/scripts/lifecycle-sim-v6.mjs +0 -347
  337. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  338. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  339. package/scripts/upload-social-preview.mjs +0 -316
  340. package/skills/_shared/model-selection.md +0 -64
  341. package/skills/contract-version-bump/SKILL.md +0 -219
  342. package/skills/daily/SKILL.md +0 -222
  343. package/skills/daily/generate.sh +0 -92
  344. package/skills/daily/templates/daily.md.tpl +0 -36
  345. package/skills/journey-audit/SKILL.md +0 -270
  346. package/skills/skill-creator/SKILL.md +0 -168
  347. package/skills/ubiquitous-language/SKILL.md +0 -97
  348. package/skills/vault-sync/package-lock.json +0 -40
  349. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  350. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * generate-hook-import-set.mjs — build the committed allowlist of modules that
4
+ * are transitively reachable from a hook entry file (GitLab #1224).
5
+ *
6
+ * WHY: a hook entry file imports a helper module on EVERY tool call. A helper
7
+ * saved in a broken intermediate state (2026-09-04: a call to a function that
8
+ * did not exist yet) turns every Bash/Edit/Write call into "Internal hook error
9
+ * — request blocked", host-wide, for every session sharing the working copy.
10
+ * `hooks/post-edit-import-probe.mjs` checks a file right after it is edited —
11
+ * but only when the file is in THIS set, so the probe stays cheap.
12
+ *
13
+ * The set is a COMMITTED artefact (`hooks/_lib/hook-import-set.json`) rather
14
+ * than a runtime crawl: the probe runs inside a 5 s PostToolUse budget and must
15
+ * not walk 142 modules per edit. `--check` re-generates and diffs, which is the
16
+ * drift guard that keeps the committed copy honest.
17
+ *
18
+ * Usage:
19
+ * node scripts/generate-hook-import-set.mjs [--plugin-root <dir>] [--out <file>] [--check]
20
+ *
21
+ * Exit codes: 0 = written / in sync; 1 = drift (with --check) or usage error.
22
+ */
23
+
24
+ import { readFileSync, writeFileSync, existsSync, statSync, realpathSync } from 'node:fs';
25
+ import path from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+ import { execFileSync } from 'node:child_process';
28
+
29
+ /** The four hook manifests whose entry files seed the crawl. */
30
+ export const HOOK_MANIFESTS = [
31
+ 'hooks.json',
32
+ 'hooks-codex.json',
33
+ 'hooks-cursor.json',
34
+ 'hooks-pi.json',
35
+ ];
36
+
37
+ /** Module extensions the crawl resolves an extension-less specifier against. */
38
+ const RESOLVE_EXTS = ['.mjs', '.js', '.cjs'];
39
+
40
+ /**
41
+ * Collect every `<something>.mjs` path mentioned in a hook manifest's command
42
+ * strings. The manifests differ per platform ($CLAUDE_PLUGIN_ROOT /
43
+ * $PI_PLUGIN_ROOT / ${PLUGIN_ROOT}), so match on the hooks/-relative tail
44
+ * rather than on any one variable spelling.
45
+ *
46
+ * @param {string} json - raw manifest contents
47
+ * @returns {string[]} plugin-root-relative entry paths (e.g. "hooks/enforce-scope.mjs")
48
+ */
49
+ export function extractEntryFiles(json) {
50
+ const out = new Set();
51
+ for (const m of json.matchAll(/hooks\/((?:[\w.-]+\/)*[\w.-]+\.mjs)/g)) {
52
+ const rel = `hooks/${m[1]}`;
53
+ // run-node.sh is the launcher, never an entry module; .../hooks/_lib/* is
54
+ // reached through imports, not through a manifest command.
55
+ out.add(rel);
56
+ }
57
+ return [...out].sort();
58
+ }
59
+
60
+ /**
61
+ * Extract relative module specifiers (static import/export-from and dynamic
62
+ * `import()`) from a module's source.
63
+ *
64
+ * @param {string} src
65
+ * @returns {string[]} relative specifiers, e.g. ["./_lib/profile-gate.mjs"]
66
+ */
67
+ export function extractRelativeSpecifiers(src) {
68
+ const out = new Set();
69
+ const patterns = [
70
+ /(?:^|[\s;}])(?:import|export)\s[^;'"]*?\sfrom\s*['"](\.[^'"]+)['"]/g,
71
+ /(?:^|[\s;}])import\s*['"](\.[^'"]+)['"]/g,
72
+ /\bimport\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g,
73
+ ];
74
+ for (const re of patterns) {
75
+ for (const m of src.matchAll(re)) out.add(m[1]);
76
+ }
77
+ return [...out];
78
+ }
79
+
80
+ /**
81
+ * Resolve a relative specifier against the importing file.
82
+ *
83
+ * @param {string} fromFile - absolute path of the importing module
84
+ * @param {string} spec - relative specifier
85
+ * @returns {string|null} absolute path of an existing file, or null
86
+ */
87
+ function resolveSpecifier(fromFile, spec) {
88
+ const base = path.resolve(path.dirname(fromFile), spec);
89
+ const candidates = [base, ...RESOLVE_EXTS.map((e) => base + e),
90
+ ...RESOLVE_EXTS.map((e) => path.join(base, `index${e}`))];
91
+ for (const c of candidates) {
92
+ try {
93
+ if (existsSync(c) && statSync(c).isFile()) return c;
94
+ } catch { /* unreadable → not a resolution */ }
95
+ }
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Crawl every hook entry file transitively and return the reachable module set.
101
+ *
102
+ * @param {string} pluginRoot
103
+ * @returns {{file: string, reachable_from: string[]}[]} sorted by `file`
104
+ */
105
+ export function buildImportSet(pluginRoot) {
106
+ /** @type {Map<string, Set<string>>} rel path → set of hook entry basenames */
107
+ const reach = new Map();
108
+
109
+ const entries = new Set();
110
+ for (const manifest of HOOK_MANIFESTS) {
111
+ const abs = path.join(pluginRoot, 'hooks', manifest);
112
+ if (!existsSync(abs)) continue;
113
+ for (const rel of extractEntryFiles(readFileSync(abs, 'utf8'))) {
114
+ if (existsSync(path.join(pluginRoot, rel))) entries.add(rel);
115
+ }
116
+ }
117
+
118
+ for (const entryRel of [...entries].sort()) {
119
+ const entryName = path.basename(entryRel);
120
+ const stack = [path.join(pluginRoot, entryRel)];
121
+ const seen = new Set();
122
+ while (stack.length > 0) {
123
+ const abs = stack.pop();
124
+ if (seen.has(abs)) continue;
125
+ seen.add(abs);
126
+
127
+ const rel = path.relative(pluginRoot, abs).split(path.sep).join('/');
128
+ if (!reach.has(rel)) reach.set(rel, new Set());
129
+ reach.get(rel).add(entryName);
130
+
131
+ let src;
132
+ try { src = readFileSync(abs, 'utf8'); } catch { continue; }
133
+ for (const spec of extractRelativeSpecifiers(src)) {
134
+ const next = resolveSpecifier(abs, spec);
135
+ if (next) stack.push(next);
136
+ }
137
+ }
138
+ }
139
+
140
+ return [...reach.entries()]
141
+ .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
142
+ .map(([file, from]) => ({ file, reachable_from: [...from].sort() }));
143
+ }
144
+
145
+ /**
146
+ * Current HEAD sha of the plugin root, or "unknown" outside a git checkout.
147
+ *
148
+ * @param {string} pluginRoot
149
+ * @returns {string}
150
+ */
151
+ function headSha(pluginRoot) {
152
+ try {
153
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
154
+ cwd: pluginRoot,
155
+ encoding: 'utf8',
156
+ stdio: ['ignore', 'pipe', 'ignore'],
157
+ }).trim();
158
+ } catch {
159
+ return 'unknown';
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Build the full artefact payload.
165
+ *
166
+ * @param {string} pluginRoot
167
+ * @returns {{generated_at: string, head: string, entries: {file: string, reachable_from: string[]}[]}}
168
+ */
169
+ export function buildArtifact(pluginRoot) {
170
+ return {
171
+ generated_at: new Date().toISOString(),
172
+ head: headSha(pluginRoot),
173
+ entries: buildImportSet(pluginRoot),
174
+ };
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // CLI
179
+ // ---------------------------------------------------------------------------
180
+
181
+ /**
182
+ * @param {string[]} argv
183
+ * @returns {number} process exit code
184
+ */
185
+ export function main(argv) {
186
+ const args = argv.slice(2);
187
+ const flag = (name) => {
188
+ const i = args.indexOf(name);
189
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : null;
190
+ };
191
+ const pluginRoot = path.resolve(flag('--plugin-root') ?? path.resolve(import.meta.dirname, '..'));
192
+ const out = path.resolve(flag('--out') ?? path.join(pluginRoot, 'hooks', '_lib', 'hook-import-set.json'));
193
+ const check = args.includes('--check');
194
+
195
+ const fresh = buildArtifact(pluginRoot);
196
+
197
+ if (check) {
198
+ if (!existsSync(out)) {
199
+ process.stderr.write(`✗ hook-import-set: ${path.relative(pluginRoot, out)} is missing — run generate-hook-import-set.mjs\n`);
200
+ return 1;
201
+ }
202
+ let committed;
203
+ try { committed = JSON.parse(readFileSync(out, 'utf8')); } catch {
204
+ process.stderr.write('✗ hook-import-set: committed file is not valid JSON\n');
205
+ return 1;
206
+ }
207
+ // Only `entries` is compared — generated_at/head are provenance, not content.
208
+ const a = JSON.stringify(committed.entries ?? null);
209
+ const b = JSON.stringify(fresh.entries);
210
+ if (a !== b) {
211
+ process.stderr.write('✗ hook-import-set: committed set differs from a fresh crawl — run `node scripts/generate-hook-import-set.mjs`\n');
212
+ return 1;
213
+ }
214
+ process.stdout.write(`✓ hook-import-set: ${fresh.entries.length} modules, in sync\n`);
215
+ return 0;
216
+ }
217
+
218
+ writeFileSync(out, JSON.stringify(fresh, null, 2) + '\n', 'utf8');
219
+ process.stdout.write(`✓ hook-import-set: wrote ${fresh.entries.length} modules to ${path.relative(pluginRoot, out)}\n`);
220
+ return 0;
221
+ }
222
+
223
+ /**
224
+ * True when this module was launched as the process entry script.
225
+ *
226
+ * A string compare of `import.meta.url` against `file://${process.argv[1]}` is
227
+ * fragile in exactly the invocations CI and husky use: any symlinked or
228
+ * realpath-differing path (measured: `/tmp` → `/private/tmp` on macOS) makes
229
+ * the compare false, so the whole CLI body becomes a silent no-op that still
230
+ * exits 0 — a fail-OPEN drift gate. A space or `#` in the path breaks the
231
+ * hand-built URL the same way. Same shape as `hooks/post-bash-write-verify.mjs`.
232
+ *
233
+ * @returns {boolean}
234
+ */
235
+ function invokedAsScript() {
236
+ const entry = process.argv[1];
237
+ if (!entry) return false;
238
+ const self = fileURLToPath(import.meta.url);
239
+ try {
240
+ return realpathSync(entry) === realpathSync(self);
241
+ } catch {
242
+ // argv[1] unresolvable (deleted/renamed mid-run) — best-effort raw compare.
243
+ return entry === self;
244
+ }
245
+ }
246
+
247
+ if (invokedAsScript()) {
248
+ process.exit(main(process.argv));
249
+ }
@@ -166,7 +166,11 @@ function releaseLock(lockFile, _myBody) {
166
166
  }
167
167
 
168
168
  /**
169
- * Read the current LWW map from disk. Returns {} on miss or parse error.
169
+ * Read the current LWW map from disk. Returns {} on a missing file (ENOENT)
170
+ * silently; an unreadable/unparseable one (EACCES/EISDIR/malformed JSON/…)
171
+ * also returns {} but with a stderr WARN (#1210 — ENOENT and other failures
172
+ * are different facts, same split as `sessions-canonical.mjs`
173
+ * `readCanonicalSessions`).
170
174
  * @param {string} currentFile
171
175
  * @returns {Record<string, object>}
172
176
  */
@@ -176,7 +180,14 @@ function readCurrentMap(currentFile) {
176
180
  const obj = JSON.parse(raw);
177
181
  if (obj && typeof obj === 'object' && !Array.isArray(obj)) return obj;
178
182
  return {};
179
- } catch {
183
+ } catch (err) {
184
+ if (!err || err.code !== 'ENOENT') {
185
+ process.stderr.write(
186
+ `⚠ readCurrentMap: cannot read ${currentFile} ` +
187
+ `(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
188
+ 'treating as EMPTY, counts below are floors\n',
189
+ );
190
+ }
180
191
  return {};
181
192
  }
182
193
  }
@@ -25,6 +25,7 @@ import { existsSync, statSync } from 'node:fs';
25
25
  import { randomUUID } from 'node:crypto';
26
26
  import path from 'node:path';
27
27
 
28
+ import { readCanonicalSessions } from './sessions-canonical.mjs';
28
29
  import { filterRealSessions } from './session-schema.mjs';
29
30
 
30
31
  // ---------------------------------------------------------------------------
@@ -61,49 +62,50 @@ export async function readDreamSignals({ repoRoot, memoryDir }) {
61
62
  }
62
63
 
63
64
  // 2. Read sessions.jsonl entries to compute lastCleanupAt + sessionsSinceCleanup
65
+ //
66
+ // #1209: migrated from a hand-rolled parse loop to `readCanonicalSessions`
67
+ // — the raw file is APPEND-ONLY (a physical session can carry more than one
68
+ // LINE: crash-recovery re-appends, #1068 stub/supersede pairs), and the
69
+ // hand-rolled loop counted every line. `sessionsSinceCleanup` is a CADENCE
70
+ // count ("how many sessions since the last cleanup"), so counting a
71
+ // duplicated line twice inflated it — this migration LOWERS the count,
72
+ // which is the fix, not a side effect. `canonicalizeSessions` also drops
73
+ // lines with no `session_id` (they cannot be deduplicated, so they were
74
+ // never a countable "session" to begin with); the existing fixtures below
75
+ // were given synthetic `session_id` values so their asserted counts still
76
+ // reflect the same intended scenario post-migration.
64
77
  let lastCleanupAt = null;
65
78
  let sessionsSinceCleanup = 0;
66
79
 
67
- if (existsSync(sessionsFilePath)) {
68
- const raw = await readFile(sessionsFilePath, 'utf8');
69
- const lines = raw.split('\n').filter((l) => l.length > 0);
70
- const entries = [];
71
- for (const line of lines) {
72
- try {
73
- entries.push(JSON.parse(line));
74
- } catch {
75
- // Malformed line — skip silently, this is a best-effort signal reader.
80
+ const entries = readCanonicalSessions({ filePath: sessionsFilePath });
81
+
82
+ // Abandoned-session filter (#834): sessions.jsonl carries phantom
83
+ // `status: 'abandoned'` stubs (session-close-backfill records for
84
+ // sessions that ended without a real close). They are legitimate DATA
85
+ // but not legitimate SIGNAL — a burst of abandoned stubs must not fire
86
+ // /memory-cleanup off zero real work. filterRealSessions() is the
87
+ // shared, tested implementation (scripts/lib/session-schema/filters.mjs).
88
+ const realEntries = filterRealSessions(entries);
89
+
90
+ // Find the most recent memory_cleanup_at timestamp across all REAL entries.
91
+ for (const entry of realEntries) {
92
+ const ts = entry.memory_cleanup_at;
93
+ if (typeof ts === 'string' && ts.length > 0) {
94
+ if (lastCleanupAt === null || ts > lastCleanupAt) {
95
+ lastCleanupAt = ts;
76
96
  }
77
97
  }
98
+ }
78
99
 
79
- // Abandoned-session filter (#834): sessions.jsonl carries phantom
80
- // `status: 'abandoned'` stubs (session-close-backfill records for
81
- // sessions that ended without a real close). They are legitimate DATA
82
- // but not legitimate SIGNAL — a burst of abandoned stubs must not fire
83
- // /memory-cleanup off zero real work. filterRealSessions() is the
84
- // shared, tested implementation (scripts/lib/session-schema/filters.mjs).
85
- const realEntries = filterRealSessions(entries);
86
-
87
- // Find the most recent memory_cleanup_at timestamp across all REAL entries.
100
+ // Count REAL entries newer than the last cleanup (or total REAL entries
101
+ // when no cleanup ever ran).
102
+ if (lastCleanupAt === null) {
103
+ sessionsSinceCleanup = realEntries.length;
104
+ } else {
88
105
  for (const entry of realEntries) {
89
- const ts = entry.memory_cleanup_at;
90
- if (typeof ts === 'string' && ts.length > 0) {
91
- if (lastCleanupAt === null || ts > lastCleanupAt) {
92
- lastCleanupAt = ts;
93
- }
94
- }
95
- }
96
-
97
- // Count REAL entries newer than the last cleanup (or total REAL entries
98
- // when no cleanup ever ran).
99
- if (lastCleanupAt === null) {
100
- sessionsSinceCleanup = realEntries.length;
101
- } else {
102
- for (const entry of realEntries) {
103
- const startedAt = entry.started_at;
104
- if (typeof startedAt === 'string' && startedAt > lastCleanupAt) {
105
- sessionsSinceCleanup += 1;
106
- }
106
+ const startedAt = entry.started_at;
107
+ if (typeof startedAt === 'string' && startedAt > lastCleanupAt) {
108
+ sessionsSinceCleanup += 1;
107
109
  }
108
110
  }
109
111
  }
@@ -158,6 +158,12 @@ export function computeSuitabilityVerdict(deps = {}) {
158
158
  const ciNotRed = ciStatus !== 'red'; // null status (absent/malformed) passes
159
159
  if (ciAbsent) warnings.push('CI signal absent');
160
160
  else if (ciMalformed) warnings.push('CI signal malformed — treated as absent');
161
+ // `{status:'unknown'}` is a WELL-FORMED reading that establishes nothing —
162
+ // since #1031 it is what a degraded probe produces, and it hits neither
163
+ // branch above. Without this line it passed G3 with NO warning at all, i.e.
164
+ // more quietly than the plain-absent case it is strictly weaker than. The
165
+ // gate stays non-blocking (only a literal 'red' fails); only the silence goes.
166
+ else if (ciStatus === 'unknown') warnings.push('CI signal unknown — treated as absent');
161
167
  const ciSegment = ciAbsent ? 'absent' : ciMalformed ? 'malformed' : `${ciStatus}`;
162
168
 
163
169
  // --- G4 — resource gate (green/warn/degraded/null pass; only 'critical' fails)
@@ -20,7 +20,7 @@ import {
20
20
  finalizeState,
21
21
  } from './telemetry.mjs';
22
22
  import { durableCommit } from './durable-telemetry.mjs';
23
- import { SO_STATE_DIR } from '../platform.mjs';
23
+ import { getStateDir } from '../platform.mjs';
24
24
 
25
25
  // Internal clamp — mirrors flags.mjs internal; not re-exported there.
26
26
  function clampNumber(value, { min, max, fallback }) {
@@ -311,7 +311,7 @@ export async function runLoop(opts = {}) {
311
311
  files: [
312
312
  jsonlPath.replace(process.cwd() + '/', ''),
313
313
  '.orchestrator/metrics/sessions.jsonl',
314
- `${SO_STATE_DIR}/STATE.md`,
314
+ `${getStateDir()}/STATE.md`,
315
315
  ],
316
316
  enabled: false,
317
317
  });