session-orchestrator 3.23.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 (393) 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 +13 -0
  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 +1401 -0
  81. package/NOTICE +11 -6
  82. package/README.md +127 -92
  83. package/agents/db-specialist.md +0 -1
  84. package/agents/eval-judge.md +1 -1
  85. package/agents/skill-applied-judge.md +1 -1
  86. package/assets/wave-lifecycle.svg +98 -0
  87. package/commands/release.md +6 -3
  88. package/commands/session.md +18 -3
  89. package/docs/README.md +4 -0
  90. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  91. package/docs/baseline.md +67 -0
  92. package/docs/ci-setup.md +249 -48
  93. package/docs/codex-setup.md +66 -22
  94. package/docs/components.md +37 -16
  95. package/docs/cursor-setup.md +6 -2
  96. package/docs/events-schema.md +51 -10
  97. package/docs/instruction-delivery.md +62 -0
  98. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  99. package/docs/migration-v4.md +341 -0
  100. package/docs/pi-setup.md +6 -1
  101. package/docs/plugin-architecture-v3.md +1 -1
  102. package/docs/rule-authoring.md +85 -19
  103. package/docs/scope-collision-guard.md +8 -8
  104. package/docs/session-config-reference.md +120 -61
  105. package/docs/session-config-template.md +40 -33
  106. package/docs/telemetry/telemetry-claims.md +11 -10
  107. package/docs/telemetry.md +187 -4
  108. package/docs/vault-docs-architecture.md +50 -11
  109. package/hooks/_lib/atomic-json.mjs +111 -0
  110. package/hooks/_lib/hook-import-set.json +1487 -0
  111. package/hooks/_lib/subagent-paths.mjs +143 -0
  112. package/hooks/_lib/subagent-transcript.mjs +562 -0
  113. package/hooks/config-protection.mjs +2 -2
  114. package/hooks/cwd-change-restore.mjs +11 -31
  115. package/hooks/enforce-commands.mjs +69 -0
  116. package/hooks/enforce-scope.mjs +35 -6
  117. package/hooks/hooks-codex.json +1 -1
  118. package/hooks/hooks-cursor.json +10 -0
  119. package/hooks/hooks-pi.json +5 -0
  120. package/hooks/hooks.json +6 -1
  121. package/hooks/loop-guard.mjs +3 -3
  122. package/hooks/on-session-end.mjs +280 -14
  123. package/hooks/on-session-start.mjs +153 -4
  124. package/hooks/on-stop.mjs +371 -17
  125. package/hooks/operator-steer.mjs +2 -2
  126. package/hooks/post-bash-write-verify.mjs +189 -4
  127. package/hooks/post-edit-import-probe.mjs +344 -0
  128. package/hooks/post-subagent-discovery-validator.mjs +278 -392
  129. package/hooks/post-tool-batch-wave-signal.mjs +272 -44
  130. package/hooks/post-tool-failure-corrective-context.mjs +11 -34
  131. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  132. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  133. package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
  134. package/hooks/skill-invocation-telemetry.mjs +17 -5
  135. package/hooks/subagent-telemetry.mjs +24 -30
  136. package/monitors/monitors.json +3 -3
  137. package/package.json +9 -1
  138. package/pi/prompts/session.md +2 -2
  139. package/plugin.json +27 -0
  140. package/scripts/autopilot.mjs +26 -12
  141. package/scripts/backfill-abandoned-sessions.mjs +130 -15
  142. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  143. package/scripts/dialectic-deriver.mjs +73 -8
  144. package/scripts/emit-event.mjs +10 -2
  145. package/scripts/export-hw-learnings.mjs +113 -1
  146. package/scripts/generate-agents-skills.mjs +378 -0
  147. package/scripts/generate-cursor-adapter.mjs +45 -8
  148. package/scripts/generate-hook-import-set.mjs +249 -0
  149. package/scripts/lib/agent-status.mjs +13 -2
  150. package/scripts/lib/auq/parse.mjs +5 -29
  151. package/scripts/lib/auto-dialectic.mjs +68 -0
  152. package/scripts/lib/auto-dream.mjs +38 -36
  153. package/scripts/lib/autonomy/suitability.mjs +6 -0
  154. package/scripts/lib/autopilot/loop.mjs +2 -2
  155. package/scripts/lib/autopilot/worktree-pipeline.mjs +82 -6
  156. package/scripts/lib/build-live-signals.mjs +25 -22
  157. package/scripts/lib/ci-status-banner.mjs +220 -75
  158. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  159. package/scripts/lib/cold-start-detector.mjs +23 -14
  160. package/scripts/lib/config/auto-dream.mjs +2 -1
  161. package/scripts/lib/config/block-header.mjs +63 -0
  162. package/scripts/lib/config/block-preprocess.mjs +177 -0
  163. package/scripts/lib/config/broken-window.mjs +2 -1
  164. package/scripts/lib/config/cold-start.mjs +2 -1
  165. package/scripts/lib/config/config-protection.mjs +22 -2
  166. package/scripts/lib/config/context-coverage.mjs +2 -1
  167. package/scripts/lib/config/cross-repo.mjs +2 -1
  168. package/scripts/lib/config/custom-phases.mjs +2 -1
  169. package/scripts/lib/config/dialectic.mjs +2 -1
  170. package/scripts/lib/config/discovery-validator.mjs +9 -3
  171. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  172. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  173. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  174. package/scripts/lib/config/docs-staleness.mjs +2 -1
  175. package/scripts/lib/config/drift-check.mjs +2 -1
  176. package/scripts/lib/config/eval.mjs +2 -1
  177. package/scripts/lib/config/events-rotation.mjs +2 -1
  178. package/scripts/lib/config/evolve.mjs +8 -2
  179. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  180. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  181. package/scripts/lib/config/handover-gate.mjs +2 -1
  182. package/scripts/lib/config/health-endpoints.mjs +388 -0
  183. package/scripts/lib/config/issue-budget.mjs +2 -1
  184. package/scripts/lib/config/loop-guard.mjs +2 -1
  185. package/scripts/lib/config/memory.mjs +2 -1
  186. package/scripts/lib/config/moc-staleness.mjs +2 -1
  187. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  188. package/scripts/lib/config/private-config-dir.mjs +67 -0
  189. package/scripts/lib/config/reconcile.mjs +2 -1
  190. package/scripts/lib/config/remote-hosts.mjs +234 -0
  191. package/scripts/lib/config/section-extractor.mjs +7 -1
  192. package/scripts/lib/config/skill-evolution.mjs +2 -1
  193. package/scripts/lib/config/slopcheck.mjs +2 -1
  194. package/scripts/lib/config/state-md-lock.mjs +2 -1
  195. package/scripts/lib/config/templates-first.mjs +2 -1
  196. package/scripts/lib/config/test.mjs +2 -1
  197. package/scripts/lib/config/vault-integration.mjs +7 -1
  198. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  199. package/scripts/lib/config/vault-staleness.mjs +2 -1
  200. package/scripts/lib/config/vault-sync.mjs +2 -1
  201. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  202. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  203. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  204. package/scripts/lib/config.mjs +31 -3
  205. package/scripts/lib/convergence-monitor.mjs +82 -16
  206. package/scripts/lib/dispatcher/enumerate.mjs +2 -17
  207. package/scripts/lib/dispatcher/rank.mjs +124 -48
  208. package/scripts/lib/ecosystem-health.mjs +16 -2
  209. package/scripts/lib/eval/engine.mjs +9 -1
  210. package/scripts/lib/eval/session-resolve.mjs +23 -4
  211. package/scripts/lib/events-schema.mjs +48 -0
  212. package/scripts/lib/events.mjs +256 -7
  213. package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
  214. package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
  215. package/scripts/lib/frontmatter-guard.mjs +131 -13
  216. package/scripts/lib/gates/gate-full.mjs +26 -0
  217. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  218. package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
  219. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  220. package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
  221. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  222. package/scripts/lib/host-identity.mjs +50 -11
  223. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  224. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  225. package/scripts/lib/learnings/io.mjs +60 -6
  226. package/scripts/lib/memory-banner.mjs +20 -8
  227. package/scripts/lib/memory-proposals/store.mjs +30 -22
  228. package/scripts/lib/owner-config-banner.mjs +43 -6
  229. package/scripts/lib/owner-config-loader.mjs +21 -10
  230. package/scripts/lib/owner-interview.mjs +3 -3
  231. package/scripts/lib/owner-yaml.mjs +207 -14
  232. package/scripts/lib/peer-discovery.mjs +20 -2
  233. package/scripts/lib/platform.mjs +108 -15
  234. package/scripts/lib/plugin-update-banner.mjs +406 -0
  235. package/scripts/lib/project-hygiene.mjs +38 -2
  236. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  237. package/scripts/lib/quality-gate.mjs +133 -44
  238. package/scripts/lib/reconcile/emitter.mjs +68 -6
  239. package/scripts/lib/reconcile/engine.mjs +249 -9
  240. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  241. package/scripts/lib/reconcile/writer.mjs +40 -18
  242. package/scripts/lib/scope-gate.mjs +36 -0
  243. package/scripts/lib/session-close-backfill.mjs +125 -18
  244. package/scripts/lib/session-discovery.mjs +57 -3
  245. package/scripts/lib/session-end/phase-skip.mjs +2 -2
  246. package/scripts/lib/session-id.mjs +12 -23
  247. package/scripts/lib/session-identity/own-session.mjs +187 -11
  248. package/scripts/lib/session-lock-shape.mjs +43 -0
  249. package/scripts/lib/session-lock.mjs +5 -10
  250. package/scripts/lib/session-registry.mjs +25 -9
  251. package/scripts/lib/session-schema/constants.mjs +36 -2
  252. package/scripts/lib/session-schema/validator.mjs +38 -4
  253. package/scripts/lib/session-start-probes.mjs +18 -1
  254. package/scripts/lib/session-transition.mjs +1 -1
  255. package/scripts/lib/sessions-canonical.mjs +446 -0
  256. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  257. package/scripts/lib/skill-health/join.mjs +17 -4
  258. package/scripts/lib/state-md.mjs +78 -0
  259. package/scripts/lib/sunset/walker.mjs +6 -0
  260. package/scripts/lib/telemetry/schema.mjs +255 -17
  261. package/scripts/lib/telemetry/sync.mjs +417 -24
  262. package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
  263. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  264. package/scripts/lib/validate/check-agents.mjs +3 -3
  265. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  266. package/scripts/lib/validate/check-doc-cli-commands.mjs +9 -33
  267. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
  268. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  269. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  270. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  271. package/scripts/lib/validate/check-skill-script-paths.mjs +455 -0
  272. package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
  273. package/scripts/lib/validate/check-unwired-features.mjs +0 -9
  274. package/scripts/lib/validate/check-validator-registration.mjs +254 -0
  275. package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
  276. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  277. package/scripts/lib/validate/markdown-fences.mjs +196 -0
  278. package/scripts/lib/vault-backfill/template.mjs +63 -6
  279. package/scripts/lib/vault-mirror/process.mjs +165 -42
  280. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  281. package/scripts/lib/vault-status/board-lock.mjs +185 -0
  282. package/scripts/lib/vault-status/board-writer.mjs +174 -135
  283. package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
  284. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  285. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  286. package/scripts/lib/wave-executor/remote-dispatch.mjs +502 -0
  287. package/scripts/lib/wave-resource-gate.mjs +133 -7
  288. package/scripts/lib/wave-sizing.mjs +4 -1
  289. package/scripts/lib/wave-transcript-tail.mjs +142 -8
  290. package/scripts/materialize-wave-scope.mjs +32 -9
  291. package/scripts/memory-propose.mjs +146 -8
  292. package/scripts/migrate-cold-start-seed.mjs +4 -1
  293. package/scripts/parse-config.mjs +60 -3
  294. package/scripts/promote-vault-strict.mjs +4 -15
  295. package/scripts/release.mjs +337 -29
  296. package/scripts/repair-invalid-sessions.mjs +3 -3
  297. package/scripts/run-quality-gate.mjs +128 -11
  298. package/scripts/site-numbers.mjs +36 -4
  299. package/scripts/sweep-expired-learnings.mjs +90 -0
  300. package/scripts/sync-vault-schema.mjs +3 -1
  301. package/scripts/telemetry.mjs +2 -2
  302. package/scripts/validate-plugin.mjs +187 -0
  303. package/scripts/validate-wave-scope.mjs +28 -8
  304. package/scripts/vault-consolidate.mjs +3 -11
  305. package/scripts/vault-integration-watcher.mjs +2 -4
  306. package/scripts/vault-mirror.mjs +111 -26
  307. package/scripts/wave-scope-binding.mjs +215 -0
  308. package/skills/_shared/instruction-file-resolution.md +10 -0
  309. package/skills/_shared/parallel-aware-auq.md +31 -2
  310. package/skills/_shared/parallel-aware-preamble.md +18 -4
  311. package/skills/_shared/platform-tools.md +1 -1
  312. package/skills/_shared/state-ownership.md +1 -1
  313. package/skills/architecture/SKILL.md +7 -5
  314. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  315. package/skills/autopilot/SKILL.md +4 -18
  316. package/skills/claude-md-drift-check/SKILL.md +5 -1
  317. package/skills/claude-md-drift-check/checker.mjs +62 -2
  318. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  319. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  320. package/skills/discovery/probes-arch.md +20 -18
  321. package/skills/dispatcher/SKILL.md +3 -2
  322. package/skills/ecosystem-health/SKILL.md +4 -1
  323. package/skills/ecosystem-health/wizard.md +5 -0
  324. package/skills/evolve/SKILL.md +87 -11
  325. package/skills/frontmatter-guard/SKILL.md +11 -5
  326. package/skills/npm-publish/SKILL.md +1 -1
  327. package/skills/reconcile/SKILL.md +38 -2
  328. package/skills/remote-offload/SKILL.md +89 -0
  329. package/skills/session-end/SKILL.md +18 -905
  330. package/skills/session-end/phase-3-6-tail.md +19 -9
  331. package/skills/session-end/plan-verification.md +221 -155
  332. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  333. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  334. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  335. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  336. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  337. package/skills/session-end/references/session-summary-template.md +62 -0
  338. package/skills/session-plan/SKILL.md +49 -0
  339. package/skills/session-start/SKILL.md +41 -900
  340. package/skills/session-start/phase-8-5-express-path.md +1 -1
  341. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  342. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  343. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  344. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  345. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  346. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  347. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  348. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  349. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  350. package/skills/vault-sync/validator.mjs +21 -27
  351. package/skills/wave-executor/SKILL.md +16 -2
  352. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  353. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  354. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  355. package/skills/wave-executor/wave-loop.md +14 -1271
  356. package/templates/_shared/journey-manifest.md +10 -6
  357. package/.cursor/commands/autopilot-multi.md +0 -14
  358. package/.cursor/commands/contract-version-bump.md +0 -14
  359. package/.cursor/commands/journey-audit.md +0 -14
  360. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  361. package/.cursor/skills/daily/SKILL.md +0 -12
  362. package/.cursor/skills/domain-model/SKILL.md +0 -13
  363. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  364. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  365. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  366. package/commands/autopilot-multi.md +0 -74
  367. package/commands/contract-version-bump.md +0 -28
  368. package/commands/journey-audit.md +0 -43
  369. package/pi/prompts/autopilot-multi.md +0 -12
  370. package/pi/prompts/contract-version-bump.md +0 -12
  371. package/pi/prompts/journey-audit.md +0 -12
  372. package/scripts/autopilot-multi.mjs +0 -885
  373. package/scripts/backfill-learnings-expires.mjs +0 -196
  374. package/scripts/backfill-learnings.mjs +0 -203
  375. package/scripts/fleet-instruction-scan.mjs +0 -141
  376. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  377. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  378. package/scripts/lib/webhook-url.mjs +0 -105
  379. package/scripts/lifecycle-sim-v6.mjs +0 -347
  380. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  381. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  382. package/scripts/upload-social-preview.mjs +0 -316
  383. package/skills/_shared/model-selection.md +0 -64
  384. package/skills/contract-version-bump/SKILL.md +0 -219
  385. package/skills/daily/SKILL.md +0 -222
  386. package/skills/daily/generate.sh +0 -92
  387. package/skills/daily/templates/daily.md.tpl +0 -36
  388. package/skills/journey-audit/SKILL.md +0 -269
  389. package/skills/skill-creator/SKILL.md +0 -168
  390. package/skills/ubiquitous-language/SKILL.md +0 -97
  391. package/skills/vault-sync/package-lock.json +0 -40
  392. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  393. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -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 =
@@ -10,8 +10,9 @@
10
10
  * Behaviour (PRD §F1.3):
11
11
  * 1. Skip if `bootstrap.lock` is missing (repo not yet bootstrapped —
12
12
  * the bootstrap-gate handles that path).
13
- * 2. Skip if `sessions.jsonl` line count ≥ `silence-after-sessions`
14
- * (operator has already engaged at least once).
13
+ * 2. Skip if the `sessions.jsonl` DISTINCT-session count ≥
14
+ * `silence-after-sessions` identities, not lines (see `countSessions`);
15
+ * the operator has already engaged at least once.
15
16
  * 3. Skip if bootstrap age < `nudge-after-hours` (give the operator a
16
17
  * reasonable window after bootstrap before nudging).
17
18
  * 4. Emit otherwise, with a `markerPath` when the migration marker
@@ -34,6 +35,7 @@ import { stat, readFile, unlink } from 'node:fs/promises';
34
35
  import path from 'node:path';
35
36
 
36
37
  import { parseBootstrapLock } from './bootstrap-lock-freshness.mjs';
38
+ import { countSessionsInJsonl } from './sessions-canonical.mjs';
37
39
 
38
40
  /** Milliseconds in one hour — exported for testability. */
39
41
  export const MS_PER_HOUR = 60 * 60 * 1000;
@@ -77,24 +79,31 @@ function buildBannerLines({ bootstrappedAt = null } = {}) {
77
79
  }
78
80
 
79
81
  /**
80
- * Best-effort line counter for NDJSON files. Returns 0 when the file is
81
- * missing OR empty OR unreadable — all three are equivalent for the
82
- * cold-start decision ("no sessions yet").
82
+ * Best-effort counter of DISTINCT physical sessions in an NDJSON ledger.
83
+ * Returns 0 when the file is missing OR empty OR unreadable — all three are
84
+ * equivalent for the cold-start decision ("no sessions yet").
85
+ *
86
+ * Counts IDENTITIES, not lines (#1167): `sessions.jsonl` is append-only, so one
87
+ * physical session can occupy two lines (a backfilled abandoned stub plus the
88
+ * authoritative record that supersedes it, or the systemic double-stub pair
89
+ * written by the two backfill writers). A raw line count therefore reports
90
+ * "the operator has already engaged" one session earlier than is true, and the
91
+ * cold-start nudge — one-shot per repo at the default threshold of 1 — is
92
+ * silently never emitted.
83
93
  *
84
94
  * Reads the full file because sessions.jsonl is small (1 line/session, ~2 KB
85
- * each — even 100 sessions = ~200 KB).
95
+ * each — even 100 sessions = ~200 KB). The async `readFile` is kept (the
96
+ * SessionStart hook's 5s budget must not block); only the collapse rules come
97
+ * from the shared canonical module.
86
98
  *
87
99
  * @param {string} filePath
88
100
  * @returns {Promise<number>}
89
101
  */
90
- async function countLines(filePath) {
102
+ async function countSessions(filePath) {
91
103
  try {
92
- const raw = await readFile(filePath, 'utf8');
93
- if (!raw) return 0;
94
- // Trailing newline must not inflate the count. split-then-filter handles
95
- // both Unix (\n) and Windows (\r\n) line endings.
96
- const lines = raw.split('\n').filter((l) => l.length > 0);
97
- return lines.length;
104
+ // Counting rule (blank/malformed lines, id-less records) lives in
105
+ // `countSessionsInJsonl`; only the async read is local.
106
+ return countSessionsInJsonl(await readFile(filePath, 'utf8'));
98
107
  } catch {
99
108
  return 0;
100
109
  }
@@ -166,7 +175,7 @@ export async function detectColdStart(opts = {}) {
166
175
 
167
176
  // PRD §F1.3 Ubiquitous: silence once the repo has ≥ N sessions. This is
168
177
  // the auto-silence path — banner is one-shot per repo (default N=1).
169
- const sessionCount = await countLines(sessionsPath);
178
+ const sessionCount = await countSessions(sessionsPath);
170
179
  if (sessionCount >= silenceAfterSessions) {
171
180
  return {
172
181
  shouldEmit: false,
@@ -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
 
@@ -31,6 +31,19 @@
31
31
  *
32
32
  * The two `**` markers are independent (`**key:` and `key:**` both match),
33
33
  * mirroring the tolerant #823 vault-integration regex.
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
+ *
43
+ * `matchBlockHeaderDetailed` (#1185) is a SEPARATE, additive matcher below for
44
+ * parsers that need what this contract deliberately rejects — an indented
45
+ * (nested) header and/or an inline value. It does not change the contract
46
+ * above; `matchBlockHeader`/`hasBlockHeader` keep exactly this behaviour.
34
47
  */
35
48
 
36
49
  /**
@@ -73,6 +86,56 @@ export function matchBlockHeader(line, key) {
73
86
  return blockHeaderRe(key).test(line);
74
87
  }
75
88
 
89
+ /**
90
+ * Build the indent-aware, inline-value-capturing block-header regex for a key.
91
+ * A strict SUPERSET of `blockHeaderRe`: it additionally matches an ARBITRARY
92
+ * leading indent (group 1) and an optional inline value trailing the colon
93
+ * (group 2) — the two things `matchBlockHeader` deliberately rejects (a
94
+ * nested sub-key, and a header carrying a value, per the module docblock).
95
+ *
96
+ * @param {string} key
97
+ * @returns {RegExp}
98
+ */
99
+ function detailedBlockHeaderRe(key) {
100
+ return new RegExp(
101
+ '^(\\s*)(?:-\\s+)?(?:\\*\\*)?' + escapeRegExp(key) + ':(?:\\*\\*)?(?:[ \\t]+(.*))?$'
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Indent + inline-value variant of `matchBlockHeader` (#1185). Additive —
107
+ * `matchBlockHeader`/`hasBlockHeader` are unchanged and every existing caller
108
+ * keeps its current behaviour untouched.
109
+ *
110
+ * Built for parsers whose header can be NESTED under a parent block (e.g.
111
+ * `health-endpoints:` one level under `ecosystem-health:`) and/or carry an
112
+ * INLINE value (`health-endpoints: [{name: …}]`) on the same line — exactly
113
+ * the two forms `matchBlockHeader` treats as "not a top-level block-opener".
114
+ * This is a separate, purpose-built matcher, not a relaxed replacement: a
115
+ * caller that only needs the boolean top-level check keeps using
116
+ * `matchBlockHeader`.
117
+ *
118
+ * The captured value is RAW text, never comment-stripped — `key: # note`
119
+ * reports `value: '# note'`, exactly as the pre-#1185 `health-endpoints.mjs`
120
+ * `HEADER_RE` did. A caller that needs comment semantics strips them itself
121
+ * (as `health-endpoints.mjs` already does for its block BODY via its own
122
+ * `stripComment()` — only the header line's raw capture moved here).
123
+ *
124
+ * @param {string} line — a single line (callers strip the trailing `\n`)
125
+ * @param {string} key — the literal block key
126
+ * @returns {{indent: number, value: string|null}|null} `null` on no match;
127
+ * `value` is `null` for a bare header (`key:`, optionally trailing
128
+ * whitespace only), else the trimmed text following the colon.
129
+ */
130
+ export function matchBlockHeaderDetailed(line, key) {
131
+ if (typeof line !== 'string' || typeof key !== 'string' || key === '') return null;
132
+ const m = line.match(detailedBlockHeaderRe(key));
133
+ if (!m) return null;
134
+ const indent = m[1].length;
135
+ const trimmed = (m[2] ?? '').trim();
136
+ return { indent, value: trimmed === '' ? null : trimmed };
137
+ }
138
+
76
139
  /**
77
140
  * Multiline presence variant: true when ANY line of `content` opens the
78
141
  * top-level block for `key`. For whole-file PRESENCE guards (e.g.
@@ -0,0 +1,177 @@
1
+ /**
2
+ * block-preprocess.mjs — THE line-preprocessing contract for the block parsers
3
+ * under `scripts/lib/config/` (#1162).
4
+ *
5
+ * Every block parser here begins with `content.split(/\r?\n/)`, then walks lines
6
+ * looking for its own header and the indented sub-keys under it. Two markdown
7
+ * shapes that are ordinary in a CLAUDE.md / AGENTS.md were invisible to that
8
+ * walk, and both failed in the dangerous direction:
9
+ *
10
+ * (a) A block commented OUT with a multi-line `<!-- … -->` was read as LIVE
11
+ * config. Commenting a key out is the most ordinary way to disable it; it
12
+ * must not arm it. (`_extractConfigSection` learned this in #1097 — the
13
+ * block parsers never did.)
14
+ * (b) The bold-bullet rendering of a SUB-key (`- **enabled:** true`) matched
15
+ * no sub-key regex, so the key was absent and its DEFAULT applied. For a
16
+ * boolean that default is `false`, which reads exactly like a deliberately
17
+ * disabled block — no error, in any log. #823 fixed this for the block
18
+ * HEADER only (see `block-header.mjs`); this fixes it for the body.
19
+ *
20
+ * ZERO IMPORTS beyond the sibling `section-extractor.mjs` skipper, stdlib only —
21
+ * `tests/lib/config/cycle-guard.test.mjs` forbids any `scripts/lib/config/*.mjs`
22
+ * from importing `../config.mjs`, and the RAW-PARITY constraint (block-header.mjs
23
+ * docblock) requires the normalisation to live in the helper each parser calls,
24
+ * never in a preprocessing step in `config.mjs`.
25
+ *
26
+ * DELIBERATE NON-GOAL — fenced code blocks. These parsers are fence-UNAWARE, and
27
+ * that is a pinned contract, not an oversight. Two tests, cited by NAME because
28
+ * line numbers rot: `tests/lib/config/frontend-slop-hook.test.mjs` — "parses a
29
+ * block wrapped in a fenced code block — the parser is fence-unaware", and
30
+ * `tests/lib/config/dispatcher-autonomy-capture.test.mjs` — "treats a header
31
+ * inside a fenced code block as PRESENT". Both assert that a ```-fenced block IS
32
+ * parsed. Teaching this module about
33
+ * fences would flip ~36 parsers at once and break those pins, so it does not.
34
+ * Revisit only as its own change, with those two tests as the blast radius.
35
+ */
36
+
37
+ import { htmlCommentSkipper } from './section-extractor.mjs';
38
+
39
+ /**
40
+ * Drop every line that lies inside — or opens — a multi-line `<!-- … -->` HTML
41
+ * comment (#1162a).
42
+ *
43
+ * Wraps `htmlCommentSkipper()` from `section-extractor.mjs` rather than
44
+ * re-deriving the state machine, so the block parsers and the Session Config
45
+ * KV extractor share ONE definition of "this line is commented-out
46
+ * documentation" by construction. A fresh closure is built per call: the skipper
47
+ * carries per-document state, and a shared instance would leak an unterminated
48
+ * comment from one document into the next.
49
+ *
50
+ * Inherits the skipper's deliberate ceiling: the opener must be at the START of
51
+ * a line (`/^\s*<!--/`), so `waves: 5 <!-- note` does NOT swallow the following
52
+ * lines — the trailing single-line comment form this repo's own convention
53
+ * encourages keeps working.
54
+ *
55
+ * Never touches `- ` list markers, indentation, or line content.
56
+ *
57
+ * FAIL-CLOSED on an UNTERMINATED comment. A stray `<!--` with no `-->` after it
58
+ * puts the skipper in the swallowing state for the REST of the document, so
59
+ * every later block silently disappears and each parser falls back to its
60
+ * defaults — for `config-protection` that is `strict` → `warn`, i.e. a guard
61
+ * disarmed by a typo, with no error anywhere. When the skipper would still be
62
+ * inside a comment at EOF, this returns the INPUT lines unfiltered (the
63
+ * pre-#1162 behaviour: the opener is treated as literal text), so nothing can
64
+ * vanish. The condition is reported via `onUnterminated` and is separately
65
+ * observable through {@link findUnterminatedComment}.
66
+ *
67
+ * @param {string[]} lines
68
+ * @param {(lineNo: number) => void} [onUnterminated] — called with the 1-based
69
+ * line number of the unterminated opener when stripping is disabled.
70
+ * @returns {string[]} a new array; the input is not mutated.
71
+ */
72
+ export function stripHtmlCommentBlocks(lines, onUnterminated) {
73
+ if (!Array.isArray(lines)) return [];
74
+ const unterminatedAt = findUnterminatedComment(lines);
75
+ if (unterminatedAt !== null) {
76
+ if (typeof onUnterminated === 'function') onUnterminated(unterminatedAt);
77
+ return lines.slice();
78
+ }
79
+ const skip = htmlCommentSkipper();
80
+ return lines.filter((line) => !skip(typeof line === 'string' ? line : ''));
81
+ }
82
+
83
+ /**
84
+ * The 1-based line number of an `<!--` that is never closed, or `null` when
85
+ * every comment in `lines` is terminated.
86
+ *
87
+ * Derived from the SAME `htmlCommentSkipper()` state machine rather than a
88
+ * second copy of it (the RAW-PARITY constraint): the skipper swallows the
89
+ * closing `-->` line too, so the document ends inside a comment exactly when
90
+ * the LAST swallowed line carries no `-->`. A swallowed line that follows a
91
+ * closing line (or the first one of all) is an opener, which is how the
92
+ * reported line number is obtained.
93
+ *
94
+ * @param {string[]} lines
95
+ * @returns {number|null}
96
+ */
97
+ export function findUnterminatedComment(lines) {
98
+ if (!Array.isArray(lines)) return null;
99
+ const skip = htmlCommentSkipper();
100
+ let openerLine = null;
101
+ /** @type {string|null} */
102
+ let lastSwallowed = null;
103
+ for (let i = 0; i < lines.length; i += 1) {
104
+ const line = typeof lines[i] === 'string' ? lines[i] : '';
105
+ if (!skip(line)) continue;
106
+ if (lastSwallowed === null || lastSwallowed.includes('-->')) openerLine = i + 1;
107
+ lastSwallowed = line;
108
+ }
109
+ if (lastSwallowed === null || lastSwallowed.includes('-->')) return null;
110
+ return openerLine;
111
+ }
112
+
113
+ /**
114
+ * `- **key:** value` / `- **key**: value` / `**key:** value` → `key: value`,
115
+ * preserving the leading indentation EXACTLY (#1162b).
116
+ *
117
+ * The indent is load-bearing: every block parser ends its block at the first
118
+ * non-empty non-indented line, so an indent-eating rewrite would truncate the
119
+ * block one line early. Only the `**` markers and the optional `- ` bullet
120
+ * immediately around the `key:` token at line start are removed; the value
121
+ * portion (including any literal `**` inside it) is untouched — same rule as the
122
+ * `_matchKVLine` Format-1 branch and the #823 header tolerance.
123
+ *
124
+ * A line that is not a bold key/value pair is returned byte-identical.
125
+ *
126
+ * @internal — exported for this module's own tests. Production callers use
127
+ * {@link preprocessBlockLines} / {@link preprocessBlockLinesNoDash}.
128
+ *
129
+ * NAMED RISK — dash-record blocks. In any block whose body uses `- ` as a RECORD
130
+ * boundary, de-dashing the first key of a record merges it into the PREVIOUS
131
+ * record, silently producing one record where two were written. Those parsers
132
+ * must use
133
+ * `preprocessBlockLinesNoDash` instead. `tests/lib/config/block-preprocess.test.mjs`
134
+ * demonstrates the merge so the next reader sees why the NoDash variant exists.
135
+ *
136
+ * @param {string[]} lines
137
+ * @returns {string[]} a new array; the input is not mutated.
138
+ */
139
+ export function normalizeBoldSubkeys(lines) {
140
+ if (!Array.isArray(lines)) return [];
141
+ return lines.map((line) => {
142
+ if (typeof line !== 'string') return line;
143
+ // (1) indent (2) optional bullet (3) **key:** or **key**: (4) rest
144
+ const m = line.match(/^(\s*)(?:-\s+)?\*\*([^*:]+?)(?::\*\*|\*\*:)\s*(.*)$/);
145
+ if (!m) return line;
146
+ const value = m[3];
147
+ return value === '' ? `${m[1]}${m[2]}:` : `${m[1]}${m[2]}: ${value}`;
148
+ });
149
+ }
150
+
151
+ /**
152
+ * THE drop-in replacement for `content.split(/\r?\n/)` in a flat or
153
+ * nested-OBJECT block parser: strip commented-out lines, then normalise bold
154
+ * sub-keys.
155
+ *
156
+ * @param {string} content — full file contents
157
+ * @returns {string[]} preprocessed lines (empty array for non-string input)
158
+ */
159
+ export function preprocessBlockLines(content) {
160
+ if (typeof content !== 'string') return [];
161
+ return normalizeBoldSubkeys(stripHtmlCommentBlocks(content.split(/\r?\n/)));
162
+ }
163
+
164
+ /**
165
+ * THE drop-in for every dash-RECORD parser — any parser whose block body uses
166
+ * `- ` as a record boundary, so the dash must survive: HTML-comment stripping
167
+ * only, no bold-subkey normalisation. (Enumerating them here goes stale; the
168
+ * rule is the criterion. `grep -l preprocessBlockLinesNoDash scripts/lib/config/`
169
+ * lists the current set.)
170
+ *
171
+ * @param {string} content — full file contents
172
+ * @returns {string[]} preprocessed lines (empty array for non-string input)
173
+ */
174
+ export function preprocessBlockLinesNoDash(content) {
175
+ if (typeof content !== 'string') return [];
176
+ return stripHtmlCommentBlocks(content.split(/\r?\n/));
177
+ }
@@ -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
  * broken-window.mjs — Parser for the top-level `broken-window-budget:` YAML block
@@ -48,7 +49,7 @@ export function _parseBrokenWindow(content) {
48
49
  'due-days': 7,
49
50
  };
50
51
 
51
- const lines = String(content ?? '').split(/\r?\n/);
52
+ const lines = preprocessBlockLines(String(content ?? ''));
52
53
  let inBlock = false;
53
54
  const blockLines = [];
54
55
 
@@ -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
  * cold-start.mjs — Parser for the top-level `cold-start:` YAML block (PRD F1.3 /
@@ -30,7 +31,7 @@ export function _parseColdStart(content) {
30
31
  'silence-after-sessions': 1,
31
32
  };
32
33
 
33
- const lines = content.split(/\r?\n/);
34
+ const lines = preprocessBlockLines(content);
34
35
  let inBlock = false;
35
36
  const blockLines = [];
36
37
 
@@ -1,4 +1,5 @@
1
1
  import { matchBlockHeader } from './block-header.mjs';
2
+ import { findUnterminatedComment, preprocessBlockLines, preprocessBlockLinesNoDash } from './block-preprocess.mjs';
2
3
  import { isSessionConfigHeading } from './section-extractor.mjs';
3
4
 
4
5
  /**
@@ -45,7 +46,7 @@ export function _parseConfigProtection(content) {
45
46
 
46
47
  if (typeof content !== 'string' || content.length === 0) return defaults;
47
48
 
48
- const lines = content.split(/\r?\n/);
49
+ const lines = preprocessBlockLines(content);
49
50
  let inBlock = false;
50
51
  const blockLines = [];
51
52
 
@@ -107,7 +108,26 @@ export function _parseConfigProtection(content) {
107
108
  export function _isConfigWeakeningAllowed(content) {
108
109
  if (typeof content !== 'string' || content.length === 0) return false;
109
110
 
110
- const lines = content.split(/\r?\n/);
111
+ // HTML-comment stripping ONLY (`preprocessBlockLinesNoDash`), never the full
112
+ // `preprocessBlockLines`: bold-subkey normalisation would make
113
+ // `- **allow-config-weakening:** true` arm the bypass, and
114
+ // `tests/lib/config/config-protection.test.mjs` pins the opposite — only the
115
+ // plain form is the supported bypass. Fail-closed beats markdown tolerance there.
116
+ // The comment strip is the OTHER direction and is mandatory (W4/F1): with a raw
117
+ // `content.split(/\r?\n/)` a COMMENTED-OUT `<!--\nallow-config-weakening: true\n-->`
118
+ // inside `## Session Config` ARMED the bypass — commenting a key out is the most
119
+ // ordinary way to disable it and must never enable it (same class as #1162a).
120
+ //
121
+ // UNTERMINATED `<!--` — the fail-closed direction INVERTS for a bypass scan.
122
+ // `stripHtmlCommentBlocks` returns the lines UNFILTERED when a comment is never
123
+ // closed, which is right for a block PARSER (nothing may silently vanish) and
124
+ // wrong here: it hands the commented-out bypass line straight back and re-arms
125
+ // the guard's own off switch. For this scan "I cannot tell where the comments
126
+ // end" must mean NOT ARMED — the bypass is opt-in, so refusing to grant it is
127
+ // the safe reading in every ambiguous document.
128
+ if (findUnterminatedComment(content.split(/\r?\n/)) !== null) return false;
129
+
130
+ const lines = preprocessBlockLinesNoDash(content);
111
131
  let inConfig = false;
112
132
  for (const line of lines) {
113
133
  if (isSessionConfigHeading(line)) { inConfig = true; continue; }
@@ -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
  * context-coverage.mjs — Parser for the top-level `context-coverage:` YAML block.
@@ -37,7 +38,7 @@ import { matchBlockHeader } from './block-header.mjs';
37
38
  export function _parseContextCoverage(content) {
38
39
  const defaults = { enabled: false, mode: 'warn' };
39
40
 
40
- const lines = content.split(/\r?\n/);
41
+ const lines = preprocessBlockLines(content);
41
42
  let inBlock = false;
42
43
  const blockLines = [];
43
44
 
@@ -20,6 +20,7 @@ import { homedir } from 'node:os';
20
20
  import { join } from 'node:path';
21
21
  import { readConfigFile } from './io.mjs';
22
22
  import { matchBlockHeader } from './block-header.mjs';
23
+ import { preprocessBlockLines } from './block-preprocess.mjs';
23
24
 
24
25
  /**
25
26
  * Resolve the cross-repo confinement root. Defaults to ~/Projects.
@@ -47,7 +48,7 @@ const SAFE_PATH_RE = /^[A-Za-z0-9._~/-]+$/;
47
48
  * @returns {string[]} list of project paths, or [] when absent/empty/none/null
48
49
  */
49
50
  export function _parseCrossRepo(content) {
50
- const lines = content.split(/\r?\n/);
51
+ const lines = preprocessBlockLines(content);
51
52
  let inBlock = false;
52
53
 
53
54
  for (const rawLine of lines) {
@@ -1,4 +1,5 @@
1
1
  import { matchBlockHeader } from './block-header.mjs';
2
+ import { preprocessBlockLinesNoDash } from './block-preprocess.mjs';
2
3
 
3
4
  /**
4
5
  * custom-phases.mjs — Parser for the `custom-phases:` Session Config block (#637).
@@ -65,7 +66,7 @@ const SAFE_PATH_RE = /^[A-Za-z0-9._~/-]+$/;
65
66
  * @returns {Array<{name: string, when: string, command: string, mode: string, review: string|null}>}
66
67
  */
67
68
  export function _parseCustomPhases(content) {
68
- const lines = content.split(/\r?\n/);
69
+ const lines = preprocessBlockLinesNoDash(content);
69
70
  let inBlock = false;
70
71
  const blockLines = [];
71
72
 
@@ -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
  * dialectic.mjs — Parser for top-level `dialectic:` YAML block (#506).
@@ -30,7 +31,7 @@ const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
30
31
  export function _parseDialectic(content) {
31
32
  const defaults = { cadence: 5, model: 'haiku', 'budget-tokens': 8000 };
32
33
 
33
- const lines = content.split(/\r?\n/);
34
+ const lines = preprocessBlockLines(content);
34
35
  let inBlock = false;
35
36
  const blockLines = [];
36
37
 
@@ -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
  * discovery-validator.mjs — Parser for the top-level `discovery-validator:`
@@ -10,6 +11,11 @@ import { matchBlockHeader } from './block-header.mjs';
10
11
  * transcript. v1 is log+warn only (events.jsonl + stderr WARN); exit 2
11
12
  * (blocking) is reserved for a future hard-gate.
12
13
  *
14
+ * OFF by default (opt-in) — issue #567's original acceptance criterion. The
15
+ * #690 flip to ON (2026-06-25) was reverted 2026-09-02 (#1191) after fleet
16
+ * measurement: 6,946 `discovery_validator_violation` events accumulated in 18
17
+ * repos that never declared the block.
18
+ *
13
19
  * Returns `{ enabled }`.
14
20
  * Tolerant parser: malformed values silently fall back to defaults.
15
21
  *
@@ -21,17 +27,17 @@ import { matchBlockHeader } from './block-header.mjs';
21
27
  * Independent of the `## Session Config` section boundary.
22
28
  *
23
29
  * Defaults:
24
- * enabled: true
30
+ * enabled: false (opt-in)
25
31
  *
26
32
  * @param {string} content — full file contents
27
33
  * @returns {{ enabled: boolean }}
28
34
  */
29
35
  export function _parseDiscoveryValidator(content) {
30
36
  const defaults = {
31
- enabled: true,
37
+ enabled: false,
32
38
  };
33
39
 
34
- const lines = content.split(/\r?\n/);
40
+ const lines = preprocessBlockLines(content);
35
41
  let inBlock = false;
36
42
  const blockLines = [];
37
43