session-orchestrator 3.24.0 → 4.0.1

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 (435) 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 +3 -2
  47. package/.codex-plugin/skills/architecture/SKILL.md +20 -0
  48. package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
  49. package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
  50. package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
  51. package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
  52. package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
  53. package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
  54. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
  55. package/.codex-plugin/skills/close/SKILL.md +21 -0
  56. package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
  57. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
  58. package/.codex-plugin/skills/debug/SKILL.md +21 -0
  59. package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
  60. package/.codex-plugin/skills/discovery/SKILL.md +21 -0
  61. package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
  62. package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
  63. package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
  64. package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
  65. package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
  66. package/.codex-plugin/skills/eli5/SKILL.md +21 -0
  67. package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
  68. package/.codex-plugin/skills/eval/SKILL.md +21 -0
  69. package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
  70. package/.codex-plugin/skills/evolve/SKILL.md +21 -0
  71. package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
  72. package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
  73. package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
  74. package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
  75. package/.codex-plugin/skills/go/SKILL.md +22 -0
  76. package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
  77. package/.codex-plugin/skills/grill/SKILL.md +21 -0
  78. package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
  79. package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
  80. package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
  81. package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
  82. package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
  83. package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
  84. package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
  85. package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
  86. package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
  87. package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
  88. package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
  89. package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
  90. package/.codex-plugin/skills/plan/SKILL.md +22 -0
  91. package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
  92. package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
  93. package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
  94. package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
  95. package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
  96. package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
  97. package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
  98. package/.codex-plugin/skills/release/SKILL.md +22 -0
  99. package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
  100. package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
  101. package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
  102. package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
  103. package/.codex-plugin/skills/session/SKILL.md +21 -0
  104. package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
  105. package/.codex-plugin/skills/session-end/SKILL.md +22 -0
  106. package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
  107. package/.codex-plugin/skills/session-start/SKILL.md +22 -0
  108. package/.codex-plugin/skills/spinout/SKILL.md +21 -0
  109. package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
  110. package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
  111. package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
  112. package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
  113. package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
  114. package/.codex-plugin/skills/test/SKILL.md +21 -0
  115. package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
  116. package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
  117. package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
  118. package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
  119. package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
  120. package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
  121. package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
  122. package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
  123. package/.cursor/commands/autopilot.md +2 -2
  124. package/.cursor/commands/bootstrap.md +1 -1
  125. package/.cursor/commands/brainstorm.md +1 -1
  126. package/.cursor/commands/debug.md +1 -1
  127. package/.cursor/commands/discovery.md +1 -1
  128. package/.cursor/commands/dispatcher.md +2 -2
  129. package/.cursor/commands/eli5.md +2 -2
  130. package/.cursor/commands/eval.md +2 -2
  131. package/.cursor/commands/evolve.md +1 -1
  132. package/.cursor/commands/go.md +1 -1
  133. package/.cursor/commands/grill.md +2 -2
  134. package/.cursor/commands/memory-cleanup.md +2 -2
  135. package/.cursor/commands/persona-panel.md +1 -1
  136. package/.cursor/commands/plan.md +1 -1
  137. package/.cursor/commands/portfolio.md +1 -1
  138. package/.cursor/commands/reconcile.md +2 -2
  139. package/.cursor/commands/release.md +2 -2
  140. package/.cursor/commands/session.md +2 -2
  141. package/.cursor/commands/spinout.md +2 -2
  142. package/.cursor/commands/sunset-review.md +2 -2
  143. package/.cursor/commands/templates-ack.md +2 -2
  144. package/.cursor/commands/test.md +2 -2
  145. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  146. package/.cursor/skills/eval/SKILL.md +1 -1
  147. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  148. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  149. package/.cursor-plugin/plugin.json +30 -0
  150. package/.orchestrator/policy/blocked-commands.json +121 -0
  151. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  152. package/.orchestrator/policy/quality-gates.example.json +16 -0
  153. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  154. package/.orchestrator/policy/templates-policy.json +27 -0
  155. package/.orchestrator/policy/test-profiles.json +47 -0
  156. package/AGENTS.md +225 -0
  157. package/CHANGELOG.md +1314 -2
  158. package/NOTICE +11 -6
  159. package/README.md +135 -94
  160. package/agents/eval-judge.md +1 -1
  161. package/agents/skill-applied-judge.md +1 -1
  162. package/assets/wave-lifecycle.svg +98 -0
  163. package/commands/release.md +6 -3
  164. package/commands/session.md +18 -3
  165. package/docs/README.md +4 -0
  166. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  167. package/docs/baseline.md +67 -0
  168. package/docs/ci-setup.md +108 -62
  169. package/docs/codex-setup.md +107 -29
  170. package/docs/components.md +38 -16
  171. package/docs/cursor-setup.md +6 -2
  172. package/docs/events-schema.md +9 -6
  173. package/docs/instruction-delivery.md +69 -0
  174. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  175. package/docs/migration-v4.md +365 -0
  176. package/docs/pi-setup.md +6 -1
  177. package/docs/plugin-architecture-v3.md +1 -1
  178. package/docs/rule-authoring.md +85 -19
  179. package/docs/scope-collision-guard.md +5 -5
  180. package/docs/session-config-reference.md +57 -56
  181. package/docs/session-config-template.md +6 -29
  182. package/docs/telemetry.md +157 -3
  183. package/docs/vault-docs-architecture.md +50 -11
  184. package/hooks/_lib/hook-import-set.json +1488 -0
  185. package/hooks/_lib/subagent-transcript.mjs +562 -0
  186. package/hooks/config-protection.mjs +2 -2
  187. package/hooks/cwd-change-restore.mjs +2 -2
  188. package/hooks/enforce-commands.mjs +69 -0
  189. package/hooks/hooks-codex.json +1 -1
  190. package/hooks/hooks-cursor.json +10 -0
  191. package/hooks/hooks-pi.json +5 -0
  192. package/hooks/hooks.json +6 -1
  193. package/hooks/loop-guard.mjs +3 -3
  194. package/hooks/on-session-end.mjs +2 -2
  195. package/hooks/on-session-start.mjs +103 -2
  196. package/hooks/on-stop.mjs +60 -14
  197. package/hooks/operator-steer.mjs +2 -2
  198. package/hooks/post-bash-write-verify.mjs +85 -0
  199. package/hooks/post-edit-import-probe.mjs +344 -0
  200. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  201. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  202. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  203. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  204. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  205. package/hooks/skill-invocation-telemetry.mjs +17 -5
  206. package/hooks/subagent-telemetry.mjs +13 -4
  207. package/monitors/monitors.json +3 -3
  208. package/package.json +9 -1
  209. package/pi/prompts/session.md +2 -2
  210. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  211. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  212. package/scripts/dialectic-deriver.mjs +73 -8
  213. package/scripts/export-hw-learnings.mjs +113 -1
  214. package/scripts/generate-agents-skills.mjs +378 -0
  215. package/scripts/generate-codex-skills.mjs +246 -0
  216. package/scripts/generate-cursor-adapter.mjs +45 -8
  217. package/scripts/generate-hook-import-set.mjs +292 -0
  218. package/scripts/lib/agent-status.mjs +13 -2
  219. package/scripts/lib/auto-dream.mjs +38 -36
  220. package/scripts/lib/autonomy/suitability.mjs +6 -0
  221. package/scripts/lib/autopilot/loop.mjs +2 -2
  222. package/scripts/lib/ci-status-banner.mjs +220 -75
  223. package/scripts/lib/codex/plugin-contract.mjs +88 -6
  224. package/scripts/lib/config/auto-dream.mjs +2 -1
  225. package/scripts/lib/config/block-header.mjs +8 -0
  226. package/scripts/lib/config/block-preprocess.mjs +177 -0
  227. package/scripts/lib/config/broken-window.mjs +2 -1
  228. package/scripts/lib/config/cold-start.mjs +2 -1
  229. package/scripts/lib/config/config-protection.mjs +22 -2
  230. package/scripts/lib/config/context-coverage.mjs +2 -1
  231. package/scripts/lib/config/cross-repo.mjs +2 -1
  232. package/scripts/lib/config/custom-phases.mjs +2 -1
  233. package/scripts/lib/config/dialectic.mjs +2 -1
  234. package/scripts/lib/config/discovery-validator.mjs +2 -1
  235. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  236. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  237. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  238. package/scripts/lib/config/docs-staleness.mjs +2 -1
  239. package/scripts/lib/config/drift-check.mjs +2 -1
  240. package/scripts/lib/config/eval.mjs +2 -1
  241. package/scripts/lib/config/events-rotation.mjs +2 -1
  242. package/scripts/lib/config/evolve.mjs +8 -2
  243. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  244. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  245. package/scripts/lib/config/handover-gate.mjs +2 -1
  246. package/scripts/lib/config/health-endpoints.mjs +7 -2
  247. package/scripts/lib/config/host-paths.mjs +20 -4
  248. package/scripts/lib/config/issue-budget.mjs +2 -1
  249. package/scripts/lib/config/loop-guard.mjs +2 -1
  250. package/scripts/lib/config/memory.mjs +2 -1
  251. package/scripts/lib/config/moc-staleness.mjs +2 -1
  252. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  253. package/scripts/lib/config/private-config-dir.mjs +67 -0
  254. package/scripts/lib/config/reconcile.mjs +2 -1
  255. package/scripts/lib/config/remote-hosts.mjs +2 -1
  256. package/scripts/lib/config/section-extractor.mjs +7 -1
  257. package/scripts/lib/config/skill-evolution.mjs +2 -1
  258. package/scripts/lib/config/slopcheck.mjs +2 -1
  259. package/scripts/lib/config/state-md-lock.mjs +2 -1
  260. package/scripts/lib/config/templates-first.mjs +2 -1
  261. package/scripts/lib/config/test.mjs +2 -1
  262. package/scripts/lib/config/vault-integration.mjs +7 -1
  263. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  264. package/scripts/lib/config/vault-staleness.mjs +2 -1
  265. package/scripts/lib/config/vault-sync.mjs +2 -1
  266. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  267. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  268. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  269. package/scripts/lib/convergence-monitor.mjs +82 -16
  270. package/scripts/lib/dispatcher/rank.mjs +124 -48
  271. package/scripts/lib/ecosystem-health.mjs +16 -2
  272. package/scripts/lib/eval/engine.mjs +9 -1
  273. package/scripts/lib/eval/session-resolve.mjs +23 -4
  274. package/scripts/lib/events.mjs +22 -6
  275. package/scripts/lib/frontmatter-guard.mjs +131 -13
  276. package/scripts/lib/gates/gate-full.mjs +30 -0
  277. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  278. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  279. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  280. package/scripts/lib/host-identity.mjs +50 -11
  281. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  282. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  283. package/scripts/lib/learnings/io.mjs +60 -6
  284. package/scripts/lib/memory-proposals/store.mjs +30 -22
  285. package/scripts/lib/owner-config-banner.mjs +41 -6
  286. package/scripts/lib/owner-config-loader.mjs +21 -10
  287. package/scripts/lib/owner-interview.mjs +3 -3
  288. package/scripts/lib/owner-yaml.mjs +215 -15
  289. package/scripts/lib/platform.mjs +108 -15
  290. package/scripts/lib/plugin-update-banner.mjs +414 -0
  291. package/scripts/lib/project-hygiene.mjs +38 -2
  292. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  293. package/scripts/lib/quality-gate.mjs +133 -44
  294. package/scripts/lib/reconcile/emitter.mjs +68 -6
  295. package/scripts/lib/reconcile/engine.mjs +51 -11
  296. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  297. package/scripts/lib/reconcile/writer.mjs +40 -18
  298. package/scripts/lib/session-close-backfill.mjs +67 -9
  299. package/scripts/lib/session-id.mjs +12 -23
  300. package/scripts/lib/session-identity/own-session.mjs +125 -10
  301. package/scripts/lib/session-lock-shape.mjs +43 -0
  302. package/scripts/lib/session-lock.mjs +5 -10
  303. package/scripts/lib/session-registry.mjs +25 -9
  304. package/scripts/lib/session-schema/constants.mjs +64 -3
  305. package/scripts/lib/session-schema/validator.mjs +38 -4
  306. package/scripts/lib/session-start-probes.mjs +30 -1
  307. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  308. package/scripts/lib/skill-health/join.mjs +17 -4
  309. package/scripts/lib/state-md.mjs +78 -0
  310. package/scripts/lib/sunset/walker.mjs +6 -0
  311. package/scripts/lib/telemetry/schema.mjs +202 -9
  312. package/scripts/lib/telemetry/sync.mjs +368 -12
  313. package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
  314. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  315. package/scripts/lib/validate/check-agents.mjs +3 -3
  316. package/scripts/lib/validate/check-codex-skills.mjs +191 -0
  317. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  318. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  319. package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
  320. package/scripts/lib/validate/check-skill-links.mjs +193 -0
  321. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  322. package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
  323. package/scripts/lib/validate/check-unwired-features.mjs +163 -15
  324. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  325. package/scripts/lib/validate/confidential-names.mjs +95 -30
  326. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  327. package/scripts/lib/validate/repo-files.mjs +48 -14
  328. package/scripts/lib/vault-backfill/template.mjs +63 -6
  329. package/scripts/lib/vault-mirror/process.mjs +165 -42
  330. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  331. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  332. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  333. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  334. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  335. package/scripts/lib/wave-resource-gate.mjs +8 -2
  336. package/scripts/lib/wave-sizing.mjs +4 -1
  337. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  338. package/scripts/materialize-wave-scope.mjs +12 -5
  339. package/scripts/memory-propose.mjs +19 -5
  340. package/scripts/migrate-cold-start-seed.mjs +4 -1
  341. package/scripts/parse-config.mjs +60 -3
  342. package/scripts/release.mjs +430 -31
  343. package/scripts/repair-invalid-sessions.mjs +3 -3
  344. package/scripts/run-quality-gate.mjs +128 -11
  345. package/scripts/site-numbers.mjs +344 -8
  346. package/scripts/sweep-expired-learnings.mjs +90 -0
  347. package/scripts/sync-vault-schema.mjs +3 -1
  348. package/scripts/telemetry.mjs +2 -2
  349. package/scripts/validate-plugin.mjs +164 -0
  350. package/scripts/validate-wave-scope.mjs +28 -8
  351. package/scripts/wave-scope-binding.mjs +215 -0
  352. package/skills/_shared/instruction-file-resolution.md +10 -0
  353. package/skills/_shared/parallel-aware-preamble.md +1 -0
  354. package/skills/_shared/platform-tools.md +1 -1
  355. package/skills/_shared/state-ownership.md +1 -1
  356. package/skills/architecture/SKILL.md +7 -5
  357. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  358. package/skills/autopilot/SKILL.md +4 -18
  359. package/skills/claude-md-drift-check/SKILL.md +5 -1
  360. package/skills/claude-md-drift-check/checker.mjs +62 -2
  361. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  362. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  363. package/skills/discovery/probes-arch.md +20 -18
  364. package/skills/dispatcher/SKILL.md +3 -2
  365. package/skills/evolve/SKILL.md +65 -26
  366. package/skills/frontmatter-guard/SKILL.md +11 -5
  367. package/skills/npm-publish/SKILL.md +1 -1
  368. package/skills/reconcile/SKILL.md +33 -0
  369. package/skills/remote-offload/SKILL.md +1 -1
  370. package/skills/session-end/SKILL.md +18 -905
  371. package/skills/session-end/phase-3-6-tail.md +10 -3
  372. package/skills/session-end/plan-verification.md +221 -155
  373. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  374. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  375. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  376. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  377. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  378. package/skills/session-end/references/session-summary-template.md +62 -0
  379. package/skills/session-plan/SKILL.md +49 -0
  380. package/skills/session-start/SKILL.md +22 -904
  381. package/skills/session-start/phase-8-5-express-path.md +1 -1
  382. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  383. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  384. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  385. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  386. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  387. package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
  388. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  389. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  390. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  391. package/skills/vault-sync/SKILL.md +10 -0
  392. package/skills/vault-sync/validator.mjs +21 -27
  393. package/skills/wave-executor/SKILL.md +15 -1
  394. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  395. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  396. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  397. package/skills/wave-executor/wave-loop.md +14 -1309
  398. package/templates/_shared/journey-manifest.md +10 -6
  399. package/.cursor/commands/autopilot-multi.md +0 -14
  400. package/.cursor/commands/contract-version-bump.md +0 -14
  401. package/.cursor/commands/journey-audit.md +0 -14
  402. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  403. package/.cursor/skills/daily/SKILL.md +0 -12
  404. package/.cursor/skills/domain-model/SKILL.md +0 -13
  405. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  406. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  407. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  408. package/commands/autopilot-multi.md +0 -74
  409. package/commands/contract-version-bump.md +0 -28
  410. package/commands/journey-audit.md +0 -43
  411. package/pi/prompts/autopilot-multi.md +0 -12
  412. package/pi/prompts/contract-version-bump.md +0 -12
  413. package/pi/prompts/journey-audit.md +0 -12
  414. package/scripts/autopilot-multi.mjs +0 -885
  415. package/scripts/backfill-learnings-expires.mjs +0 -196
  416. package/scripts/backfill-learnings.mjs +0 -203
  417. package/scripts/fleet-instruction-scan.mjs +0 -141
  418. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  419. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  420. package/scripts/lib/webhook-url.mjs +0 -105
  421. package/scripts/lifecycle-sim-v6.mjs +0 -347
  422. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  423. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  424. package/scripts/upload-social-preview.mjs +0 -316
  425. package/skills/_shared/model-selection.md +0 -64
  426. package/skills/contract-version-bump/SKILL.md +0 -219
  427. package/skills/daily/SKILL.md +0 -222
  428. package/skills/daily/generate.sh +0 -92
  429. package/skills/daily/templates/daily.md.tpl +0 -36
  430. package/skills/journey-audit/SKILL.md +0 -270
  431. package/skills/skill-creator/SKILL.md +0 -168
  432. package/skills/ubiquitous-language/SKILL.md +0 -97
  433. package/skills/vault-sync/package-lock.json +0 -40
  434. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  435. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -80,12 +80,40 @@ function parseFrontmatter(content) {
80
80
  return fields;
81
81
  }
82
82
 
83
+ /**
84
+ * Values that YAML would read as a non-string scalar even though every
85
+ * character in them is "safe". `argument-hint: no` is a boolean, not the
86
+ * string "no"; `description: 3` is an integer. A consumer asserting
87
+ * `typeof value === 'string'` fails on a file that looks perfectly fine.
88
+ */
89
+ const YAML_SCALAR_LOOKALIKE = /^(?:true|false|yes|no|on|off|null|nan|[-+]?\.?inf|~)$/i;
90
+
91
+ /**
92
+ * A value safe to emit as a YAML *plain* (unquoted) scalar.
93
+ *
94
+ * Deliberately an ALLOW-list. Its predecessor was a deny-list of special
95
+ * characters, which is unenumerable by construction: it caught `[` and `:`
96
+ * but not a leading `-` (block-sequence entry), not surrounding whitespace,
97
+ * and not the scalar look-alikes above. An allow-list fails closed — an
98
+ * unforeseen shape gets quoted, which is never wrong, only noisier.
99
+ */
100
+ const YAML_PLAIN_SAFE = /^[A-Za-z0-9_][A-Za-z0-9 _.,()/-]*$/;
101
+
102
+ /**
103
+ * Render a frontmatter VALUE as YAML.
104
+ *
105
+ * @param {unknown} value
106
+ * @returns {string|null} the YAML scalar, or `null` when there is nothing to emit
107
+ */
83
108
  function yamlQuote(value) {
84
- if (value === undefined || value === '') return null;
85
- if (/[:#[\]{}&*!|>'"%@`]/.test(value) || value.includes('\n')) {
86
- return JSON.stringify(value);
87
- }
88
- return value;
109
+ if (value === undefined || value === null || value === '') return null;
110
+ const text = String(value);
111
+ if (text.trim() === '') return null;
112
+ if (text !== text.trim()) return JSON.stringify(text);
113
+ if (!YAML_PLAIN_SAFE.test(text)) return JSON.stringify(text);
114
+ if (YAML_SCALAR_LOOKALIKE.test(text)) return JSON.stringify(text);
115
+ if (/^\d/.test(text)) return JSON.stringify(text);
116
+ return text;
89
117
  }
90
118
 
91
119
  function frontmatterLine(key, value) {
@@ -103,10 +131,16 @@ function renderCommand(commandFile) {
103
131
  const commandPath = path.join(COMMANDS_DIR, commandFile);
104
132
  const commandName = commandFile.replace(/\.md$/, '');
105
133
  const fields = parseFrontmatter(readFileSync(commandPath, 'utf8'));
134
+ // EVERY value goes through yamlQuote(). `argument-hint` did not, and its
135
+ // canonical form is a bare bracket list (`[mode] [--flag]`) — a YAML flow
136
+ // sequence, which is GH#54: Copilot CLI >= 1.0.65 silently DROPS a command
137
+ // file whose `argument-hint` is not a string. Fixed in `commands/` and
138
+ // `pi/prompts/` by 93b40dd (v3.16.0) and re-introduced here on every
139
+ // generation, because this mirror re-derived the frontmatter by hand.
106
140
  const frontmatter = [
107
141
  '---',
108
- frontmatterLine('description', yamlQuote(fields.description) ?? fields.description),
109
- frontmatterLine('argument-hint', fields['argument-hint']),
142
+ frontmatterLine('description', yamlQuote(fields.description)),
143
+ frontmatterLine('argument-hint', yamlQuote(fields['argument-hint'])),
110
144
  '---',
111
145
  ].filter(Boolean).join('\n');
112
146
 
@@ -132,9 +166,12 @@ function renderSkill(skillName) {
132
166
  const skillPath = path.join(SKILLS_DIR, skillName, 'SKILL.md');
133
167
  const fields = parseFrontmatter(readFileSync(skillPath, 'utf8'));
134
168
  const description = clampDescription(fields.description || `Session Orchestrator skill: ${skillName}`);
169
+ // Same rule as renderCommand: no frontmatter value is emitted raw. `name` is
170
+ // a directory basename today, so it is plain-safe in practice — routing it
171
+ // through yamlQuote() is what keeps that true after the next skill is added.
135
172
  const lines = [
136
173
  '---',
137
- `name: ${skillName}`,
174
+ `name: ${yamlQuote(skillName)}`,
138
175
  `description: ${yamlQuote(description)}`,
139
176
  ];
140
177
  if (!isUserInvocable(fields['user-invocable'])) {
@@ -0,0 +1,292 @@
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
+ * node scripts/generate-hook-import-set.mjs --help
21
+ *
22
+ * Exit codes: 0 = written / in sync (or --help); 1 = drift (with --check);
23
+ * 2 = usage error (unknown flag, or a valued flag missing its value) — the
24
+ * write is NEVER performed on a usage error.
25
+ */
26
+
27
+ import { readFileSync, writeFileSync, existsSync, statSync, realpathSync } from 'node:fs';
28
+ import path from 'node:path';
29
+ import { fileURLToPath } from 'node:url';
30
+ import { execFileSync } from 'node:child_process';
31
+
32
+ /** The four hook manifests whose entry files seed the crawl. */
33
+ export const HOOK_MANIFESTS = [
34
+ 'hooks.json',
35
+ 'hooks-codex.json',
36
+ 'hooks-cursor.json',
37
+ 'hooks-pi.json',
38
+ ];
39
+
40
+ /** Module extensions the crawl resolves an extension-less specifier against. */
41
+ const RESOLVE_EXTS = ['.mjs', '.js', '.cjs'];
42
+
43
+ /**
44
+ * Collect every `<something>.mjs` path mentioned in a hook manifest's command
45
+ * strings. The manifests differ per platform ($CLAUDE_PLUGIN_ROOT /
46
+ * $PI_PLUGIN_ROOT / ${PLUGIN_ROOT}), so match on the hooks/-relative tail
47
+ * rather than on any one variable spelling.
48
+ *
49
+ * @param {string} json - raw manifest contents
50
+ * @returns {string[]} plugin-root-relative entry paths (e.g. "hooks/enforce-scope.mjs")
51
+ */
52
+ export function extractEntryFiles(json) {
53
+ const out = new Set();
54
+ for (const m of json.matchAll(/hooks\/((?:[\w.-]+\/)*[\w.-]+\.mjs)/g)) {
55
+ const rel = `hooks/${m[1]}`;
56
+ // run-node.sh is the launcher, never an entry module; .../hooks/_lib/* is
57
+ // reached through imports, not through a manifest command.
58
+ out.add(rel);
59
+ }
60
+ return [...out].sort();
61
+ }
62
+
63
+ /**
64
+ * Extract relative module specifiers (static import/export-from and dynamic
65
+ * `import()`) from a module's source.
66
+ *
67
+ * @param {string} src
68
+ * @returns {string[]} relative specifiers, e.g. ["./_lib/profile-gate.mjs"]
69
+ */
70
+ export function extractRelativeSpecifiers(src) {
71
+ const out = new Set();
72
+ const patterns = [
73
+ /(?:^|[\s;}])(?:import|export)\s[^;'"]*?\sfrom\s*['"](\.[^'"]+)['"]/g,
74
+ /(?:^|[\s;}])import\s*['"](\.[^'"]+)['"]/g,
75
+ /\bimport\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g,
76
+ ];
77
+ for (const re of patterns) {
78
+ for (const m of src.matchAll(re)) out.add(m[1]);
79
+ }
80
+ return [...out];
81
+ }
82
+
83
+ /**
84
+ * Resolve a relative specifier against the importing file.
85
+ *
86
+ * @param {string} fromFile - absolute path of the importing module
87
+ * @param {string} spec - relative specifier
88
+ * @returns {string|null} absolute path of an existing file, or null
89
+ */
90
+ function resolveSpecifier(fromFile, spec) {
91
+ const base = path.resolve(path.dirname(fromFile), spec);
92
+ const candidates = [base, ...RESOLVE_EXTS.map((e) => base + e),
93
+ ...RESOLVE_EXTS.map((e) => path.join(base, `index${e}`))];
94
+ for (const c of candidates) {
95
+ try {
96
+ if (existsSync(c) && statSync(c).isFile()) return c;
97
+ } catch { /* unreadable → not a resolution */ }
98
+ }
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * Crawl every hook entry file transitively and return the reachable module set.
104
+ *
105
+ * @param {string} pluginRoot
106
+ * @returns {{file: string, reachable_from: string[]}[]} sorted by `file`
107
+ */
108
+ export function buildImportSet(pluginRoot) {
109
+ /** @type {Map<string, Set<string>>} rel path → set of hook entry basenames */
110
+ const reach = new Map();
111
+
112
+ const entries = new Set();
113
+ for (const manifest of HOOK_MANIFESTS) {
114
+ const abs = path.join(pluginRoot, 'hooks', manifest);
115
+ if (!existsSync(abs)) continue;
116
+ for (const rel of extractEntryFiles(readFileSync(abs, 'utf8'))) {
117
+ if (existsSync(path.join(pluginRoot, rel))) entries.add(rel);
118
+ }
119
+ }
120
+
121
+ for (const entryRel of [...entries].sort()) {
122
+ const entryName = path.basename(entryRel);
123
+ const stack = [path.join(pluginRoot, entryRel)];
124
+ const seen = new Set();
125
+ while (stack.length > 0) {
126
+ const abs = stack.pop();
127
+ if (seen.has(abs)) continue;
128
+ seen.add(abs);
129
+
130
+ const rel = path.relative(pluginRoot, abs).split(path.sep).join('/');
131
+ if (!reach.has(rel)) reach.set(rel, new Set());
132
+ reach.get(rel).add(entryName);
133
+
134
+ let src;
135
+ try { src = readFileSync(abs, 'utf8'); } catch { continue; }
136
+ for (const spec of extractRelativeSpecifiers(src)) {
137
+ const next = resolveSpecifier(abs, spec);
138
+ if (next) stack.push(next);
139
+ }
140
+ }
141
+ }
142
+
143
+ return [...reach.entries()]
144
+ .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
145
+ .map(([file, from]) => ({ file, reachable_from: [...from].sort() }));
146
+ }
147
+
148
+ /**
149
+ * Current HEAD sha of the plugin root, or "unknown" outside a git checkout.
150
+ *
151
+ * @param {string} pluginRoot
152
+ * @returns {string}
153
+ */
154
+ function headSha(pluginRoot) {
155
+ try {
156
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
157
+ cwd: pluginRoot,
158
+ encoding: 'utf8',
159
+ stdio: ['ignore', 'pipe', 'ignore'],
160
+ }).trim();
161
+ } catch {
162
+ return 'unknown';
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Build the full artefact payload.
168
+ *
169
+ * @param {string} pluginRoot
170
+ * @returns {{generated_at: string, head: string, entries: {file: string, reachable_from: string[]}[]}}
171
+ */
172
+ export function buildArtifact(pluginRoot) {
173
+ return {
174
+ generated_at: new Date().toISOString(),
175
+ head: headSha(pluginRoot),
176
+ entries: buildImportSet(pluginRoot),
177
+ };
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // CLI
182
+ // ---------------------------------------------------------------------------
183
+
184
+ const USAGE = [
185
+ 'Usage: node scripts/generate-hook-import-set.mjs [--plugin-root <dir>] [--out <file>] [--check]',
186
+ '',
187
+ ' --plugin-root <dir> repository/plugin root to crawl (default: the repo this script lives in)',
188
+ ' --out <file> artefact path (default: <plugin-root>/hooks/_lib/hook-import-set.json)',
189
+ ' --check re-crawl and diff against the committed artefact instead of writing',
190
+ ' --help, -h print this help',
191
+ '',
192
+ 'Exit codes: 0 = written / in sync; 1 = drift (with --check); 2 = usage error.',
193
+ ].join('\n');
194
+
195
+ /**
196
+ * @param {string[]} argv
197
+ * @returns {number} process exit code
198
+ */
199
+ export function main(argv) {
200
+ const args = argv.slice(2);
201
+
202
+ /** @type {{'--plugin-root': string|null, '--out': string|null}} */
203
+ const valued = { '--plugin-root': null, '--out': null };
204
+ let check = false;
205
+
206
+ for (let i = 0; i < args.length; i += 1) {
207
+ const arg = args[i];
208
+ if (arg === '--help' || arg === '-h') {
209
+ process.stdout.write(`${USAGE}\n`);
210
+ return 0;
211
+ }
212
+ if (arg === '--check') {
213
+ check = true;
214
+ continue;
215
+ }
216
+ if (arg === '--plugin-root' || arg === '--out') {
217
+ // A valued flag whose value is missing — or whose "value" is the next FLAG
218
+ // — is a usage error, not a silent default. Without the leading-dash test,
219
+ // `--out --check` swallows `--check` as the artefact path: checking is
220
+ // disabled, the WRITE branch runs, and a file literally named `--check`
221
+ // lands in the cwd.
222
+ const value = args[i + 1];
223
+ if (value === undefined || value.startsWith('-')) {
224
+ process.stderr.write(`✗ hook-import-set: ${arg} requires a value\n${USAGE}\n`);
225
+ return 2;
226
+ }
227
+ valued[arg] = value;
228
+ i += 1;
229
+ continue;
230
+ }
231
+ process.stderr.write(`✗ hook-import-set: unknown argument "${arg}"\n${USAGE}\n`);
232
+ return 2;
233
+ }
234
+
235
+ const pluginRoot = path.resolve(valued['--plugin-root'] ?? path.resolve(import.meta.dirname, '..'));
236
+ const out = path.resolve(valued['--out'] ?? path.join(pluginRoot, 'hooks', '_lib', 'hook-import-set.json'));
237
+
238
+ const fresh = buildArtifact(pluginRoot);
239
+
240
+ if (check) {
241
+ if (!existsSync(out)) {
242
+ process.stderr.write(`✗ hook-import-set: ${path.relative(pluginRoot, out)} is missing — run generate-hook-import-set.mjs\n`);
243
+ return 1;
244
+ }
245
+ let committed;
246
+ try { committed = JSON.parse(readFileSync(out, 'utf8')); } catch {
247
+ process.stderr.write('✗ hook-import-set: committed file is not valid JSON\n');
248
+ return 1;
249
+ }
250
+ // Only `entries` is compared — generated_at/head are provenance, not content.
251
+ const a = JSON.stringify(committed.entries ?? null);
252
+ const b = JSON.stringify(fresh.entries);
253
+ if (a !== b) {
254
+ process.stderr.write('✗ hook-import-set: committed set differs from a fresh crawl — run `node scripts/generate-hook-import-set.mjs`\n');
255
+ return 1;
256
+ }
257
+ process.stdout.write(`✓ hook-import-set: ${fresh.entries.length} modules, in sync\n`);
258
+ return 0;
259
+ }
260
+
261
+ writeFileSync(out, JSON.stringify(fresh, null, 2) + '\n', 'utf8');
262
+ process.stdout.write(`✓ hook-import-set: wrote ${fresh.entries.length} modules to ${path.relative(pluginRoot, out)}\n`);
263
+ return 0;
264
+ }
265
+
266
+ /**
267
+ * True when this module was launched as the process entry script.
268
+ *
269
+ * A string compare of `import.meta.url` against `file://${process.argv[1]}` is
270
+ * fragile in exactly the invocations CI and husky use: any symlinked or
271
+ * realpath-differing path (measured: `/tmp` → `/private/tmp` on macOS) makes
272
+ * the compare false, so the whole CLI body becomes a silent no-op that still
273
+ * exits 0 — a fail-OPEN drift gate. A space or `#` in the path breaks the
274
+ * hand-built URL the same way. Same shape as `hooks/post-bash-write-verify.mjs`.
275
+ *
276
+ * @returns {boolean}
277
+ */
278
+ function invokedAsScript() {
279
+ const entry = process.argv[1];
280
+ if (!entry) return false;
281
+ const self = fileURLToPath(import.meta.url);
282
+ try {
283
+ return realpathSync(entry) === realpathSync(self);
284
+ } catch {
285
+ // argv[1] unresolvable (deleted/renamed mid-run) — best-effort raw compare.
286
+ return entry === self;
287
+ }
288
+ }
289
+
290
+ if (invokedAsScript()) {
291
+ process.exit(main(process.argv));
292
+ }
@@ -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
  });