session-orchestrator 3.24.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1125 -2
  81. package/NOTICE +11 -6
  82. package/README.md +127 -94
  83. package/agents/eval-judge.md +1 -1
  84. package/agents/skill-applied-judge.md +1 -1
  85. package/assets/wave-lifecycle.svg +98 -0
  86. package/commands/release.md +6 -3
  87. package/commands/session.md +18 -3
  88. package/docs/README.md +4 -0
  89. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  90. package/docs/baseline.md +67 -0
  91. package/docs/ci-setup.md +108 -62
  92. package/docs/codex-setup.md +65 -21
  93. package/docs/components.md +36 -15
  94. package/docs/cursor-setup.md +6 -2
  95. package/docs/events-schema.md +9 -6
  96. package/docs/instruction-delivery.md +62 -0
  97. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  98. package/docs/migration-v4.md +341 -0
  99. package/docs/pi-setup.md +6 -1
  100. package/docs/plugin-architecture-v3.md +1 -1
  101. package/docs/rule-authoring.md +85 -19
  102. package/docs/scope-collision-guard.md +5 -5
  103. package/docs/session-config-reference.md +57 -56
  104. package/docs/session-config-template.md +6 -29
  105. package/docs/telemetry.md +157 -3
  106. package/docs/vault-docs-architecture.md +50 -11
  107. package/hooks/_lib/hook-import-set.json +1487 -0
  108. package/hooks/_lib/subagent-transcript.mjs +562 -0
  109. package/hooks/config-protection.mjs +2 -2
  110. package/hooks/cwd-change-restore.mjs +2 -2
  111. package/hooks/enforce-commands.mjs +69 -0
  112. package/hooks/hooks-codex.json +1 -1
  113. package/hooks/hooks-cursor.json +10 -0
  114. package/hooks/hooks-pi.json +5 -0
  115. package/hooks/hooks.json +6 -1
  116. package/hooks/loop-guard.mjs +3 -3
  117. package/hooks/on-session-end.mjs +2 -2
  118. package/hooks/on-session-start.mjs +103 -2
  119. package/hooks/on-stop.mjs +36 -11
  120. package/hooks/operator-steer.mjs +2 -2
  121. package/hooks/post-bash-write-verify.mjs +85 -0
  122. package/hooks/post-edit-import-probe.mjs +344 -0
  123. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  124. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  125. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  126. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  127. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  128. package/hooks/skill-invocation-telemetry.mjs +17 -5
  129. package/hooks/subagent-telemetry.mjs +13 -4
  130. package/monitors/monitors.json +3 -3
  131. package/package.json +9 -1
  132. package/pi/prompts/session.md +2 -2
  133. package/plugin.json +27 -0
  134. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  135. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  136. package/scripts/dialectic-deriver.mjs +73 -8
  137. package/scripts/export-hw-learnings.mjs +113 -1
  138. package/scripts/generate-agents-skills.mjs +378 -0
  139. package/scripts/generate-cursor-adapter.mjs +45 -8
  140. package/scripts/generate-hook-import-set.mjs +249 -0
  141. package/scripts/lib/agent-status.mjs +13 -2
  142. package/scripts/lib/auto-dream.mjs +38 -36
  143. package/scripts/lib/autonomy/suitability.mjs +6 -0
  144. package/scripts/lib/autopilot/loop.mjs +2 -2
  145. package/scripts/lib/ci-status-banner.mjs +220 -75
  146. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  147. package/scripts/lib/config/auto-dream.mjs +2 -1
  148. package/scripts/lib/config/block-header.mjs +8 -0
  149. package/scripts/lib/config/block-preprocess.mjs +177 -0
  150. package/scripts/lib/config/broken-window.mjs +2 -1
  151. package/scripts/lib/config/cold-start.mjs +2 -1
  152. package/scripts/lib/config/config-protection.mjs +22 -2
  153. package/scripts/lib/config/context-coverage.mjs +2 -1
  154. package/scripts/lib/config/cross-repo.mjs +2 -1
  155. package/scripts/lib/config/custom-phases.mjs +2 -1
  156. package/scripts/lib/config/dialectic.mjs +2 -1
  157. package/scripts/lib/config/discovery-validator.mjs +2 -1
  158. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  159. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  160. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  161. package/scripts/lib/config/docs-staleness.mjs +2 -1
  162. package/scripts/lib/config/drift-check.mjs +2 -1
  163. package/scripts/lib/config/eval.mjs +2 -1
  164. package/scripts/lib/config/events-rotation.mjs +2 -1
  165. package/scripts/lib/config/evolve.mjs +8 -2
  166. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  167. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  168. package/scripts/lib/config/handover-gate.mjs +2 -1
  169. package/scripts/lib/config/health-endpoints.mjs +7 -2
  170. package/scripts/lib/config/issue-budget.mjs +2 -1
  171. package/scripts/lib/config/loop-guard.mjs +2 -1
  172. package/scripts/lib/config/memory.mjs +2 -1
  173. package/scripts/lib/config/moc-staleness.mjs +2 -1
  174. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  175. package/scripts/lib/config/private-config-dir.mjs +67 -0
  176. package/scripts/lib/config/reconcile.mjs +2 -1
  177. package/scripts/lib/config/remote-hosts.mjs +2 -1
  178. package/scripts/lib/config/section-extractor.mjs +7 -1
  179. package/scripts/lib/config/skill-evolution.mjs +2 -1
  180. package/scripts/lib/config/slopcheck.mjs +2 -1
  181. package/scripts/lib/config/state-md-lock.mjs +2 -1
  182. package/scripts/lib/config/templates-first.mjs +2 -1
  183. package/scripts/lib/config/test.mjs +2 -1
  184. package/scripts/lib/config/vault-integration.mjs +7 -1
  185. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  186. package/scripts/lib/config/vault-staleness.mjs +2 -1
  187. package/scripts/lib/config/vault-sync.mjs +2 -1
  188. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  189. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  190. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  191. package/scripts/lib/convergence-monitor.mjs +82 -16
  192. package/scripts/lib/dispatcher/rank.mjs +124 -48
  193. package/scripts/lib/ecosystem-health.mjs +16 -2
  194. package/scripts/lib/eval/engine.mjs +9 -1
  195. package/scripts/lib/eval/session-resolve.mjs +23 -4
  196. package/scripts/lib/events.mjs +22 -6
  197. package/scripts/lib/frontmatter-guard.mjs +131 -13
  198. package/scripts/lib/gates/gate-full.mjs +26 -0
  199. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  200. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  201. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  202. package/scripts/lib/host-identity.mjs +50 -11
  203. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  204. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  205. package/scripts/lib/learnings/io.mjs +60 -6
  206. package/scripts/lib/memory-proposals/store.mjs +30 -22
  207. package/scripts/lib/owner-config-banner.mjs +43 -6
  208. package/scripts/lib/owner-config-loader.mjs +21 -10
  209. package/scripts/lib/owner-interview.mjs +3 -3
  210. package/scripts/lib/owner-yaml.mjs +207 -14
  211. package/scripts/lib/platform.mjs +108 -15
  212. package/scripts/lib/plugin-update-banner.mjs +406 -0
  213. package/scripts/lib/project-hygiene.mjs +38 -2
  214. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  215. package/scripts/lib/quality-gate.mjs +133 -44
  216. package/scripts/lib/reconcile/emitter.mjs +68 -6
  217. package/scripts/lib/reconcile/engine.mjs +13 -4
  218. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  219. package/scripts/lib/reconcile/writer.mjs +40 -18
  220. package/scripts/lib/session-close-backfill.mjs +67 -9
  221. package/scripts/lib/session-id.mjs +12 -23
  222. package/scripts/lib/session-identity/own-session.mjs +125 -10
  223. package/scripts/lib/session-lock-shape.mjs +43 -0
  224. package/scripts/lib/session-lock.mjs +5 -10
  225. package/scripts/lib/session-registry.mjs +25 -9
  226. package/scripts/lib/session-schema/constants.mjs +36 -2
  227. package/scripts/lib/session-schema/validator.mjs +38 -4
  228. package/scripts/lib/session-start-probes.mjs +18 -1
  229. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  230. package/scripts/lib/skill-health/join.mjs +17 -4
  231. package/scripts/lib/state-md.mjs +78 -0
  232. package/scripts/lib/sunset/walker.mjs +6 -0
  233. package/scripts/lib/telemetry/schema.mjs +181 -9
  234. package/scripts/lib/telemetry/sync.mjs +368 -12
  235. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  236. package/scripts/lib/validate/check-agents.mjs +3 -3
  237. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  238. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  239. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  240. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  241. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  242. package/scripts/lib/validate/check-unwired-features.mjs +0 -2
  243. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  244. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  245. package/scripts/lib/vault-backfill/template.mjs +63 -6
  246. package/scripts/lib/vault-mirror/process.mjs +165 -42
  247. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  248. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  249. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  250. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  251. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  252. package/scripts/lib/wave-resource-gate.mjs +8 -2
  253. package/scripts/lib/wave-sizing.mjs +4 -1
  254. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  255. package/scripts/materialize-wave-scope.mjs +12 -5
  256. package/scripts/memory-propose.mjs +19 -5
  257. package/scripts/migrate-cold-start-seed.mjs +4 -1
  258. package/scripts/parse-config.mjs +60 -3
  259. package/scripts/release.mjs +337 -29
  260. package/scripts/repair-invalid-sessions.mjs +3 -3
  261. package/scripts/run-quality-gate.mjs +128 -11
  262. package/scripts/sweep-expired-learnings.mjs +90 -0
  263. package/scripts/sync-vault-schema.mjs +3 -1
  264. package/scripts/telemetry.mjs +2 -2
  265. package/scripts/validate-plugin.mjs +161 -0
  266. package/scripts/validate-wave-scope.mjs +28 -8
  267. package/scripts/wave-scope-binding.mjs +215 -0
  268. package/skills/_shared/instruction-file-resolution.md +10 -0
  269. package/skills/_shared/parallel-aware-preamble.md +1 -0
  270. package/skills/_shared/platform-tools.md +1 -1
  271. package/skills/_shared/state-ownership.md +1 -1
  272. package/skills/architecture/SKILL.md +7 -5
  273. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  274. package/skills/autopilot/SKILL.md +4 -18
  275. package/skills/claude-md-drift-check/SKILL.md +5 -1
  276. package/skills/claude-md-drift-check/checker.mjs +62 -2
  277. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  278. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  279. package/skills/discovery/probes-arch.md +20 -18
  280. package/skills/dispatcher/SKILL.md +3 -2
  281. package/skills/evolve/SKILL.md +65 -26
  282. package/skills/frontmatter-guard/SKILL.md +11 -5
  283. package/skills/npm-publish/SKILL.md +1 -1
  284. package/skills/reconcile/SKILL.md +33 -0
  285. package/skills/remote-offload/SKILL.md +1 -1
  286. package/skills/session-end/SKILL.md +18 -905
  287. package/skills/session-end/phase-3-6-tail.md +10 -3
  288. package/skills/session-end/plan-verification.md +221 -155
  289. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  290. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  291. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  292. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  293. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  294. package/skills/session-end/references/session-summary-template.md +62 -0
  295. package/skills/session-plan/SKILL.md +49 -0
  296. package/skills/session-start/SKILL.md +22 -904
  297. package/skills/session-start/phase-8-5-express-path.md +1 -1
  298. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  299. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  300. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  301. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  302. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  303. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  304. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  305. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  306. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  307. package/skills/vault-sync/validator.mjs +21 -27
  308. package/skills/wave-executor/SKILL.md +15 -1
  309. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  310. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  311. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  312. package/skills/wave-executor/wave-loop.md +14 -1309
  313. package/templates/_shared/journey-manifest.md +10 -6
  314. package/.cursor/commands/autopilot-multi.md +0 -14
  315. package/.cursor/commands/contract-version-bump.md +0 -14
  316. package/.cursor/commands/journey-audit.md +0 -14
  317. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  318. package/.cursor/skills/daily/SKILL.md +0 -12
  319. package/.cursor/skills/domain-model/SKILL.md +0 -13
  320. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  321. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  322. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  323. package/commands/autopilot-multi.md +0 -74
  324. package/commands/contract-version-bump.md +0 -28
  325. package/commands/journey-audit.md +0 -43
  326. package/pi/prompts/autopilot-multi.md +0 -12
  327. package/pi/prompts/contract-version-bump.md +0 -12
  328. package/pi/prompts/journey-audit.md +0 -12
  329. package/scripts/autopilot-multi.mjs +0 -885
  330. package/scripts/backfill-learnings-expires.mjs +0 -196
  331. package/scripts/backfill-learnings.mjs +0 -203
  332. package/scripts/fleet-instruction-scan.mjs +0 -141
  333. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  334. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  335. package/scripts/lib/webhook-url.mjs +0 -105
  336. package/scripts/lifecycle-sim-v6.mjs +0 -347
  337. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  338. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  339. package/scripts/upload-social-preview.mjs +0 -316
  340. package/skills/_shared/model-selection.md +0 -64
  341. package/skills/contract-version-bump/SKILL.md +0 -219
  342. package/skills/daily/SKILL.md +0 -222
  343. package/skills/daily/generate.sh +0 -92
  344. package/skills/daily/templates/daily.md.tpl +0 -36
  345. package/skills/journey-audit/SKILL.md +0 -270
  346. package/skills/skill-creator/SKILL.md +0 -168
  347. package/skills/ubiquitous-language/SKILL.md +0 -97
  348. package/skills/vault-sync/package-lock.json +0 -40
  349. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  350. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -3,8 +3,22 @@
3
3
  * Checks CI status for the current HEAD commit and returns a structured
4
4
  * result for session-start Phase 4 banner rendering.
5
5
  *
6
- * Plain-JS — no Zod dependency. Never throws. Returns null on any
7
- * no-op condition (no VCS, CLI missing, timeout, parse failure).
6
+ * Plain-JS — no Zod dependency. Never throws.
7
+ *
8
+ * THREE return states (#1031), not two:
9
+ * - `null` — read, and there is nothing to report (a
10
+ * benign ABSENCE: no VCS remote, no pipeline
11
+ * worth a banner, green with no soft failures)
12
+ * - `{status, ok, details, …}` — a real CI reading (`green` | `red` |
13
+ * `unknown`)
14
+ * - `{severity, message, degraded, ok:false}` — the state could NOT be read.
15
+ * `degraded` is a member of the frozen {@link DEGRADED_REASONS} enum
16
+ * exported below. This must NEVER be read as "CI is green".
17
+ *
18
+ * Until #1031 every unreadable state collapsed onto `null`, which in the banner
19
+ * contract reads as all-clear — the exact confusion #1022 (`gh repo view -R` →
20
+ * `unknown shorthand flag`) hid behind on every GitHub repo. The shape is the
21
+ * one `scripts/lib/mirror-issues-banner.mjs` established.
8
22
  *
9
23
  * Supports GitLab (via glab) and GitHub (via gh).
10
24
  * VCS is auto-detected from the repo's git remotes via
@@ -29,6 +43,78 @@ const execFileAsync = promisify(_execFile);
29
43
  /** Default timeout in milliseconds for CLI invocations. */
30
44
  export const DEFAULT_TIMEOUT_MS = 8000;
31
45
 
46
+ /**
47
+ * Closed set of `degraded` reasons (#1031). A degraded result means "the CI
48
+ * state was NOT successfully read" — never "CI is green".
49
+ *
50
+ * Deliberately THIS module's own enum rather than a merged one shared with
51
+ * `mirror-issues-banner.mjs` / `git-config-drift.mjs`: those two already carry
52
+ * DIFFERENT member sets for their own failure surfaces, and a merged superset
53
+ * would hand every consumer members its probe can never emit — an enum whose
54
+ * members are not exhaustively reachable cannot be switched on exhaustively,
55
+ * which is the only reason to freeze one. The members here are exactly what
56
+ * this probe can fail at:
57
+ *
58
+ * - `cli-missing` a required binary (`git`, `glab`, `gh`) is not on PATH.
59
+ * - `timeout` a subprocess outlived its budget. Not silent since #1031:
60
+ * a hang is not actionable, but "state unknown" IS the
61
+ * finding, and silence renders identically to green.
62
+ * - `parse-error` a CLI answered with output this module could not read
63
+ * (unparseable JSON, or valid JSON of the wrong shape).
64
+ * - `query-failed` residual bucket — a present CLI ran and rejected the
65
+ * invocation, or a resolvable remote whose API target could
66
+ * not be derived. Folding these into `parse-error` would
67
+ * mislabel a rejected flag as malformed output.
68
+ * - `git-error` the VCS probe itself failed (`git remote -v` non-zero for
69
+ * a reason other than "not a work tree"). Distinct from
70
+ * `query-failed` because it means the module never got as
71
+ * far as choosing a CI platform.
72
+ *
73
+ * @type {readonly ['cli-missing','timeout','parse-error','query-failed','git-error']}
74
+ */
75
+ export const DEGRADED_REASONS = Object.freeze([
76
+ 'cli-missing',
77
+ 'timeout',
78
+ 'parse-error',
79
+ 'query-failed',
80
+ 'git-error',
81
+ ]);
82
+
83
+ /**
84
+ * Build the third return state. Distinct from `null` on purpose: `null` in the
85
+ * banner contract reads as "all clear", which a failed read has NOT
86
+ * established.
87
+ *
88
+ * The `detail` is appended bounded (BV-004: 200 chars — long enough for a
89
+ * `Command failed: <argv>` first line, short enough not to bury the
90
+ * session-start banner it prints beside; revisit if a CLI starts emitting a
91
+ * diagnostic that needs more). Callers pass ALREADY-redacted text — every
92
+ * source here is either a fixed string or a `redactUrlCredentials` result.
93
+ *
94
+ * @param {'cli-missing'|'timeout'|'parse-error'|'query-failed'|'git-error'} reason
95
+ * @param {string} [detail] Optional, already-redacted diagnostic tail
96
+ * @returns {{ severity: 'warn', ok: false, message: string, degraded: string }}
97
+ */
98
+ function degradedResult(reason, detail) {
99
+ // Redaction (the callers' job) and control-byte escaping are DIFFERENT
100
+ // protections: `redactUrlCredentials` removes secrets, it does not neutralise
101
+ // ANSI/CR. `parseCliJson` already escapes the text it embeds (see the call at
102
+ // the JSON.parse catch), but the degraded tail reaches the operator's terminal
103
+ // by a second route — a CLI stderr quoted into `err.message` — and skipped it.
104
+ // Escape here so BOTH routes are covered at their common exit. Slice first,
105
+ // escape after: the budget is 200 chars of DETAIL, and escaping cannot then
106
+ // leave a cut mid-`\uXXXX`.
107
+ const tail = detail ? ` — ${escapeControlBytes(String(detail).trim().slice(0, 200))}` : '';
108
+ return {
109
+ severity: 'warn',
110
+ ok: false,
111
+ message:
112
+ `⚠ ci-status: CI status for HEAD could not be determined (${reason}) — ` +
113
+ `state UNKNOWN, not "green".${tail}`,
114
+ degraded: reason,
115
+ };
116
+ }
117
+
32
118
  /**
33
119
  * Wraps execFile with a per-call timeout race.
34
120
  *
@@ -148,10 +234,10 @@ function parseCliJson(stdout, label, expect) {
148
234
  // `cause` preserves the original for a debugger; the reason is ALSO
149
235
  // inlined into the message because the outer catch reads `err.message`
150
236
  // only — a cause-only wrapper would lose it on the operator-facing line.
151
- throw new Error(
237
+ throw tagParseError(new Error(
152
238
  `${label} returned unparseable JSON (${reason}) — ${shown}`,
153
239
  { cause: err },
154
- );
240
+ ));
155
241
  }
156
242
 
157
243
  // A shape mismatch reports the JSON TYPE, never the payload. The parse
@@ -161,25 +247,47 @@ function parseCliJson(stdout, label, expect) {
161
247
  // its contents echoed anywhere. A type name carries no body content.
162
248
  const actual = jsonTypeOf(parsed);
163
249
  if (expect === 'array' && actual !== 'array') {
164
- throw new Error(`${label} returned JSON of an unexpected shape — expected an array, got ${actual}`);
250
+ throw tagParseError(new Error(`${label} returned JSON of an unexpected shape — expected an array, got ${actual}`));
165
251
  }
166
252
  if (expect === 'object' && actual !== 'object') {
167
- throw new Error(`${label} returned JSON of an unexpected shape — expected an object, got ${actual}`);
253
+ throw tagParseError(new Error(`${label} returned JSON of an unexpected shape — expected an object, got ${actual}`));
168
254
  }
169
255
  return parsed;
170
256
  }
171
257
 
258
+ /**
259
+ * Stamp the `parse-error` degraded reason onto an error raised by
260
+ * {@link parseCliJson}.
261
+ *
262
+ * Carried on the error object rather than re-derived from its message text in
263
+ * the outer catch: a message-substring test would silently reclassify the
264
+ * moment a wording changes, and the two failures (`unparseable JSON`, wrong
265
+ * shape) are one class only because THIS function says so.
266
+ *
267
+ * @param {Error} err
268
+ * @returns {Error}
269
+ */
270
+ function tagParseError(err) {
271
+ return Object.assign(err, { degradedReason: 'parse-error' });
272
+ }
273
+
172
274
  /**
173
275
  * The one failure this probe can produce that the frozen
174
276
  * `REMOTE_RESOLUTION_REASONS` set has no member for: the async timeout race
175
277
  * every subprocess here runs under has no counterpart in the SYNCHRONOUS
176
278
  * `vcs-repo-spec.mjs` core (it uses `execFileSync`, which cannot time out).
177
279
  *
178
- * Deliberately NOT a query-failure for warning purposes — a timeout stays
179
- * SILENT, matching how `checkCiStatus`'s outer catch has always treated one
180
- * (`msg === 'timeout'` silent `null`). Warning on a hung `git` but not on a
181
- * hung `glab` would be an inconsistency inside a single banner, and a hang is
182
- * not a fact an operator can act on the way "git is not installed" is.
280
+ * Deliberately NOT a query-failure for WARNING purposes — a timeout emits no
281
+ * `console.warn`, matching how `checkCiStatus`'s outer catch has always
282
+ * treated one, and warning on a hung `git` but not on a hung `glab` would be
283
+ * an inconsistency inside a single banner.
284
+ *
285
+ * It is no longer SILENT, though (#1031). The old argument — "a hang is not a
286
+ * fact an operator can act on the way 'git is not installed' is" — is
287
+ * superseded: what the operator acts on is not the hang, it is that CI state
288
+ * is UNKNOWN, and returning `null` rendered that identically to green. A
289
+ * timeout therefore yields `degradedResult('timeout')` on both this path and
290
+ * the outer catch.
183
291
  */
184
292
  const PROBE_TIMEOUT = 'probe-timeout';
185
293
 
@@ -203,6 +311,32 @@ const PROBE_TIMEOUT = 'probe-timeout';
203
311
  */
204
312
  const SILENT_QUERY_FAILURES = new Set(['not-a-git-repo']);
205
313
 
314
+ /**
315
+ * Project a `REMOTE_RESOLUTION_REASONS` query failure onto this module's own
316
+ * {@link DEGRADED_REASONS} member.
317
+ *
318
+ * Written as explicit branches rather than a lookup table so that EVERY reason
319
+ * literal this module can emit sits inside a literal call to the builder
320
+ * call — which is what makes the enum-membership census in
321
+ * `tests/lib/ci-status-banner.test.mjs` a regex over the source instead of a
322
+ * hand-typed list that drifts.
323
+ *
324
+ * The fallthrough is `query-failed`, not a throw: a reason added to the frozen
325
+ * upstream set must degrade visibly (fail-toward-visible), never crash the
326
+ * probe or slip back onto the silent path.
327
+ *
328
+ * @param {string} reason
329
+ * @param {string} [detail] Already-redacted git stderr
330
+ * @returns {{ severity: 'warn', ok: false, message: string, degraded: string }}
331
+ */
332
+ function degradedForVcsReason(reason, detail) {
333
+ // git itself is a CLI, and "not on PATH" is the same fact for it as for
334
+ // glab/gh — one member, not two spellings of it.
335
+ if (reason === 'git-unavailable') return degradedResult('cli-missing', detail);
336
+ if (reason === 'git-error') return degradedResult('git-error', detail);
337
+ return degradedResult('query-failed', detail);
338
+ }
339
+
206
340
  /**
207
341
  * Classify a rejected ASYNC `execFile` into a `REMOTE_RESOLUTION_REASONS`
208
342
  * member.
@@ -651,24 +785,22 @@ async function checkGithub(repoRoot, deps = {}) {
651
785
  /**
652
786
  * Checks CI status for the current HEAD commit.
653
787
  *
654
- * Returns `null` (silent no-op) when:
655
- * - The repo has no usable VCS remote (not a git repo, no remotes at all,
656
- * or >= 2 remotes with no preference match)a benign, measured absence
657
- * - Required CLI (glab / gh) not in PATH
658
- * - Any CLI invocation times out
659
- *
660
- * Also returns `null`, but with a `console.warn` trace, when the VCS-detection
661
- * QUERY ITSELF failed (`git` not on PATH, `git remote -v` erroring), when a
662
- * present CLI rejected its invocation, or when a CLI returned output this
663
- * module could not parse (see {@link parseCliJson}). `null` alone cannot
664
- * express "could not read" see the outer catch and Step 1 for why the warn
665
- * channel carries it.
666
- *
667
- * The unparseable-output case was listed above as SILENT until 2026-08-28.
668
- * That was never the behaviour it has always fallen through to the outer
669
- * catch's warn branch (measured at 30940cb). The drift survived because the
670
- * one test covering it asserted only the `null` and let the file-wide
671
- * `console.warn` spy swallow the rest.
788
+ * Returns `null` (silent no-op) ONLY for a measured ABSENCE: the repo has no
789
+ * usable VCS remote (not a git repo, no remotes at all, or >= 2 remotes with no
790
+ * preference match). That is a complete answerthere is no CI here.
791
+ *
792
+ * Returns a DEGRADED result (`{severity:'warn', ok:false, message, degraded}`,
793
+ * see {@link DEGRADED_REASONS}) when the state could not be read: the
794
+ * VCS-detection query itself failed (`git` not on PATH, `git remote -v`
795
+ * erroring), a required CLI is missing, an invocation timed out, a present CLI
796
+ * rejected its invocation, a GitLab API target could not be derived, or a CLI
797
+ * returned output this module could not parse (see {@link parseCliJson}).
798
+ * Callers MUST treat that as "state unknown", never as clean the generic
799
+ * degraded path in `scripts/lib/session-start-probes.mjs` does exactly that.
800
+ *
801
+ * Missing-CLI and timeout additionally stay SILENT on the `console.warn`
802
+ * channel (they are normal states on a CLI-less machine); every other degraded
803
+ * reason also emits a warn.
672
804
  *
673
805
  * @param {{
674
806
  * repoRoot?: string,
@@ -686,6 +818,11 @@ async function checkGithub(repoRoot, deps = {}) {
686
818
  * `resolveGitlabProjectTarget`, which proves host and project path from one
687
819
  * sanitized remote; GitHub retains the #872 spec/host resolvers.
688
820
  * @returns {Promise<null | {
821
+ * severity: 'warn',
822
+ * ok: false,
823
+ * message: string,
824
+ * degraded: 'cli-missing'|'timeout'|'parse-error'|'query-failed'|'git-error',
825
+ * } | {
689
826
  * status: 'green'|'red'|'unknown',
690
827
  * ok: boolean,
691
828
  * lastGreen?: { sha: string, pipelineId: number, ageCommits: number, ageDays: number|null },
@@ -723,24 +860,31 @@ export async function checkCiStatus(opts = {}, deps = {}) {
723
860
  if (!vcs) {
724
861
  const detected = await detectVcs(repoRoot, depsWithExec);
725
862
  if (!detected.ok) {
726
- // The two-state `null` return is fixed by 13 sibling banners, so the
727
- // third state lives in the WARN channel: an ABSENCE (`no-remotes`,
728
- // `no-matching-remote`, `unsafe-value`) is a real, benign answer and
729
- // stays silent warning there would train operators to ignore this
730
- // line, which is the more expensive error. A QUERY FAILURE
731
- // (`git-unavailable`, `git-error`) means the question could not be
732
- // asked at all, and that is precisely the state that was
733
- // indistinguishable from "nothing to report" before #1039.
734
- // `not-a-git-repo` is the one query failure this banner reads as an
735
- // absence see {@link SILENT_QUERY_FAILURES}.
863
+ // An ABSENCE (`no-remotes`, `no-matching-remote`, `unsafe-value`) is a
864
+ // real, benign answer and stays `null` AND silent degrading there
865
+ // would print a banner on every session-start in a remote-less repo and
866
+ // train operators to ignore this line, which is the more expensive
867
+ // error. A QUERY FAILURE (`git-unavailable`, `git-error`) means the
868
+ // question could not be asked at all: since #1031 that is the third
869
+ // state, no longer a warn-only side channel on top of an all-clear
870
+ // `null`. `not-a-git-repo` is the one query failure this banner reads
871
+ // as an absence see {@link SILENT_QUERY_FAILURES}.
872
+ if (detected.reason === PROBE_TIMEOUT) {
873
+ return degradedResult('timeout');
874
+ }
736
875
  if (isQueryFailure(detected.reason) && !SILENT_QUERY_FAILURES.has(detected.reason)) {
737
- const detail = detected.stderr
738
- ? ` — ${redactUrlCredentials(detected.stderr).trim()}`
876
+ const stderr = detected.stderr
877
+ ? redactUrlCredentials(detected.stderr).trim()
739
878
  : '';
879
+ const detail = stderr ? ` — ${stderr}` : '';
740
880
  console.warn(
741
881
  `WARN ci-status-banner: VCS detection failed (${detected.reason}), banner suppressed — ` +
742
882
  `CI state is UNKNOWN, not "green".${detail}`,
743
883
  );
884
+ return degradedForVcsReason(
885
+ detected.reason,
886
+ stderr ? `${detected.reason}: ${stderr}` : detected.reason,
887
+ );
744
888
  }
745
889
  return null;
746
890
  }
@@ -765,7 +909,10 @@ export async function checkCiStatus(opts = {}, deps = {}) {
765
909
  'WARN ci-status-banner: a GitLab remote was detected but its host/project path ' +
766
910
  'could not be derived, banner suppressed — CI state is UNKNOWN, not "green".',
767
911
  );
768
- return null;
912
+ return degradedResult(
913
+ 'query-failed',
914
+ 'a GitLab remote was detected but its host/project path could not be derived',
915
+ );
769
916
  }
770
917
  return await checkGitlab(repoRoot, now, { ...depsWithExec, gitlabProject });
771
918
  }
@@ -779,47 +926,45 @@ export async function checkCiStatus(opts = {}, deps = {}) {
779
926
  // Unknown VCS value — silent no-op.
780
927
  return null;
781
928
  } catch (err) {
782
- // Swallow all errors: ENOENT (CLI missing), timeout, parse failures.
783
- // These are all no-op conditions per the spec.
929
+ // Never throws: every failure below leaves as the third return state.
784
930
  const msg = err instanceof Error ? err.message : String(err);
785
931
 
786
- // Timeout and ENOENT (missing CLI) silent null.
787
- if (
788
- msg === 'timeout' ||
789
- (err && err.code === 'ENOENT')
790
- ) {
791
- return null;
932
+ // Timeout and ENOENT (missing CLI) stay SILENT on the warn channel — a
933
+ // machine without `glab` is a normal state, and a warn on every
934
+ // session-start there drowns the #1022 signal this channel carries. Since
935
+ // #1031 they are no longer silent on the RETURN channel: neither state
936
+ // established that CI is green, so both degrade.
937
+ if (msg === 'timeout') return degradedResult('timeout');
938
+ if (err && err.code === 'ENOENT') {
939
+ return degradedResult('cli-missing', redactUrlCredentials(msg));
792
940
  }
793
941
 
794
942
  // Everything else means the CLI was PRESENT but the invocation failed —
795
- // non-zero exit, rejected flag, unparseable output. Returning a bare null
796
- // here makes that state indistinguishable from "CLI not installed", which
797
- // is exactly how #1022 (`gh repo view -R` → `unknown shorthand flag: 'R'`)
798
- // stayed invisible on every GitHub repo. THIS module keeps the two-state
799
- // contract (null ⇒ silent no-op) for backwards compatibility with the
800
- // Phase-4 callers already written against it, so leave a stderr trace
801
- // instead: non-blocking, but the next defect of this class is no longer
802
- // silent. Credentials redacted defense-in-depth (#907) — the message can
803
- // quote the failed argv, which carries the repo spec.
943
+ // non-zero exit, rejected flag, unparseable output. A bare `null` here made
944
+ // that state indistinguishable from "CLI not installed" AND from "CI is
945
+ // green", which is exactly how #1022 (`gh repo view -R` → `unknown
946
+ // shorthand flag: 'R'`) stayed invisible on every GitHub repo.
804
947
  //
805
- // DIRECTION FOR NEW BANNERS do not copy this shape. The
806
- // absence-preserving form is `scripts/lib/mirror-issues-banner.mjs`: a
807
- // THIRD return state `{ severity, message, degraded }` whose `degraded`
808
- // is a member of the closed `DEGRADED_REASONS` enum exported there, so
809
- // "could not read" stays distinguishable from "read, and clean".
948
+ // #1031 finished the migration this comment used to describe as pending:
949
+ // the third state `{ severity, message, degraded }` — the shape
950
+ // `scripts/lib/mirror-issues-banner.mjs` established now leaves here too,
951
+ // alongside (not instead of) the stderr trace. `parse-error` is carried on
952
+ // the error by `tagParseError`; everything else is the residual
953
+ // `query-failed`.
810
954
  //
811
- // The REMOTE probe (`detectVcs`) has since been pulled onto that shape
812
- // (#1039): it returns a reason from the frozen `REMOTE_RESOLUTION_REASONS`
813
- // set and Step 1 branches on `isQueryFailure` — absence silent, query
814
- // failure warned. This outer catch is what remains unmigrated: it still
815
- // collapses every CLI-side failure onto `null`, which a caller reads as
816
- // all-clear, and it cannot be widened without changing a return contract
817
- // 13 sibling banners share. Same verdict, stated caller-side, in
818
- // `skills/session-start/SKILL.md` § Phase 4 (the mirror-issues
819
- // paragraph): "Do not reproduce it."
955
+ // Credentials redacted defense-in-depth (#907) the message can quote the
956
+ // failed argv, which carries the repo spec — and the redacted text is what
957
+ // reaches BOTH the warn and the operator-facing `message`.
958
+ const redacted = redactUrlCredentials(msg);
820
959
  console.warn(
821
- `WARN ci-status-banner: CI status check failed, banner suppressed — ${redactUrlCredentials(msg)}`,
960
+ `WARN ci-status-banner: CI status check failed, banner suppressed — ${redacted}`,
822
961
  );
823
- return null;
962
+ // Literal reasons on BOTH branches, never `degradedResult(reason)` with a
963
+ // computed variable: the enum census in the test file is a regex over this
964
+ // source, and a variable hides the member from it.
965
+ if (err && typeof err === 'object' && err.degradedReason === 'parse-error') {
966
+ return degradedResult('parse-error', redacted);
967
+ }
968
+ return degradedResult('query-failed', redacted);
824
969
  }
825
970
  }
@@ -43,18 +43,94 @@ const REQUIRED_CODEX_EVENTS = [
43
43
  'Stop',
44
44
  ];
45
45
 
46
- const FORBIDDEN_CODEX_EVENTS = new Set([
47
- 'SessionEnd',
48
- 'PostToolUseFailure',
49
- 'PostToolBatch',
50
- 'CwdChanged',
46
+ /**
47
+ * Every hook event the Codex runtime knows, MEASURED — not quoted from release
48
+ * notes. Read out of the `codex` binary shipped as codex-cli **0.144.4**
49
+ * (`@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/bin/codex`,
50
+ * 2026-09-06), which embeds one JSON Schema pair per event:
51
+ *
52
+ * strings -a <codex> | grep '"title": "'
53
+ * -> pre-tool-use / post-tool-use / permission-request / pre-compact
54
+ * / post-compact / session-start / subagent-start / subagent-stop
55
+ * / user-prompt-submit / stop .command.{input,output} (10 pairs)
56
+ *
57
+ * The same binary carries the manifest deserializer's key list
58
+ * (`PreToolUse PermissionRequest PostToolUse PreCompact PostCompact
59
+ * SessionStart SubagentStart SubagentStop` + the substring-deduped `Stop` /
60
+ * `UserPromptSubmit`) directly beside its `unexpected map key` error string —
61
+ * so an event key outside this set is rejected, and rejecting one key rejects
62
+ * the manifest that carries it.
63
+ *
64
+ * **`SessionEnd` and `Interrupt` are NOT in it.** They are not merely unwired
65
+ * here: at 0.144.4 they do not exist, and adding either to hooks-codex.json
66
+ * risks taking every already-working hook down with it. Newer Codex releases
67
+ * (0.148+ async hooks, 0.150+ `Interrupt`) are documented-but-unverified on
68
+ * this host — re-measure against the shipped binary before widening this set,
69
+ * never against a changelog.
70
+ */
71
+ export const CODEX_NATIVE_EVENTS = new Set([
72
+ 'SessionStart',
73
+ 'UserPromptSubmit',
74
+ 'PreToolUse',
75
+ 'PermissionRequest',
76
+ 'PostToolUse',
77
+ 'PreCompact',
78
+ 'PostCompact',
79
+ 'SubagentStart',
80
+ 'SubagentStop',
81
+ 'Stop',
51
82
  ]);
52
83
 
53
- const DISALLOWED_EDIT_HANDLERS = [
84
+ /**
85
+ * Claude hook events this repo wires that Codex 0.144.4 has no counterpart for.
86
+ * DERIVED from {@link CODEX_NATIVE_EVENTS} rather than hand-listed, so a new
87
+ * Claude event is forbidden on Codex by construction until someone measures it
88
+ * into the native set above.
89
+ */
90
+ const FORBIDDEN_CODEX_EVENTS = new Set(
91
+ ['SessionEnd', 'PostToolUseFailure', 'PostToolBatch', 'CwdChanged', 'Interrupt']
92
+ .filter((event) => !CODEX_NATIVE_EVENTS.has(event)),
93
+ );
94
+
95
+ /**
96
+ * The tool-name vocabulary Codex puts in `tool_name`, measured at 0.144.4 from
97
+ * the same binary. This is the load-bearing incompatibility, and it is NOT the
98
+ * one the older comments in `check-hooks-symmetry.mjs` claimed.
99
+ *
100
+ * `pre-tool-use.command.input` REQUIRES `tool_name` and `tool_input` (alongside
101
+ * `cwd`, `hook_event_name`, `model`, `permission_mode`, `session_id`,
102
+ * `tool_use_id`, `transcript_path`, `turn_id`) — so the payload fields ARE
103
+ * delivered. What differs is their VALUES: Codex has no `Bash`, `Edit`, `Write`
104
+ * or `MultiEdit` tool (`strings -a <codex> | grep -c '"Bash"'` -> 0). Every
105
+ * PreToolUse guard in `hooks/` opens with an equality gate on one of those four
106
+ * names and returns `emitAllow()` otherwise, so wiring one here yields a
107
+ * handler that runs, matches nothing, and allows everything — false
108
+ * enforcement, which is worse than a registered gap.
109
+ *
110
+ * The adapter this actually needs is a tool-name MAP (shell/exec_command/
111
+ * unified_exec -> Bash, apply_patch -> Edit/Write), not a payload bridge.
112
+ */
113
+ export const CODEX_TOOL_NAMES = Object.freeze([
114
+ 'shell',
115
+ 'exec_command',
116
+ 'unified_exec',
117
+ 'apply_patch',
118
+ 'update_plan',
119
+ 'view_image',
120
+ ]);
121
+
122
+ /**
123
+ * Hook handlers that read an Edit/Write `tool_input.file_path` payload. Codex
124
+ * emits `apply_patch` payloads of a different shape, so wiring one of these into
125
+ * hooks-codex.json produces a handler that silently never fires. Exported so the
126
+ * contract test can assert the census rather than re-list it.
127
+ */
128
+ export const DISALLOWED_EDIT_HANDLERS = [
54
129
  'enforce-scope.mjs',
55
130
  'config-protection.mjs',
56
131
  'post-edit-validate.mjs',
57
132
  'post-tooluse-frontend-slop.mjs',
133
+ 'post-edit-import-probe.mjs',
58
134
  ];
59
135
 
60
136
  const CODEX_HOOK_WRAPPER_COMMAND =
@@ -1,4 +1,5 @@
1
1
  import { matchBlockHeader } from './block-header.mjs';
2
+ import { preprocessBlockLines } from './block-preprocess.mjs';
2
3
 
3
4
  /**
4
5
  * auto-dream.mjs — Parser for the top-level `auto-dream:` YAML block (issue #566).
@@ -32,7 +33,7 @@ export function _parseAutoDream(content) {
32
33
  'min-confidence': 0.5,
33
34
  };
34
35
 
35
- const lines = content.split(/\r?\n/);
36
+ const lines = preprocessBlockLines(content);
36
37
  let inBlock = false;
37
38
  const blockLines = [];
38
39
 
@@ -32,6 +32,14 @@
32
32
  * The two `**` markers are independent (`**key:` and `key:**` both match),
33
33
  * mirroring the tolerant #823 vault-integration regex.
34
34
  *
35
+ * COMMENT-STRIPPING IS THE CALLER'S JOB (#1222). Every matcher here sees RAW
36
+ * lines: a header inside a `<!-- … -->` HTML comment matches exactly like a live
37
+ * one. That is deliberate — stripping comments needs the `block-preprocess.mjs`
38
+ * state machine, and importing it would end this module's zero-import leaf
39
+ * status. A caller that must agree with a comment-STRIPPING parser preprocesses
40
+ * its content first (`preprocessBlockLinesNoDash(content).join('\n')`) — see
41
+ * `dispatcher-autonomy-capture.mjs` `isDispatcherAutonomyBlockPresent()`.
42
+ *
35
43
  * `matchBlockHeaderDetailed` (#1185) is a SEPARATE, additive matcher below for
36
44
  * parsers that need what this contract deliberately rejects — an indented
37
45
  * (nested) header and/or an inline value. It does not change the contract