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
@@ -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
  * moc-staleness.mjs — Parser for the top-level `moc-staleness:` YAML block.
@@ -31,7 +32,7 @@ export function _parseMocStaleness(content) {
31
32
  mode: 'warn',
32
33
  };
33
34
 
34
- const lines = content.split(/\r?\n/);
35
+ const lines = preprocessBlockLines(content);
35
36
  let inBlock = false;
36
37
  const blockLines = [];
37
38
 
@@ -20,6 +20,7 @@
20
20
  import { parseThreshold } from '../persona-panel/threshold.mjs';
21
21
  import { ALLOWED_MODEL_ALIASES, MODEL_ID_RE } from '../agent-frontmatter.mjs';
22
22
  import { matchBlockHeader } from './block-header.mjs';
23
+ import { preprocessBlockLines } from './block-preprocess.mjs';
23
24
 
24
25
  const ALLOWED_AFTER = new Set(['quality', 'impl-polish']);
25
26
  const ALLOWED_MODE = new Set(['off', 'warn', 'strict']);
@@ -169,7 +170,7 @@ export function _normalizePersonaGateWave(parsed) {
169
170
  * @returns {string[]}
170
171
  */
171
172
  function _extractBlock(content, key) {
172
- const lines = content.split(/\r?\n/);
173
+ const lines = preprocessBlockLines(content);
173
174
  const blockLines = [];
174
175
  let inBlock = false;
175
176
 
@@ -0,0 +1,67 @@
1
+ /**
2
+ * private-config-dir.mjs — THE resolver for the host-private config directory (#1223).
3
+ *
4
+ * Every per-host artefact below that directory (`host-private.json`,
5
+ * `owner.yaml`, the self-alias ledger) moves with it, so the precedence has to
6
+ * live in exactly ONE place. Before #1223 three copies with three different
7
+ * precedences existed: `owner-yaml.mjs`'s import-time homedir-only
8
+ * `OWNER_YAML_PATH`, `owner-config-loader.mjs`'s XDG-only + untrimmed
9
+ * `resolveOwnerConfigPath()`, and `host-identity.mjs`'s `_privateDir()`. The
10
+ * live consequence: `SO_CONFIG_HOME=<sandbox>` moved the alias ledger but NOT
11
+ * owner.yaml, which kept being read from the operator's REAL home — the
12
+ * CLAUDE.md (Codex CLI alias: AGENTS.md) "vault-dir resolves HOST-LOCALLY"
13
+ * hazard class.
14
+ *
15
+ * ── Why this is a standalone ZERO-IMPORT leaf ────────────────────────────────
16
+ *
17
+ * Two independent consumers constrain it, and neither tolerates the other's
18
+ * import graph:
19
+ *
20
+ * 1. `host-identity.mjs` is reachable from LIVE hooks (via `session-lock.mjs`),
21
+ * `hooks/on-stop.mjs` among them. on-stop must degrade with one actionable
22
+ * line when `node_modules` is absent (GH#63), so nothing on ITS subgraph —
23
+ * this leaf included — may pull a bare specifier. Putting the resolver in
24
+ * `owner-yaml.mjs` dragged `js-yaml` onto that subgraph and turned the GH#63
25
+ * contract red with `ERR_MODULE_NOT_FOUND: Cannot find package 'js-yaml'`.
26
+ * Since GitLab #1230 (2026-09-06) `owner-yaml.mjs` resolves `js-yaml`
27
+ * lazily, so that particular collision can no longer recur — but the
28
+ * constraint on THIS file is unchanged, because it is what let the resolver
29
+ * be shared in the first place. Measured 2026-09-06 over a `hooks/` +
30
+ * `scripts/` copy with no `node_modules`, the four hooks that reach
31
+ * `owner-yaml.mjs` (on-session-start, on-session-end, post-edit-validate,
32
+ * skill-invocation-telemetry) now load instead of throwing.
33
+ * 2. `tests/husky/pre-commit-owner-leakage.test.mjs` copies the CP11 scanner's
34
+ * import chain FILE BY FILE into a tmp repo. Every repo-local import added
35
+ * to a file on that chain must be added to the copied-file list there.
36
+ *
37
+ * Hence: `node:os` + `node:path` only. No repo-local imports, no bare
38
+ * specifiers beyond the two Node builtins.
39
+ */
40
+
41
+ import { homedir } from 'node:os';
42
+ import { join } from 'node:path';
43
+
44
+ /**
45
+ * Resolve the host-private config directory.
46
+ *
47
+ * Precedence, most specific first: `SO_CONFIG_HOME` names the private dir
48
+ * ITSELF; `XDG_CONFIG_HOME` names its PARENT (the `session-orchestrator`
49
+ * segment is appended); without either, `~/.config/session-orchestrator`.
50
+ *
51
+ * `.trim() || fallback` throughout — a whitespace-only env var is truthy and
52
+ * would short-circuit a bare `||`, returning the spaces verbatim
53
+ * (`.claude/rules/development.md` § Error Handling, env-var fallback
54
+ * whitespace trap).
55
+ *
56
+ * Reads env at CALL time, so a test may stub `process.env` after import.
57
+ *
58
+ * @param {{ env?: Record<string, string|undefined> }} [opts]
59
+ * @returns {string} absolute path to the host-private config directory
60
+ */
61
+ export function resolvePrivateConfigDir({ env = process.env } = {}) {
62
+ const own = (env.SO_CONFIG_HOME || '').trim();
63
+ if (own) return own;
64
+ const xdg = (env.XDG_CONFIG_HOME || '').trim();
65
+ if (xdg) return join(xdg, 'session-orchestrator');
66
+ return join(homedir(), '.config', 'session-orchestrator');
67
+ }
@@ -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
  * reconcile.mjs — Parser for the top-level `reconcile:` YAML block.
@@ -169,7 +170,7 @@ export function _parseReconcile(content) {
169
170
  'max-proposals-per-run': 10,
170
171
  };
171
172
 
172
- const lines = content.split(/\r?\n/);
173
+ const lines = preprocessBlockLines(content);
173
174
  let inBlock = false;
174
175
  const blockLines = [];
175
176
 
@@ -0,0 +1,234 @@
1
+ import { matchBlockHeader } from './block-header.mjs';
2
+ import { preprocessBlockLinesNoDash } from './block-preprocess.mjs';
3
+
4
+ /**
5
+ * remote-hosts.mjs — Parser for the `remote-hosts:` Session Config block (#1160).
6
+ *
7
+ * Lets a repo DECLARE ssh-reachable hosts that heavy wave roles (test / ui / perf)
8
+ * may be offloaded to instead of shrinking the wave under local resource pressure.
9
+ * This module only declares — it never probes the network, never dispatches, and
10
+ * never decides. Placement is `scripts/lib/wave-resource-gate.mjs`; the dispatch
11
+ * adapter lives in `scripts/lib/wave-executor/`.
12
+ *
13
+ * Exports:
14
+ * REMOTE_HOST_DEFAULTS — per-host defaults
15
+ * ALLOWED_REMOTE_ROLES — the agent-mapping roles a host may accept
16
+ * _parseRemoteHosts(content) — PURE, no side effects beyond a stderr WARN when a
17
+ * record is dropped. Returns [] when absent/empty.
18
+ *
19
+ * Block shape:
20
+ * remote-hosts:
21
+ * - alias: m5 # required; becomes `-H <alias>` argv
22
+ * roles-allowed: [test, ui, perf] # subset of ALLOWED_REMOTE_ROLES
23
+ * repo-path: ~/Projects/Alice # optional
24
+ * claude-path: ~/.local/bin/claude # optional
25
+ *
26
+ * Modelled on custom-phases.mjs (per-entry SAFE-regex validation, drop-with-warn,
27
+ * column-0 block termination, silent enum fallback). The one shape it adds is the
28
+ * inline `[a, b]` list for `roles-allowed`.
29
+ */
30
+
31
+ /** Per-host defaults. */
32
+ export const REMOTE_HOST_DEFAULTS = Object.freeze({
33
+ 'roles-allowed': ['test', 'ui', 'perf'],
34
+ 'repo-path': null,
35
+ 'claude-path': null,
36
+ });
37
+
38
+ /**
39
+ * The roles a remote host may accept. A deliberate SUBSET of `ALLOWED_ROLES` in
40
+ * scripts/lib/config.mjs — impl / db / security / compliance / docs stay local.
41
+ *
42
+ * NOT the wave-role enum ("Impl-Core", "Quality", …): those are two different
43
+ * enums and conflating them is the documented trap (see resolveApwCap's docstring
44
+ * in wave-resource-gate.mjs). The wave→agent-mapping-role translation is
45
+ * `OFFLOADABLE_WAVE_ROLES` in that same module.
46
+ *
47
+ * @type {readonly string[]}
48
+ */
49
+ export const ALLOWED_REMOTE_ROLES = Object.freeze(['test', 'ui', 'perf']);
50
+
51
+ // A host `alias` is an ssh destination that reaches argv as `-H <alias>`: letters,
52
+ // digits, hyphen, underscore, dot only. No spaces, no shell metacharacters.
53
+ //
54
+ // The first character is anchored separately from the rest because a charset
55
+ // cannot express a POSITION: an alias of `-H` passes any hyphen-inclusive
56
+ // allowlist and then reaches argv as an OPTION token, where the CLI swallows the
57
+ // operand behind it. Interior hyphens (`m5-box`) stay legal.
58
+ const SAFE_NAME_RE = /^[A-Za-z0-9._][A-Za-z0-9._-]*$/;
59
+
60
+ // `repo-path` / `claude-path` are filesystem paths handed to a remote shell —
61
+ // same allowlist as custom-phases' `review`, with the same leading-hyphen anchor
62
+ // as SAFE_NAME_RE above (`--x` is a flag, not a path).
63
+ const SAFE_PATH_RE = /^[A-Za-z0-9._~/][A-Za-z0-9._~/-]*$/;
64
+
65
+ /**
66
+ * Parse the top-level `remote-hosts:` YAML list block from markdown content.
67
+ *
68
+ * A record missing `alias`, or carrying an unsafe `alias` / `repo-path` /
69
+ * `claude-path`, is DROPPED with a stderr WARN. Unknown entries in
70
+ * `roles-allowed` are filtered out with a WARN; a record whose list is empty
71
+ * after filtering is dropped (it could never be selected anyway).
72
+ *
73
+ * @param {string} content — full CLAUDE.md / AGENTS.md file content
74
+ * @returns {Array<{alias: string, 'roles-allowed': string[], 'repo-path': string|null, 'claude-path': string|null}>}
75
+ */
76
+ export function _parseRemoteHosts(content) {
77
+ const lines = preprocessBlockLinesNoDash(String(content ?? ''));
78
+ let inBlock = false;
79
+ const blockLines = [];
80
+
81
+ for (const rawLine of lines) {
82
+ const line = rawLine.replace(/\r$/, '');
83
+
84
+ if (!inBlock) {
85
+ if (matchBlockHeader(line, 'remote-hosts')) inBlock = true;
86
+ continue;
87
+ }
88
+
89
+ // Block terminates at the first non-indented, non-empty line (next top-level key).
90
+ if (line.length > 0 && !/^\s/.test(line)) break;
91
+
92
+ blockLines.push(line);
93
+ }
94
+
95
+ if (blockLines.length === 0) return [];
96
+
97
+ const records = [];
98
+ /** @type {Record<string, string>|null} */
99
+ let current = null;
100
+
101
+ const flush = () => {
102
+ if (current === null) return;
103
+ const rec = _validateRecord(current);
104
+ if (rec !== null) records.push(rec);
105
+ current = null;
106
+ };
107
+
108
+ for (const rawLine of blockLines) {
109
+ // Strip inline comments + trailing whitespace, preserve leading indent.
110
+ const clean = rawLine.replace(/\s*#.*$/, '').replace(/\s+$/, '');
111
+ if (!clean.trim()) continue;
112
+
113
+ const dashMatch = clean.match(/^\s*-\s+(.*)$/);
114
+ if (dashMatch) {
115
+ flush();
116
+ current = {};
117
+ const inlineKv = dashMatch[1].match(/^([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/);
118
+ if (inlineKv) _assignKv(current, inlineKv[1], inlineKv[2]);
119
+ continue;
120
+ }
121
+
122
+ const kvMatch = clean.match(/^\s+([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/);
123
+ if (kvMatch && current !== null) {
124
+ _assignKv(current, kvMatch[1], kvMatch[2]);
125
+ }
126
+ }
127
+
128
+ flush();
129
+
130
+ return records;
131
+ }
132
+
133
+ /**
134
+ * Assign a raw key/value onto a record being built, stripping surrounding quotes.
135
+ * Unknown keys are silently ignored (additive-friendly).
136
+ *
137
+ * @param {Record<string, string>} record
138
+ * @param {string} key
139
+ * @param {string} rawValue
140
+ */
141
+ function _assignKv(record, key, rawValue) {
142
+ let v = rawValue.trim();
143
+ if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) v = v.slice(1, -1);
144
+ else if (v.startsWith("'") && v.endsWith("'") && v.length >= 2) v = v.slice(1, -1);
145
+
146
+ switch (key) {
147
+ case 'alias':
148
+ case 'roles-allowed':
149
+ case 'repo-path':
150
+ case 'claude-path':
151
+ record[key] = v;
152
+ break;
153
+ default:
154
+ break;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Parse an inline `[a, b]` (or bare `a, b`) list into trimmed non-empty strings.
160
+ *
161
+ * @param {string} raw
162
+ * @returns {string[]}
163
+ */
164
+ function _parseInlineList(raw) {
165
+ const inner = raw.trim().replace(/^\[/, '').replace(/\]$/, '');
166
+ return inner
167
+ .split(',')
168
+ .map((s) => s.trim().replace(/^["']|["']$/g, ''))
169
+ .filter((s) => s !== '');
170
+ }
171
+
172
+ /**
173
+ * Validate + normalise a raw record.
174
+ *
175
+ * @param {Record<string, string>} raw
176
+ * @returns {{alias: string, 'roles-allowed': string[], 'repo-path': string|null, 'claude-path': string|null}|null}
177
+ */
178
+ function _validateRecord(raw) {
179
+ const alias = (raw.alias ?? '').trim();
180
+ if (alias === '') {
181
+ process.stderr.write('remote-hosts: dropped record missing required field: alias\n');
182
+ return null;
183
+ }
184
+ if (!SAFE_NAME_RE.test(alias)) {
185
+ process.stderr.write(
186
+ `remote-hosts: dropped record with unsafe alias: ${JSON.stringify(alias)}\n`,
187
+ );
188
+ return null;
189
+ }
190
+
191
+ let rolesAllowed = [...REMOTE_HOST_DEFAULTS['roles-allowed']];
192
+ const rawRoles = (raw['roles-allowed'] ?? '').trim();
193
+ if (rawRoles !== '') {
194
+ const declared = _parseInlineList(rawRoles);
195
+ const kept = declared.filter((r) => ALLOWED_REMOTE_ROLES.includes(r));
196
+ const dropped = declared.filter((r) => !ALLOWED_REMOTE_ROLES.includes(r));
197
+ if (dropped.length > 0) {
198
+ process.stderr.write(
199
+ `remote-hosts: host '${alias}' declares unknown role(s) ${dropped.join(', ')} ` +
200
+ `(allowed: ${ALLOWED_REMOTE_ROLES.join(', ')}) — ignored\n`,
201
+ );
202
+ }
203
+ if (kept.length === 0) {
204
+ process.stderr.write(
205
+ `remote-hosts: dropped record '${alias}' — roles-allowed is empty after filtering\n`,
206
+ );
207
+ return null;
208
+ }
209
+ rolesAllowed = kept;
210
+ }
211
+
212
+ const paths = {};
213
+ for (const key of ['repo-path', 'claude-path']) {
214
+ let value = REMOTE_HOST_DEFAULTS[key];
215
+ const rawPath = (raw[key] ?? '').trim();
216
+ if (rawPath !== '' && rawPath !== 'null' && rawPath !== 'none') {
217
+ if (!SAFE_PATH_RE.test(rawPath)) {
218
+ process.stderr.write(
219
+ `remote-hosts: dropped record '${alias}' with shell metacharacter in ${key}: ${JSON.stringify(rawPath)}\n`,
220
+ );
221
+ return null;
222
+ }
223
+ value = rawPath;
224
+ }
225
+ paths[key] = value;
226
+ }
227
+
228
+ return {
229
+ alias,
230
+ 'roles-allowed': rolesAllowed,
231
+ 'repo-path': paths['repo-path'],
232
+ 'claude-path': paths['claude-path'],
233
+ };
234
+ }
@@ -214,10 +214,16 @@ function isCodeFence(line) {
214
214
  * this repo's own convention encourages. Revisit if a fleet CLAUDE.md is ever
215
215
  * measured opening a multi-line comment after a value.
216
216
  *
217
+ * EXPORTED since #1162 for a third consumer: `block-preprocess.mjs`, which
218
+ * gives the ~36 block parsers under `scripts/lib/config/` the same
219
+ * commented-out-is-not-live-config semantics. Same argument as the export of
220
+ * `isSessionConfigHeading` — a fourth copy of this state machine that drifts
221
+ * loose is worse than no check at all. Behaviour is unchanged by the export.
222
+ *
217
223
  * @returns {(line: string) => boolean} true when the line is inside (or opens)
218
224
  * a multi-line HTML comment and must be ignored by both consumers.
219
225
  */
220
- function htmlCommentSkipper() {
226
+ export function htmlCommentSkipper() {
221
227
  let inComment = false;
222
228
  return function skipHtmlComment(line) {
223
229
  if (inComment) {
@@ -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
  * skill-evolution.mjs — Parser for the top-level `skill-evolution:` YAML block
@@ -39,7 +40,7 @@ export function _parseSkillEvolution(content) {
39
40
  'judge-budget-tokens': 8000,
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
  * slopcheck.mjs — Parser for the top-level `slopcheck:` YAML block
@@ -36,7 +37,7 @@ export function _parseSlopcheck(content) {
36
37
  sources: [...DEFAULT_SOURCES],
37
38
  };
38
39
 
39
- const lines = content.split(/\r?\n/);
40
+ const lines = preprocessBlockLines(content);
40
41
  let inBlock = false;
41
42
  const blockLines = [];
42
43
 
@@ -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
  * state-md-lock.mjs — Parser for the top-level `state-md-lock:` YAML block
@@ -30,7 +31,7 @@ export function _parseStateMdLock(content) {
30
31
  'timeout-ms': 10000,
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 { preprocessBlockLines } from './block-preprocess.mjs';
2
3
 
3
4
  /**
4
5
  * templates-first.mjs — Parser for the top-level `templates-first:` YAML block
@@ -34,7 +35,7 @@ export function _parseTemplatesFirst(content) {
34
35
  hosts: [...DEFAULT_HOSTS],
35
36
  };
36
37
 
37
- const lines = content.split(/\r?\n/);
38
+ const lines = preprocessBlockLines(content);
38
39
  let inBlock = false;
39
40
  const blockLines = [];
40
41
 
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { validatePathInsideProject } from '../path-utils.mjs';
10
10
  import { matchBlockHeader } from './block-header.mjs';
11
+ import { preprocessBlockLines } from './block-preprocess.mjs';
11
12
 
12
13
  /**
13
14
  * Parse the top-level `test:` YAML block from markdown content.
@@ -27,7 +28,7 @@ export function _parseTest(content) {
27
28
  'retention-days': 30,
28
29
  };
29
30
 
30
- const lines = content.split(/\r?\n/);
31
+ const lines = preprocessBlockLines(content);
31
32
  let inBlock = false;
32
33
  const blockLines = [];
33
34
 
@@ -22,6 +22,7 @@
22
22
 
23
23
  import { _coerceBoolean, _coerceInteger } from './coercers.mjs';
24
24
  import { matchBlockHeader } from './block-header.mjs';
25
+ import { preprocessBlockLines } from './block-preprocess.mjs';
25
26
 
26
27
  // ---------------------------------------------------------------------------
27
28
  // vault-integration
@@ -68,7 +69,12 @@ export function _parseVaultIntegration(content) {
68
69
  const defaults = { enabled: false, 'vault-dir': null, mode: 'warn', 'vault-name': null };
69
70
  if (typeof content !== 'string' || content === '') return defaults;
70
71
 
71
- const lines = content.split(/\r?\n/);
72
+ // #1162: NOT a raw split. `preprocessBlockLines` drops HTML-commented lines
73
+ // (a commented-out block was read as live config) and rewrites bold-bullet
74
+ // sub-keys (`- **enabled:** true` fell back to the `false` default). This
75
+ // parser has no dash-RECORD body, so the bold pass is safe here — see the
76
+ // named risk in block-preprocess.mjs.
77
+ const lines = preprocessBlockLines(content);
72
78
 
73
79
  // Pass 1: inline-object form. Matches `vault-integration: { ... }` with or
74
80
  // without a leading `- ` (baseline list-item form per #497).
@@ -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
  * vault-mirror-quality.mjs — Parser for the top-level `vault-mirror:` YAML block,
@@ -30,7 +31,7 @@ export function _parseVaultMirrorQuality(content) {
30
31
  },
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 { preprocessBlockLines } from './block-preprocess.mjs';
2
3
 
3
4
  /**
4
5
  * vault-staleness.mjs — Parser for the top-level `vault-staleness:` YAML block.
@@ -17,7 +18,7 @@ export function _parseVaultStaleness(content) {
17
18
  mode: 'warn',
18
19
  };
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
 
@@ -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
  * vault-sync.mjs — Parser for the top-level `vault-sync:` YAML block.
@@ -16,7 +17,7 @@ import { matchBlockHeader } from './block-header.mjs';
16
17
  export function _parseVaultSync(content) {
17
18
  const defaults = { enabled: false, mode: 'warn', 'vault-dir': null, exclude: [] };
18
19
 
19
- const lines = content.split(/\r?\n/);
20
+ const lines = preprocessBlockLines(content);
20
21
  let inBlock = false;
21
22
  const blockLines = [];
22
23
 
@@ -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
  * verification-auto-fix.mjs — Parser for the top-level `verification-auto-fix:`
@@ -31,7 +32,7 @@ export function _parseVerificationAutoFix(content) {
31
32
  'max-retries': 2,
32
33
  };
33
34
 
34
- const lines = content.split(/\r?\n/);
35
+ const lines = preprocessBlockLines(content);
35
36
  let inBlock = false;
36
37
  const blockLines = [];
37
38
 
@@ -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
  * wave-reviewers.mjs — Parser for the `wave-reviewers` / `persona-reviewers` sub-block.
@@ -58,7 +59,7 @@ export function _parseWaveReviewers(content) {
58
59
  * @returns {string[]}
59
60
  */
60
61
  function _extractBlock(content, key) {
61
- const lines = content.split(/\r?\n/);
62
+ const lines = preprocessBlockLines(content);
62
63
  const blockLines = [];
63
64
  let inBlock = false;
64
65
 
@@ -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
  * worktree-orphans.mjs — Parser for the top-level `worktree-orphans:` YAML block.
@@ -85,7 +86,7 @@ export function _parseWorktreeOrphans(content) {
85
86
 
86
87
  if (typeof content !== 'string' || content === '') return defaults;
87
88
 
88
- const lines = content.split(/\r?\n/);
89
+ const lines = preprocessBlockLines(content);
89
90
  let inBlock = false;
90
91
  const blockLines = [];
91
92
 
@@ -61,6 +61,7 @@ import { _parseAutoDream } from './config/auto-dream.mjs';
61
61
  import { _parseStateMdLock } from './config/state-md-lock.mjs';
62
62
  import { _parseHandoverGate } from './config/handover-gate.mjs';
63
63
  import { _parseIssueBudget } from './config/issue-budget.mjs';
64
+ import { _parseHealthEndpoints, _parseEcosystemHealthBlockEnabled } from './config/health-endpoints.mjs';
64
65
  import { _parseBrokenWindow } from './config/broken-window.mjs';
65
66
  import { _parseSlopcheck } from './config/slopcheck.mjs';
66
67
  import { _parseDiscoveryValidator } from './config/discovery-validator.mjs';
@@ -71,6 +72,7 @@ import { _parseEval } from './config/eval.mjs';
71
72
  import { _parseMemory } from './config/memory.mjs';
72
73
  import { _parseReconcile } from './config/reconcile.mjs';
73
74
  import { _parseCustomPhases } from './config/custom-phases.mjs';
75
+ import { _parseRemoteHosts } from './config/remote-hosts.mjs';
74
76
  import { _parseEvolve, _parseEvolveDecay } from './config/evolve.mjs';
75
77
  import { _parseSkillEvolution } from './config/skill-evolution.mjs';
76
78
  import { _parseDispatcherAutonomy, resolveDispatcherAutonomy } from './config/dispatcher-autonomy.mjs';
@@ -198,7 +200,14 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
198
200
 
199
201
  // Boolean fields
200
202
  const persistence = _coerceBoolean(kv, 'persistence', true);
201
- const ecosystemHealth = _coerceBoolean(kv, 'ecosystem-health', false);
203
+ // ecosystem-health has TWO spellings and only the scalar one reaches `kv`:
204
+ // the wizard writes a valueless `ecosystem-health:` header opening a block,
205
+ // which carries no `key: value` pair and is therefore invisible to the KV
206
+ // map (#1174). Scalar wins when present; otherwise the block form decides.
207
+ const ecosystemHealth =
208
+ _getVal(kv, 'ecosystem-health', undefined) !== undefined
209
+ ? _coerceBoolean(kv, 'ecosystem-health', false)
210
+ : (_parseEcosystemHealthBlockEnabled(mdContent) ?? false);
202
211
  const discoveryOnClose = _coerceBoolean(kv, 'discovery-on-close', false);
203
212
  const reasoningOutput = _coerceBoolean(kv, 'reasoning-output', false);
204
213
  const groundingCheck = _coerceBoolean(kv, 'grounding-check', true);
@@ -214,7 +223,6 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
214
223
  const ssotFiles = _coerceList(kv, 'ssot-files', undefined);
215
224
  const discoveryProbes = _coerceList(kv, 'discovery-probes', '[all]');
216
225
  const discoveryExcludePaths = _coerceList(kv, 'discovery-exclude-paths', '[]');
217
- const healthEndpoints = _coerceList(kv, 'health-endpoints', undefined);
218
226
  const worktreeExclude = _coerceList(
219
227
  kv,
220
228
  'worktree-exclude',
@@ -230,6 +238,11 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
230
238
  // follow-up (see docs/session-config-reference.md). This wires the parser + value only.
231
239
  const worktreeCleanup = _coerceEnum(kv, 'worktree-cleanup', 'default', ['default', 'aggressive']);
232
240
 
241
+ // remote-hosts: opt-in ssh-reachable hosts heavy roles may be offloaded to (#1160).
242
+ // Parsed HERE, before agent-mapping, because the `ssh:<alias>` channel below
243
+ // validates its target against the declared aliases. Defaults to [].
244
+ const remoteHosts = _parseRemoteHosts(mdContent);
245
+
233
246
  // Object fields
234
247
  const agentMapping = _coerceObject(kv, 'agent-mapping');
235
248
  if (agentMapping !== null) {
@@ -240,10 +253,11 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
240
253
  // dispatched natively, exactly as before.
241
254
  // impl: code-implementer → native Agent dispatch (unchanged)
242
255
  // impl: cursor:composer-2.5 → foreign model over the Cursor channel
256
+ // test: ssh:m5 → same Claude, another host (#1160)
243
257
  // An unknown prefix is a typo or a channel this build cannot route, and
244
258
  // must fail loudly: silently accepting it would dispatch to an agent that
245
259
  // does not exist, which surfaces only as an empty wave much later.
246
- const KNOWN_CHANNELS = ['cursor', 'session-orchestrator'];
260
+ const KNOWN_CHANNELS = ['cursor', 'session-orchestrator', 'ssh'];
247
261
  // Accumulate every defect, then report once. A config with two bad entries
248
262
  // must name BOTH — throwing on the first one makes the operator re-run the
249
263
  // parse per defect, and each re-run hides the ones behind it.
@@ -272,6 +286,15 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
272
286
  `role '${k}' has channel '${channel}' but no target in value '${v}' ` +
273
287
  `(expected '${channel}:<model-or-agent>')`
274
288
  );
289
+ } else if (channel === 'ssh' && !remoteHosts.some((h) => h.alias === target)) {
290
+ // An ssh target is a host ALIAS, and the only place aliases are declared
291
+ // is the remote-hosts: block. An undeclared alias would reach argv as
292
+ // `-H <target>` and fail at connect time, one wave too late.
293
+ const declared = remoteHosts.map((h) => h.alias).join(', ') || 'none';
294
+ valueProblems.push(
295
+ `role '${k}' names ssh host '${target}' which is not declared in remote-hosts: ` +
296
+ `(declared: ${declared})`
297
+ );
275
298
  }
276
299
  }
277
300
  }
@@ -442,6 +465,10 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
442
465
  const evolveDecay = _parseEvolveDecay(mdContent);
443
466
 
444
467
  // persona-gate-wave: opt-in mid-wave persona-panel hook (#458). Returns null when absent.
468
+ // health-endpoints: content-scoped (NOT _coerceList, which bails to null on
469
+ // any '{' and cannot see the wizard's nested block form at all) — #1174.
470
+ const healthEndpoints = _parseHealthEndpoints(mdContent);
471
+
445
472
  const personaGateWave = _parsePersonaGateWave(mdContent);
446
473
  if (personaGateWave !== null && personaGateWave.enabled === true && personaGateWave.mode === 'off') {
447
474
  process.stderr.write(
@@ -538,6 +565,7 @@ export function parseSessionConfig(mdContent, { hostPaths } = {}) {
538
565
  'wave-reviewers': waveReviewers,
539
566
  'persona-gate-wave': personaGateWave,
540
567
  'custom-phases': customPhases,
568
+ 'remote-hosts': remoteHosts,
541
569
  'evolve.extra-sources': evolveExtraSources,
542
570
  'evolve.decay': evolveDecay,
543
571
  };