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
@@ -81,13 +81,14 @@ import {
81
81
  rmSync,
82
82
  realpathSync,
83
83
  } from 'node:fs';
84
- import { join } from 'node:path';
84
+ import { join, relative as relativePath, sep } from 'node:path';
85
85
  import { tmpdir } from 'node:os';
86
86
  import { spawnSync } from 'node:child_process';
87
87
  import { parseArgs } from 'node:util';
88
88
  import { fileURLToPath } from 'node:url';
89
89
 
90
90
  import { resolveRepoSpec } from './lib/vcs-repo-spec.mjs';
91
+ import { enumerateRepoFiles } from './lib/validate/enumerate-repo-files.mjs';
91
92
 
92
93
  const PACKAGE_NAME = 'session-orchestrator';
93
94
  const SPAWN_OPTS = { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 };
@@ -119,6 +120,13 @@ export const SURFACES = [
119
120
  file: '.claude-plugin/plugin.json',
120
121
  patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/],
121
122
  },
123
+ {
124
+ // Cursor's native manifest replaces the root Agent Plugins manifest so
125
+ // Codex can load its own component paths and cache version independently.
126
+ // It remains a required version surface for every release.
127
+ file: '.cursor-plugin/plugin.json',
128
+ patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/],
129
+ },
122
130
  {
123
131
  file: '.claude-plugin/marketplace.json',
124
132
  patterns: [/"version":\s*"(\d+\.\d+\.\d+)"/g],
@@ -181,6 +189,14 @@ export const SURFACES = [
181
189
  patterns: [/data-metric="version"[^>]*>(\d+\.\d+\.\d+)</g],
182
190
  checkOnly: true,
183
191
  },
192
+ {
193
+ // The German landing page (2026-09-07 redesign) mirrors the EN metric cells
194
+ // one-to-one; same single writer (scripts/site-numbers.mjs --write), so it is
195
+ // checkOnly for the same reason as site/index.html above.
196
+ file: 'site/de/index.html',
197
+ patterns: [/data-metric="version"[^>]*>(\d+\.\d+\.\d+)</g],
198
+ checkOnly: true,
199
+ },
184
200
  {
185
201
  file: 'site/llms.txt',
186
202
  patterns: [/Version:\s*(\d+\.\d+\.\d+)/],
@@ -307,7 +323,18 @@ function hasPathSegment(path, segment) {
307
323
 
308
324
  export const LEAKAGE_PATTERNS = [
309
325
  { name: 'tests/', matches: (path) => hasPathSegment(path, 'tests') },
310
- { name: '.orchestrator/', matches: (path) => /\.orchestrator\//.test(path) },
326
+ // `.orchestrator/policy/` is CARVED OUT and everything else under
327
+ // `.orchestrator/` still leaks: the destructive-guard FLOOR policy ships to npm
328
+ // consumers since 4.0.0 (Codex P1 — measured: `npm pack` carried 0 policy
329
+ // entries, so `loadEffectivePolicy` returned `rules:null` and the guard allowed
330
+ // everything on a consumer install). The carve-out is a `policy/` PATH segment,
331
+ // not a prefix: `.orchestrator/metrics/`, `.orchestrator/tmp/`,
332
+ // `.orchestrator/runtime/`, `*.lock` and the `.orchestrator/policy-backup/`
333
+ // prefix trick are all still caught.
334
+ {
335
+ name: '.orchestrator/',
336
+ matches: (path) => /\.orchestrator\//.test(path) && !/^\.orchestrator\/policy\//.test(path),
337
+ },
311
338
  { name: '.claude/', matches: (path) => hasPathSegment(path, '.claude') },
312
339
  { name: '.github/', matches: (path) => /\.github\//.test(path) },
313
340
  { name: 'node_modules', matches: (path) => /node_modules/.test(path) },
@@ -341,7 +368,18 @@ const INTENTIONAL_TEST_ASSET_PATHS = new Set([
341
368
  // tests/scripts/site-numbers.test.mjs, which forbids ANY vX.Y.Z and the current
342
369
  // package version outside a `data-metric` cell on EVERY shipped page, and
343
370
  // exempts exactly the lines marked `site-numbers:historical`.
344
- export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|docs\/|tests\/|skills\/npm-publish\/|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|commands\/release\.md)/;
371
+ // 4.0.1 (measured 2026-09-07): the 4.0.0 sweep would have flagged five files whose only literal
372
+ // is PROSE HISTORY of the major ("moved out of agents/ in 4.0.0", "removed in 4.0.0",
373
+ // "releases 4.0.0 would have blocked on itself") — CLAUDE.md, its generated twin AGENTS.md,
374
+ // CONTRIBUTING.md, NOTICE and .husky/pre-push. None of them is a version SURFACE (no
375
+ // `"version":`, badge or `vX.Y.Z` form), so they join the history allowlist rather than being
376
+ // reworded to dodge the sweep.
377
+ // Four more prose-history files surfaced once the detail line stopped truncating at five hits
378
+ // (same 2026-09-07 sweep): site/llms-full.txt ("The v4.0.0 release REMOVES public surfaces" — its
379
+ // version SURFACE is checked separately by the SURFACES row, so the sweep on it is redundant),
380
+ // skills/architecture/references/domain-model.md ("Merged here in v4.0.0"), skills/autopilot/SKILL.md
381
+ // ("4.0.0 — see docs/migration-v4.md") and templates/_shared/journey-manifest.md ("Retired … in 4.0.0").
382
+ export const HISTORY_ALLOWLIST = /^(CHANGELOG\.md|README\.md|CLAUDE\.md|AGENTS\.md|CONTRIBUTING\.md|NOTICE|\.husky\/pre-push|docs\/|tests\/|skills\/npm-publish\/|skills\/architecture\/references\/domain-model\.md|skills\/autopilot\/SKILL\.md|templates\/_shared\/journey-manifest\.md|scripts\/release\.mjs|\.orchestrator\/|site\/leaderboard\.json|site\/guide\/index\.html|site\/llms-full\.txt|commands\/release\.md)/;
345
383
 
346
384
  /** Pure check over packed-entry lines. Returns violations: {name, line}[]. */
347
385
  export function checkLeakage(lines) {
@@ -422,15 +460,129 @@ export const MIN_PACKED_ENTRIES = 400;
422
460
  // ---------------------------------------------------------------------------
423
461
 
424
462
  /**
425
- * Drift sweep verdict over a `git grep -l` result.
463
+ * A regex matching `literal` as a VERSION TOKEN, not as a substring.
464
+ *
465
+ * THE BUG (measured 2026-09-07, mid-release): the sweep matched the previous tag `4.0.0`
466
+ * inside `>=24.0.0` — `package.json`'s own engines field and a `scripts/lib/` string that
467
+ * quotes it — so a release could not be cut without either rewording an engines constraint or
468
+ * widening the allowlist over two files that carry no version surface at all. The boundary is
469
+ * therefore part of WHAT IS SWEPT FOR, not an allowlist row: `24.0.0`, `14.0.0` and `4.0.0.1`
470
+ * are different literals, at every path, forever.
471
+ *
472
+ * @param {string} literal — the previous release version
473
+ * @returns {RegExp} global regex; `4.0.0` matches only when not preceded by `[0-9.]` and not
474
+ * continued by a further numeric component (`(?!\.?[0-9])`).
475
+ */
476
+ function versionTokenRegex(literal) {
477
+ return new RegExp(`(?<![0-9.])${literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?!\\.?[0-9])`, 'g');
478
+ }
479
+
480
+ /**
481
+ * Is EVERY token occurrence of `literal` on this line a dependency range (`^X.Y.Z` / `~X.Y.Z`)?
482
+ *
483
+ * One bare occurrence anywhere on the line is enough to call the whole line drift — a comment
484
+ * that also happens to mention a ranged dep must not be excused by that mention. Occurrences are
485
+ * counted with {@link versionTokenRegex}, so `>=24.0.0` is not an occurrence of `4.0.0` here
486
+ * either — otherwise a line pinning `^4.0.0` beside an engines constraint would read as drift.
487
+ *
488
+ * @param {string} content — the matching line's text
489
+ * @param {string} literal — the previous release version, matched as a token
490
+ * @returns {boolean}
491
+ */
492
+ export function isDependencyRangeOnly(content, literal) {
493
+ const re = versionTokenRegex(literal);
494
+ let seen = 0;
495
+ for (let m = re.exec(content); m; m = re.exec(content)) {
496
+ seen += 1;
497
+ const before = m.index > 0 ? content[m.index - 1] : '';
498
+ if (before !== '^' && before !== '~') return false;
499
+ }
500
+ return seen > 0;
501
+ }
502
+
503
+ /** Lockfiles whose dependency entries are third-party history, never our surface. */
504
+ const LOCKFILE_BASENAMES = new Set(['package-lock.json', 'npm-shrinkwrap.json']);
505
+
506
+ /** Code files in which a `//`, `*`, `/*` or `#` line is comment prose, never a version surface. */
507
+ const CODE_COMMENT_EXTENSIONS = new Set(['.mjs', '.js', '.cjs', '.ts', '.sh']);
508
+
509
+ /**
510
+ * Is this `path:line:content` row version HISTORY rather than a stale surface?
511
+ *
512
+ * Three classes, all measured on the 4.0.1 cut (2026-09-07) as FALSE POSITIVES of the raw
513
+ * substring sweep, and all expressed as PREDICATES for the same reason the range carve-out
514
+ * above is one: a per-path allowlist row fixes the instance and leaves the class open.
515
+ *
516
+ * 1. **Dependency range** — `^X.Y.Z` / `~X.Y.Z` (see {@link isDependencyRangeOnly}).
517
+ * 2. **Lockfile dependency entry** — `package-lock.json` carried 72 hits for `4.0.0`, every one
518
+ * a third-party package version or an engines range. Our OWN entry there is still swept: the
519
+ * root package's `"version"` line, which npm writes in the `packages[""]` record at the top
520
+ * of the file. NAMED CEILING (BV-004): "at the top" is read as `line <= 20`, which covers
521
+ * every lockfileVersion-3 file npm writes today (the root record starts at line 5). Revisit
522
+ * trigger: a lockfile whose root `"version"` sits below line 20 — then key on the enclosing
523
+ * JSON path instead of the line number.
524
+ * 3. **Comment prose in a code file** — `// (pre-4.0.0 checkouts, …)` and a `* since 4.0.0`
525
+ * docblock line. No SURFACES pattern is ever a comment (every one is `"version": "X.Y.Z"`,
526
+ * `vX.Y.Z` or a badge), so excusing comment lines cannot mask a stale surface.
527
+ *
528
+ * @param {string} file — repo-relative path
529
+ * @param {number} line — 1-based line number
530
+ * @param {string} content — the matching line's text
531
+ * @param {string} prevTag — the previous release literal being swept for
532
+ * @returns {boolean} true = history, skip the row
533
+ */
534
+ function isHistoryRow(file, line, content, prevTag) {
535
+ if (!versionTokenRegex(prevTag).test(content)) return true;
536
+ if (isDependencyRangeOnly(content, prevTag)) return true;
537
+ const base = file.split('/').pop();
538
+ if (LOCKFILE_BASENAMES.has(base)) {
539
+ const isRootVersionLine = line <= 20 && new RegExp(`"version":\\s*"${prevTag.replace(/\./g, '\\.')}"`).test(content);
540
+ return !isRootVersionLine;
541
+ }
542
+ const dot = base.lastIndexOf('.');
543
+ const ext = dot === -1 ? '' : base.slice(dot);
544
+ if (CODE_COMMENT_EXTENSIONS.has(ext)) {
545
+ const trimmed = content.trim();
546
+ if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) return true;
547
+ if (ext === '.sh' && trimmed.startsWith('#')) return true;
548
+ }
549
+ return false;
550
+ }
551
+
552
+ /**
553
+ * Drift sweep verdict over a grep-shaped result (`-l` file list or `-n` line hits).
426
554
  *
427
- * `git grep` exit codes: 0 = matches found, 1 = no match (the success case
428
- * here), anything else = it did not run. Measured on git 2.x: a bad regex and
429
- * a bad pathspec both exit 128; git also documents 2 for usage errors. The old
430
- * inline code read `.stdout` without ever looking at `.status`, so BOTH the
431
- * no-match case and the it-crashed case produced an empty hit list and the
432
- * same reassuring detail line, "no tracked file still carries X". A sweep that
433
- * never ran is not a clean sweep.
555
+ * Exit-code contract, kept identical to `git grep`'s because that is what this
556
+ * evaluator was written against and what {@link collectDriftHits} now emits:
557
+ * 0 = matches found, 1 = no match (the success case here), anything else = it
558
+ * did not run. Measured on git 2.x: a bad regex and a bad pathspec both exit
559
+ * 128; git also documents 2 for usage errors. The old inline code read
560
+ * `.stdout` without ever looking at `.status`, so BOTH the no-match case and
561
+ * the it-crashed case produced an empty hit list and the same reassuring
562
+ * detail line, "no file still carries X". A sweep that never ran is not a
563
+ * clean sweep.
564
+ *
565
+ * ONE CLASS OF MATCH IS NOT OURS TO BUMP: a dependency RANGE that happens to equal our own
566
+ * previous version. `skills/vault-sync` pins `zod` at `^3.24.0` (the projects-baseline pin), so
567
+ * the 4.0.0 sweep collected two files whose literal belongs to zod and must NOT move when we
568
+ * release. The carve-out is deliberately a PREDICATE and not two allowlist rows: a per-path row
569
+ * fixes this instance and leaves the class open for the next dependency that lands on our version
570
+ * number — the unenumerable-table failure `.claude/rules/measurement-discipline.md` records. A
571
+ * caret- or tilde-prefixed literal is a range, and no version surface of this package is ever
572
+ * written as one (see SURFACES above: every pattern is an exact `"version": "X.Y.Z"`, `vX.Y.Z`
573
+ * or badge form), so the predicate cannot mask a stale surface.
574
+ *
575
+ * THREE FURTHER CLASSES are history for the same reason, all measured 2026-09-07 mid-release
576
+ * and all decided by {@link isHistoryRow}, never by an allowlist row: a literal that is only a
577
+ * SUBSTRING of a longer version (`4.0.0` inside `>=24.0.0`), a `package-lock.json` /
578
+ * `npm-shrinkwrap.json` row that is not the root package's own `"version"` line (72 of the 72
579
+ * lockfile hits on that cut were third-party), and COMMENT PROSE in a code file
580
+ * (`// (pre-4.0.0 checkouts, …)`). See that function for each one's ceiling.
581
+ *
582
+ * Accepts BOTH `git grep` output shapes. A bare `path` (from `-l`) carries no content and is
583
+ * therefore always a hit — the fail-closed reading, unchanged, and the shape {@link collectDriftHits}
584
+ * emits for a file it could not READ. `path:line:content` (from `-n`)
585
+ * is judged per line, and the file counts as drift as soon as ONE matching line is not a range.
434
586
  *
435
587
  * @param {{status: number, stdout?: string, stderr?: string}} grep
436
588
  * @param {string} prevTag — the previous release literal being swept for
@@ -441,21 +593,102 @@ export function evaluateDriftSweep(grep, prevTag, allowlist) {
441
593
  if (grep.status !== 0 && grep.status !== 1) {
442
594
  return {
443
595
  ok: false,
444
- detail: `git grep did not run (exit ${grep.status}): ${(grep.stderr || '').trim().slice(0, 200)} — sweep for ${prevTag} is inconclusive`,
596
+ detail: `drift sweep did not run (exit ${grep.status}): ${(grep.stderr || '').trim().slice(0, 200)} — sweep for ${prevTag} is inconclusive`,
445
597
  };
446
598
  }
447
- const hits = (grep.stdout || '')
448
- .split('\n')
449
- .filter(Boolean)
450
- .filter((f) => !allowlist.test(f));
599
+ const hits = [];
600
+ for (const row of (grep.stdout || '').split('\n').filter(Boolean)) {
601
+ const withContent = row.match(/^(.+?):(\d+):(.*)$/);
602
+ const file = withContent ? withContent[1] : row;
603
+ if (allowlist.test(file)) continue;
604
+ if (withContent && isHistoryRow(file, Number(withContent[2]), withContent[3], prevTag)) continue;
605
+ if (!hits.includes(file)) hits.push(file);
606
+ }
451
607
  return {
452
608
  ok: hits.length === 0,
453
609
  detail: hits.length
454
- ? `still carry ${prevTag}: ${hits.slice(0, 5).join(', ')}`
455
- : `no tracked file outside the allowlist still carries ${prevTag}`,
610
+ ? `still carry ${prevTag} (${hits.length} file(s)): ${hits.slice(0, 5).join(', ')}${hits.length > 5 ? ', …' : ''}`
611
+ : `no file outside the allowlist still carries ${prevTag} (tracked + untracked-not-ignored)`,
456
612
  };
457
613
  }
458
614
 
615
+ /**
616
+ * Bytes of a file inspected when deciding whether it is binary.
617
+ *
618
+ * A NUL byte in the first 8 KB is the same heuristic `git grep`/`grep` use to
619
+ * declare a file binary. NAMED CEILING (BV-004): a text file whose only NUL
620
+ * sits past 8 KB is scanned as text (harmless — it produces no version match),
621
+ * and a binary whose first 8 KB happen to be NUL-free is scanned as text and
622
+ * may emit mojibake rows. Revisit trigger: the first drift-sweep row naming a
623
+ * file nobody recognises as text.
624
+ */
625
+ const BINARY_SNIFF_BYTES = 8192;
626
+
627
+ /**
628
+ * Produce the drift-sweep hit list over the files that EXIST in the working
629
+ * tree — tracked or not — in the exact `{status, stdout, stderr}` shape
630
+ * {@link evaluateDriftSweep} already consumes.
631
+ *
632
+ * ## Why not `git grep` any more (#1248)
633
+ *
634
+ * `git grep` searches the INDEX: it is blind to an untracked file, so the
635
+ * moment a stale version literal is most likely to exist (a doc or manifest
636
+ * written for this release and not yet staged) is exactly the moment the sweep
637
+ * cannot see it and reports clean. The population is now
638
+ * `scripts/lib/validate/enumerate-repo-files.mjs`
639
+ * (`git ls-files --cached --others --exclude-standard`) — tracked PLUS
640
+ * untracked, minus everything `.gitignore` excludes, so `node_modules/` and
641
+ * the peer worktrees under `.claude/worktrees/` stay out without a prune list
642
+ * having to guess at them.
643
+ *
644
+ * What `git grep` bought — searching hidden directories a plain `rg` skips,
645
+ * which is how the forgotten `.codex-plugin` manifest was found — is kept:
646
+ * `git ls-files` lists dotted paths like any other.
647
+ *
648
+ * ## Fail-closed, in two places
649
+ *
650
+ * Enumeration itself throwing is reported as exit 128, which
651
+ * {@link evaluateDriftSweep} reads as "inconclusive" = FAIL — the same reading
652
+ * a crashed `git grep` got. A single file that enumerates but cannot be READ
653
+ * is emitted as a CONTENT-LESS row, which that evaluator already treats as a
654
+ * hit: a file we could not sweep is never silently a clean file.
655
+ *
656
+ * @param {object} options
657
+ * @param {string} options.repoRoot absolute repository root
658
+ * @param {string} options.prevTag the previous release literal to sweep for
659
+ * @param {(o: {repoRoot: string}) => string[]} [options.enumerate] injection seam for tests
660
+ * @param {(absolute: string) => Buffer} [options.read] injection seam for tests
661
+ * @returns {{status: number, stdout: string, stderr: string}}
662
+ */
663
+ export function collectDriftHits({ repoRoot, prevTag, enumerate = enumerateRepoFiles, read = readFileSync }) {
664
+ let files;
665
+ try {
666
+ files = enumerate({ repoRoot });
667
+ } catch (err) {
668
+ return { status: 128, stdout: '', stderr: `enumerateRepoFiles failed: ${err && err.message}` };
669
+ }
670
+ const rows = [];
671
+ for (const absolute of files) {
672
+ const relative = relativePath(repoRoot, absolute).split(sep).join('/');
673
+ let buf;
674
+ try {
675
+ buf = read(absolute);
676
+ } catch {
677
+ // Content-less row = hit (see the evaluator's contract above).
678
+ rows.push(relative);
679
+ continue;
680
+ }
681
+ if (buf.subarray(0, BINARY_SNIFF_BYTES).includes(0)) continue;
682
+ const text = buf.toString('utf8');
683
+ if (!text.includes(prevTag)) continue;
684
+ const lines = text.split('\n');
685
+ for (let i = 0; i < lines.length; i += 1) {
686
+ if (lines[i].includes(prevTag)) rows.push(`${relative}:${i + 1}:${lines[i]}`);
687
+ }
688
+ }
689
+ return { status: rows.length ? 0 : 1, stdout: rows.length ? `${rows.join('\n')}\n` : '', stderr: '' };
690
+ }
691
+
459
692
  /**
460
693
  * Registry-collision verdict over `npm view <pkg> versions --json`.
461
694
  *
@@ -589,6 +822,53 @@ export function evaluateNpmAuth(whoami) {
589
822
  : { ok: false, detail: 'npm whoami exited 0 with an empty identity — the token could not be confirmed' };
590
823
  }
591
824
 
825
+ /**
826
+ * Turn a `checkCiStatus` result into the `ci-green-on-head` preflight row.
827
+ *
828
+ * Three input states since #1031, and the middle one is why this is a named
829
+ * function rather than a ternary: `degraded` means the check could NOT be READ.
830
+ * Interpolating `ci.status` there printed `status: undefined` — a red row whose
831
+ * detail names no cause, which reads as "CI is broken" when the truth is "we
832
+ * never found out". Only an actual `status: 'green'` reading passes; a release
833
+ * must never proceed on an unknown CI state.
834
+ *
835
+ * @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
836
+ * @returns {{ok: boolean, detail: string}}
837
+ */
838
+ export function evaluateCiRow(ci) {
839
+ if (ci === null || ci === undefined) return { ok: false, detail: 'CI status unavailable' };
840
+ if (ci.degraded) return { ok: false, detail: `CI status unknown (${ci.degraded})` };
841
+ const job = ci.failingJobName ? ` (${ci.failingJobName})` : '';
842
+ return { ok: ci.status === 'green', detail: `status: ${ci.status}${job}` };
843
+ }
844
+
845
+ /**
846
+ * Turn the GitHub-mirror CI reading into the `ci-green-on-head-github` row.
847
+ *
848
+ * WHY A SECOND CI ROW AT ALL: `ci-green-on-head` reads whatever platform
849
+ * `detectVcsFamily` picks for `origin` — GitLab here — and the GitLab pipeline
850
+ * runs Linux only. The **macOS** matrix leg exists solely on the GitHub mirror
851
+ * (`.github/workflows/test.yml`), i.e. on the operator's own platform. A
852
+ * release could therefore go out fully green with the macOS leg red, and the
853
+ * release path would never have asked.
854
+ *
855
+ * SELF-DISABLING, and that is the one place this row is allowed to pass without
856
+ * evidence: a checkout with no `github` remote has no mirror to be red, so the
857
+ * row is `skipped` rather than red. Everything else keeps the three-state
858
+ * contract of `evaluateCiRow` unchanged — `unknown` and `degraded` FAIL, because
859
+ * "we could not read the mirror" is not "the mirror is green" (the fail-closed
860
+ * house rule at the top of this file).
861
+ *
862
+ * @param {string|undefined} repoSpec — `resolveRepoSpec({vcs:'github'})`, undefined when no github remote resolves
863
+ * @param {null | {status?: string, failingJobName?: string, degraded?: string}} ci
864
+ * @returns {{ok: boolean, detail: string}}
865
+ */
866
+ export function evaluateGithubCiRow(repoSpec, ci) {
867
+ if (!repoSpec) return { ok: true, detail: 'skipped — no github remote' };
868
+ const row = evaluateCiRow(ci);
869
+ return { ok: row.ok, detail: `${repoSpec} — ${row.detail}` };
870
+ }
871
+
592
872
  /**
593
873
  * Flag-combination gate, applied before any work.
594
874
  *
@@ -697,12 +977,18 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
697
977
  const changelog = checkChangelogEntry(readFileSync(join(repoRoot, 'CHANGELOG.md'), 'utf8'), target);
698
978
  add('changelog-entry', changelog.ok, changelog.problems.join('; '));
699
979
 
700
- // 3b. Drift sweep: no TRACKED file outside the surfaces table + allowlist
701
- // may still carry the previous release's version literal. `git grep` (not
702
- // rg) on purpose — it searches every tracked file including hidden
703
- // directories, which is exactly how the forgotten .codex-plugin manifest
704
- // was invisible to a plain rg census. Allowlisted: files that legitimately
705
- // carry version HISTORY.
980
+ // 3b. Drift sweep: no file outside the surfaces table + allowlist may still
981
+ // carry the previous release's version literal.
982
+ //
983
+ // POPULATION (#1248, closed here): this used to be one `git grep`, which
984
+ // searches the INDEX and is therefore blind to UNTRACKED files — the sweep
985
+ // measured "no TRACKED file still carries X" while its row read as a
986
+ // whole-tree census. It now enumerates via
987
+ // `scripts/lib/validate/enumerate-repo-files.mjs` (tracked PLUS
988
+ // untracked-not-ignored) and greps in-process; see {@link collectDriftHits}
989
+ // for the fail-closed contract and why hidden directories (the forgotten
990
+ // .codex-plugin manifest) are still covered. Allowlisted: files that
991
+ // legitimately carry version HISTORY.
706
992
  const tagList = run('git', ['tag', '-l', 'v*', '--sort=-v:refname'], { cwd: repoRoot });
707
993
  const prevTag = (tagList.stdout || '')
708
994
  .split('\n').map((t) => t.trim().replace(/^v/, ''))
@@ -712,8 +998,11 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
712
998
  // one of them means the sweep is unnecessary.
713
999
  add('drift-sweep', false, `git tag -l failed (exit ${tagList.status}) — cannot determine the previous release to sweep for`);
714
1000
  } else if (prevTag) {
715
- const grep = run('git', ['grep', '-l', '--fixed-strings', prevTag, '--', '.'], { cwd: repoRoot });
716
- const sweep = evaluateDriftSweep(grep, prevTag, HISTORY_ALLOWLIST);
1001
+ // Line-shaped rows (not a bare file list): the verdict needs the matching LINE, because a
1002
+ // caret-ranged dependency that equals our previous version is not drift and a file list
1003
+ // cannot show that.
1004
+ const hits = collectDriftHits({ repoRoot, prevTag });
1005
+ const sweep = evaluateDriftSweep(hits, prevTag, HISTORY_ALLOWLIST);
717
1006
  add('drift-sweep', sweep.ok, sweep.detail);
718
1007
  } else {
719
1008
  add('drift-sweep', true, 'no previous tag to sweep against');
@@ -769,8 +1058,29 @@ async function preflight(repoRoot, target, { skipCi = false } = {}) {
769
1058
  } else {
770
1059
  const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
771
1060
  const ci = await checkCiStatus({ repoRoot, timeoutMs: 15000 });
772
- const green = ci !== null && ci.status === 'green';
773
- add('ci-green-on-head', green, ci === null ? 'CI status unavailable' : `status: ${ci.status}${ci.failingJobName ? ` (${ci.failingJobName})` : ''}`);
1061
+ const row = evaluateCiRow(ci);
1062
+ add('ci-green-on-head', row.ok, row.detail);
1063
+ }
1064
+
1065
+ // 6b. CI green on the GitHub mirror too. The row above asks the platform
1066
+ // `detectVcsFamily` picks for `origin` (GitLab), whose pipeline is Linux-only;
1067
+ // the macOS matrix leg lives exclusively in `.github/workflows/test.yml`.
1068
+ // `vcs: 'github'` forces the probe onto the mirror without touching the
1069
+ // detection order. The GitHub check-runs path reads `commits/HEAD`, i.e. the
1070
+ // mirror's default-branch head — which is HEAD only because `head-pushed-github`
1071
+ // above proves github/main == local HEAD; that row is this one's precondition,
1072
+ // not a duplicate of it.
1073
+ if (skipCi) {
1074
+ add('ci-green-on-head-github', true, 'SKIPPED via --skip-ci');
1075
+ } else {
1076
+ const githubSpec = resolveRepoSpec({ repoRoot, vcs: 'github' });
1077
+ let githubCi = null;
1078
+ if (githubSpec) {
1079
+ const { checkCiStatus } = await import('./lib/ci-status-banner.mjs');
1080
+ githubCi = await checkCiStatus({ repoRoot, vcs: 'github', timeoutMs: 15000 });
1081
+ }
1082
+ const row = evaluateGithubCiRow(githubSpec, githubCi);
1083
+ add('ci-green-on-head-github', row.ok, row.detail);
774
1084
  }
775
1085
 
776
1086
  // 7. Leakage gate over the actual pack file list.
@@ -952,6 +1262,37 @@ export function waitForRegistryPropagation(repoRoot, target, deps = {}) {
952
1262
  * @param {{runImpl?: Function, waitImpl?: Function, attempts?: number, delaySeconds?: number}} [deps]
953
1263
  * @returns {{receipt: {confirmed: boolean, target: string, detail: string}, propagation: ReturnType<typeof waitForRegistryPropagation>}}
954
1264
  */
1265
+ /**
1266
+ * The exact `npm publish` invocation: argv plus spawn options.
1267
+ *
1268
+ * WHY THE ENV PIN, AND WHY THIS IS A NAMED FUNCTION: `npm_config_loglevel` is
1269
+ * INHERITED, and the `+ <pkg>@<version>` receipt line — the ONE piece of
1270
+ * evidence `evaluatePublishReceipt` accepts as the irreversible boundary — is
1271
+ * printed at `notice` level. Any ancestor that ran under `npm run --silent`
1272
+ * (the husky pre-push gate does exactly that) therefore hands this child a
1273
+ * silent loglevel, npm publishes successfully and prints nothing, the receipt
1274
+ * reads as unconfirmed, `publish()` throws, and `main()` exits 2 = "pre-receipt,
1275
+ * safe to rerun" while the registry already holds the version. That is the worst
1276
+ * failure this file can produce, and it is the same inherited-silent trap the
1277
+ * leakage gate's `npm pack --dry-run` was already pinned against (see preflight
1278
+ * step 7). The pin makes the receipt independent of the caller's environment.
1279
+ *
1280
+ * Exported because `publish()` itself deliberately is not (it is the
1281
+ * irreversible act) — the invocation it builds is pure, so the pin is testable
1282
+ * without a publishable seam.
1283
+ *
1284
+ * @param {string} repoRoot
1285
+ * @param {string} userconfigPath — the 0600 temp npmrc carrying the token
1286
+ * @returns {{cmd: string, args: string[], opts: {cwd: string, env: object}}}
1287
+ */
1288
+ export function publishInvocation(repoRoot, userconfigPath) {
1289
+ return {
1290
+ cmd: 'npm',
1291
+ args: ['publish', '--access', 'public', '--userconfig', userconfigPath],
1292
+ opts: { cwd: repoRoot, env: { ...process.env, npm_config_loglevel: 'notice' } },
1293
+ };
1294
+ }
1295
+
955
1296
  // Deliberately NOT exported: this is the irreversible act, and every production
956
1297
  // path to it runs through main() -> preflight() (leakage gate, CI gate, dirty-tree
957
1298
  // gate). Exporting it made the whole gate chain bypassable by any importer, and no
@@ -959,9 +1300,10 @@ export function waitForRegistryPropagation(repoRoot, target, deps = {}) {
959
1300
  function publish(repoRoot, target, deps = {}) {
960
1301
  const token = loadNpmToken(repoRoot);
961
1302
  const runImpl = deps.runImpl ?? run;
962
- const res = withTempUserconfig(token, (tmpRc) =>
963
- runImpl('npm', ['publish', '--access', 'public', '--userconfig', tmpRc], { cwd: repoRoot }),
964
- );
1303
+ const res = withTempUserconfig(token, (tmpRc) => {
1304
+ const call = publishInvocation(repoRoot, tmpRc);
1305
+ return runImpl(call.cmd, call.args, call.opts);
1306
+ });
965
1307
  const receipt = evaluatePublishReceipt(res, target);
966
1308
  if (!receipt.confirmed) throw new Error(receipt.detail);
967
1309
 
@@ -1099,6 +1441,46 @@ export async function runPublishRelease(repoRoot, target, deps = {}) {
1099
1441
  };
1100
1442
  }
1101
1443
 
1444
+ /**
1445
+ * Render the partial tag/push state left behind by a failed post-receipt tail.
1446
+ *
1447
+ * Pure so the reconciliation lines are unit-testable against a synthetic
1448
+ * outcome: the branch that produces them only exists after an irreversible npm
1449
+ * publish, which no test may perform.
1450
+ *
1451
+ * Absent progress is reported as absent, never as "nothing happened": a
1452
+ * `tagAndPushImpl` that threw before attaching `releaseProgress` leaves state
1453
+ * genuinely unknown, and the operator must inspect rather than assume.
1454
+ *
1455
+ * @param {{tag?: string|null, localTagCreated?: boolean, remotes?: Array<{remote: string, mainPushed?: boolean, tagPushed?: boolean}>}} [tagProgress]
1456
+ * @returns {string[]}
1457
+ */
1458
+ export function describeTagProgress(tagProgress) {
1459
+ const lines = ['\nPARTIAL TAG/PUSH STATE (the npm receipt is already irreversible):'];
1460
+ if (!tagProgress || typeof tagProgress !== 'object') {
1461
+ lines.push(' tag/push progress was not recorded — inspect `git tag -l` and both remotes manually.');
1462
+ return lines;
1463
+ }
1464
+ const tag = tagProgress.tag || 'the release tag';
1465
+ lines.push(
1466
+ tagProgress.localTagCreated === true
1467
+ ? ` local tag ${tag}: CREATED (the next \`--check\` will fail \`tag-free-local\` until it is pushed or deleted).`
1468
+ : ` local tag ${tag}: not created.`,
1469
+ );
1470
+ const remotes = Array.isArray(tagProgress.remotes) ? tagProgress.remotes : [];
1471
+ if (remotes.length === 0) {
1472
+ lines.push(' no remote was reached — neither main nor the tag was pushed anywhere.');
1473
+ return lines;
1474
+ }
1475
+ for (const remote of remotes) {
1476
+ lines.push(
1477
+ ` ${remote.remote}: main ${remote.mainPushed === true ? 'pushed' : 'NOT pushed'}, ` +
1478
+ `tag ${remote.tagPushed === true ? 'pushed' : 'NOT pushed'}.`,
1479
+ );
1480
+ }
1481
+ return lines;
1482
+ }
1483
+
1102
1484
  /**
1103
1485
  * Print a completed publish-tail outcome and return the CLI exit code.
1104
1486
  *
@@ -1120,6 +1502,16 @@ export function printPublishOutcome(outcome, target, io = {}) {
1120
1502
  }
1121
1503
  if (!tagAndPushFailed) {
1122
1504
  log(` tagged ${outcome.tag} (AFTER publish) and pushed main+tag to: ${outcome.pushed.join(', ')}.`);
1505
+ } else {
1506
+ // The npm receipt is already irreversible at this point, so the ONLY thing
1507
+ // that helps the operator is the exact partial state tag-and-push reached.
1508
+ // `runPublishRelease` collects it (`tagProgress.localTagCreated` plus a
1509
+ // per-remote `{mainPushed, tagPushed}`); this printer used to reference
1510
+ // none of it and suppressed the `pushed:` line as well, so the operator was
1511
+ // told only THAT it failed. The local-tag line matters twice over: a
1512
+ // created local tag makes the next `--check` fail `tag-free-local`, which
1513
+ // reads as a mysterious collision unless it was announced here.
1514
+ for (const line of describeTagProgress(outcome.tagProgress)) error(line);
1123
1515
  }
1124
1516
 
1125
1517
  if (outcome.release.skipped) {
@@ -1377,6 +1769,13 @@ async function main() {
1377
1769
  if (!values.publish) return 0;
1378
1770
 
1379
1771
  console.log(`\nPublishing ${PACKAGE_NAME}@${target} ...`);
1772
+ // No spawn timeout is set anywhere in this file, on purpose: a kill in the
1773
+ // middle of `npm publish` or a tag push is the very failure the receipt
1774
+ // boundary exists to avoid. The wall-clock cost is real, though — each of
1775
+ // the two `git push` remotes re-runs the husky pre-push full gate (~2.5 min
1776
+ // each, measured) and the site poll waits up to 120 s.
1777
+ console.log(' This tail can run ~7 minutes (pre-push gate x2 remotes + up to 120s site poll).');
1778
+ console.log(' Run it with a >=600s command timeout or in the background — do NOT kill it mid-run.');
1380
1779
  const outcome = await runPublishRelease(repoRoot, target, { publishImpl: publish });
1381
1780
  return printPublishOutcome(outcome, target);
1382
1781
  }
@@ -40,7 +40,7 @@ import path from 'node:path';
40
40
  import { fileURLToPath } from 'node:url';
41
41
 
42
42
  import { repairLedger, CANONICAL_LEDGER_REL } from './lib/session-record-repair.mjs';
43
- import { SO_PROJECT_DIR } from './lib/platform.mjs';
43
+ import { getProjectDir } from './lib/platform.mjs';
44
44
 
45
45
  const USAGE =
46
46
  'Usage: node scripts/repair-invalid-sessions.mjs [--dry-run|--apply] [--json]\n' +
@@ -52,7 +52,7 @@ const USAGE =
52
52
  CANONICAL_LEDGER_REL +
53
53
  ');\n' +
54
54
  ' must resolve inside <repo-root>/.orchestrator/metrics/\n' +
55
- ' --repo-root project root (default: resolved SO_PROJECT_DIR)\n' +
55
+ ' --repo-root project root (default: resolved project dir)\n' +
56
56
  'Exit codes: 0 completed, 1 arg error, 2 system error, 3 post-verification failed\n';
57
57
 
58
58
  /**
@@ -168,7 +168,7 @@ async function main() {
168
168
 
169
169
  // --apply is an explicit opt-in; absent it (or with --dry-run) we never write.
170
170
  const apply = values.apply === true && values['dry-run'] !== true;
171
- const repoRoot = values['repo-root'] || SO_PROJECT_DIR;
171
+ const repoRoot = values['repo-root'] || getProjectDir();
172
172
  const requestedFile = values.file || path.join(repoRoot, CANONICAL_LEDGER_REL);
173
173
 
174
174
  // MED-3: bound the target to <repoRoot>/.orchestrator/metrics/ BEFORE any I/O.