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
package/CHANGELOG.md CHANGED
@@ -7,10 +7,1252 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [4.0.1] - 2026-09-07
11
+
12
+ A PATCH release, two work streams. Codex CLI command entrypoints (closing Epic #1263 and
13
+ sub-issues #1264/#1265/#1266) make every `commands/*.md` and `skills/*/SKILL.md` entry
14
+ discoverable and selectable inside Codex. A 5-wave mechanical-fix session (7 Discovery + 12
15
+ Impl-Core + 5 Impl-Polish + a 4-reviewer/Codex-gpt-6-astra Quality panel with 5 fixers) closes
16
+ 16 follow-up issues surfaced by the `4.0.0` 360° audit. The site redesign already staged in
17
+ `[Unreleased]` (session-orchestrator.com relaunch, closing GitLab #1237 and #1080 points
18
+ 1/3/5/6/7) folds in unchanged below as `### Changed`.
19
+
20
+ ### Added
21
+
22
+ - **Codex command workflows are now discoverable and selectable skills — 51 unique generated
23
+ entries (25 command-backed, 26 skill-backed), closing an 8-command discovery gap (`close`,
24
+ `go`, `harness-audit`, `portfolio`, `release`, `session`, `templates-ack`, `test`) and
25
+ resolving 17 same-name command/skill overlaps in the command's favor (`cf0876c3`, Refs
26
+ #1263/#1264/#1265/#1266).** `scripts/generate-codex-skills.mjs` builds `.codex-plugin/skills/`
27
+ as the union of `commands/` and `skills/` names; each generated entry links via a
28
+ package-relative path to its canonical `commands/<name>.md` or `skills/<name>/SKILL.md`, so it
29
+ resolves from the installed bundle, not only the source checkout. Command adapters read the
30
+ full canonical command before resolving any internal skill call directly, avoiding recursive
31
+ dispatch back to the public entry. Trailing prompt text passes through as `$ARGUMENTS` — no
32
+ shell expansion, no global substitution.
33
+ - **`disable-model-invocation: true` now maps to `agents/openai.yaml`
34
+ `policy.allow_implicit_invocation: false`, so `go`, `close`, `bootstrap`, `brainstorm`, `plan`,
35
+ and `release` keep requiring explicit selection under Codex too (`cf0876c3`, Refs
36
+ #1263/#1264).** All other commands retain their source setting.
37
+ - **`scripts/lib/validate/check-codex-skills.mjs` independently re-derives the expected Codex
38
+ surface from the emitted artifacts, not from the generator's own expected text, and is wired
39
+ into `scripts/validate-plugin.mjs` (`cf0876c3`, Refs #1263/#1265).** It checks source-name
40
+ coverage, manifest registration, frontmatter/metadata types, invocation-policy booleans, and
41
+ canonical-link targets; its CLI also re-runs `generate-codex-skills.mjs --check`.
42
+ - **Native Codex `commands: []` now suppresses the installer's automatic `commands/` migration
43
+ (`ae16dfcb`, Refs #1263/#1265/#1266).** Without it, a real install on Codex 0.153.3 added nine
44
+ `source-command-*` aliases (including `source-command-close`) beside the generated entries,
45
+ with no invocation policy attached. `scripts/lib/codex/plugin-contract.mjs` now requires
46
+ `commands` to be an empty array when present.
47
+ - **The root `plugin.json` (added in 4.0.0) is replaced by `.cursor-plugin/plugin.json`,
48
+ Cursor's native manifest format (`cf0876c3`, Refs #1263/#1264).** Read-only `plugin/read`
49
+ probes on Codex CLI 0.153.3 and desktop 0.153.4 found the root Agent Plugins manifest
50
+ overrides Codex's own declared skill path (fixing it to `./skills`) and root version —
51
+ silently defeating the generated `.codex-plugin/skills/` surface even though every adapter
52
+ unit test passed. `package.json` `files[]` and `scripts/release.mjs`'s version-surface table
53
+ are updated accordingly (`.cursor-plugin/` added, root `plugin.json` removed).
54
+ - **Installed acceptance verified end to end (`3862505c`, `a244b870`, Refs #1263/#1266).**
55
+ Public `codex plugin add session-orchestrator@local --json` produced enabled version
56
+ `4.0.0+codex.20260907174300`; Codex desktop 0.153.4 returns exactly 51 unique enabled skills
57
+ via both `plugin/read` and `skills/list`, no migration aliases, and all six explicit-only
58
+ commands retain `allow_implicit_invocation: false`. Visible-picker selection is **not yet
59
+ confirmed** — Computer Use cannot drive the Codex desktop app — and remains open under #1266
60
+ (see Notes).
61
+ - **Documented the Codex command entrypoints across `README.md`, `docs/codex-setup.md`,
62
+ `docs/components.md`, `docs/migration-v4.md`, and `docs/instruction-delivery.md` (`cf0876c3`,
63
+ Refs #1263/#1266).**
64
+
65
+ ### Changed
66
+
67
+ - Public website redesigned (session-orchestrator.com): light-first design in the family look of the author's personal site (Bricolage Grotesque, Source Sans 3, IBM Plex Mono, all self-hosted; Archivo removed).
68
+ - Two reading layers on the landing page: a plain-words layer for non-developers above a developer layer with install commands, the wave schedule, and the measured-claims table.
69
+ - German landing page at `/de` with `hreflang` alternates (`en`, `de`, `x-default`), same section ids and census spans as the English page.
70
+ - three.js hero (vendored `three@0.185.1`, two files: `three.module.min.js` + `three.core.min.js`), loaded via a lazy dynamic import skipped under `prefers-reduced-motion`, which renders the static `hero-fallback.svg` instead.
71
+ - Six AI-generated illustrations (gpt-image-2), each captioned "AI-generated illustration (gpt-image-2)" in EN/DE.
72
+ - FAQ section with `FAQPage` JSON-LD on both the EN and DE landing pages.
73
+ - Author block linking to the personal site (root + `utm_source=session-orchestrator`), closing GitLab #1237.
74
+ - `scripts/site-numbers.mjs` gained `npm-downloads-30d` and `github-stars`: fetched only under `--write` (5 s timeout, snapshot kept on failure), answered from the `site/_census.json` snapshot under `--check`; `SO_SITE_NUMBERS_OFFLINE=1` skips both metrics. Marker-bounded census blocks (`<!-- census:start -->` / `<!-- census:end -->`) added to `site/llms.txt` and `site/llms-full.txt` (GitLab #1080, points 1/3/5/6/7).
75
+ - New tests: `tests/site/voice-gate.test.mjs` (em-dash, superlative, "we", emoji gate) and `tests/site/structure.test.mjs` (EN/DE parity, hreflang, asset presence, AI-illustration disclosure).
76
+ - `vercel.json` cache headers for `/assets`, `/img`, `/vendor`; ESLint now ignores `site/vendor/**` (vendored three.js) and declares browser globals for `site/assets/**`.
77
+
78
+ ### Fixed
79
+
80
+ - **`CHANGELOG.md`'s `[4.0.0]` entry and `docs/migration-v4.md` corrected two overclaims found
81
+ by post-tag review (`a4315993`).** `package.json` `files[]` admits the whole
82
+ `.orchestrator/policy/` directory, not a curated file list — the packlist test, not npm, is
83
+ what pins the shipped set to the tracked set. `pi-install.mjs --settings-only` actually
84
+ **rewrites** an existing Pi settings file (upsert), unlike `cursor-install.mjs`, which skips
85
+ existing destinations. Note for readers of the published `v4.0.0` tag: this correction lands
86
+ after that tag, so the tagged CHANGELOG text still carries the two overclaims — only `main`
87
+ and `4.0.1` have the corrected wording.
88
+ - **`tests/scripts/site-numbers.test.mjs` fixtures run offline
89
+ (`SO_SITE_NUMBERS_OFFLINE=1`) (`4ea84d3e`).** Without it, the census-block tests fetched live
90
+ npm download counts and went red whenever the live count moved past the fixture snapshot
91
+ (measured in CI 2026-09-07: 1237 vs. 1165).
92
+ - **`tests/lib/validate/check-auq-clarity.test.mjs` gets a 120 s hook timeout for its
93
+ `validate-plugin.mjs` spawn in `beforeAll` (`19b66e6e`).** GitLab pipelines 8790 and 8791
94
+ (2026-09-07) failed only here: the shared runner exceeded vitest's default 30 s `hookTimeout`
95
+ running the full validator (~12 s locally). Aligns with the sibling
96
+ `tests/scripts/validate-plugin.test.mjs`.
97
+
98
+ #### 4.0.1 — mechanical-fix session (16 follow-up issues from the 4.0.0 audit)
99
+
100
+ - **Reconcile-idempotency now has a contract and a regression test, not just a claim (#1242).**
101
+ Discovery found the acceptance criterion already held — tracked Provenance markers already
102
+ dedupe re-runs, a fresh-clone simulation regenerated 0 of 30 keys — so the delivered fix is a
103
+ contract doc (the scan is authoritative, the sidecar is a cache) plus a mutation-proven
104
+ fresh-clone consolidated-shape test, not new production code.
105
+ - **`check-unwired-features.mjs`'s Category S4 no longer conflates "genuinely unreachable" with
106
+ "reachable only via coordinator dispatch" (#1239).** Discovery measured 52 raw S4 findings, 46
107
+ of them (88.5%) false positives — modules invoked only through a coordinator `Task`/skill
108
+ dispatch, never a static import. S4 now splits into 5 truly-unreachable findings and 46
109
+ coordinator-invoked findings reported as advisory rather than blocking, with `skills/`
110
+ recognised as a first-class reachability edge source; a same-wave fix-pass tightened the
111
+ coordinator-invoked match to token boundaries (a Codex-repro case where a bare substring match
112
+ over-matched) and dropped the advisory line's WARN severity per `host-resources.md` HR-101.
113
+ The check's exit code is unchanged.
114
+ - **`inspectConfidentialNames()` is a new function; `loadConfidentialNames()` keeps its 4.0.0
115
+ `string[] | null` public contract (#1250).** This session's own Wave-2 refactor had changed
116
+ `loadConfidentialNames()`'s return shape to an object — a semver-breaking change the Wave-4
117
+ review panel and Codex gpt-6-astra both caught before the cut, not after. The fix reinstates
118
+ `loadConfidentialNames()` as a thin wrapper over the richer `inspectConfidentialNames()`. A new
119
+ `'all-dropped'` status (every entry in the confidential-names file invalid) now fails closed
120
+ instead of silently returning an empty list, and the loader's WARN on a missing/malformed file
121
+ is redacted to the file's basename — the same fix-pass had regressed the full path into
122
+ stderr, reopening the class of leak `check-owner-leakage.mjs` exists to prevent; found and
123
+ fixed inside this session's own review cycle, before release.
124
+ - **`loadHostPaths()` no longer loads `owner.yaml` twice (#1251).** Health-check state now
125
+ passes through from the caller's single load instead of triggering a second read.
126
+ - **Four small fixes under #1262: an env-configured standalone confidential-names copy now fails
127
+ closed instead of passing through silently (point 2); `docs/migration-v4.md` gained rollback
128
+ prose (point 3); `OPTIONAL_OBJECT_SECTIONS` is now an exported constant with a parity test,
129
+ replacing a duplicated mirror list (point 4); a stale comment on the full quality-gate path was
130
+ corrected (point 5).** Point 1 (a Codex-surface regeneration check) was dropped — Discovery
131
+ measured the existing set-equality pack test as the stronger pin, so no code change was needed.
132
+ - **`VALID_SESSION_PROFILES` moved to a single source of truth in
133
+ `scripts/lib/session-schema/constants.mjs`, with `SHARED_LIST_BOUNDS`/`INGEST_LIST_BOUNDS`
134
+ parity enforced (#1252).** A re-export shim stayed at the old location for compatibility,
135
+ pinned by an identity test.
136
+ - **Four git-fixture files and the two largest `rmSync` call sites now route through a shared
137
+ tmp-fixture helper instead of ad hoc cleanup; `listRepoFiles()` now swallows only
138
+ `ENOENT`/`ENOTDIR`, not every error (#1253).** This is a partial pass, not a sweep: 501 bare
139
+ **recursive** `rmSync` call sites remain unrouted (574 claimed → 524 measured → 501 after this
140
+ session) and are carried forward as #1268 with a Revisit-Trigger, not silently left
141
+ unmentioned.
142
+ - **`check-skill-links.mjs` now scans `docs/` (previously skill/command files only), takes a
143
+ `dirs` option, and right-anchors its GitLab `-/issues` carve-out (#1258).** The carve-out was
144
+ previously unanchored and could match a URL fragment elsewhere in a line. Two dangling
145
+ documentation links were repaired, both pointing at content that had moved to the vault
146
+ archive.
147
+ - **An unknown or valueless CLI flag on `generate-hook-import-set.mjs` now exits 2 with a usage
148
+ message instead of triggering the write (#1249).** `--out --check` no longer writes a file
149
+ literally named `--check` — the argv loop had read `--check` as `--out`'s value.
150
+ - **A platform allowlist check now uses `Object.hasOwn()` (#1256).** Discovery's companion claim
151
+ in the same issue — that a related export was dead code — was checked against real call sites
152
+ and refuted; that half closes as "working as designed".
153
+ - **Desktop notifications now carry a platform-aware label (#1254).** `detectPlatform()` drives
154
+ the OSC notify text so a Codex CLI session's notification reads differently from a Claude Code
155
+ session's.
156
+ - **A new session-start probe, `telemetry-flush-health`, surfaces telemetry-sender problems that
157
+ were previously silent (#1255).** It tail-reads the last `orchestrator.telemetry.flush` event:
158
+ a `sandbox:*` refusal reason now renders as a WARN banner instead of nothing, and an
159
+ unreadable ledger renders explicitly as `ledger-unreadable` rather than falling through to
160
+ silence. The reason string is bounded to 120 characters with control characters stripped.
161
+ - **The tests:src ratio census now resolves calls made through `fixtureGit`/`fixtureGitSpawn`
162
+ wrappers, including the `NO_BACKGROUND_WRITER` spread pattern, and strips comments before
163
+ reading a wrapped call's second positional argument (#1259).** Applicable call sites rose from
164
+ 17 to 200 (measured 2026-09-07 on the release tree with
165
+ `node scripts/lib/validate/check-test-git-config-target.mjs .`) — the tokenizer previously
166
+ couldn't see wrapper calls at all — and the reporting floor was raised from 20 to 100 to match.
167
+ - **Four tests now pin Node 24's `err.url` behaviour using a real spawned `node` child process,
168
+ not an in-process `vitest` failure (#1260).** An in-process `ERR_MODULE_NOT_FOUND` never
169
+ carries a `url` field, so the previous test could not observe the behaviour it claimed to pin.
170
+ The standalone single-file copy path is confirmed inert / fails closed for the same case.
171
+ - **A Discovery premise was refuted before any code changed (#1257).** `vault-sync`'s
172
+ sub-package dependency pins looked accidental; they are intentional, because CI already
173
+ installs the sub-package separately. Documented in `skills/vault-sync/SKILL.md` §
174
+ Dependencies instead of "fixed".
175
+ - **`release.mjs`'s drift sweep now judges version literals as whole tokens and skips comment
176
+ prose and lockfile dependency rows; its history allowlist admits nine files whose only literal
177
+ is prose history of the 4.0.0 major (`CLAUDE.md`, its generated twin `AGENTS.md`,
178
+ `CONTRIBUTING.md`, `NOTICE`, `.husky/pre-push`, `site/llms-full.txt`,
179
+ `skills/architecture/references/domain-model.md`, `skills/autopilot/SKILL.md`,
180
+ `templates/_shared/journey-manifest.md`).** Measured 2026-09-07 mid-release: `4.0.0` matched
181
+ inside `>=24.0.0` in package.json, 72 third-party rows in package-lock.json, and `// pre-4.0.0`
182
+ comments — none a version surface. The sweep's detail line now carries the total hit count
183
+ instead of truncating at five, which is how the last four files had stayed hidden.
184
+
185
+ ### Notes
186
+
187
+ - **4.0.1 is a PATCH: no public export changed shape.** `loadConfidentialNames()` keeps its
188
+ `string[] | null` contract; the new exports (`inspectConfidentialNames`, `SHARED_LIST_BOUNDS`,
189
+ `INGEST_LIST_BOUNDS`, `OPTIONAL_OBJECT_SECTIONS`, `stripComments`, `wrapperHasCwd`,
190
+ `checkTelemetryFlushHealth`, `renderBanner`, `TAIL_BYTES`, and `VALID_SESSION_PROFILES` in
191
+ `constants.mjs`) are additive. Reviewed by a 4-reviewer panel plus Codex gpt-6-astra on the
192
+ packed tarball; 2 P1 + 3 P2 Codex findings were fixed before the cut, not after.
193
+ - **Visible Codex picker confirmation (#1266) remains an operator step; not a release gate.**
194
+ - **The telemetry ingest server was redeployed for this release (#1261).** Operational step, not
195
+ a code change — rollback tag `telemetry-ingest:1.0.0-pre401` kept in place; health confirmed
196
+ post-restart.
197
+
198
+ ## [4.0.0] - 2026-09-06
199
+
200
+ > **A MAJOR, because public surfaces are REMOVED** (see *Removed (BREAKING)* below).
201
+ > This entry was folded out of `[Unreleased]` by hand at cut time. That is a manual
202
+ > editorial act and not an automated one: `scripts/release.mjs` carries no writer for it —
203
+ > `checkChangelogEntry()` (`release.mjs:283`) only REQUIRES a dated `## [4.0.0]` heading as
204
+ > the topmost release with `[Unreleased]` emptied (`release.mjs:296`), and `--check`
205
+ > separately requires HEAD to be present on BOTH remotes (`evaluateRemoteHeadParity`,
206
+ > `release.mjs:556`) before it will tag. An earlier draft of this note asserted that the
207
+ > script renames the heading; it does not, and a release trusting that would have failed
208
+ > its own preflight. The reason the heading is written no earlier than the cut is the
209
+ > `3.22.1` failure this repo already paid for once — a release section for a release that
210
+ > never completed, unnoticed for 12 days (`e4674109`).
211
+
212
+ **Predecessor commits (v3.24.0 → `e4674109`).** Three commits (`ee8ea425` Wave 2,
213
+ `e22a702e` Wave 3, `37169158` Wave 4; 71 files, +3,505/−678, measured
214
+ `git diff --stat 2cb8708b..37169158`), no `BREAKING CHANGE:` footer and no `!` subject.
215
+ Twelve Wave-2 follow-up issues land as mechanism: identity resolution folds onto the
216
+ process-local reader on a third surface, seven ledger readers move onto one canonical
217
+ `sessions.jsonl` collapse, and a `secret_masker.applied` guard closes its last two gaps.
218
+ Wave 3 arms the schema-drift CI gate; Wave 4 is a 3-Opus-reviewer panel plus fixpass that
219
+ closed 2 HIGH findings inside the panel's own Wave-2/3 diff.
220
+
221
+ **The 360° audit session (`main-2026-09-06-deep-1`) — what makes this a major.** Everything
222
+ below marked *4.0.0 — audit session* is that session's own change set, which lands as this
223
+ release's commit: 277 working-tree entries at fold time (`git status --short | wc -l`,
224
+ 2026-09-06), on top of 13 already-committed predecessors measuring 256 files, +13,537/−1,669
225
+ (`git diff --stat v3.24.0..HEAD | tail -1`, same day). Fourteen read-only Discovery agents measured the plugin against its own
226
+ telemetry, the 90-day fleet ledger and the vendor docs; the synthesis is
227
+ [`docs/audits/2026-09-06-360-audit.md`](docs/audits/2026-09-06-360-audit.md), per-agent
228
+ evidence under `docs/audits/2026-09-06-360-audit/w1/`. Nothing was removed on judgement: the
229
+ rule was 0 telemetry ∧ 0 fleet invocation in 90 days ∧ no runtime consumer, and
230
+ prose-invoked skills (0 by construction) were exempt. Upgrade guide:
231
+ [`docs/migration-v4.md`](docs/migration-v4.md).
232
+
233
+ ### Added
234
+
235
+ #### 4.0.0 — audit session
236
+
237
+ - **Root `AGENTS.md`, root `plugin.json`, and a portable `.agents/skills/` mirror — this repo
238
+ was previously invisible to Copilot CLI and to agent-plugins.org clients.** The root
239
+ `AGENTS.md` is byte-identical to `CLAUDE.md` (`diff -q AGENTS.md CLAUDE.md` → no
240
+ difference), so a harness that resolves project instructions from `AGENTS.md` now finds
241
+ this repo's own Session Config instead of nothing — before this there was no root
242
+ `AGENTS.md` at all, meaning the plugin's own repository could not read its own config on a
243
+ harness that looks for that filename. `plugin.json` declares
244
+ `$schema: https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`, the cross-vendor
245
+ manifest shape Cursor, Amazon, Microsoft, OpenAI and Vercel converged on.
246
+ `.agents/skills/<name>/SKILL.md` mirrors all 43 skills (`ls .agents/skills | wc -l` → 43)
247
+ for harnesses that read that convention rather than a plugin manifest.
248
+ - **`ultradeep` — a session PROFILE over `deep`, deliberately NOT a fourth session type.**
249
+ `/session ultradeep` resolves to `session-type: deep` + `session-profile: ultradeep` in
250
+ STATE.md frontmatter (`commands/session.md` § Argument alias). `session-type` never becomes
251
+ `ultradeep`: `VALID_SESSION_TYPES` is a closed set in
252
+ `scripts/lib/session-schema/constants.mjs`, and a fourth member would degrade silently in
253
+ two places (`telemetry/schema.mjs` maps an unknown type to `"other"`,
254
+ `session-close-backfill.mjs` labels it `housekeeping`). The profile changes only the wave
255
+ SHAPE — 7 waves with a coordinator-direct Synthesis-Gate at W2 (`agents: 0`, the one wave
256
+ whose whole job is to stop and ask before any code is written), per the new row at
257
+ `skills/session-plan/SKILL.md:336`. `SESSION_PROFILE_FIELD` in `scripts/lib/state-md.mjs`
258
+ is the reader; absent is not empty. Budgets (`ultradeep.max-*`) are deliberately NOT
259
+ implemented — the PRD defers thresholds until three runs have been measured. Spec:
260
+ [`docs/prd/2026-09-06-ultradeep-session-profile.md`](docs/prd/2026-09-06-ultradeep-session-profile.md).
261
+ - **A plugin-update banner at session-start — the first code in this repo that compares
262
+ INSTALLED against AVAILABLE.** `scripts/lib/plugin-update-banner.mjs` (`checkPluginUpdate`,
263
+ imported lazily from `hooks/on-session-start.mjs:976`) reads the RUNNING package's own
264
+ `package.json` — resolved as `../..` from `scripts/lib/`, i.e. the loaded bytes, never
265
+ `$CLAUDE_PLUGIN_ROOT`, which is precisely what resolved to the checkout while a stale cache
266
+ copy was executing — and compares it against the npm `dist-tags.latest`. The measured gap:
267
+ this host ran the marketplace cache at **3.19.0** (installed 2026-08-09) while repo and npm
268
+ were at **3.24.0** — five minors, four weeks, no warning, because the freshness probe
269
+ shells out to `git -C <plugin-dir> log -1` against a FILE COPY and
270
+ `classifyVersionMismatch()` downgrades anything below a major jump to `info`. Contract:
271
+ fail SILENT, never optimistic — offline, non-2xx, malformed JSON, timeout and unusable
272
+ cache each return `null` ("no statement"), never "you are up to date" (the #1031 collapse
273
+ class).
274
+ - **A telemetry sandbox guard — `detectSandbox()` in `scripts/lib/telemetry/sync.mjs`.**
275
+ This session's own read-only Wave-1 benches sent 6 production pings from agent sandboxes:
276
+ `hooks/on-session-end.mjs` ran from the repo checkout with `SO_CONFIG_HOME` redirected
277
+ while `owner.yaml` was unreachable inside the sandbox. The sender now refuses when
278
+ `SO_TELEMETRY_DISABLED=1` (`sandbox:telemetry-disabled`), `DO_NOT_TRACK` is set
279
+ (`sandbox:do-not-track`), the config home is split from the state dir
280
+ (`sandbox:config-home-split`), or the state dir resolves under a temp root
281
+ (`sandbox:temp-root` — matched after realpath, because macOS's `/var` → `/private/var`
282
+ symlink defeats a raw string prefix). Every refusal is reported with its `sandbox:*` reason
283
+ rather than as a silent no-op.
284
+ - **[`docs/baseline.md`](docs/baseline.md) — the `projects-baseline` relationship written
285
+ down.** One line: the baseline is a **private, optional** companion repository, read when
286
+ present, degraded to a documented fallback when absent, required by nothing in this plugin
287
+ and needed by no public consumer. The measurement behind it: of 49 baseline-touching files,
288
+ 17 are docs-only, 15 test-only, 13 soft, and only 4 hard-runtime — **2 of those 4 pointed
289
+ at a hardcoded `$HOME/Projects/projects-baseline` that does not exist**.
290
+
291
+ #### Predecessor commits (v3.24.0 → `e4674109`)
292
+
293
+ - **`scripts/lib/wave-executor/dispatch-common.mjs` (#1204).** Six symbols
294
+ (`NEVER_FOREIGN_ROLES`, `DEFAULT_TIMEOUT_SEC`, `DEFAULT_KILL_GRACE_MS`, `isSafeRunId`,
295
+ `isNeverForeignRole`, `runChild`) extracted out of `foreign-dispatch.mjs` into one shared
296
+ module, imported by both dispatch call sites (`foreign-dispatch.mjs`, `remote-dispatch.mjs`)
297
+ and their two test files. A redundant `isNeverForeignRole` check that had never lived in
298
+ `wave-resource-gate.mjs`'s own invariant is now a documented non-check there instead of a
299
+ silent gap.
300
+ - **`scripts/lib/learnings/evolve-telemetry.mjs` (#1206).** Mechanical
301
+ `orchestrator.evolve.completed` / `orchestrator.dialectic.completed` emitters replace
302
+ hand-written prose emit sites in `skills/evolve/SKILL.md`: `recordEvolveCompletion()` is
303
+ called from `scripts/sweep-expired-learnings.mjs`'s `--prune` exit path (the one store
304
+ write `/evolve analyze` already performs), and `recordDialecticRun()` from
305
+ `scripts/dialectic-deriver.mjs`. Both skip the emit with a stderr note when no `repoRoot`
306
+ is given, so test runs never write the fleet ledger. `docs/events-schema.md` gained both
307
+ event's producer lines.
308
+ - **`hooks/post-edit-import-probe.mjs` — a PostToolUse hook that catches a broken
309
+ hook-reachable module at save time, not at the next tool call (#1224).** On every
310
+ Edit/Write/MultiEdit to a `.mjs`/`.js`/`.cjs` file that is a member of the new committed
311
+ allowlist `hooks/_lib/hook-import-set.json` (149 modules at generation time — every module
312
+ transitively reachable from the four hook manifests' entry files), it runs ESLint's
313
+ `no-undef` on that single file (primary check) plus an `import()` probe scoped to
314
+ `scripts/lib/**` only (secondary — `hooks/*.mjs` is excluded because half of them run
315
+ `main()` at module bottom). Reports via `additionalContext` + the new
316
+ `orchestrator.hook.import_probe_failed` event (payload: `file`, `check`, `error`,
317
+ `reachable_from`, `duration_ms` — documented in `docs/events-schema.md`); never blocks
318
+ (exit 0 always); kill switch `SO_DISABLED_HOOKS=post-edit-import-probe`. The allowlist is
319
+ built by the new `scripts/generate-hook-import-set.mjs` (`--check` re-crawls and diffs) and
320
+ is guarded three ways: a new blocking `validate-plugin.mjs` check
321
+ (`runHookImportSetCheck`), a new `.husky/pre-commit` stage scoped to commits touching
322
+ `hooks/` or `scripts/lib/`, and — deliberately absent — no Codex wiring
323
+ (`hooks-codex.json`), documented as an intentional asymmetry in
324
+ `check-hooks-symmetry.mjs`'s `DOCUMENTED_ASYMMETRIES` (same missing Edit/Write-payload
325
+ adapter gap as `post-edit-validate.mjs`). Wired into `hooks.json`, `hooks-cursor.json`, and
326
+ `hooks-pi.json`. Rationale: the 2026-09-04 host-wide hook-block incident documented
327
+ elsewhere in this section (a `SyntaxError`/`ReferenceError` in an intermediate save of
328
+ `own-session.mjs` blocked every Bash/Edit call on the host for ~8 minutes) was a
329
+ **call-time** defect neither `node --check` (syntactically valid) nor a bare `await
330
+ import()` (module-level evaluation succeeds) catches — ESLint's `no-undef` does. The
331
+ allowlist has since been regenerated to include `scripts/lib/config/private-config-dir.mjs`
332
+ (new, #1223 below), which became hook-reachable via `host-identity.mjs`: measured
333
+ 2026-09-05, `node scripts/generate-hook-import-set.mjs --check` → `✓ hook-import-set: 150
334
+ modules, in sync`.
335
+
336
+ ### Changed
337
+
338
+ #### 4.0.0 — audit session
339
+
340
+ - **43 generated rule files consolidated into 8 thematic ones; `.claude/rules/` goes 61 → 26
341
+ files.** Measured now: `ls .claude/rules/*.md | wc -l` → 26, of which **zero** still match
342
+ the generated-rule naming (`anti-pattern-*`, `proven-pattern-*`, `recurring-issue-*`,
343
+ `convention-*`, `fragile-*`); at `HEAD` (`e4674109`) the same directory held 61 files
344
+ (`git ls-tree -r --name-only HEAD .claude/rules/ | grep -c '\.md$'`). The eight
345
+ replacements are `identity-and-locks`, `guard-design`, `measurement-discipline`,
346
+ `test-hygiene`, `process-contracts`, `toolchain-and-build`, `git-and-worktrees`,
347
+ `review-and-adapter-contracts`. Grounds: the 43 files were delivered TWICE per dispatched
348
+ agent (natively plus via rule injection) — 112 kB, 46 % overhead — and 10 of them restated
349
+ an always-on rule's insight verbatim.
350
+ - **The three largest instruction files are SPLIT into `references/`, not shortened.**
351
+ `skills/session-start/SKILL.md` 1,275 → 388 lines (9 reference files);
352
+ `skills/session-end/SKILL.md` 1,203 → 316 (6 reference files);
353
+ `skills/wave-executor/wave-loop.md` 1,337 → a 39-line index over 3 references
354
+ (`wave-loop-dispatch.md`, `wave-loop-review.md`, `wave-loop-scope-manifest.md`). Baselines
355
+ measured `git show HEAD:<path> | wc -l`, current `wc -l`. Content moved rather than
356
+ vanished: a phase you need is one hop away, a phase you do not need is no longer in every
357
+ agent's context.
358
+ - **`orchestrator.session.stopped` → `orchestrator.turn.stopped` (GitLab #1234).** The
359
+ emitter fires per assistant TURN, not per session: measured over the 90-day fleet window,
360
+ **15,538 records against 2,016 distinct `orchestrator.session.started` ids = 7.7 per
361
+ session**, with 184 for one id — so six consumers reading it as a session-lifecycle signal
362
+ were wrong by that factor. `hooks/on-stop.mjs:489`/`:493` emits BOTH names from the same
363
+ payload object for one deprecation generation (so they cannot disagree); the legacy name
364
+ additionally carries `deprecated: true`, letting a reader tell at a glance that it matched
365
+ the old name. **Removal of `orchestrator.session.stopped`: 2027-03-06.** Readers switch the
366
+ name they match on and change nothing else. NOT affected: the SubagentStop branch keeps
367
+ emitting `orchestrator.agent.stopped`, whose per-agent cardinality is correct. Contract:
368
+ `docs/events-schema.md:92-93`.
369
+ - **The telemetry `fleet` field becomes `fleet_self_declared`, and attribution moves
370
+ server-side.** The old name asserted something the client cannot know: 394 of 490 pings
371
+ were the operator's own second Mac, mis-attributed as "external", because the flag hung off
372
+ an `owner.yaml` line rather than off a person. The client field now says what it is —
373
+ self-declared, derived from the RESOLVED consent state (`enabled-fleet` / `enabled-env`)
374
+ rather than from a raw `owner.yaml` read (`scripts/lib/telemetry/schema.mjs:552-555`) — and
375
+ the authoritative classification is an anon-id allowlist in the ingest server
376
+ (`server/ingest/config.mjs`, `server/ingest/validate.mjs:190-196`). `fleet` remains in the
377
+ payload, identical in value for the whole generation so the server's existing column stays
378
+ comparable: **DEPRECATED 2026-09-06, removal 2027-03-06** (`docs/telemetry.md:42-43`).
379
+
380
+ - **`zod` and `yaml` hoisted from a nested skill's own `package.json` to this repo's
381
+ root `dependencies`.** `skills/vault-sync/package.json` resolved them as its own
382
+ transitive install; a fresh top-level `npm ci` without that nested install step
383
+ left `vault-sync`'s schema validation unable to resolve either package. Root
384
+ `package.json` now pins `yaml@^2.9.0` and `zod@^3.25.76` directly (the nested
385
+ `package.json` still carries its own copies — redundant, harmless, left as a
386
+ cleanup candidate).
387
+ - **`scripts/release.mjs`'s preflight gains a GitHub-mirror CI row and two
388
+ publish-time hardenings.** `evaluateGithubCiRow()` adds `ci-green-on-head-github`
389
+ as its own preflight check (26 rows total) via `checkCiStatus({ vcs: 'github' })`
390
+ — `--skip-ci` marks it skipped, and a repo with no configured GitHub remote also
391
+ skips it rather than failing; `publishInvocation()` now pins
392
+ `npm_config_loglevel=notice` on the publish spawn (an inherited silent level from
393
+ an outer gate run suppressed output the packlist gate depends on); and
394
+ `describeTagProgress()` is printed on a post-receipt tag/push failure, so a
395
+ publish that succeeds but fails to tag no longer leaves the operator guessing
396
+ what state the release is in. `commands/release.md` step 7 documents the ≥600s
397
+ Bash wrapper this needs; `skills/npm-publish/SKILL.md` is updated to match.
398
+ - **The release drift sweep moves off `git grep` onto `enumerateRepoFiles()`
399
+ (#1248) — the same gitignore/untracked blind spot the two checkers below were
400
+ fixed for, closed once.** `collectDriftHits()` in `scripts/release.mjs` now
401
+ enumerates via `git ls-files --cached --others --exclude-standard` instead of
402
+ shelling out to `git grep` for the previous release tag, so an untracked file is
403
+ swept for a leftover version literal exactly as a tracked one is.
404
+ - **The Full Gate now names which test FILES failed, not just the pass/fail
405
+ count.** `extractFailedTestFiles()` (`scripts/lib/gates/gate-helpers.mjs`) parses
406
+ vitest's own failure output; `gate-full.mjs` carries the result as a new
407
+ `failed_files[]` field alongside the existing counts, on both the returned
408
+ object and the `orchestrator.quality_gate.*` event payload. Separately,
409
+ `.husky/pre-push` now passes the real repo root to the gate as an **argv flag**,
410
+ `--ledger-root <path>` (`npm run --silent quality-gate -- --ledger-root
411
+ "$repo_root"`), so a gate run against a materialised temp worktree writes its
412
+ event into the REAL repo ledger instead of a tree an `EXIT` trap deletes seconds
413
+ later — the root cause of several `quality_gate.passed` records the test suite
414
+ itself had polluted into `.orchestrator/metrics/events.jsonl`.
415
+ `resolveLedgerRoot()` (`scripts/run-quality-gate.mjs`) validates the flag as an
416
+ existing directory that itself contains `.orchestrator/`; anything else is one
417
+ stderr WARN and a fallback to the pre-existing resolution, never a crash. **This
418
+ shipped first as an exported `SO_GATE_LEDGER_ROOT` env var and was replaced
419
+ before release, inside the same session**: the Wave-4 architect-panel review
420
+ measured that an exported env var is inherited by every descendant process,
421
+ including the gate's OWN vitest children three levels down (`npm run
422
+ quality-gate` → `gate-full.mjs` → `npm test`), where `run-quality-gate.test.mjs`'s
423
+ telemetry tests spread `...process.env` and so wrote their fixture events to the
424
+ pinned root instead of their own tmp fixture — measured **8 of 9 telemetry tests
425
+ red** under the hook's own environment. An argv flag reaches only the one
426
+ process meant to read it. `run-quality-gate.test.mjs` still sandboxes
427
+ `CLAUDE_PROJECT_DIR` per test and additionally scrubs any ambient
428
+ `SO_GATE_LEDGER_ROOT` before each run (belt-and-braces against a leftover from
429
+ the removed env-var form); a new `tests/husky/pre-push-gate.test.mjs` case
430
+ ("hands the real repo root to the gate as --ledger-root") pins the hook's own
431
+ argv, and 0 production readers of `SO_GATE_LEDGER_ROOT` remain repo-wide
432
+ (`grep -rn SO_GATE_LEDGER_ROOT` outside test files and history comments → no
433
+ match, measured 2026-09-06).
434
+ - **The session-start plugin-update banner is now English and platform-aware.**
435
+ `checkPluginUpdate()` (`scripts/lib/plugin-update-banner.mjs`) accepts an
436
+ optional `platform` and picks the matching upgrade command from
437
+ `PLATFORM_UPDATE_INSTRUCTIONS` (`claude`/`codex`/`cursor`/`pi`), falling back to
438
+ a generic `npm update`-shaped instruction when the platform is unset or
439
+ unknown — previously every consumer saw the same `claude plugin update` line
440
+ regardless of harness.
441
+ - **Six documentation corrections following the 4.0.0 removals above.** The
442
+ `autopilot` SKILL's Configuration section is now a 4-line pointer to
443
+ `docs/migration-v4.md` instead of describing the removed `bg-isolation` field in
444
+ full; `NOTICE`'s attribution table is repointed at
445
+ `skills/architecture/references/{domain-model,ADR-FORMAT,CONTEXT-FORMAT}.md` and
446
+ notes `ubiquitous-language`'s removal while keeping its MIT attribution history;
447
+ `check-agents.mjs`'s warn-string citations of `agents/AGENTS.md` now cite
448
+ `docs/agent-authoring.md`; `templates/_shared/journey-manifest.md` gained a
449
+ retirement notice (kept as an optional per-repo template — no command consumes
450
+ it anymore); `skills/architecture/SKILL.md` documents when to load
451
+ `references/domain-model.md`; and `README.md` picked up matching corrections
452
+ (Windows is untested rather than "runs natively"; the update banner is
453
+ patch-silent; nothing is sent without consent EXCEPT the update check's own
454
+ anonymous `GET`, opt-out via `SO_DISABLE_UPDATE_CHECK`/`DO_NOT_TRACK`).
455
+
456
+ #### Predecessor commits (v3.24.0 → `e4674109`)
457
+
458
+ - **Quality-gate ownership check now folds onto the process-local reader (#1205).**
459
+ `quality-gate.mjs`'s private copy of `readOwnSessionIds()` is gone; the ownership check now
460
+ calls `readProcessLocalSessionIds()` directly (removing the STATE.md-lock fallback), with
461
+ the first test of the ownership path itself.
462
+ - **Wave-scope manifest binding writes `attributionForRecord()`, not a hand-rolled call
463
+ (#1207).** `skills/wave-executor/wave-loop.md`'s manifest-binding step now sources
464
+ `session`/`wave` from the same `attributionForRecord()` used by `emitEvent()`'s own
465
+ correlation fill, instead of a raw `sessionAttribution()` call; the STATE.md prose
466
+ comparison the previous step asked for is removed as a redundant, sometimes-wrong check
467
+ (a peer-owned lock and STATE.md agree with each other, not with this process).
468
+ - **Seven readers now go through `readCanonicalSessions()` instead of their own duplicate
469
+ parse (#1209).** `session-id.mjs`, `auto-dream.mjs`, `dispatcher/rank.mjs`,
470
+ `eval/engine.mjs` (+ new `eval/session-resolve.mjs`), `harness-audit/categories/category4.mjs`
471
+ (via `canonicalizeSessions({ keepUnidentified: true })`, since a bare wrapper broke 3
472
+ id-less fixtures), `skill-health/join.mjs`, `sessions-staleness-banner.mjs`, and
473
+ `dialectic-deriver.mjs` each had a red-on-HEAD fake-regression proof before the fix
474
+ (auto-dream 3→2 sessions, rank 50→<6 candidates, eval 2→1 peers). `category9` is
475
+ deliberately untouched.
476
+ - **Four modules now distinguish silent `ENOENT` from a loud read failure (#1210).**
477
+ `session-close-backfill.mjs`, `reconcile/engine.mjs`, `memory-proposals/store.mjs` (via a
478
+ new shared helper — a decorative `err.code` branch there is now real), and
479
+ `agent-status.mjs` all warn on stderr for `EACCES`/`EISDIR`/other, and stay silent only on
480
+ a genuinely missing file.
481
+ - **German distributional-claim patterns join the English set (#1211).** Six German
482
+ quantifier-triggered patterns (`N von M`, `100% von/der/aller`, `alle N`, `keine
483
+ verbleibenden`, `jeder/jede/jedes`, `keiner/keine/keins von/der`) plus a narrow German
484
+ `CARDINAL_NOUN` extension (`zeilen?|dateien?|datei|aufrufer|eintr(?:ag|(?:ä|ae)ge)`) land
485
+ in `hooks/post-subagent-discovery-validator.mjs`. Measured against a 41-line German claim
486
+ corpus pulled from this repo's own `events.jsonl`: the quantifier patterns flag 7/41
487
+ (same order of magnitude as the English baseline); a wider bare-cardinal noun set
488
+ (`Sessions`/`Repos`/`Treffer`/`Stellen`/`Module`/`Tests`) was tried and rejected after it
489
+ flagged 39/41 (~57% false-positive rate) — those six nouns stay reachable only through the
490
+ lexically-anchored quantifier patterns. German `GATE_SUMMARY` recap forms are also
491
+ recognised and skipped, matching the existing English recap-skip.
492
+ - **`skills/vault-sync/validator.mjs`'s vendored schema catches up with upstream (#531).**
493
+ `peer-card` (#503), `board` (#738), and the optional `source-repo` field (#725) were
494
+ vendor-ahead of `projects-baseline` until this session; upstream-lifted in
495
+ `projects-baseline@cb9ec97`. `sync-vault-schema.mjs` now strips a bare `//` line (with a
496
+ regression test), and the provenance notes documenting the vendoring history moved outside
497
+ the generated-block sentinels so a `--write` regeneration can no longer drop them (measured:
498
+ it did, once).
499
+ - **The schema-drift CI gate is armed (#1175, follow-up to #531).** `SCHEMA_DRIFT_OPTIONAL`
500
+ is `"false"` in both `.gitlab-ci.yml` jobs that read it — `schema-drift-check` (line 636)
501
+ and `pipeline-gate` (line 723) — so a missing `SCHEMA_DRIFT_TOKEN` now hard-fails (exit 4)
502
+ instead of degrading to an amber "not verified" pass. A test pins the armed state (a half
503
+ revert of either flag turns 2 tests red). Proof: pipelines 8355–8357 @ `bca78dae` (bogus
504
+ enum) failed with `schema-drift-check` reporting "drift detected"; pipeline 8358 @
505
+ `dc9522dd` (branch `proof/1175-schema-drift-green`) passed with the check running WITH the
506
+ token, in sync. `docs/ci-setup.md` documents the Project-Access-Token recipe and the
507
+ now-current state (was previously the follow-up gap this same doc named).
508
+ - **`#1203` — the `enforce-scope.mjs` peer-manifest fix confirmed already SHIPPED via
509
+ #1194.** `tests/hooks/enforce-scope.test.mjs:1219/:1245/:1302` (56/56) prove the behaviour
510
+ landed with #1194; no further code change was needed.
511
+ - **`scripts/lib/platform.mjs`'s five module-constant exports become lazy, memoized
512
+ accessors; the five constants are REMOVED (#1153 P5).** They were internal accessor
513
+ constants of this plugin, never a documented public API, and every in-repo importer is
514
+ migrated — an external importer of these five names (none known) switches to the
515
+ getters; no `BREAKING CHANGE:` footer is carried for that reason. `SO_PLATFORM`, `SO_PLUGIN_ROOT`,
516
+ `SO_PROJECT_DIR`, `SO_STATE_DIR`, `SO_CONFIG_FILE` used to be `export const … =
517
+ detect…()` evaluated at module load, so every one of ~31 static importers — including
518
+ the hottest deny-capable hooks, which run on every tool call — paid a filesystem
519
+ walk-up (`statSync`/`existsSync` per ancestor directory) merely for importing the
520
+ module, whether or not the value was ever read. They are replaced by
521
+ `getPlatform()`/`getPluginRoot()`/`getProjectDir()`/`getStateDir()`/`getConfigFile()`,
522
+ computed on first call and memoized for the process (plus a test-only
523
+ `_resetPlatformCache()`); the deprecated names are fully REMOVED, not kept as
524
+ deprecated live bindings — a re-introduction is caught by the named-export assertion in
525
+ `tests/lib/platform.test.mjs`. 23 non-test call sites across `scripts/` and `hooks/` now
526
+ call a getter (re-measured 2026-09-05: `grep -rlE "getPlatform\(\)|getPluginRoot\(\)|getProjectDir\(\)|getStateDir\(\)|getConfigFile\(\)" scripts/ hooks/ --include="*.mjs" | grep -v /tests/ | grep -v platform.mjs | wc -l` → 23; it was 22 on 2026-09-04, before this session's own new modules landed).
527
+ - **Wave-scope manifest session keys renamed to `session_id`/`semantic_session_id`,
528
+ canonical since #1153 P2.** The pre-#1153 spellings `session`/`semantic_session` are
529
+ still ACCEPTED on the read side for one release (`MANIFEST_SESSION_KEYS` in
530
+ `scripts/lib/session-identity/own-session.mjs` is the shared SSOT list every writer and
531
+ reader now imports); `scripts/validate-wave-scope.mjs` flags a manifest that carries
532
+ BOTH spellings with conflicting values as an error rather than silently preferring one
533
+ and dropping the other. `scripts/wave-scope-binding.mjs` (new — #1153 P4) replaces the
534
+ inline `node --input-type=module -e` block `skills/wave-executor/wave-loop.md` § Scope
535
+ Manifest previously asked the coordinator to retype once per wave; an unbound `{}`
536
+ manifest now emits `orchestrator.scope.unbound_manifest` (0 hits repo-wide before this
537
+ file existed), making the previously-silent fail-closed case countable. `--merge` folds
538
+ the binding into an existing manifest in place.
539
+ - **`_privateDir()` (host-identity.mjs) now honours `SO_CONFIG_HOME` (#1153 P6).** Two
540
+ overrides, most specific first: `SO_CONFIG_HOME` names the private config directory
541
+ itself; `XDG_CONFIG_HOME` (same variable `owner-config-loader.mjs` already reads) names
542
+ its parent. Both read via `.trim() || fallback`, not a bare `||` (the whitespace-only
543
+ env-var trap). Without either, the homedir default `~/.config/session-orchestrator` is
544
+ unchanged.
545
+ - **`scripts/lib/session-identity/own-session.mjs`'s static import closure cut from 3,567
546
+ to 269 lines (#1153 P7).** It no longer imports `../session-lock.mjs` — which drags
547
+ `session-lock` → `exclusivity-matrix` → `file-lock` → `io` → `host-identity` behind it —
548
+ and instead shares the shape predicate `isLockShape()` via a new zero-import module,
549
+ `scripts/lib/session-lock-shape.mjs`, with `session-lock.mjs`'s own `parseLock()`. This
550
+ matters because `hooks/enforce-scope.mjs` loads `own-session.mjs` on every Edit/Write;
551
+ anything it imports joins that hook's static closure.
552
+ - **codex-cli 0.144.4 fixes the `plugin add` failure documented against 0.141.0
553
+ (#1163, FIXED-UPSTREAM — no code change here).** Re-verified end-to-end 2026-09-04
554
+ against this repo's unchanged flat layout: `codex plugin add session-orchestrator@kanevry
555
+ --json` now exits 0 where it previously failed with `plugin session-orchestrator was
556
+ not found in marketplace kanevry`. `docs/codex-setup.md`'s "Short-Form Marketplace Add"
557
+ section is promoted to the recommended install path and the prior failure kept as a
558
+ historical note; a new "Switching Marketplace Sources" section documents that
559
+ `marketplace add` silently REPLACES an already-registered marketplace of the same
560
+ declared name.
561
+ - **`#1152` closed — the reported defect was a false premise (two separate writers, not
562
+ one drifting one).** No code change; investigation confirmed the two paths write
563
+ independently and neither needed reconciling.
564
+ - **`#1214` — the suspected `maskerWouldChange` gap in the vault narrative-mirror sink was
565
+ a false premise, refuted by measurement.** Unlike the two `vault-mirror/process.mjs`
566
+ sinks fixed under #1028 (five-field comparisons that can match while a raw needle
567
+ survives on disk), `scripts/lib/vault-status/narrative-mirror.mjs` compares the WHOLE
568
+ rendered document, and the candidate always passes through the CURRENT `maskNarrative` —
569
+ so a value that masker would redact can never appear in it, and a false
570
+ `matchesModuloRedaction` match cannot occur. Measured 2026-09-04 at HEAD `cd785003` in
571
+ both directions (no marker on disk; marker on disk plus a second needle entering the
572
+ env): both returned `written` with the raw value gone. No probe added; two invariant
573
+ tests in `tests/lib/vault-status/narrative-mirror.test.mjs` pin it. Named ceiling: the
574
+ invariant depends on `maskNarrative` walking every rendered string, and `repo` is fed to
575
+ `renderNarrative` OUTSIDE that walk (a directory basename, not STATE.md content) — a
576
+ future rendered field added the same way would reopen this.
577
+ - **Three host-private-config-dir resolvers collapse into one (#1223).** New zero-import
578
+ leaf `scripts/lib/config/private-config-dir.mjs` (`node:os` + `node:path` only) exports
579
+ `resolvePrivateConfigDir({env}?)` — precedence `SO_CONFIG_HOME` (the dir itself) >
580
+ `XDG_CONFIG_HOME` (its parent) > `~/.config/session-orchestrator`, each `.trim()`ed. Before
581
+ this, `owner-yaml.mjs`'s import-time `OWNER_YAML_PATH` (homedir-only, no overrides at all),
582
+ `owner-config-loader.mjs`'s `resolveOwnerConfigPath()` (XDG-only, untrimmed), and
583
+ `host-identity.mjs`'s `_privateDir()` each had a different precedence — so
584
+ `SO_CONFIG_HOME=<sandbox>` moved the self-alias ledger but not `owner.yaml`, which kept
585
+ reading the operator's real home (the CLAUDE.md "vault-dir resolves HOST-LOCALLY" hazard
586
+ class). `host-identity.mjs` now delegates `_privateDir()` to the leaf directly (it is
587
+ reachable from live hooks via `session-lock.mjs`, so the leaf must stay import-free);
588
+ `owner-yaml.mjs` re-exports `resolvePrivateConfigDir` and adds a call-time
589
+ `resolveOwnerYamlPath(env?)`; the old `OWNER_YAML_PATH` constant is fully REMOVED (verified
590
+ 0 remaining consumers repo-wide), not kept as a deprecated back-compat export.
591
+ `owner-config-loader.mjs` and `owner-interview.mjs` now call through
592
+ `resolveOwnerYamlPath()` instead of their own copies. `tests/husky/pre-commit-owner-leakage.test.mjs`'s
593
+ file-by-file copied import chain (the CP11 scanner's dynamic-import fixture) gained the new
594
+ leaf as a required copy.
595
+ - **`skills/wave-executor/wave-loop.md` gains a "Shell variables used in this section"
596
+ preamble (#1225).** `$PLUGIN_ROOT`, `$WAVE`, `$ROLE`, `$STATE_DIR` are defined once ahead
597
+ of the Scope Manifest steps instead of being re-explained inline (the `$PLUGIN_ROOT`
598
+ per-platform expansion parenthetical is now a single cross-reference instead of a repeated
599
+ clause).
600
+ - **`scripts/lib/ci-status-banner.mjs` gains a three-state contract instead of two (#1031).**
601
+ A frozen `DEGRADED_REASONS` enum (`cli-missing`, `timeout`, `parse-error`, `query-failed`,
602
+ `git-error`) now distinguishes "state could not be read" from "no CI here" — before this,
603
+ 4 return sites collapsed every CLI-missing / timeout / unparseable-output / VCS-probe-error
604
+ case onto the same `null` an absent remote returns, which the banner reads as all-clear
605
+ (the same collapse class documented for `mirror-issues-banner.mjs` in
606
+ `skills/session-start/SKILL.md`, now fixed on this probe too). Consumers updated to the new
607
+ shape: the probe registry in `scripts/lib/session-start-probes.mjs` (`ci-status` entry's
608
+ `render`/`severityOf` now treat a `degraded` result as `warn`, not `ok`);
609
+ `scripts/lib/dispatcher/rank.mjs` (new exported `normalizeCiSignal()` reduces every shape —
610
+ bare string, `{status}`, `{degraded}`, `null` — to `{ciStatus, ciDegraded}`, mapping
611
+ `degraded` to `'unknown'` for scoring parity with the old behaviour while surfacing the
612
+ reason into `signals.readiness.ciDegraded` and a `warnings` entry); `scripts/release.mjs`'s
613
+ `evaluateCiRow()` treats `ci.degraded` as "CI status unknown (\<reason\>)", never as green.
614
+ `skills/session-start/SKILL.md`'s CI-status paragraph documents the new degraded banner
615
+ line. `scripts/lib/qg-command-drift-banner.mjs` is converted too (W3-P1): it mints its own
616
+ `degradedBanner(reason)` and passes a nested `detailed.degraded` straight through
617
+ (`:59`/`:99`/`:102-103`), so a config that could not be READ is reported as `warn` rather
618
+ than read as "no drift".
619
+ - **`hooks/post-tool-batch-wave-signal.mjs` starts emitting the `files_changed` measurement
620
+ `scripts/lib/convergence-monitor.mjs`'s `shrinking_diff` signal has always read but no
621
+ producer ever wrote (#980).** At an N→N+1 wave-open transition, the `orchestrator.wave.completed`
622
+ emitted for wave N now carries `files_changed` (the deduped union of
623
+ `git diff --name-only <wave_start_sha>` and `git ls-files --others --exclude-standard`,
624
+ measured against the working tree because the coordinator commits at session close, not per
625
+ wave) and `files_changed_source: 'worktree-vs-wave-start-sha'`; `wave_start_sha` (`git
626
+ rev-parse HEAD`) is persisted into `.orchestrator/current-session.json` under the same
627
+ ownership gate as the other wave keys when a wave opens. Both keys are optional and
628
+ absent-is-not-zero — any git failure, a 1.5s timeout, or a missing `wave_start_sha` omits
629
+ them, and the monitor reads the absence as `null` rather than firing on a fabricated 0.
630
+ `skills/convergence-monitoring/SIGNALS.md` gained a "Live monitor input" subsection under
631
+ all three signals (shrinking_diff, pass_rate_plateau, velocity_drop), each quoting a fresh
632
+ measurement of `.orchestrator/metrics/events.jsonl` (2026-09-05): 144 pre-existing
633
+ `wave.completed` records carried no `files_changed` key at all before this fix; 33
634
+ `orchestrator.quality_gate.*` records carry both `wave_number` and a well-formed `counts`
635
+ object (making `pass_rate_plateau` fireable, `_evaluateSignals` now a test-only export);
636
+ 11,754 `orchestrator.agent.stopped` records make `velocity_drop` fireable, versus 0 for the
637
+ `agent.dispatched` type the reader also accepts. `monitors/monitors.json`'s
638
+ `convergence-monitor` description corrected — it never opened `sessions.jsonl` (a stale
639
+ claim from an earlier draft of the monitor).
640
+ - **`scripts/validate-wave-scope.mjs`'s internal `validateSession()` renamed to
641
+ `validateSessionBinding()` (#1153 P11).** Rename only, no behaviour change — the old name
642
+ read as "validate the session" when it validates the wave-scope manifest's session-BINDING
643
+ fields specifically; a previously-considered `skipped[]` return addition was dropped as a
644
+ false premise (the function already reports via `errors`/`warnings`).
645
+ - **`docs/rule-authoring.md`'s `LEARNING_TYPE_REGISTRY` table gains its fourth axis,
646
+ `hostScoped` (#1153 P12, follow-up to #1090).** The table previously transcribed only
647
+ `ttlDays`/`agentProposable`/`ruleConvertible`, so a drifted `host_class`-gating cell was
648
+ structurally invisible to `tests/docs/rule-authoring-registry-parity.test.mjs`; the parser
649
+ regex and parity-diff logic there now check all four axes bijectively.
650
+ `hardware-pattern` is the only `hostScoped: true` type today.
651
+ - **`docs/README.md` gains a "Superseded design notes" section (#1153 P14).** Because
652
+ `docs/specs/` is gitignored, a correction written INTO a spec can never be committed — the
653
+ correction for `docs/specs/2026-05-26-parallel-aware-sessions-design.md` (which still
654
+ specifies PID-based lock liveness) lives here instead: liveness has been heartbeat-age
655
+ based since #1137, and the recorded PID has been consulted nowhere since #1151.
656
+ `skills/_shared/parallel-aware-preamble.md` cross-references it.
657
+
658
+ ### Removed (BREAKING)
659
+
660
+ Every entry under *4.0.0 — audit session* below is a REMOVED PUBLIC SURFACE and the reason
661
+ this release is a major. Each names its replacement or states explicitly that there is none.
662
+ Upgrade guide: [`docs/migration-v4.md`](docs/migration-v4.md).
663
+
664
+ **Deviation from the deprecate-then-remove norm** (`.claude/rules/development.md` § Package
665
+ Lifecycle: stub + first-call `console.warn` for ≥1 minor cycle). The 5 skills and 3 commands
666
+ below are HARD-deleted, not stubbed — an explicit operator decision made 2026-09-06. The
667
+ removal test was **0 telemetry ∧ 0 fleet invocation over 90 days ∧ no runtime consumer**, and
668
+ that 90-day window is honestly a *search* window, not 90 days of shipped exposure: `journey-audit`
669
+ itself had existed for only 9 days (added 2026-08-28) before this cut. Full rationale and the
670
+ manual cleanup steps: [`docs/migration-v4.md`](docs/migration-v4.md).
671
+
672
+ #### 4.0.0 — audit session
673
+
674
+ - **5 skills removed** — `find skills -name SKILL.md | wc -l` → 43, against
675
+ `git ls-tree -r --name-only HEAD skills/ | grep -c 'SKILL.md$'` → 49 (the sixth delta is
676
+ the `domain-model` merge below):
677
+ - **`skills/daily/`** (plus `generate.sh`, its template and 2 test files) — **no
678
+ replacement.** 0 telemetry, 0 fleet invocations in 90 days, no runtime consumer; only its
679
+ own test and documentation referenced it. Recurring daily notes are a Routine / scheduled
680
+ task, not a session skill (`.claude/rules/loop-and-monitor.md` § LM-004).
681
+ - **`skills/skill-creator/`** — **replaced by Anthropic's official `skill-creator` plugin**
682
+ plus `claude plugin init`. 0/0/0 with 0 in-repo references.
683
+ - **`skills/ubiquitous-language/`** — **no replacement.** 0/0/0.
684
+ - **`skills/contract-version-bump/`** — **no replacement.** 0/0/0; one comment referenced
685
+ it.
686
+ - **`skills/journey-audit/`** — **no replacement.** 0 journey manifests exist fleet-wide,
687
+ so the skill was never runnable in the first place.
688
+ - **3 commands removed** — `commands/*.md` 28 → 25, mirrored in `.cursor/commands/` and
689
+ `pi/prompts/` (both also 25): **`/contract-version-bump`**, **`/journey-audit`** (with
690
+ their skills above) and **`/autopilot-multi`**. **No replacement** for any of the three.
691
+ `/autopilot` is a different command, unaffected, and stays.
692
+ - **Session Config `autopilot.bg-isolation` removed** — the `autopilot:` block's only field,
693
+ retired together with `/autopilot-multi` above (`scripts/parse-config.mjs` never parsed an
694
+ `autopilot` key at all, so removing it changes no runtime behaviour). **No replacement** —
695
+ delete the block from your `CLAUDE.md`/`AGENTS.md`; single-story `/autopilot` takes no
696
+ Session Config block and is unaffected.
697
+ - **8 top-level scripts removed** (`scripts/*.mjs`, 0 runtime callers each, with their test
698
+ files): `autopilot-multi.mjs`, `backfill-learnings.mjs`, `backfill-learnings-expires.mjs`,
699
+ `fleet-instruction-scan.mjs`, `lifecycle-sim-v6.mjs`, `migrate-learnings-jsonl.mjs`,
700
+ `migrate-subagents-jsonl.mjs`, `upload-social-preview.mjs` — **no replacement.** The two
701
+ `migrate-*` scripts were one-shot format migrations whose target format has been canonical
702
+ for several releases; the two `backfill-learnings*` scripts were one-shot ledger repairs.
703
+ - **`skills/_shared/model-selection.md` removed — no replacement, 0 consumers.**
704
+ - **`skills/domain-model/` removed and MERGED into `skills/architecture/references/`** — its
705
+ three files now live as `references/domain-model.md`, `references/ADR-FORMAT.md` and
706
+ `references/CONTEXT-FORMAT.md`. It was reference-only material with 4 inbound links, never
707
+ a dispatchable skill; `/architecture` is the entry point.
708
+ - **Two non-agents moved OUT of `agents/`** — `agents/*.md` 16 → 14. The agent loader
709
+ registers every `.md` file in that directory as a dispatchable agent, and
710
+ `agents/AGENTS.md` — the authoring SPEC — was measurably dispatched as an agent 5 times. It
711
+ is now [`docs/agent-authoring.md`](docs/agent-authoring.md), and
712
+ `agents/memory-proposal-collector.md` is now
713
+ [`docs/memory-proposal-flow.md`](docs/memory-proposal-flow.md). Same content, no longer a
714
+ dispatch target. **Anything dispatching either by name must stop** — they are
715
+ documentation, and were only ever documentation.
716
+
717
+ - **`scripts/lib/autopilot/multi-killswitch.mjs` and
718
+ `scripts/lib/autopilot/dep-graph.mjs` deleted, with their test files** —
719
+ orphaned by the `/autopilot-multi` removal above, with only their own tests as
720
+ consumers. **No replacement**; single-story `/autopilot` never imported either
721
+ module.
722
+
723
+ #### Predecessor commits (v3.24.0 → `e4674109`)
724
+
725
+ - **`scripts/lib/webhook-url.mjs` deleted — zero callers repo-wide (#1168).** `resolveWebhookUrl`/
726
+ `WebhookConfigError` and their test file (`tests/lib/webhook-url.test.mjs`, 192 lines) are gone,
727
+ −364 lines total; the `## Webhooks (#228)` section in `docs/session-config-reference.md`
728
+ (the `webhooks.<kind>.url` Session Config surface it backed) is removed with it. The still-live
729
+ Clank Event Bus webhook (`scripts/lib/events.mjs`, `CLANK_EVENT_SECRET`/`CLANK_EVENT_URL`) is
730
+ unrelated and unaffected — its doc section is promoted from `### Clank Event Bus` to
731
+ `## Clank Event Bus` in the same edit, since it is no longer a subsection of the now-removed one.
732
+
733
+ ### Fixed
734
+
735
+ #### 4.0.0 — audit session
736
+ - **`tests/telemetry/sync.test.mjs`: the two `detectSandbox` "real operator shape" cases no longer use `process.cwd()`** — the husky pre-push gate runs the suite in a tracked-tree copy under `$TMPDIR`, where cwd IS a temp root and the guard correctly answers `sandbox:temp-root`; both cases were red on every push attempt while green in the checkout (measured 2026-09-06 — the first push of the 4.0.0 review session failed on exactly this file, unnamed until the gate learned to name `failed_files`). They now probe a path under `os.homedir()`.
737
+
738
+ - **`js-yaml` out of the eager hook import graph — 4 of 27 hooks crashed on every turn
739
+ without `node_modules`; now 0 (GH#62 / GH#63 / GitLab #1230).**
740
+ `scripts/lib/owner-yaml.mjs` carried a static `import yaml from 'js-yaml'` and sits on the
741
+ import graph of `on-session-start`, `on-session-end`, `post-edit-validate` and
742
+ `skill-invocation-telemetry` — so a skipped or interrupted `npm install`, a half-synced
743
+ plugin cache, or an EPERM sandbox killed all four at MODULE-LOAD time with
744
+ `ERR_MODULE_NOT_FOUND` and exit 1, on every single turn. `js-yaml` now resolves lazily
745
+ inside `loadOwnerConfig`/`writeOwnerConfig`. Measured 2026-09-06 @ `e4674109` with `hooks/`
746
+ and `scripts/` copied to a tmp dir carrying no `node_modules` anywhere up the tree:
747
+ **23/27 hooks exited 0 before the fix, 27/27 after.** Pinned by the new
748
+ `tests/hooks/hooks-run-without-node-modules.test.mjs`, which EXECUTES every hook instead of
749
+ reading source text — the pre-existing static-import guard would have accepted a lazy
750
+ `require()` of a package that does not exist. The `zx` half of #1230 is stale and now says
751
+ so: `scripts/lib/worktree/listing.mjs`'s `zx` imports are already lazy and caught, verified
752
+ by installing a `js-yaml` stub alone and watching all four hooks reach rc=0 with `zx` still
753
+ absent.
754
+ - **`picomatch` declared in `package.json` dependencies.** It is imported by four runtime
755
+ modules (`rule-loader.mjs`, `command-blocker.mjs`, `validate-vendored-rules.mjs`,
756
+ `reconcile/emitter.mjs`) and was absent from the manifest at `HEAD`
757
+ (`git show HEAD:package.json | grep picomatch` → no match) — reachable only as somebody
758
+ else's transitive dependency, the GH#62 class exactly.
759
+ - **The Cursor adapter generator wrote a malformed `argument-hint` into 24 of 28 command
760
+ files (GH#54).** `scripts/generate-cursor-adapter.mjs` now renders every frontmatter value
761
+ through `yamlQuote()`, which JSON-quotes anything failing `YAML_PLAIN_SAFE` (and anything
762
+ that looks like a YAML scalar, or starts with a digit). Measured after the fix:
763
+ `grep -c "argument-hint: \[" .cursor/commands/*.md` reports zero files carrying the array
764
+ shape. The regression survived for as long as it did because the generator was tested
765
+ against itself; it is now pinned against the spec.
766
+ - **`generateFrontmatterSnippet()` THREW when the baseline was absent.**
767
+ `scripts/lib/frontmatter-guard.mjs` resolves the schema through
768
+ `resolveHostPath('baseline-path')` and treats a missing schema as `null` — the honest
769
+ outcome, with the documented fallback enum set — instead of raising inside a pre-dispatch
770
+ guard. `scripts/lib/vault-backfill/template.mjs` uses the same resolver.
771
+ - **The session registry wrote into the operator's REAL host-private config from a
772
+ sandbox.** `scripts/lib/session-registry.mjs` now derives its `sessions/` directory from
773
+ `resolvePrivateConfigDir()` (`SO_CONFIG_HOME` > `XDG_CONFIG_HOME` >
774
+ `~/.config/session-orchestrator`) instead of its own homedir-only path, so a redirected
775
+ config home actually redirects it.
776
+ - **`node scripts/parse-config.mjs --json` failed with `File not found: --json`.** The flag
777
+ is now an explicit NO-OP alias for the default (stdout has always been JSON), because the
778
+ bootstrap documentation names that exact command as THE validation command and
779
+ `cli-design.md` § JSON-First Output requires every CLI to accept `--json`.
780
+ - **The vault-staleness probe measured against the clock instead of against the repo
781
+ (#1238).** `skills/discovery/probes/vault-staleness.mjs` now asks whether the upstream repo
782
+ advanced PAST the last sync, not what `now - lastSync` is — a mirror of a repo nobody has
783
+ committed to in three weeks is CURRENT, not three weeks stale. Both sides of the comparison
784
+ (`lastSync`, `lastCommit`) come from one read of the same `_overview.md` frontmatter.
785
+ Measured against the live vault before the fix (2026-09-05): **33 of 48 overviews reported
786
+ "stale", 26 of them >7d, with a demonstrably healthy sync chain.** An overview without
787
+ `lastCommit` still falls back to the wall-clock comparison, marked
788
+ `basis: 'probe-runtime'` and carried at lower confidence so a consumer can tell a measured
789
+ delta from a guessed one.
790
+ - **The discovery-validator scanned the WRONG transcript, so effectively every violation it
791
+ recorded was the coordinator's own prose (#1191).** `input.transcript_path` on
792
+ `SubagentStop` is the PARENT/main session transcript, not the subagent's — measured
793
+ 2026-09-02 on a seeded random sample of 60 violations: **100 % coordinator text,
794
+ scope-adjusted precision 0 %**, with `agent` reported as `"unknown"` in 90.8 % of 1,541
795
+ events. The hook now reads `<transcriptDir>/<session>/subagents/agent-<agent_id>.jsonl`
796
+ (the layout `subagent-telemetry.mjs` and `wave-transcript-tail.mjs` already use) and NEVER
797
+ falls back to the parent path: a scan of the wrong transcript is worse than no scan. Claims
798
+ are additionally collapsed per distinct normalised claim with an `occurrences` count
799
+ (`normalizeClaim()` / `dedupeViolations()` in `hooks/_lib/subagent-transcript.mjs`, #1198)
800
+ — the worst repo in the fleet held 3,360 records over 205 distinct claim texts, a ×16.4
801
+ duplication factor.
802
+ - **The `ecosystem-health` watcher could never start.** Its trigger in
803
+ `monitors/monitors.json` was `on-skill-invoke:ecosystem-health` — self-referential: the
804
+ watcher could only start when the `ecosystem-health` skill ran, and that skill has 0
805
+ recorded invocations fleet-wide, so it never started once. The trigger is now
806
+ `on-skill-invoke:session-start`.
807
+
808
+ - **CP11 (the confidential-names owner-leakage check) could fail OPEN and still
809
+ print `PASS` (#1244).** `getConfidentialNamePatterns()`
810
+ (`scripts/lib/validate/check-owner-leakage.mjs`) collapsed three different
811
+ outcomes onto the same `[]` degrade: (a) the standalone single-file copy, where
812
+ the helpers are genuinely unresolvable — the only case meant to go inert; (b)
813
+ CP11 simply unconfigured; and (c) CP11 configured but unreadable (missing
814
+ `js-yaml`, an unparseable `owner.yaml`, or a wholesale-discarded partial
815
+ config), which used to read identically to (b) and print the clean verdict it
816
+ never earned. Case (c) now returns a `disabledReason` and the scanner prints
817
+ `CP11 DISABLED: <reason>` plus a counted FAIL — with no path in the reason
818
+ string, since this scanner's output is mirrored to a public CI log.
819
+ `owner-yaml.mjs`'s whole-file discard on an invalid REQUIRED section (#820) now
820
+ merges a still-VALID optional object section (e.g. `paths:`) back onto its
821
+ default instead of discarding it too, so a correctly declared
822
+ `confidential-names-file` no longer vanishes because an unrelated section was
823
+ malformed; a genuinely invalid optional section is reported via
824
+ `droppedSections`, and an unparseable file gets the new `reason: 'unparseable'`.
825
+ A third, unrelated fail-open closed in the same file: `isMain` now compares
826
+ `realpathSync()` of both sides, so a symlinked invocation path (`/tmp` →
827
+ `/private/tmp` on macOS) can no longer make the scanner exit 0 having scanned
828
+ nothing.
829
+ - **CP11's own DISABLED path (above) was itself overbroad, and its degrade
830
+ path had two further defects — all three closed together in
831
+ `scripts/lib/validate/check-owner-leakage.mjs` and `.husky/pre-commit`
832
+ (Wave-4 Codex second-look).** First, DISABLED fired even when NO
833
+ confidential-names file was ever configured: a healthy `owner.yaml` with
834
+ only an unrelated invalid optional section (e.g. `paths: { vault-dir: 42
835
+ }`) and no `confidential-names-file` key reported `CP11 DISABLED` and a
836
+ counted FAIL, when the correct verdict is "inactive." A new
837
+ `rawConfidentialNamesKeyState()` re-reads the RAW (pre-validation) key
838
+ before classifying, so DISABLED now fires only when a names path was
839
+ actually configured; a healthy owner.yaml with `paths:` present but no
840
+ names-file key now stays `PASS` with no CP11 line at all, pinned by a new
841
+ case in `tests/lib/validate/check-owner-leakage.test.mjs`. Second, the
842
+ standalone-copy inert degrade (the ONE case meant to go silently
843
+ patterns-`[]`) was scoped by a bare `ERR_MODULE_NOT_FOUND` check, so a
844
+ missing TRANSITIVE module — not just the scanner's own three vendored
845
+ sibling imports — also went inert instead of failing closed.
846
+ `isMissingDirectSibling()` now inspects `err.url` (the URL Node's own
847
+ `ERR_MODULE_NOT_FOUND` carries) against the scanner's three known
848
+ direct-sibling import URLs; a miss anywhere deeper now THROWS. Third,
849
+ `.husky/pre-commit` was capturing but discarding the scanner's own stderr
850
+ on failure and printing the same "privacy leak detected" line regardless
851
+ of cause; it now prints the scanner's (redacted) diagnostic tail and
852
+ distinguishes "the scanner could not run" from "the scanner found a
853
+ leak." 259/259 tests pass; 4 fake-regression probes (each reverting one
854
+ of the three fixes above) still turn red; a live repo scan still PASSes.
855
+ - **`checkOwnerConfig()`'s whole-file-discard message claimed "the entire
856
+ file was discarded" even when a validly-configured OPTIONAL section
857
+ survived the discard onto its default (#1244 merge rule, Fixed above).**
858
+ `scripts/lib/owner-config-banner.mjs` now diffs each optional object
859
+ section (`paths`, `dispatcher`) in the merged config against
860
+ `getDefaults()`: a section that differs was real and survived, and the
861
+ banner names it — `"paths" kept, everything else defaulted` — instead of
862
+ claiming total loss; `droppedSections` renders on this branch too.
863
+ Reproduced with a tmp owner.yaml carrying an invalid `owner.name`
864
+ (required, triggers the whole-file path) alongside a valid
865
+ `paths.confidential-names-file` (a Wave-4 architect-panel finding); a new
866
+ case in `tests/lib/owner-config-banner.test.mjs` pins the "kept" wording.
867
+ 168/168 tests pass; 2 fake-regression probes still turn red; a real,
868
+ healthy host's banner still returns null.
869
+ - **`check-skill-script-paths` and `check-validator-registration` answered an
870
+ existence question from the git INDEX, so a brand-new untracked file's own
871
+ citation was invisible to them until `git add` (#1248).** Measured in a clone
872
+ (Wave-1 discovery): an untracked skill citing a non-existent script reported
873
+ `1 passed, 0 failed` before staging and `0 passed, 1 failed` after — same tree,
874
+ same defect. New shared `scripts/lib/validate/enumerate-repo-files.mjs`
875
+ (`enumerateRepoFiles()`) answers "does this exist under these roots right now"
876
+ via `git ls-files --cached --others --exclude-standard`, honouring
877
+ `.gitignore` without reopening #1143 (a bare `readdirSync` walk was tried and
878
+ REFUTED: it pulls a gitignored peer worktree under `.claude/worktrees/` into
879
+ the census — measured 287 vs 290 `.md` files with one peer worktree present).
880
+ Both checkers, and the release drift sweep (Changed, above), now share this
881
+ one enumerator instead of three different partial views.
882
+ - **`enumerateRepoFiles()` (above) silently dropped `stat` failures other than
883
+ `ENOENT`/`ENOTDIR`, so a permission-denied file read as "does not exist"
884
+ instead of "unknown" — the release drift sweep (Changed, above) could
885
+ report a clean sweep on 0 actual reads (Wave-4 Codex second-look).** It
886
+ now throws a typed `RepoEnumerationError`
887
+ (`scripts/lib/validate/enumerate-repo-files.mjs`) for
888
+ `EACCES`/`EPERM`/`ELOOP`/`EIO`/`ENAMETOOLONG`/anything else it cannot
889
+ classify as "provably absent," carrying the path and the underlying error
890
+ code; only `ENOENT` and `ENOTDIR` are still treated as absent.
891
+ `collectDriftHits()` (`scripts/release.mjs`) reports this as exit 128,
892
+ which the release preflight already reads as "inconclusive" — the same
893
+ reading a crashed `git grep` got, never a silent clean. 195/195 tests
894
+ pass across the 5 touched files; the Codex reproduction now shows status
895
+ 128 / `ok:false` instead of a clean sweep; `validate-plugin.mjs` still
896
+ 234/0. Two known sibling sites of the same swallow-class are filed as
897
+ follow-up issues rather than fixed here: `listRepoFiles()`
898
+ (`scripts/lib/repo-files.mjs`) has the identical catch-all, and 3 further
899
+ test files still hand-roll `git init` + a bare recursive `rmSync` in
900
+ template-string form (invisible to a literal-string census) rather than
901
+ routing through `tests/_helpers/tmp-fixture.mjs`.
902
+ - **15 more fixture-shaped test files are routed onto the
903
+ `tests/_helpers/tmp-fixture.mjs` helper (the detached-`git-maintenance` fix,
904
+ below) this session — 3 of them deleting a weaker inline copy of the same
905
+ helper that `enumerate-repo-files.test.mjs`, `check-validator-registration.test.mjs`
906
+ and `check-skill-script-paths.test.mjs` had each written for themselves
907
+ (above), plus 11 legacy files and a `release.test.mjs` git-grep-to-fixture
908
+ probe.** `tests/unit/quality-gate-session-config.test.mjs` — the one file
909
+ this pass left out of scope, needed here because it was also touching
910
+ `enumerate-repo-files.test.mjs` — is itself routed in this same session (23
911
+ insertions / 30 deletions: `execSync('git init', ...)` →
912
+ `fixtureGit(['init', ...])`, `rmSync(repoRoot, ...)` → `removeTree(repoRoot)`,
913
+ `mkdtempSync(...)` → `makeTmpDir(...)`). Net −28 LOC across the routed
914
+ files; 526/526 of their own tests unchanged per file; eslint clean. The
915
+ self-census this whole effort is measured against
916
+ (`tests/lib/validate/check-test-git-config-target.test.mjs`) cannot resolve
917
+ a fixture helper's own `...spread` token to a git subcommand, so its floor
918
+ was lowered from `>50` to `>20` with a Revisit-Trigger comment naming the
919
+ exact condition ("the census learning to resolve the helper's spread")
920
+ rather than silently loosened — the remaining git-fixture files are reduced
921
+ this session, not proven at zero: 3 further sites in template-string form
922
+ are filed as follow-up issues (above).
923
+ - **The full test suite failed 1–2 tests per run, never the same one twice — a
924
+ detached `git maintenance` child racing the fixture's own cleanup.**
925
+ `GIT_TRACE=1` on a bare fixture commit showed `git maintenance run --auto
926
+ --quiet --detach` still writing into a temp `.git` directory after
927
+ `execFileSync` returned, so the `afterEach` `rmSync` raced a live process and
928
+ failed with `ENOTEMPTY`. New `tests/_helpers/tmp-fixture.mjs` (`fixtureGit`,
929
+ `fixtureGitSpawn`, `removeTree`, `makeTmpDir`) sets `maintenance.auto=false`,
930
+ `gc.auto=0` and `core.fsmonitor=false` on every fixture repo, and retries
931
+ removal (`maxRetries: 5`) for what config alone cannot reach. 24 fixture files
932
+ (118 git call sites, 35 `rmSync` sites, 39 `mkdtemp` sites) now route through
933
+ it; the detached-maintenance trace line is gone from the routed files, and the
934
+ 494 tests across those 24 files still pass.
935
+ - **10 of the 43 learning-provenance markers the 43→8 generated-rule
936
+ consolidation (Changed, above) dropped are restored, across
937
+ `git-and-worktrees`, `guard-design`, `measurement-discipline`,
938
+ `process-contracts`, `review-and-adapter-contracts`, `test-hygiene` and
939
+ `toolchain-and-build.md` (+121/−19 across the 7 files).** That consolidation's
940
+ own claim — "each keeping its provenance markers" — was false for 10 of them
941
+ when written; a fresh census now counts 43 of 43 keys and 43 of 43 ids (a
942
+ previously reported "42 ids" was itself a UUID-filter miss, not a real 42nd
943
+ gap). One of the 10 was a markers-only restoration against an
944
+ already-present duplicate prose block; the other 9 restore both the prose and
945
+ its evidence line.
946
+ - **`docs/migration-v4.md` §6 Rollback and its "Cursor still shows the removed
947
+ commands" recipe both had defects that would have re-broken what they were
948
+ trying to fix (Wave-4 Codex second-look).** §6's `cursor-install.mjs` /
949
+ `pi-install.mjs` rollback lines were missing the target-project argument
950
+ both scripts require — they default to `process.cwd()`
951
+ (`scripts/cursor-install.mjs:24-51`), so an operator following the doc
952
+ literally would link the installer into the session-orchestrator checkout
953
+ itself, not their own project. Both lines now carry
954
+ `/path/to/your-project` explicitly, with a note that both installers are
955
+ ADD-ONLY: they skip any destination that already exists, so re-running the
956
+ 3.24.0-checkout installer never resyncs a `hooks.json` or Pi settings file
957
+ written under 4.0.0 — only adds what your project is missing. The
958
+ "Cursor still shows the removed commands" recipe used `rm -f` to delete
959
+ three named files, which deletes a REGULAR file with the same name exactly
960
+ as readily as a symlink — including a command an operator wrote themselves
961
+ under one of those three retired names; it is now a `[ -L "$p" ] && rm
962
+ "$p"` loop, symlink-only. Separately, the session-start plugin-update
963
+ banner's Pi instruction (Changed, above) led with the dev-fallback checkout
964
+ recipe though the documented PRIMARY Pi install path is `pi install
965
+ npm:session-orchestrator` (`docs/pi-setup.md:16`, §"Option 1") —
966
+ `PLATFORM_UPDATE_INSTRUCTIONS.pi` (`scripts/lib/plugin-update-banner.mjs`)
967
+ now leads with re-running that exact command, naming the checkout +
968
+ `--settings-only` recipe only as the fallback for a dev-fallback
969
+ registration. 25/25 tests pass; `check-skill-links.mjs` reports 86;
970
+ `validate-plugin.mjs` 234/0.
971
+ - **`#1229` closed — the session-lock heartbeat already refreshes on `Stop`,
972
+ `PostToolBatch` and `SessionStart`, not only once per wave; the reported gap
973
+ was a false premise, refuted by measurement.** Live call sites confirmed at
974
+ `hooks/on-stop.mjs:438` (fires every turn), `hooks/post-tool-batch-wave-signal.mjs:377`,
975
+ and `hooks/on-session-start.mjs:1078`; a heartbeat mid-wave measured 3 seconds
976
+ old. No `UserPromptSubmit` hook exists to add a fourth site.
977
+ - **`#1230` closed — `js-yaml` is already lazily required in `owner-yaml.mjs`
978
+ (`:120-127`, memoised, one WARN on failure) and all 27 hooks already exit 0
979
+ with both `zx` and `js-yaml` blocked; the reported gap was a false premise,
980
+ refuted by measurement.** The eager-import class the Predecessor-commits Fixed
981
+ entry above already closed was re-verified still holding at this session's
982
+ base commit — no regression, no further code change needed.
983
+
984
+ #### Predecessor commits (v3.24.0 → `e4674109`)
985
+
986
+ - **Two silent Session Config parsing bugs fixed across 39 block-shaped parsers
987
+ (#1162).** New shared module `scripts/lib/config/block-preprocess.mjs` fixes both: (a) a
988
+ block commented out with a multi-line `<!-- … -->` was read as LIVE config —
989
+ `stripHtmlCommentBlocks()` now strips it via the existing `htmlCommentSkipper()` state
990
+ machine; (b) the bold-bullet sub-key rendering (`- **enabled:** true`) matched no sub-key
991
+ regex and silently fell back to its default — `normalizeBoldSubkeys()` now normalises it
992
+ to `enabled: true` before parsing. `preprocessBlockLines()` (35 standard parsers) and
993
+ `preprocessBlockLinesNoDash()` (4 dash-RECORD parsers — `custom-phases`, `remote-hosts`,
994
+ `evolve`'s `_parseEvolve`, `health-endpoints`) are the two drop-ins for
995
+ `content.split(/\r?\n/)`; NoDash skips bold-normalisation because de-dashing a record's
996
+ first key would silently merge it into the previous record.
997
+ `config-protection.mjs`'s `_isConfigWeakeningAllowed()` and
998
+ `hooks/pre-bash-destructive-guard.mjs`'s bypass scan deliberately use HTML-comment
999
+ stripping ONLY, never bold-normalisation — the bold form
1000
+ (`- **allow-config-weakening:** true` / `- **allow-destructive-ops:** true`) must not arm
1001
+ a bypass. An UNTERMINATED `<!--` fails closed in both directions: a block parser gets its
1002
+ lines back UNFILTERED, while both bypass scanners treat it as NOT ARMED (said out loud on
1003
+ stderr, since a silently-ignored bypass would look identical to an operator typo);
1004
+ `scripts/parse-config.mjs` prints one stderr WARN per session, not one per parser. Two
1005
+ new parity test files pin the contract per parser: `tests/lib/config/preprocess-parity-a-l.test.mjs`
1006
+ and `-m-z.test.mjs`.
1007
+ - **PSA-007's git-write detector is argument-aware, closing a false-positive AND a
1008
+ false-negative class (#1172, #1215).** `isGitWrite()` in
1009
+ `scripts/lib/wave-transcript-tail.mjs` used to match the subcommand literal alone; it now
1010
+ parses the arguments too. `git stash list`/`show`, `--version`, `--help`/`-h`,
1011
+ `--dry-run`, and `-n` on every subcommand except `commit` (where `-n` means
1012
+ `--no-verify`, still a real write — measured 2026-09-04 that `git commit -h` prints
1013
+ `-n, --no-verify` while `add`/`rm`/`push -h` all print `-n, --[no-]dry-run`) now read as
1014
+ reads, not writes. The value-taking global flags `-C`/`-c`/`--git-dir`/`--work-tree`/
1015
+ `--namespace`/`--exec-path`/`--config-env`, and `command`/`env VAR=x` prefixes, are now
1016
+ absorbed so `git -C /tmp stash` and `env FOO=x git commit` are still recognised as git
1017
+ writes at all (measured false negative before the fix: `isGitWrite('git -C /tmp stash')`
1018
+ returned `false`). `FIXTURE_CONTEXT_RE`'s `cd /tmp` match now also fires on the BARE path
1019
+ with no trailing `/` — the reported false alarm was
1020
+ `cd /tmp && git init && git commit -m x`. 32 table rows added (70 cases in the file
1021
+ after the change) in `tests/lib/wave-transcript-tail.test.mjs`.
1022
+ - **`buildLiveSignals()`'s injectable `_scanBacklog` test seam was unused at 5 call sites,
1023
+ so the affected test suite shelled out to a live `glab issue list` on every run
1024
+ (#1169).** `tests/lib/autopilot.test.mjs` now passes a null-returning `_scanBacklog` stub
1025
+ at every call site that does not assert on `signals.backlog` (`selectMode`/`computeDelta`
1026
+ never read that field, so the stub is behaviour-preserving). `scripts/lib/autopilot/loop.mjs`
1027
+ also migrated off the removed `SO_STATE_DIR` constant onto `getStateDir()` (#1153 P5) in
1028
+ the same pass. Test-phase wall time for the affected suite dropped from ~750ms to ~25ms,
1029
+ with no live network calls remaining.
1030
+ - **One flaky spawn-timeout test pinned with margin instead of disabled (#1217).**
1031
+ `tests/unit/plugin-manifests-exit-codes.test.mjs`'s "exits 1 (not 2) when plugin.json
1032
+ contains broken JSON" case now passes `{ timeout: 30_000 }` explicitly — measured
1033
+ 887–965ms unloaded (W1-D4, 2026-09-04), with one observed 10,095ms outlier attributed to
1034
+ host contention, not spawn cost. Same discriminating-margin pattern as the three tests
1035
+ hardened earlier in this file; no repo-wide pinned-slow list exists (#976 never
1036
+ implemented).
1037
+ - **Legacy `.bak.`-delimited backup files were invisible to both rotation and restore
1038
+ (#1173).** `scripts/lib/learnings/io.mjs` exports `backupSuffixOf()`/`isBackupOf()`,
1039
+ accepting both the canonical `.bak-<ISO>` delimiter this module writes and the legacy
1040
+ `.bak.<label>-<ts>` delimiter pre-#721 writers left behind (e.g.
1041
+ `learnings.jsonl.bak.evolve-<ts>`), shared with `backfill-learnings-from-vault`'s restore
1042
+ sweep so the two predicates cannot drift apart again. `rotateBackups()` now sorts on the
1043
+ SUFFIX (a leading non-digit label stripped first) rather than the whole filename —
1044
+ sorting on the whole name grouped every `.`-delimited legacy file after every
1045
+ `-`-delimited one regardless of age (`-` is 0x2D, `.` is 0x2E), so rotation pruned only
1046
+ hyphen-form backups. `scripts/backfill-learnings-expires.mjs` itself was still emitting
1047
+ the dot form on `--apply` — its own backups were the ones going unrotated and unrestored
1048
+ — and now emits the canonical hyphen form (`tests/scripts/backfill-learnings-expires.test.mjs`
1049
+ pins both the new emission and the absence of the old one).
1050
+ - **A peer session's `wave-scope.json` drove this session's Bash gates on two hooks, and a
1051
+ rebind of the manifest could suppress its own tamper notice (#1153 P1).**
1052
+ `hooks/enforce-commands.mjs` and `hooks/post-bash-write-verify.mjs` both gain a Gate 3b
1053
+ ownership check — `classifyManifestSession()` against `readProcessLocalSessionIds()` (hook
1054
+ payload + `CLAUDE_CODE_SESSION_ID`, deliberately not the lock-file tier that made a
1055
+ peer's manifest classify as `'own'` under #1194) — and stand down silently when the
1056
+ manifest provably names another session, emitting `orchestrator.scope.foreign_session_ignored`
1057
+ for observability. In `post-bash-write-verify.mjs` the ordering is the fix itself: the
1058
+ control-file hash/enforcement snapshot is now computed BEFORE the Gate 3b stand-down, so
1059
+ a `cat >` rebind of `wave-scope.json` to a fabricated `session_id` can no longer disarm
1060
+ this session's gates AND suppress the #938 control-file notice in the same stroke — the
1061
+ notice now fires once, on the call where the SESSION BINDING itself changed.
1062
+ - **`processSession`'s two skipped-noop returns had no `maskerWouldChange` re-probe
1063
+ (#1028, found by the Wave-4 security-reviewer as MED, confirmed HIGH by qa-strategist).**
1064
+ The session-note generator's skip paths (`process.mjs:953`/`:991`) now carry the same
1065
+ `maskerWouldChange` guard the learning-note generator already had at 3 sites
1066
+ (`:703`/`:773`/`:797`); a missing `!force` check in the disambiguation-collision branch is
1067
+ also fixed. 4 new tests, red on HEAD before the fix.
1068
+ - **The legacy-flat heal wrote the masked copy to the namespaced path while the plaintext
1069
+ legacy original stayed in place and became unreachable (#1028, qa-strategist HIGH-2).**
1070
+ Both generators (learning-note and session-note) now re-render the leaking legacy note
1071
+ masked IN PLACE (same fix shape at both `#660 IDEMPOTENCY DUAL-PROBE` sites), marking the
1072
+ result `action: 'created'` + `meta.healed_legacy_flat: true` instead of leaving a second,
1073
+ unreachable plaintext copy on disk.
1074
+ - **`orchestrator.secret_masker.applied` now emits from all three channels that mask vault
1075
+ content, not two.** `scripts/lib/vault-mirror/process.mjs`,
1076
+ `scripts/lib/vault-status/narrative-mirror.mjs`, and `scripts/export-hw-learnings.mjs`
1077
+ (documented in its own comment as "the third") each carry the emit at the end of their run.
1078
+ - **`quality-gate.mjs`'s fail-open corrective-context path was silent (#1205, Wave-4
1079
+ security-reviewer LOW).** Without `CLAUDE_CODE_SESSION_ID` (Codex/Cursor with no
1080
+ process-local witness), a peer's `corrective_context` was kept without any signal. It now
1081
+ prints a stderr WARN naming the UNVERIFIED session id before keeping the context; behaviour
1082
+ is unchanged, only the visibility.
1083
+ - **`check-skill-script-paths` had 24 dead script citations sitting unannotated inside
1084
+ `docs/` (#1208).** `docs/` is now in `SCAN_DIRS`; 24 dead citations were annotated with the
1085
+ existing `<!-- path-check: planned #<iid> | historical | example -->` marker convention
1086
+ (measured: 1,087 citations across 259 files, 53 total annotated after this pass). A new
1087
+ `--strict-sh` mode extends the same dead-path scan to `.sh` citations, reported as an
1088
+ advisory count line in `validate-plugin` output (0 findings currently).
1089
+ - **A block commented out with `<!-- … -->` still armed the dispatcher-autonomy one-time
1090
+ capture AUQ (#1222).** `isDispatcherAutonomyBlockPresent()` called the shared
1091
+ `hasBlockHeader()` matcher directly on raw content, while `scripts/parse-config.mjs`
1092
+ preprocesses with comment-stripping first — so a `dispatcher-autonomy:` header sitting
1093
+ inside a comment parsed to defaults (correctly) but was reported PRESENT (incorrectly),
1094
+ meaning the operator was never asked. The guard now preprocesses with the same
1095
+ `preprocessBlockLinesNoDash()` the parser uses before testing for the header, so an
1096
+ unterminated `<!--` still fails toward PRESENT (one un-asked question, not a silently
1097
+ disarmed bypass) matching the module's own documented one-time-capture contract.
1098
+ `block-header.mjs`'s docblock now states explicitly that comment-stripping is the caller's
1099
+ job. The three further planned call sites in `skill-evolution.mjs`, `persona-gate-wave.mjs`,
1100
+ and `wave-reviewers.mjs` are NOT part of this change — none of the three currently calls
1101
+ `hasBlockHeader()`/`isDispatcherAutonomyBlockPresent()` at all, so there is nothing to sweep
1102
+ there yet.
1103
+ - **`reapStaleBudgetFiles()`'s exact-boundary comparison had no falsifying test (#1153
1104
+ P8).** A file aged exactly `maxAgeDays` is meant to be KEPT (`mtimeMs >= now -
1105
+ maxAgeDays*86400000`); the existing 30-vs-1-day test stayed green even if `>=` were
1106
+ weakened to `>`, which would silently reap a peer session's issue-budget file the instant
1107
+ it turned `maxAgeDays` old. A new test in `tests/unit/hook-issue-budget.test.mjs` seeds one
1108
+ file exactly on the cutoff and one a second past it and asserts the former survives and the
1109
+ latter does not.
1110
+ - **`sessionFromLock()`'s `host_id` fallback for a pre-#1072 lock had no test (#1153 P9).**
1111
+ Two new cases in `tests/lib/session-discovery.test.mjs` pin both branches of
1112
+ `lock.host_id ?? stableHostname(...)`: a lock that already carries `host_id` passes it
1113
+ through unchanged, and a lock written before #1072 (no `host_id` field) derives it from the
1114
+ raw `host` — without the fallback pin, a lock-sourced session could silently regress to the
1115
+ raw, non-normalised host for comparison against the registry path's normalised twin.
1116
+ - **`markCandidateProcessed()`'s mint branch stamped a self-contradictory terminal record
1117
+ (#1153 P10, follow-up to #1042).** A freshly-minted candidate (no prior sidecar record) was
1118
+ always given `status: 'proposed'` even while being stamped `processed_at` +
1119
+ `outcome: 'rejected'` in the same write — a terminal, declined candidate that still read as
1120
+ a live proposal to anything rendering `status`. `status` now agrees with `outcome`
1121
+ (`'rejected'` when the outcome is `'rejected'`, `'proposed'` otherwise, the only two
1122
+ ReconcileCandidate status values). Separately, the function used to return the
1123
+ freshly-built stamp even when `mergeCandidates`'s dedupe rule kept an OLDER terminal record
1124
+ on disk instead — caller and store then disagreed about the verdict, silently. It now reads
1125
+ the record back out of the merge result and returns that, plus a new `alreadyProcessed`
1126
+ flag (`written` stays `true` in that case: the store IS in the intended terminal state, and
1127
+ `written: false` is reserved for a genuine write failure). Four new tests in
1128
+ `tests/lib/reconcile/idempotency.test.mjs`, red on HEAD before the fix.
1129
+ - **A learning whose `file_paths` were entirely unusable produced the same generic
1130
+ "no activation axis" rejection as a learning with no `file_paths` at all (#1153 P13).**
1131
+ `globsFromFilePaths()` now records each skipped entry (glob-metachar / control-char / quote)
1132
+ into an out-parameter sink, rendered by the new `formatDroppedFilePaths()` into a
1133
+ `— dropped file_paths: N (…)` suffix on the never-always-on rejection reason — the message
1134
+ engine.mjs forwards verbatim into the operator-visible candidates ledger. Each dropped value
1135
+ is `JSON.stringify`-escaped (so a raw newline or ANSI control char in a hostile `file_paths`
1136
+ entry cannot inject into the reason string) and hard-truncated to 60 chars with no
1137
+ mid-escape cut. Two new tests in `tests/lib/reconcile/emitter.test.mjs` pin both the naming
1138
+ and the escaping.
1139
+ - **`writer.mjs`'s `isOperatorRejection()` inferred an operator rejection from rendered
1140
+ `content` being non-empty, which an operator-declined proposal with an empty rendered body
1141
+ could fail (#1153 P15, closes the inference gap left open by #1042's own documented
1142
+ ceiling).** `skills/session-end/phase-3-6-tail.md` step 6 now stamps every proposal the
1143
+ operator left unselected with an explicit `operatorRejected: true` flag before it joins the
1144
+ engine's `rejected` array; `isOperatorRejection()` keys on that flag first, falling back to
1145
+ the old content-presence heuristic (marked `@deprecated`, kept only for a consumer repo
1146
+ pinning a pre-P15 skill body) when the flag is absent. Two new tests in
1147
+ `tests/lib/reconcile/writer.test.mjs` cover the flag-wins-over-empty-content case and the
1148
+ legacy flagless fallback.
1149
+
1150
+ ### Security
1151
+
1152
+ #### 4.0.0 — audit session
1153
+
1154
+ - **The published npm tarball shipped 0 of the 6 files under
1155
+ `.orchestrator/policy/` — including the destructive-command guard's own floor
1156
+ policy (`blocked-commands.json`) — so an npm-installed consumer without a host
1157
+ overlay ran `hooks/pre-bash-destructive-guard.mjs` against `rules: null` and it
1158
+ silently ALLOWED every command the floor exists to block (found by the Wave-1
1159
+ Codex external review, P1: `npm pack --dry-run | grep -c orchestrator/policy`
1160
+ → 0).** `package.json`'s `files[]` now lists `.orchestrator/policy/`;
1161
+ `npm pack --dry-run --json | grep -c "orchestrator/policy"` reports **6**
1162
+ policy files today (`blocked-commands.json`, `ecosystem.schema.json`,
1163
+ `quality-gates.example.json`, `quality-gates.schema.json`,
1164
+ `templates-policy.json`, `test-profiles.json`) where it reported 0 before,
1165
+ pinned by the new `tests/scripts/pack-policy-floor.test.mjs` (a real
1166
+ `npm pack --dry-run`, not a mock). The release leakage gate's `.orchestrator/`
1167
+ exclusion is narrowed to carve THIS directory back IN (`scripts/release.mjs`
1168
+ around line 319) — the operator's own `metrics/`, `debug/` artefacts and live
1169
+ `*.lock` files stay excluded. `files[]` admits the directory, not a file list — what
1170
+ keeps the shipped set equal to the TRACKED policy set is the packlist test below, not npm. **The
1171
+ Wave-4 review panel found this test asserted only a FLOOR (≥ 6 files) while
1172
+ `package.json`'s `files[]` admits the WHOLE `.orchestrator/policy/` directory
1173
+ and the carve-out above matched the path anywhere in the string, not just at
1174
+ its start** — an untracked private file dropped into that directory would
1175
+ ship, invisible to a floor check. `pack-policy-floor.test.mjs` now asserts SET
1176
+ EQUALITY between the packed policy entries and `git ls-files
1177
+ .orchestrator/policy` (tracked-only, sorted); the carve-out regex is anchored
1178
+ to the path start (`/^\.orchestrator\/policy\//`, previously unanchored),
1179
+ with the VCS-trust rationale — everything under this one directory ships
1180
+ because it is tracked, not because of a per-file allowlist — recorded in a
1181
+ comment beside it.
1182
+ - **`session_profile` was the one free-form string field on the telemetry
1183
+ wire — both an internal Wave-1 review and the Codex review reproduced sending
1184
+ an arbitrary string through it.** `VALID_SESSION_PROFILES` (`['ultradeep']`
1185
+ today) is now enforced on BOTH ends: the client
1186
+ (`scripts/lib/telemetry/schema.mjs`) omits the field when it is not a known
1187
+ profile rather than passing it through, and the ingest server
1188
+ (`server/ingest/validate.mjs`) rejects a record whose `session_profile` fails
1189
+ the same enum plus a 32-char length ceiling. Reproduced before the fix: a
1190
+ crafted `session_profile` string reached the wire; after, the client sends
1191
+ nothing and the server rejects a forged one directly. `detectSandbox()`
1192
+ (`scripts/lib/telemetry/sync.mjs`) now also fails CLOSED — a probe that
1193
+ itself throws returns `{ sandbox: true, reason: 'sandbox:probe-failed' }`
1194
+ instead of falling through to "not a sandbox". `docs/telemetry.md` gained the
1195
+ field table, the no-free-text-fields claim, and a new section documenting the
1196
+ npm update-check's own egress (`SO_DISABLE_UPDATE_CHECK`, `DO_NOT_TRACK`,
1197
+ `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`).
1198
+ - **The `session_profile` whitelist above covered only the LIVE send path — a
1199
+ record already sitting in the offline queue when that whitelist landed still
1200
+ carried whatever it was enqueued with, and `flush()`
1201
+ (`scripts/lib/telemetry/sync.mjs`) forwarded every queued batch to the sender
1202
+ UNCHANGED.** Reproduced by the Wave-4 Codex second-look: a record the ingest
1203
+ server rejects is retried, still rejected, still requeued — the offline queue
1204
+ grew 1 → 2 → 3 and never drained. `sanitizeQueuedRecord()` now re-projects
1205
+ (`projectUsagePing`) and re-whitelists `session_profile` on every queued
1206
+ record before it joins a send batch, closing the same gap the live path was
1207
+ already closed for; the Codex reproduction now shows the queue back at 0
1208
+ after 3 consecutive rejections. A batch the server rejects with HTTP 400/422
1209
+ is now EVICTED (`reason: 'rejected-evicted'`) instead of re-queued: the
1210
+ ingest API validates a batch all-or-nothing with no per-record index, so the
1211
+ only bounded response to "the server will never accept this payload" is to
1212
+ drop the whole batch (named ceiling, BV-004: revisit if the server ever
1213
+ reports which record failed). `defaultSender` now attaches the HTTP status to
1214
+ the thrown error (`err.status`) so `flush()` can tell a schema rejection
1215
+ (evict) from a transport failure (re-queue, unchanged behaviour) — an
1216
+ injected sender that throws a bare `Error` still falls through to the
1217
+ pre-existing re-queue path. `docs/telemetry.md` documents `rejected-evicted`
1218
+ under "When a ping is sent".
1219
+
1220
+ ### Notes
1221
+
1222
+ - **Full Gate (Wave 4, `37169158`):** typecheck 432 OK, lint 0, 642 files / 15,926 passed /
1223
+ 0 failed / 11 skipped, `validate-plugin` 249/0, owner-leakage PASS (macOS). Quoted from
1224
+ the Wave-4 commit trailer, not re-run for this entry.
1225
+ - **The Wave-4 review panel (3 Opus reviewers: security-reviewer, session-reviewer,
1226
+ qa-strategist) found both HIGH findings above inside its own Wave-2/Wave-3 diff** — the
1227
+ `#1028` masker-guard residue the panel itself was reviewing. Both were closed same-wave by
1228
+ a dedicated fixpass (F-A/F-B/F-C) with fresh red-on-HEAD proofs before the green fix,
1229
+ plus test-hygiene cleanup (`dispatch-common.test.mjs` lost 12 structure-only assertions;
1230
+ `evolve-telemetry.test.mjs`'s regression tests no longer stat the real repo ledger) and
1231
+ three LOW fixes (`evolve-telemetry.mjs`'s `catch {}` now WARNs on stderr;
1232
+ `post-subagent-discovery-validator.mjs`'s German pattern now matches singular `Eintrag`;
1233
+ `.gitlab-ci.yml`/`docs/ci-setup.md` prose brought in line with the now-armed drift gate).
1234
+ - **A use-before-define in an intermediate save of `own-session.mjs` blocked every
1235
+ Bash/Edit call of every session on this host for roughly 8 minutes.** `hooks/enforce-commands.mjs`
1236
+ and `hooks/enforce-scope.mjs` both import the module live on every tool call (the #1153
1237
+ P1 Gate 3b wiring), so a `SyntaxError`/`ReferenceError` mid-refactor link-crashed both
1238
+ hooks host-wide rather than degrading to one repo's GUARD INACTIVE banner. Follow-up
1239
+ issue pending to make an intermediate, uncommitted save of a hot-path hook dependency
1240
+ fail more locally.
1241
+ - **This session's `session.lock` heartbeat was only refreshed once per wave, not
1242
+ continuously.** A long session-start/plan phase between heartbeats let a foreign
1243
+ `SessionEnd` reap the lock as stale before this session's own wave loop had a chance to
1244
+ refresh it. Deviation logged in this session's narrative; a follow-up issue for a
1245
+ time-based (not wave-based) heartbeat refresh is to be filed at close, not yet opened as of
1246
+ this diff.
1247
+ - **Full Gate (session close, uncommitted vs `4b451303`):** 651 files / 16,261 passed /
1248
+ 0 failed / 16 skipped, typecheck 435 OK, lint 0, `validate-plugin` 250/0, Semgrep 0 new
1249
+ findings, hook-import-set-check in sync (150 modules) — supersedes the Wave-4 note above.
1250
+
10
1251
  ## [3.24.0] - 2026-09-02
11
1252
 
12
- Five commits since v3.23.0 (4 `feat`, 1 `docs`; 152 files, +14,596/−942), no
13
- `BREAKING CHANGE:` footer and no `!` subject.
1253
+ Six commits since v3.23.0 (4 `feat`, 1 `fix`, 1 `docs`; 160 files,
1254
+ +15,586/−981 before the release commit), no `BREAKING CHANGE:` footer and no
1255
+ `!` subject.
14
1256
 
15
1257
  One `docs` commit (`c3ab4801`) adds ADR-0013, naming the ownership-check-before-deregister
16
1258
  ordering that closed Wave 4 of the previous session. One `feat` commit (`a019d5a4`, deep
@@ -303,6 +1545,19 @@ mint (UUIDv7, semantic session ids) were rejected or dropped by readers built fo
303
1545
  only — every resume looked foreign — and call sites that needed a repo root passed
304
1546
  `undefined` instead, so fail-open paths ran on every invocation.
305
1547
 
1548
+ This tag is also the first to carry the thirty-three commits of the 2026-08-22 → 2026-08-25
1549
+ line (`141d418..0626387`: 20 `fix`, 11 `feat`, 2 `docs`; 297 files, +24,535/−1,318 — measured
1550
+ 2026-09-06 @ `befdda47`, `git log --format=%s v3.22.0..0626387` → 33 subjects). A `3.22.1` cut
1551
+ dated 2026-08-25 had changelogged that line, but the release commit never left its working
1552
+ copy — no push, no tag, no registry tarball — and the next day's work resumed from `0626387`
1553
+ without it. Its entries are carried below under *From the 2026-08-22 → 2026-08-25 line*. One
1554
+ fact sits under all of them: **a surface that said it was live, and a measurement that never
1555
+ had a caller.** Cursor advertised as a first-class platform and loaded only `.cursor/rules` —
1556
+ no slash commands, no skills, no hook bridge; `/session` was chat text. The same class shows up
1557
+ in the rest of the line: 18 probes with zero mechanical callers, three vault writers that could
1558
+ not report their own failure, gates that wrote and never read, and `detectPlatform()` walking
1559
+ to `$HOME` until 63 of 84 telemetry records said `platform=pi`.
1560
+
306
1561
  ### Added
307
1562
 
308
1563
  - **Eight core rules ship as sanitized copies, with a report-only leak scanner (#1098).**
@@ -330,6 +1585,27 @@ only — every resume looked foreign — and call sites that needed a repo root
330
1585
  session-start Phase 0.5, Phase 1.2.1, and both `parallel-aware-*` docs. A real
331
1586
  `git worktree` two-root integration test goes phantom-peer red → green.
332
1587
 
1588
+ #### From the 2026-08-22 → 2026-08-25 line
1589
+
1590
+ - **Cursor is a native surface, not a rules overlay.** `.cursor/commands/*.md`
1591
+ (27 slash-command wrappers), `.cursor/skills/<name>/SKILL.md` (47 wrappers),
1592
+ `.cursor/hooks.json` → `scripts/lib/cursor-hook-bridge.mjs`. Same pattern as
1593
+ Pi: one maintained body in `commands/` and `skills/`, generated adapters,
1594
+ `validate-plugin` check (`check-cursor-adapter.mjs --check`). `cursor-install.mjs`
1595
+ now links commands and skills and writes `hooks.json` for other projects.
1596
+ Cursor has no Skill tool — commands Read `skills/<name>/SKILL.md`.
1597
+ - **The coordinator can see its agents, and a foreign model can build (#1113, #1150).**
1598
+ Wave supervision plus `cursor-agent` CLI foreign dispatch (detached worktree,
1599
+ filesystem-measured result, mandatory Claude review, `NEVER_FOREIGN_ROLES`).
1600
+ - **`/reconcile` ran for the first time in seven weeks** — 10 learnings became
1601
+ conditional rules (`feat(rules)`).
1602
+ - **Express-path** gained a mechanical caller and a single documented reading.
1603
+ - **18 measurement probes** now have a mechanical caller (`feat(probes)`);
1604
+ session-start no longer depends on prose to run them.
1605
+ - **Vault writers report their own failure**; the vault mirror reports that it
1606
+ is healthy (`feat(vault)`, `feat(telemetry)`).
1607
+ - **Ingest server** counts answers; the weekly digest has a caller.
1608
+
333
1609
  ### Fixed
334
1610
 
335
1611
  - **Six onboarding contradictions + a `cursor-install` TARGET guard (#1078).** First
@@ -424,6 +1700,36 @@ only — every resume looked foreign — and call sites that needed a repo root
424
1700
  one) plus a matching `hookTimeout` bump, and two further spawn-timeout assertions gained
425
1701
  the same discriminating margin between contention and a genuine failure.
426
1702
 
1703
+ #### From the 2026-08-22 → 2026-08-25 line
1704
+
1705
+ - **Cursor hooks were a reference mapping that silent-no-op'd (#919).**
1706
+ `hooks/hooks-cursor.json` is the live handler map. The bridge projects Cursor
1707
+ stdin onto `tool_name` / `tool_input` and maps deny envelopes onto Cursor
1708
+ `{ permission }`. `afterFileEdit` remains post-hoc (cannot unwrite).
1709
+ - **`detectPlatform()` walked to `$HOME` and found `~/.pi`** — 63 of 84
1710
+ telemetry records carried `platform=pi` (`fix(platform)`).
1711
+ - **Identity compared the machine name, then the working copy, then DHCP**
1712
+ (#1072, #1085 follow-through). Budget and lock judge the session. One machine,
1713
+ one name.
1714
+ - **A vault probe wrote into the operator's real vault**; the mirror now writes
1715
+ on the second run too (#1033).
1716
+ - **Guards that saw the wrong thing or discarded the right one** (#1029, #1130,
1717
+ #1077, #1125, #979, and a second trio that measured nothing).
1718
+ - **Reconcile: a No stays a No**; three paths that turned a rule into silent
1719
+ reject; gates that wrote and never read (EventDrop #484, #386, #385, #429);
1720
+ a type registry is enough (#1042, #1151).
1721
+ - **STATE.md: a broken list item costs the item, never the document (#1111).**
1722
+ - **Telemetry: noop-silent mirror, recorded aborts, budget reaper (#1151, #1127).**
1723
+ Consent question and flush no longer hang on prose.
1724
+ - **Privacy: the operator's machine name leaves the examples (#1151 Review).**
1725
+ - **Archive: a citation is not a declaration; foreign files are not mine
1726
+ (#1112, #1123).**
1727
+ - **Scope manifest knows whom it belongs to, and the hook asks (#1123, #1082).**
1728
+ - **Security copy claimed SAST/Semgrep levels that never existed**; the private
1729
+ npm scope no longer rides along with a copied baseline.
1730
+ - **A dying suite at import is counted as what it is**; a blocking guard now
1731
+ says why.
1732
+
427
1733
  ### Changed
428
1734
 
429
1735
  - **Host-registry census counts `semantic_session_id` (#1066).** `hooks/on-session-start.mjs`
@@ -453,6 +1759,12 @@ Two of this session's diffs were authored by foreign models under the #1150 adap
453
1759
  #1078 by composer-2.5, #1093 by grok-4.6 — each passing mandatory Claude review before
454
1760
  landing. First production use of the foreign-dispatch path.
455
1761
 
1762
+ npm 3.22.0 never contained the 2026-08-22 → 2026-08-25 line; 3.23.0 is the first registry
1763
+ tarball that does (`npm view session-orchestrator versions` @ 2026-09-06: 3.22.0, 3.23.0,
1764
+ 3.24.0 — no 3.22.1). The 3.22.1 cut that first documented the line was a release commit that
1765
+ was never pushed, which is why those entries surface here, one release late, re-measured at
1766
+ `befdda47` rather than quoted.
1767
+
456
1768
  ## [3.22.0] - 2026-08-22
457
1769
 
458
1770
  Twenty commits (12 `fix`, 5 `docs`, 2 `feat`, 1 `chore`; 150 files, +17,312/−2,575), no