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
@@ -39,6 +39,7 @@
39
39
  import { readFileSync, writeFileSync } from 'node:fs';
40
40
 
41
41
  import { hasBlockHeader } from './block-header.mjs';
42
+ import { preprocessBlockLinesNoDash } from './block-preprocess.mjs';
42
43
 
43
44
  const ALLOWED_AUTONOMY = ['off', 'advisory', 'autonomous-gated'];
44
45
  const DEFAULT_CONFIDENCE_FLOOR = 0.5;
@@ -113,6 +114,18 @@ export function getDispatcherAutonomyQuestion() {
113
114
  * the shared `matchBlockHeader()` matcher (block-header.mjs). `hasBlockHeader()`
114
115
  * splits on `/\r?\n/` and tests each line, so `\r` and trailing whitespace are
115
116
  * eaten by the `\s*$` tail and the identical set of inputs matches.
117
+ *
118
+ * PARSER PARITY, both halves (#1222): the parser preprocesses its content with
119
+ * `preprocessBlockLines()` — it STRIPS `<!-- … -->` HTML comments and is
120
+ * fence-BLIND. This guard therefore preprocesses too, with
121
+ * `preprocessBlockLinesNoDash()` (comment-strip only; the full
122
+ * `preprocessBlockLines` additionally de-dashes bold sub-keys, which this
123
+ * whole-file presence scan has no use for), and stays fence-blind as well. Before
124
+ * that, a block commented OUT (`<!--\ndispatcher-autonomy:\n autonomy: off\n-->`)
125
+ * parsed to DEFAULTS while being reported PRESENT — so the one-time capture AUQ
126
+ * never fired and the operator was never asked. The two halves must move
127
+ * together: teach one about comments or fences and the other follows in
128
+ * lock-step, or the guard and the parser disagree again.
116
129
  * Verified-equivalent edge cases:
117
130
  * - CRLF line endings (`dispatcher-autonomy:\r\n`) → PRESENT (`\s*$` eats the `\r`).
118
131
  * - Trailing spaces / tabs on the header line → PRESENT (`\s*$`).
@@ -130,6 +143,15 @@ export function getDispatcherAutonomyQuestion() {
130
143
  * - Header present but BODY malformed (garbage non-yaml lines) → PRESENT. A
131
144
  * malformed block is the operator's to fix, never a re-prompt trigger — the
132
145
  * parser tolerantly falls back to defaults, and capture must not re-ask.
146
+ * - Header inside a `<!-- … -->` comment → ABSENT (#1222), matching the parser.
147
+ * - Header inside a comment that is never closed (a stray `<!--` with no
148
+ * `-->`) → PRESENT. `stripHtmlCommentBlocks()` returns the lines UNFILTERED
149
+ * in that case, so nothing silently vanishes, and the parser — which uses the
150
+ * same helper — sees the header too. Unlike `_isConfigWeakeningAllowed()` in
151
+ * config-protection.mjs, this scan does NOT invert that direction: there the
152
+ * scanned line ARMS a bypass, so ambiguity must mean "not armed"; here the
153
+ * only cost of a stray PRESENT is one un-asked question, while diverging from
154
+ * the parser would re-open exactly the bug this fix closes.
133
155
  *
134
156
  * No-throw: null / non-string / empty → false.
135
157
  *
@@ -138,7 +160,8 @@ export function getDispatcherAutonomyQuestion() {
138
160
  */
139
161
  export function isDispatcherAutonomyBlockPresent(claudeMdContent) {
140
162
  if (typeof claudeMdContent !== 'string' || claudeMdContent.length === 0) return false;
141
- return hasBlockHeader(claudeMdContent, 'dispatcher-autonomy');
163
+ const preprocessed = preprocessBlockLinesNoDash(claudeMdContent).join('\n');
164
+ return hasBlockHeader(preprocessed, 'dispatcher-autonomy');
142
165
  }
143
166
 
144
167
  /**
@@ -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
  * dispatcher-autonomy.mjs — Parser + resolver for the top-level
@@ -40,7 +41,7 @@ export function _parseDispatcherAutonomy(content) {
40
41
  'confidence-floor': 0.5,
41
42
  };
42
43
 
43
- const lines = content.split(/\r?\n/);
44
+ const lines = preprocessBlockLines(content);
44
45
  let inBlock = false;
45
46
  const blockLines = [];
46
47
 
@@ -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
  * docs-orchestrator.mjs — Parser for the top-level `docs-orchestrator:` YAML block.
@@ -13,7 +14,7 @@ import { matchBlockHeader } from './block-header.mjs';
13
14
  export function _parseDocsOrchestrator(content) {
14
15
  const defaults = { enabled: false, audiences: ['user', 'dev', 'vault'], mode: 'warn' };
15
16
 
16
- const lines = content.split(/\r?\n/);
17
+ const lines = preprocessBlockLines(content);
17
18
  let inBlock = false;
18
19
  const blockLines = [];
19
20
 
@@ -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
  * docs-staleness.mjs — Parser for the top-level `docs-staleness:` YAML block.
@@ -29,7 +30,7 @@ export function _parseDocsStaleness(content) {
29
30
  mode: 'warn',
30
31
  };
31
32
 
32
- const lines = content.split(/\r?\n/);
33
+ const lines = preprocessBlockLines(content);
33
34
  let inBlock = false;
34
35
  const blockLines = [];
35
36
 
@@ -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
  * drift-check.mjs — Parser for the top-level `drift-check:` YAML block.
@@ -39,7 +40,7 @@ export function _parseDriftCheck(content) {
39
40
  'check-docs-parity': true,
40
41
  };
41
42
 
42
- const lines = content.split(/\r?\n/);
43
+ const lines = preprocessBlockLines(content);
43
44
  let inBlock = false;
44
45
  const blockLines = [];
45
46
 
@@ -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
  * eval.mjs — Parser for the top-level `eval:` YAML block (#809, Epic #803).
@@ -51,7 +52,7 @@ const ALLOWED_REPORT = new Set(['html', 'none']);
51
52
  export function _parseEval(content) {
52
53
  const defaults = { enabled: false, mode: 'warn', judge: 'off', report: 'html', handle: null };
53
54
 
54
- const lines = content.split(/\r?\n/);
55
+ const lines = preprocessBlockLines(content);
55
56
  let inBlock = false;
56
57
  const blockLines = [];
57
58
 
@@ -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
  * events-rotation.mjs — Parser for the top-level `events-rotation:` YAML block.
@@ -17,7 +18,7 @@ import { matchBlockHeader } from './block-header.mjs';
17
18
  export function _parseEventsRotation(content) {
18
19
  const defaults = { enabled: true, 'max-size-mb': 10, 'max-backups': 5 };
19
20
 
20
- const lines = content.split(/\r?\n/);
21
+ const lines = preprocessBlockLines(content);
21
22
  let inBlock = false;
22
23
  const blockLines = [];
23
24
 
@@ -35,6 +35,7 @@
35
35
  import pathModule from 'node:path';
36
36
 
37
37
  import { matchBlockHeader } from './block-header.mjs';
38
+ import { preprocessBlockLines, preprocessBlockLinesNoDash } from './block-preprocess.mjs';
38
39
 
39
40
  /** Per-entry defaults (none beyond the required field-set; documented for symmetry). */
40
41
  export const EVOLVE_EXTRA_SOURCE_DEFAULTS = Object.freeze({
@@ -67,7 +68,9 @@ const SAFE_PATH_RE = /^[A-Za-z0-9._~/-]+$/;
67
68
  * @returns {Array<{path: string, kind: string, 'learning-type': string}>}
68
69
  */
69
70
  export function _parseEvolve(content) {
70
- const lines = content.split(/\r?\n/);
71
+ // NoDash: `- ` is the RECORD boundary under `extra-sources:` — de-dashing the
72
+ // first key of a record would merge it into the previous one (#1162).
73
+ const lines = preprocessBlockLinesNoDash(content);
71
74
  let inEvolve = false;
72
75
  let inExtraSources = false;
73
76
  const blockLines = [];
@@ -254,7 +257,10 @@ export function _parseEvolveDecay(content) {
254
257
  'floor-factor': EVOLVE_DECAY_DEFAULTS['floor-factor'],
255
258
  };
256
259
 
257
- const lines = content.split(/\r?\n/);
260
+ // Standard variant: this parser reads only FLAT `decay-*` sub-keys and builds
261
+ // no records, so bold-subkey normalisation is safe here even though the same
262
+ // `evolve:` block also carries the `extra-sources:` dash records (#1162).
263
+ const lines = preprocessBlockLines(content);
258
264
  let inEvolve = false;
259
265
  const blockLines = [];
260
266
 
@@ -18,8 +18,9 @@
18
18
  import { promises as fs } from 'node:fs';
19
19
  import path from 'node:path';
20
20
 
21
- import { SO_PROJECT_DIR } from '../platform.mjs';
21
+ import { getProjectDir } from '../platform.mjs';
22
22
  import { matchBlockHeader } from './block-header.mjs';
23
+ import { preprocessBlockLines } from './block-preprocess.mjs';
23
24
 
24
25
  /**
25
26
  * Parse the top-level `frontend-slop-hook:` YAML block from markdown content.
@@ -35,7 +36,7 @@ import { matchBlockHeader } from './block-header.mjs';
35
36
  export function _parseFrontendSlopHook(content) {
36
37
  const defaults = { enabled: false };
37
38
 
38
- const lines = content.split(/\r?\n/);
39
+ const lines = preprocessBlockLines(content);
39
40
  let inBlock = false;
40
41
  const blockLines = [];
41
42
 
@@ -86,7 +87,10 @@ export function _parseFrontendSlopHook(content) {
86
87
  * @returns {Promise<{ enabled: boolean }>}
87
88
  */
88
89
  export async function loadFrontendSlopHookConfig(opts = {}) {
89
- const root = opts.repoRoot || SO_PROJECT_DIR;
90
+ // Called at USE time, never at module load: the deprecated `SO_PROJECT_DIR`
91
+ // named export is a live binding that stays `undefined` until some getter has
92
+ // run in the process, so a bare call resolved the root to undefined (#1153 P5).
93
+ const root = opts.repoRoot || getProjectDir();
90
94
  const candidates = [
91
95
  path.join(root, 'CLAUDE.md'),
92
96
  path.join(root, 'AGENTS.md'),
@@ -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
  * gitlab-portfolio.mjs — Parser for the top-level `gitlab-portfolio:` YAML block.
@@ -66,7 +67,7 @@ export function coerceGitlabPortfolio(raw) {
66
67
  * @returns {{ enabled: boolean, mode: 'warn'|'strict'|'off', 'stale-days': number, 'critical-labels': string[] }}
67
68
  */
68
69
  export function _parseGitlabPortfolio(content) {
69
- const lines = content.split(/\r?\n/);
70
+ const lines = preprocessBlockLines(content);
70
71
  let inBlock = false;
71
72
  const blockLines = [];
72
73
 
@@ -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
  * handover-gate.mjs — Parser for the top-level `handover-gate:` YAML block
@@ -43,7 +44,7 @@ export function _parseHandoverGate(content) {
43
44
  'max-open-questions': 3,
44
45
  };
45
46
 
46
- const lines = content.split(/\r?\n/);
47
+ const lines = preprocessBlockLines(content);
47
48
  let inBlock = false;
48
49
  const blockLines = [];
49
50
 
@@ -0,0 +1,388 @@
1
+ /**
2
+ * health-endpoints.mjs — Content-scoped parser for the `health-endpoints:`
3
+ * Session Config key, plus the block-form reading of `ecosystem-health:`.
4
+ *
5
+ * WHY THIS EXISTS (#1174): `health-endpoints` was read off the FLAT KV map via
6
+ * `_coerceList`, which bails to `null` the moment a value contains `{` and
7
+ * cannot see a nested YAML block at all. The ecosystem wizard
8
+ * (`scripts/lib/ecosystem-wizard/config-writer.mjs` `_buildEcosystemSnippetLines`)
9
+ * writes exactly the block form:
10
+ *
11
+ * ecosystem-health:
12
+ * health-endpoints:
13
+ * - name: API
14
+ * url: https://api.example.com/health
15
+ *
16
+ * Measured before the fix: the flat KV map ended up with bare `name` / `url`
17
+ * entries (last one wins → silent data loss), `health-endpoints` resolved to
18
+ * `null`, and because the header line `ecosystem-health:` carries no value it
19
+ * never matched the KV regex either — so the whole feature stayed dark while
20
+ * the wizard reported success. Consumers (`skills/ecosystem-health/SKILL.md`,
21
+ * `docs/session-config-reference.md`) expect an array of `{ name, url }`.
22
+ *
23
+ * ACCEPTED FORMS (contract — every one of these is written by some producer in
24
+ * this repo today):
25
+ *
26
+ * A) inline object array (docs/USER-GUIDE.md):
27
+ * health-endpoints: [{name: "API", url: "https://a/health"}, {name: "W", url: "http://w:8080/z"}]
28
+ *
29
+ * B) nested block, top-level OR one level under `ecosystem-health:`
30
+ * (the wizard's output, skills/ecosystem-health/wizard.md):
31
+ * health-endpoints:
32
+ * - name: API
33
+ * url: https://a/health
34
+ * Block list items may themselves be inline objects
35
+ * (`- { name: API, url: … }`) — the form docs/session-config-template.md
36
+ * uses.
37
+ *
38
+ * C) bare list of URLs — each URL becomes its own name
39
+ * (`{ name: <url>, url: <url> }`). Both spellings are accepted, because
40
+ * an operator extending the wizard's BLOCK by hand writes the block one:
41
+ * health-endpoints: [https://a/health, https://b/health]
42
+ * and
43
+ * health-endpoints:
44
+ * - https://a/health
45
+ *
46
+ * Empty list (`[]`) → `[]`. `none` / `null` / key absent → `null`.
47
+ *
48
+ * NOT accepted (deliberately): the `name=url` shorthand. An item carrying `=`
49
+ * is treated verbatim as a Form-C URL, never split.
50
+ *
51
+ * Malformed input (an entry missing `name` or `url`, an unmatched brace) →
52
+ * `null` plus exactly ONE `console.warn` line prefixed `config: health-endpoints:`.
53
+ * Never throws: a broken config key must not take down every session-start.
54
+ *
55
+ * Consumers: `scripts/lib/config.mjs` (`health-endpoints`, `ecosystem-health`).
56
+ *
57
+ * The header line itself (indent + optional inline value) is matched via the
58
+ * shared `matchBlockHeaderDetailed()` (#1185) rather than a private regex —
59
+ * this was the one parser in `scripts/lib/config/` whose header needed BOTH
60
+ * arbitrary indent (nested under `ecosystem-health:`) AND an inline value
61
+ * (Form A/C), which the plain `matchBlockHeader()` contract deliberately
62
+ * excludes. See `scripts/lib/config/block-header.mjs` for the shared matcher.
63
+ */
64
+
65
+ import { matchBlockHeader, matchBlockHeaderDetailed } from './block-header.mjs';
66
+ import { preprocessBlockLines, preprocessBlockLinesNoDash } from './block-preprocess.mjs';
67
+ import { _coerceBoolean } from './coercers.mjs';
68
+
69
+ /**
70
+ * A scalar that is a URL: a scheme followed by `//`. Used to tell a Form-C
71
+ * bare-URL list item (`- https://a/health`) from a `key: value` pair
72
+ * (`- name: API`) inside a block. `https://…` would otherwise match the
73
+ * key/value regex with key `https`, which is why the check must come first.
74
+ */
75
+ const URL_SCALAR_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//;
76
+
77
+ /**
78
+ * Strip a trailing YAML comment. Only a `#` PRECEDED BY WHITESPACE counts, so a
79
+ * URL fragment (`https://a/health#frag`) survives untouched.
80
+ * @param {string} s
81
+ * @returns {string}
82
+ */
83
+ function stripComment(s) {
84
+ return s.replace(/\s+#.*$/, '');
85
+ }
86
+
87
+ /**
88
+ * Strip one layer of matching surrounding quotes.
89
+ * @param {string} v
90
+ * @returns {string}
91
+ */
92
+ function unquote(v) {
93
+ if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) {
94
+ return v.slice(1, -1);
95
+ }
96
+ return v;
97
+ }
98
+
99
+ /**
100
+ * Split on commas that sit at brace/bracket depth 0 and outside quotes — so a
101
+ * comma inside a quoted URL never splits an entry.
102
+ * @param {string} s
103
+ * @returns {string[]}
104
+ */
105
+ function splitTopLevel(s) {
106
+ const out = [];
107
+ let depth = 0;
108
+ let quote = null;
109
+ let cur = '';
110
+ for (const ch of s) {
111
+ if (quote) {
112
+ cur += ch;
113
+ if (ch === quote) quote = null;
114
+ continue;
115
+ }
116
+ if (ch === '"' || ch === "'") { quote = ch; cur += ch; continue; }
117
+ if (ch === '{' || ch === '[') depth++;
118
+ else if (ch === '}' || ch === ']') depth--;
119
+ if (ch === ',' && depth === 0) { out.push(cur); cur = ''; continue; }
120
+ cur += ch;
121
+ }
122
+ out.push(cur);
123
+ return out;
124
+ }
125
+
126
+ /**
127
+ * Split `{…}, {…}` into the inner text of each object. Quote-aware, so a `}`
128
+ * inside a quoted value does not close an object.
129
+ * @param {string} s
130
+ * @returns {string[]|null} null on an unmatched brace.
131
+ */
132
+ function splitObjects(s) {
133
+ const out = [];
134
+ let depth = 0;
135
+ let quote = null;
136
+ let cur = '';
137
+ for (const ch of s) {
138
+ if (quote) {
139
+ if (depth > 0) cur += ch;
140
+ if (ch === quote) quote = null;
141
+ continue;
142
+ }
143
+ if (ch === '"' || ch === "'") {
144
+ quote = ch;
145
+ if (depth > 0) cur += ch;
146
+ continue;
147
+ }
148
+ if (ch === '{') {
149
+ depth++;
150
+ if (depth === 1) { cur = ''; continue; }
151
+ } else if (ch === '}') {
152
+ depth--;
153
+ if (depth < 0) return null;
154
+ if (depth === 0) { out.push(cur); cur = ''; continue; }
155
+ }
156
+ if (depth > 0) cur += ch;
157
+ }
158
+ if (depth !== 0 || quote) return null;
159
+ return out;
160
+ }
161
+
162
+ /**
163
+ * Parse the inner text of one `{ name: X, url: Y }` object.
164
+ * @param {string} inner
165
+ * @returns {{name: string, url: string}|null} null when name or url is missing.
166
+ */
167
+ function parseObjectBody(inner) {
168
+ let name = null;
169
+ let url = null;
170
+ for (const pair of splitTopLevel(inner)) {
171
+ const colonIdx = pair.indexOf(':');
172
+ if (colonIdx === -1) continue;
173
+ const k = pair.slice(0, colonIdx).trim().toLowerCase();
174
+ const v = unquote(pair.slice(colonIdx + 1).trim());
175
+ if (k === 'name') name = v;
176
+ else if (k === 'url') url = v;
177
+ }
178
+ if (!name || !url) return null;
179
+ return { name, url };
180
+ }
181
+
182
+ /**
183
+ * Parse an inline value (everything right of `health-endpoints:` on one line).
184
+ * @param {string} raw
185
+ * @returns {{ok: true, value: {name: string, url: string}[]|null} | {ok: false}}
186
+ */
187
+ function parseInlineValue(raw) {
188
+ let v = raw.trim();
189
+ // A trailing comment is only stripped when the value ends with `]` — that
190
+ // keeps `#` inside an unbracketed value (a URL fragment) intact.
191
+ const bracketed = v.match(/^(\[.*\])\s*(?:#.*)?$/s);
192
+ if (bracketed) v = bracketed[1];
193
+
194
+ if (v === '' || v === 'none' || v === 'null') return { ok: true, value: null };
195
+
196
+ const stripped = v.replace(/^\[/, '').replace(/\]$/, '').trim();
197
+ if (stripped === '') return { ok: true, value: [] };
198
+
199
+ if (stripped.includes('{')) {
200
+ const bodies = splitObjects(stripped);
201
+ if (bodies === null) return { ok: false };
202
+ const entries = [];
203
+ for (const body of bodies) {
204
+ const entry = parseObjectBody(body);
205
+ if (entry === null) return { ok: false };
206
+ entries.push(entry);
207
+ }
208
+ return entries.length === 0 ? { ok: false } : { ok: true, value: entries };
209
+ }
210
+
211
+ // Form C — bare list of URLs; the URL is its own name.
212
+ const urls = splitTopLevel(stripped).map((s) => unquote(s.trim())).filter((s) => s.length > 0);
213
+ if (urls.length === 0) return { ok: true, value: [] };
214
+ return { ok: true, value: urls.map((u) => ({ name: u, url: u })) };
215
+ }
216
+
217
+ /**
218
+ * Parse the indented block that follows a valueless `health-endpoints:` header.
219
+ * Terminates at the first non-blank line indented no deeper than the header —
220
+ * the same rule `issue-budget.mjs` uses, generalised so a header nested under
221
+ * `ecosystem-health:` terminates correctly too.
222
+ *
223
+ * @param {string[]} lines — all document lines
224
+ * @param {number} start — index of the first line after the header
225
+ * @param {number} headerIndent — indent width of the header line
226
+ * @returns {{ok: true, value: {name: string, url: string}[]|null} | {ok: false}}
227
+ */
228
+ function parseBlock(lines, start, headerIndent) {
229
+ /** @type {{name: string|null, url: string|null}[]} */
230
+ const entries = [];
231
+ let cur = null;
232
+
233
+ for (let i = start; i < lines.length; i++) {
234
+ const line = lines[i].replace(/\r$/, '');
235
+ if (line.trim() === '') continue;
236
+ const indent = line.match(/^[ \t]*/)[0].length;
237
+ if (indent <= headerIndent) break;
238
+
239
+ const clean = stripComment(line).replace(/\s+$/, '');
240
+ if (clean.trim() === '') continue;
241
+
242
+ const itemMatch = clean.match(/^[ \t]*-\s*(.*)$/);
243
+ if (itemMatch) {
244
+ const rest = itemMatch[1].trim();
245
+ if (rest.startsWith('{')) {
246
+ const bodies = splitObjects(rest);
247
+ if (bodies === null || bodies.length === 0) return { ok: false };
248
+ for (const body of bodies) {
249
+ const entry = parseObjectBody(body);
250
+ if (entry === null) return { ok: false };
251
+ entries.push(entry);
252
+ }
253
+ cur = null;
254
+ continue;
255
+ }
256
+ // Form C inside a block: a bare URL item is its own name — the same rule
257
+ // the inline form applies. Without this, `- https://a/health` fell into
258
+ // assignKV, matched as key `https`, and the whole key WARNed to null
259
+ // while the identical inline list parsed fine.
260
+ const scalar = unquote(rest);
261
+ if (URL_SCALAR_RE.test(scalar)) {
262
+ entries.push({ name: scalar, url: scalar });
263
+ cur = null;
264
+ continue;
265
+ }
266
+ cur = { name: null, url: null };
267
+ entries.push(cur);
268
+ if (rest === '') continue;
269
+ if (!assignKV(cur, rest)) return { ok: false };
270
+ continue;
271
+ }
272
+
273
+ const kvMatch = clean.match(/^[ \t]*[a-zA-Z][a-zA-Z0-9_-]*:\s*/);
274
+ if (!kvMatch) return { ok: false };
275
+ if (cur === null) return { ok: false };
276
+ if (!assignKV(cur, clean.trim())) return { ok: false };
277
+ }
278
+
279
+ if (entries.length === 0) return { ok: true, value: null };
280
+ for (const e of entries) {
281
+ if (!e.name || !e.url) return { ok: false };
282
+ }
283
+ return { ok: true, value: entries.map((e) => ({ name: e.name, url: e.url })) };
284
+ }
285
+
286
+ /**
287
+ * Assign one `key: value` line into an in-progress entry. Unknown keys are
288
+ * ignored (forward-compatible with a future `timeout:` sub-key); a line that
289
+ * is not a `key: value` pair at all is a parse failure.
290
+ *
291
+ * @param {{name: string|null, url: string|null}} entry
292
+ * @param {string} text
293
+ * @returns {boolean} false when the line is not a key/value pair.
294
+ */
295
+ function assignKV(entry, text) {
296
+ const m = text.match(/^([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/);
297
+ if (!m) return false;
298
+ const k = m[1].toLowerCase();
299
+ const v = unquote(m[2].trim());
300
+ if (k === 'name') entry.name = v;
301
+ else if (k === 'url') entry.url = v;
302
+ return true;
303
+ }
304
+
305
+ /**
306
+ * Parse `health-endpoints` from the raw markdown content.
307
+ *
308
+ * Scans the FULL document (not just the `## Session Config` fence), matching
309
+ * every sibling block parser in this directory, and takes the FIRST
310
+ * `health-endpoints:` occurrence — top-level or nested under `ecosystem-health:`.
311
+ *
312
+ * @param {string} content — full file contents
313
+ * @returns {{name: string, url: string}[]|null} `null` when absent, explicitly
314
+ * `none`/`null`, or malformed (malformed additionally emits one WARN).
315
+ */
316
+ export function _parseHealthEndpoints(content) {
317
+ if (typeof content !== 'string' || content === '') return null;
318
+
319
+ // NoDash: `- name:` / `- url:` / `- {name, url}` list items are RECORD
320
+ // boundaries here — de-dashing would merge two endpoints into one (#1162).
321
+ const lines = preprocessBlockLinesNoDash(content);
322
+ for (let i = 0; i < lines.length; i++) {
323
+ const line = lines[i].replace(/\r$/, '');
324
+ const m = matchBlockHeaderDetailed(line, 'health-endpoints');
325
+ if (!m) continue;
326
+
327
+ const headerIndent = m.indent;
328
+ const inline = m.value ?? '';
329
+ const result = inline !== ''
330
+ ? parseInlineValue(inline)
331
+ : parseBlock(lines, i + 1, headerIndent);
332
+
333
+ if (!result.ok) {
334
+ console.warn(
335
+ `config: health-endpoints: malformed value at line ${i + 1} — expected a list of {name, url} entries; ignoring.`
336
+ );
337
+ return null;
338
+ }
339
+ return result.value;
340
+ }
341
+ return null;
342
+ }
343
+
344
+ /**
345
+ * Read `ecosystem-health` in its BLOCK form (`ecosystem-health:` with no value,
346
+ * followed by an indented body — what the wizard writes). The scalar form
347
+ * (`ecosystem-health: true`) is read by the caller off the KV map and takes
348
+ * precedence; this function only answers the case the KV map cannot see.
349
+ *
350
+ * A non-empty block means the feature is configured, hence enabled — unless the
351
+ * block carries an explicit `enabled: false`.
352
+ *
353
+ * @param {string} content — full file contents
354
+ * @returns {boolean|null} `null` when no block form is present (caller keeps its default).
355
+ */
356
+ export function _parseEcosystemHealthBlockEnabled(content) {
357
+ if (typeof content !== 'string' || content === '') return null;
358
+
359
+ // Standard variant: this parser reads only the flat `enabled:` sub-key and
360
+ // builds no records (#1162).
361
+ const lines = preprocessBlockLines(content);
362
+ let inBlock = false;
363
+ const blockLines = [];
364
+ for (const rawLine of lines) {
365
+ const line = rawLine.replace(/\r$/, '');
366
+ if (!inBlock) {
367
+ if (matchBlockHeader(line, 'ecosystem-health')) inBlock = true;
368
+ continue;
369
+ }
370
+ if (line.length > 0 && !/^\s/.test(line)) break;
371
+ blockLines.push(line);
372
+ }
373
+
374
+ const meaningful = blockLines.filter((l) => stripComment(l).trim() !== '');
375
+ if (!inBlock || meaningful.length === 0) return null;
376
+
377
+ for (const l of meaningful) {
378
+ const m = stripComment(l).trim().match(/^enabled:\s*(\S+)$/i);
379
+ // Same truth table as the scalar path — `_coerceBoolean` accepts only
380
+ // true/false (case-insensitively) and THROWS on anything else. A local
381
+ // `=== 'true'` comparison silently mapped `yes` to false, so the block form
382
+ // disagreed with `ecosystem-health: yes` on the very same document.
383
+ // NB: `_coerceBoolean` uses its `key` argument for BOTH the map lookup and
384
+ // the error text, so the synthetic map must be keyed by that same string.
385
+ if (m) return _coerceBoolean(new Map([['ecosystem-health.enabled', m[1]]]), 'ecosystem-health.enabled', false);
386
+ }
387
+ return true;
388
+ }
@@ -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
  * issue-budget.mjs — Parser for the `issue-budget:` block-style Session Config key.
@@ -65,7 +66,7 @@ export function _parseIssueBudget(content) {
65
66
 
66
67
  if (typeof content !== 'string' || content === '') return { ...defaults };
67
68
 
68
- const lines = content.split(/\r?\n/);
69
+ const lines = preprocessBlockLines(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
  * loop-guard.mjs — Parser for the top-level `loop-guard:` YAML block
@@ -57,7 +58,7 @@ export function _parseLoopGuard(content) {
57
58
  window: DEFAULT_WINDOW,
58
59
  };
59
60
 
60
- const lines = content.split(/\r?\n/);
61
+ const lines = preprocessBlockLines(content);
61
62
  let inBlock = false;
62
63
  const blockLines = [];
63
64
 
@@ -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
  * memory.mjs — Parser for the top-level `memory:` YAML block.
@@ -55,7 +56,7 @@ export function _parseMemory(content) {
55
56
  },
56
57
  };
57
58
 
58
- const lines = content.split(/\r?\n/);
59
+ const lines = preprocessBlockLines(content);
59
60
  let inBlock = false;
60
61
  const blockLines = [];
61
62