session-orchestrator 3.24.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1125 -2
  81. package/NOTICE +11 -6
  82. package/README.md +127 -94
  83. package/agents/eval-judge.md +1 -1
  84. package/agents/skill-applied-judge.md +1 -1
  85. package/assets/wave-lifecycle.svg +98 -0
  86. package/commands/release.md +6 -3
  87. package/commands/session.md +18 -3
  88. package/docs/README.md +4 -0
  89. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  90. package/docs/baseline.md +67 -0
  91. package/docs/ci-setup.md +108 -62
  92. package/docs/codex-setup.md +65 -21
  93. package/docs/components.md +36 -15
  94. package/docs/cursor-setup.md +6 -2
  95. package/docs/events-schema.md +9 -6
  96. package/docs/instruction-delivery.md +62 -0
  97. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  98. package/docs/migration-v4.md +341 -0
  99. package/docs/pi-setup.md +6 -1
  100. package/docs/plugin-architecture-v3.md +1 -1
  101. package/docs/rule-authoring.md +85 -19
  102. package/docs/scope-collision-guard.md +5 -5
  103. package/docs/session-config-reference.md +57 -56
  104. package/docs/session-config-template.md +6 -29
  105. package/docs/telemetry.md +157 -3
  106. package/docs/vault-docs-architecture.md +50 -11
  107. package/hooks/_lib/hook-import-set.json +1487 -0
  108. package/hooks/_lib/subagent-transcript.mjs +562 -0
  109. package/hooks/config-protection.mjs +2 -2
  110. package/hooks/cwd-change-restore.mjs +2 -2
  111. package/hooks/enforce-commands.mjs +69 -0
  112. package/hooks/hooks-codex.json +1 -1
  113. package/hooks/hooks-cursor.json +10 -0
  114. package/hooks/hooks-pi.json +5 -0
  115. package/hooks/hooks.json +6 -1
  116. package/hooks/loop-guard.mjs +3 -3
  117. package/hooks/on-session-end.mjs +2 -2
  118. package/hooks/on-session-start.mjs +103 -2
  119. package/hooks/on-stop.mjs +36 -11
  120. package/hooks/operator-steer.mjs +2 -2
  121. package/hooks/post-bash-write-verify.mjs +85 -0
  122. package/hooks/post-edit-import-probe.mjs +344 -0
  123. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  124. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  125. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  126. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  127. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  128. package/hooks/skill-invocation-telemetry.mjs +17 -5
  129. package/hooks/subagent-telemetry.mjs +13 -4
  130. package/monitors/monitors.json +3 -3
  131. package/package.json +9 -1
  132. package/pi/prompts/session.md +2 -2
  133. package/plugin.json +27 -0
  134. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  135. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  136. package/scripts/dialectic-deriver.mjs +73 -8
  137. package/scripts/export-hw-learnings.mjs +113 -1
  138. package/scripts/generate-agents-skills.mjs +378 -0
  139. package/scripts/generate-cursor-adapter.mjs +45 -8
  140. package/scripts/generate-hook-import-set.mjs +249 -0
  141. package/scripts/lib/agent-status.mjs +13 -2
  142. package/scripts/lib/auto-dream.mjs +38 -36
  143. package/scripts/lib/autonomy/suitability.mjs +6 -0
  144. package/scripts/lib/autopilot/loop.mjs +2 -2
  145. package/scripts/lib/ci-status-banner.mjs +220 -75
  146. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  147. package/scripts/lib/config/auto-dream.mjs +2 -1
  148. package/scripts/lib/config/block-header.mjs +8 -0
  149. package/scripts/lib/config/block-preprocess.mjs +177 -0
  150. package/scripts/lib/config/broken-window.mjs +2 -1
  151. package/scripts/lib/config/cold-start.mjs +2 -1
  152. package/scripts/lib/config/config-protection.mjs +22 -2
  153. package/scripts/lib/config/context-coverage.mjs +2 -1
  154. package/scripts/lib/config/cross-repo.mjs +2 -1
  155. package/scripts/lib/config/custom-phases.mjs +2 -1
  156. package/scripts/lib/config/dialectic.mjs +2 -1
  157. package/scripts/lib/config/discovery-validator.mjs +2 -1
  158. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  159. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  160. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  161. package/scripts/lib/config/docs-staleness.mjs +2 -1
  162. package/scripts/lib/config/drift-check.mjs +2 -1
  163. package/scripts/lib/config/eval.mjs +2 -1
  164. package/scripts/lib/config/events-rotation.mjs +2 -1
  165. package/scripts/lib/config/evolve.mjs +8 -2
  166. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  167. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  168. package/scripts/lib/config/handover-gate.mjs +2 -1
  169. package/scripts/lib/config/health-endpoints.mjs +7 -2
  170. package/scripts/lib/config/issue-budget.mjs +2 -1
  171. package/scripts/lib/config/loop-guard.mjs +2 -1
  172. package/scripts/lib/config/memory.mjs +2 -1
  173. package/scripts/lib/config/moc-staleness.mjs +2 -1
  174. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  175. package/scripts/lib/config/private-config-dir.mjs +67 -0
  176. package/scripts/lib/config/reconcile.mjs +2 -1
  177. package/scripts/lib/config/remote-hosts.mjs +2 -1
  178. package/scripts/lib/config/section-extractor.mjs +7 -1
  179. package/scripts/lib/config/skill-evolution.mjs +2 -1
  180. package/scripts/lib/config/slopcheck.mjs +2 -1
  181. package/scripts/lib/config/state-md-lock.mjs +2 -1
  182. package/scripts/lib/config/templates-first.mjs +2 -1
  183. package/scripts/lib/config/test.mjs +2 -1
  184. package/scripts/lib/config/vault-integration.mjs +7 -1
  185. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  186. package/scripts/lib/config/vault-staleness.mjs +2 -1
  187. package/scripts/lib/config/vault-sync.mjs +2 -1
  188. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  189. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  190. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  191. package/scripts/lib/convergence-monitor.mjs +82 -16
  192. package/scripts/lib/dispatcher/rank.mjs +124 -48
  193. package/scripts/lib/ecosystem-health.mjs +16 -2
  194. package/scripts/lib/eval/engine.mjs +9 -1
  195. package/scripts/lib/eval/session-resolve.mjs +23 -4
  196. package/scripts/lib/events.mjs +22 -6
  197. package/scripts/lib/frontmatter-guard.mjs +131 -13
  198. package/scripts/lib/gates/gate-full.mjs +26 -0
  199. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  200. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  201. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  202. package/scripts/lib/host-identity.mjs +50 -11
  203. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  204. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  205. package/scripts/lib/learnings/io.mjs +60 -6
  206. package/scripts/lib/memory-proposals/store.mjs +30 -22
  207. package/scripts/lib/owner-config-banner.mjs +43 -6
  208. package/scripts/lib/owner-config-loader.mjs +21 -10
  209. package/scripts/lib/owner-interview.mjs +3 -3
  210. package/scripts/lib/owner-yaml.mjs +207 -14
  211. package/scripts/lib/platform.mjs +108 -15
  212. package/scripts/lib/plugin-update-banner.mjs +406 -0
  213. package/scripts/lib/project-hygiene.mjs +38 -2
  214. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  215. package/scripts/lib/quality-gate.mjs +133 -44
  216. package/scripts/lib/reconcile/emitter.mjs +68 -6
  217. package/scripts/lib/reconcile/engine.mjs +13 -4
  218. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  219. package/scripts/lib/reconcile/writer.mjs +40 -18
  220. package/scripts/lib/session-close-backfill.mjs +67 -9
  221. package/scripts/lib/session-id.mjs +12 -23
  222. package/scripts/lib/session-identity/own-session.mjs +125 -10
  223. package/scripts/lib/session-lock-shape.mjs +43 -0
  224. package/scripts/lib/session-lock.mjs +5 -10
  225. package/scripts/lib/session-registry.mjs +25 -9
  226. package/scripts/lib/session-schema/constants.mjs +36 -2
  227. package/scripts/lib/session-schema/validator.mjs +38 -4
  228. package/scripts/lib/session-start-probes.mjs +18 -1
  229. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  230. package/scripts/lib/skill-health/join.mjs +17 -4
  231. package/scripts/lib/state-md.mjs +78 -0
  232. package/scripts/lib/sunset/walker.mjs +6 -0
  233. package/scripts/lib/telemetry/schema.mjs +181 -9
  234. package/scripts/lib/telemetry/sync.mjs +368 -12
  235. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  236. package/scripts/lib/validate/check-agents.mjs +3 -3
  237. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  238. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  239. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  240. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  241. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  242. package/scripts/lib/validate/check-unwired-features.mjs +0 -2
  243. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  244. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  245. package/scripts/lib/vault-backfill/template.mjs +63 -6
  246. package/scripts/lib/vault-mirror/process.mjs +165 -42
  247. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  248. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  249. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  250. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  251. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  252. package/scripts/lib/wave-resource-gate.mjs +8 -2
  253. package/scripts/lib/wave-sizing.mjs +4 -1
  254. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  255. package/scripts/materialize-wave-scope.mjs +12 -5
  256. package/scripts/memory-propose.mjs +19 -5
  257. package/scripts/migrate-cold-start-seed.mjs +4 -1
  258. package/scripts/parse-config.mjs +60 -3
  259. package/scripts/release.mjs +337 -29
  260. package/scripts/repair-invalid-sessions.mjs +3 -3
  261. package/scripts/run-quality-gate.mjs +128 -11
  262. package/scripts/sweep-expired-learnings.mjs +90 -0
  263. package/scripts/sync-vault-schema.mjs +3 -1
  264. package/scripts/telemetry.mjs +2 -2
  265. package/scripts/validate-plugin.mjs +161 -0
  266. package/scripts/validate-wave-scope.mjs +28 -8
  267. package/scripts/wave-scope-binding.mjs +215 -0
  268. package/skills/_shared/instruction-file-resolution.md +10 -0
  269. package/skills/_shared/parallel-aware-preamble.md +1 -0
  270. package/skills/_shared/platform-tools.md +1 -1
  271. package/skills/_shared/state-ownership.md +1 -1
  272. package/skills/architecture/SKILL.md +7 -5
  273. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  274. package/skills/autopilot/SKILL.md +4 -18
  275. package/skills/claude-md-drift-check/SKILL.md +5 -1
  276. package/skills/claude-md-drift-check/checker.mjs +62 -2
  277. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  278. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  279. package/skills/discovery/probes-arch.md +20 -18
  280. package/skills/dispatcher/SKILL.md +3 -2
  281. package/skills/evolve/SKILL.md +65 -26
  282. package/skills/frontmatter-guard/SKILL.md +11 -5
  283. package/skills/npm-publish/SKILL.md +1 -1
  284. package/skills/reconcile/SKILL.md +33 -0
  285. package/skills/remote-offload/SKILL.md +1 -1
  286. package/skills/session-end/SKILL.md +18 -905
  287. package/skills/session-end/phase-3-6-tail.md +10 -3
  288. package/skills/session-end/plan-verification.md +221 -155
  289. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  290. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  291. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  292. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  293. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  294. package/skills/session-end/references/session-summary-template.md +62 -0
  295. package/skills/session-plan/SKILL.md +49 -0
  296. package/skills/session-start/SKILL.md +22 -904
  297. package/skills/session-start/phase-8-5-express-path.md +1 -1
  298. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  299. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  300. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  301. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  302. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  303. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  304. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  305. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  306. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  307. package/skills/vault-sync/validator.mjs +21 -27
  308. package/skills/wave-executor/SKILL.md +15 -1
  309. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  310. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  311. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  312. package/skills/wave-executor/wave-loop.md +14 -1309
  313. package/templates/_shared/journey-manifest.md +10 -6
  314. package/.cursor/commands/autopilot-multi.md +0 -14
  315. package/.cursor/commands/contract-version-bump.md +0 -14
  316. package/.cursor/commands/journey-audit.md +0 -14
  317. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  318. package/.cursor/skills/daily/SKILL.md +0 -12
  319. package/.cursor/skills/domain-model/SKILL.md +0 -13
  320. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  321. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  322. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  323. package/commands/autopilot-multi.md +0 -74
  324. package/commands/contract-version-bump.md +0 -28
  325. package/commands/journey-audit.md +0 -43
  326. package/pi/prompts/autopilot-multi.md +0 -12
  327. package/pi/prompts/contract-version-bump.md +0 -12
  328. package/pi/prompts/journey-audit.md +0 -12
  329. package/scripts/autopilot-multi.mjs +0 -885
  330. package/scripts/backfill-learnings-expires.mjs +0 -196
  331. package/scripts/backfill-learnings.mjs +0 -203
  332. package/scripts/fleet-instruction-scan.mjs +0 -141
  333. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  334. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  335. package/scripts/lib/webhook-url.mjs +0 -105
  336. package/scripts/lifecycle-sim-v6.mjs +0 -347
  337. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  338. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  339. package/scripts/upload-social-preview.mjs +0 -316
  340. package/skills/_shared/model-selection.md +0 -64
  341. package/skills/contract-version-bump/SKILL.md +0 -219
  342. package/skills/daily/SKILL.md +0 -222
  343. package/skills/daily/generate.sh +0 -92
  344. package/skills/daily/templates/daily.md.tpl +0 -36
  345. package/skills/journey-audit/SKILL.md +0 -270
  346. package/skills/skill-creator/SKILL.md +0 -168
  347. package/skills/ubiquitous-language/SKILL.md +0 -97
  348. package/skills/vault-sync/package-lock.json +0 -40
  349. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  350. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * wave-scope-binding.mjs — print the session binding for a `wave-scope.json`
4
+ * manifest, and record the fail-closed case as an event (#1153 P4).
5
+ *
6
+ * Usage:
7
+ * node scripts/wave-scope-binding.mjs [--merge] [--wave N] [--role R] [--repo-root DIR]
8
+ *
9
+ * Prints ONE JSON object on stdout:
10
+ *
11
+ * {"session_id":"<raw session_id>","semantic_session_id":"<semantic id>"}
12
+ *
13
+ * Those key names are the canonical ones since #1153 P2 — the same spelling
14
+ * `.orchestrator/session.lock` and `current-session.json` already use. Readers
15
+ * additionally accept the pre-#1153 `session` / `semantic_session` spellings
16
+ * until the next minor release (`MANIFEST_SESSION_KEYS` in
17
+ * `scripts/lib/session-identity/own-session.mjs`); this writer never emits them.
18
+ *
19
+ * Keys whose value is unavailable are OMITTED, never written as `""` — an empty
20
+ * id is present-but-equal-to-nobody, which every reader classifies as FOREIGN
21
+ * (the one disposition that skips enforcement entirely), and
22
+ * `validate-wave-scope.mjs` rejects it outright.
23
+ *
24
+ * WHY A COMMAND AND NOT PROSE: this binding used to be an inline
25
+ * `node --input-type=module -e` block in `skills/wave-executor/wave-loop.md`,
26
+ * retyped by the coordinator once per wave. An unbound manifest (`{}`) is the
27
+ * fail-closed direction and therefore SILENT — indistinguishable from a
28
+ * coordinator that skipped the step. `orchestrator.scope.unbound_manifest` is
29
+ * what makes the silent case countable; 0 hits repo-wide before this file.
30
+ *
31
+ * The binding itself is NOT recomputed here. It is exactly one
32
+ * `attributionForRecord()` call, which reads `.orchestrator/session.lock` and
33
+ * confirms the raw `session_id` against this process's own identity before
34
+ * returning anything — under a peer-owned lock it returns `{}` rather than the
35
+ * peer's ids. Duplicating that logic would be the one-fact-two-copies class this
36
+ * repo keeps paying for.
37
+ */
38
+
39
+ import { parseArgs } from 'node:util';
40
+ import { resolve as resolvePath } from 'node:path';
41
+ import { fileURLToPath } from 'node:url';
42
+ import { attributionForRecord, emitEvent } from './lib/events.mjs';
43
+ import { MANIFEST_SESSION_KEYS } from './lib/session-identity/own-session.mjs';
44
+
45
+ const HELP = `Usage: node scripts/wave-scope-binding.mjs [--merge] [--wave N] [--role R] [--repo-root DIR]
46
+
47
+ Print the session-binding keys for a wave-scope.json manifest as one JSON object.
48
+ Empty values are OMITTED; an unbound binding prints {} and emits exactly one
49
+ orchestrator.scope.unbound_manifest event.
50
+
51
+ With --merge, read the DRAFT manifest as one JSON object on stdin and print that
52
+ same manifest with the binding keys merged in (or with them omitted, plus the
53
+ unbound event, when the binding is {}). Every other field is passed through.
54
+
55
+ Options:
56
+ --merge Read the draft manifest on stdin, print it bound.
57
+ --wave <n> Wave number, recorded in the unbound event payload.
58
+ --role <role> Wave role, recorded in the unbound event payload.
59
+ --repo-root <dir> Repo root to resolve the lock and the events log against
60
+ (default: process.cwd()).
61
+ -h, --help Show this help and exit 0.
62
+
63
+ Output:
64
+ stdout — exactly one JSON object. Diagnostics go to stderr.
65
+ `;
66
+
67
+ /**
68
+ * Resolve the manifest binding for `repoRoot`.
69
+ *
70
+ * @param {string} repoRoot
71
+ * @returns {{ session_id?: string, semantic_session_id?: string }} binding with
72
+ * empty values omitted
73
+ */
74
+ export function resolveBinding(repoRoot) {
75
+ const attribution = attributionForRecord(repoRoot) ?? {};
76
+ /** @type {{ session_id?: string, semantic_session_id?: string }} */
77
+ const out = {};
78
+ const session = typeof attribution.session_id === 'string' ? attribution.session_id.trim() : '';
79
+ const semantic = typeof attribution.semantic_session_id === 'string'
80
+ ? attribution.semantic_session_id.trim()
81
+ : '';
82
+ if (session) out.session_id = session;
83
+ if (semantic) out.semantic_session_id = semantic;
84
+ return out;
85
+ }
86
+
87
+ async function main(argv) {
88
+ let parsed;
89
+ try {
90
+ parsed = parseArgs({
91
+ args: argv,
92
+ options: {
93
+ wave: { type: 'string' },
94
+ role: { type: 'string' },
95
+ 'repo-root': { type: 'string' },
96
+ merge: { type: 'boolean' },
97
+ help: { type: 'boolean', short: 'h' },
98
+ },
99
+ allowPositionals: false,
100
+ });
101
+ } catch (error) {
102
+ process.stderr.write(`wave-scope-binding: ${error instanceof Error ? error.message : String(error)}\n`);
103
+ process.stderr.write(HELP);
104
+ process.exitCode = 2;
105
+ return;
106
+ }
107
+
108
+ if (parsed.values.help) {
109
+ process.stdout.write(HELP);
110
+ return;
111
+ }
112
+
113
+ const repoRoot = parsed.values['repo-root'] ?? process.cwd();
114
+ const binding = resolveBinding(repoRoot);
115
+
116
+ if (Object.keys(binding).length === 0) {
117
+ // The fail-closed case, and the whole reason this is a command: an unbound
118
+ // manifest enforces against EVERY session in the checkout, and until now it
119
+ // left no trace at all. Best-effort — the binding is still printed if the
120
+ // append fails, because a broken events log must not stall a wave.
121
+ try {
122
+ await emitEvent(
123
+ 'orchestrator.scope.unbound_manifest',
124
+ {
125
+ // Numeric when it parses as one, so the field matches every other
126
+ // `wave` in the stream; `null` (never `undefined`) when absent, since
127
+ // an undefined `wave` lets emitEvent's correlation envelope fill it
128
+ // from the live manifest — the very artefact this event says is
129
+ // unbound.
130
+ wave: Number.isFinite(Number(parsed.values.wave)) && parsed.values.wave !== undefined
131
+ ? Number(parsed.values.wave)
132
+ : (parsed.values.wave ?? null),
133
+ role: parsed.values.role ?? null,
134
+ reason: 'no-confirmed-session-attribution',
135
+ },
136
+ { repoRoot },
137
+ );
138
+ } catch (error) {
139
+ process.stderr.write(
140
+ `wave-scope-binding: could not record unbound_manifest event: ${error instanceof Error ? error.message : String(error)}\n`,
141
+ );
142
+ }
143
+ }
144
+
145
+ if (!parsed.values.merge) {
146
+ process.stdout.write(`${JSON.stringify(binding)}\n`);
147
+ return;
148
+ }
149
+
150
+ // --merge: the coordinator pipes the draft manifest in and gets the SAME
151
+ // manifest back with the binding merged, instead of hand-copying two keys
152
+ // out of the printed object into the JSON it is about to write (#1207).
153
+ let draftRaw;
154
+ try {
155
+ draftRaw = await readStdin();
156
+ } catch (error) {
157
+ process.stderr.write(
158
+ `wave-scope-binding: could not read the draft manifest from stdin: ${error instanceof Error ? error.message : String(error)}\n`,
159
+ );
160
+ process.exitCode = 1;
161
+ return;
162
+ }
163
+ let draft;
164
+ try {
165
+ draft = JSON.parse(draftRaw);
166
+ } catch {
167
+ process.stderr.write('wave-scope-binding: --merge expects ONE JSON object on stdin\n');
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ if (!draft || typeof draft !== 'object' || Array.isArray(draft)) {
172
+ process.stderr.write('wave-scope-binding: --merge expects ONE JSON object on stdin\n');
173
+ process.exitCode = 1;
174
+ return;
175
+ }
176
+
177
+ // Any binding key already in the draft is dropped first: under an unbound or
178
+ // peer-owned lock the merged manifest must name NOBODY, and a stale key
179
+ // surviving the merge would name somebody. Omitted, never `""` — see above.
180
+ // BOTH spellings are dropped (close-review 2026-09-04, HIGH): a legacy
181
+ // `session`/`semantic_session` pair surviving the merge is still READ by every
182
+ // consumer and would name a foreign session — `foreign` = gates stand down.
183
+ const merged = { ...draft };
184
+ for (const key of [...MANIFEST_SESSION_KEYS.current, ...MANIFEST_SESSION_KEYS.legacy]) delete merged[key];
185
+ // Explicit per-key copy, never Object.assign: `merged` derives from a
186
+ // JSON.parse'd draft, and a `__proto__` payload key would reach the
187
+ // Object.prototype setter through [[Set]] semantics (CWE-1321, semgrep
188
+ // prototype-pollution-object-assign — CI-red on ce6a28aa).
189
+ for (const key of MANIFEST_SESSION_KEYS.current) {
190
+ if (typeof binding[key] === 'string' && binding[key]) merged[key] = binding[key];
191
+ }
192
+ process.stdout.write(`${JSON.stringify(merged)}\n`);
193
+ }
194
+
195
+ /**
196
+ * Read all of stdin as UTF-8. Resolves to `''` when stdin is closed/empty.
197
+ * @returns {Promise<string>}
198
+ */
199
+ function readStdin() {
200
+ return new Promise((resolve, reject) => {
201
+ let buf = '';
202
+ process.stdin.setEncoding('utf8');
203
+ process.stdin.on('data', (chunk) => {
204
+ buf += chunk;
205
+ });
206
+ process.stdin.on('end', () => resolve(buf));
207
+ process.stdin.on('error', reject);
208
+ });
209
+ }
210
+
211
+ // `import.meta.main` is not available on every supported Node — compare argv[1]
212
+ // instead, so importing this module from a test never runs the CLI.
213
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolvePath(process.argv[1])) {
214
+ await main(process.argv.slice(2));
215
+ }
@@ -28,6 +28,16 @@ Apply these steps in order against the repo root. Stop at the first hit. Never m
28
28
  - Never read both files. Never concatenate, diff, or cross-validate them — the SSOT is whichever the rule selects.
29
29
  - The resolved kind (`claude` | `agents`) is part of the contract. Consumers that report paths in JSON output (e.g., `skills/claude-md-drift-check/checker.mjs`) must surface the resolved path so users on either platform can audit the result.
30
30
 
31
+ ## Interaction with the root `AGENTS.md` this repo now ships
32
+
33
+ Since the cross-harness portable surface landed, this repo carries BOTH files at its root. That does not weaken the rule above — it is what makes the rule safe to hold while still serving foreign readers:
34
+
35
+ - **Our own readers still never read both.** `resolveInstructionFile()` picks exactly one (`CLAUDE.md` wins ties), and every consumer listed below goes through it. Nothing merges, diffs, or cross-validates the two as sources.
36
+ - **The root `AGENTS.md` exists for FOREIGN readers**, not for ours. 7 of 8 surveyed harnesses (Codex CLI, Cursor, Copilot CLI, OpenCode, Amp, Kiro, …) read `AGENTS.md`; only Claude Code reads `CLAUDE.md`, and only Copilot CLI reads both. Without a root `AGENTS.md` this repo's `## Session Config` was unreachable from six of them.
37
+ - **It is byte-identical by construction, and generated.** `scripts/generate-agents-skills.mjs` copies `CLAUDE.md` verbatim; `--check` (wired into `scripts/validate-plugin.mjs`) fails CI on any divergence. **Never edit `AGENTS.md`** — edit `CLAUDE.md` and regenerate. A consumer repo may instead symlink it; both shapes are accepted by the drift gate.
38
+ - **Why a copy and not a symlink here:** `package.json` `files[]` does not publish `CLAUDE.md`, so a symlink would be DANGLING in the npm tarball; and `core.symlinks` defaults to false on Windows without Developer Mode, where git materialises the link as a 10-byte regular file containing the literal text `CLAUDE.md` — a pointer with no Session Config, which is precisely the failure this file guards against.
39
+ - **The invariant is "the two cannot disagree", not "exactly one file exists."** `claude-md-drift-check` Check 7 (`vault-dir-parity`) enforces it: alias-by-construction → parity satisfied; two independent files that diverge → error. Check 9's probe 2a uses the same predicate so a defect in `CLAUDE.md` is never reported twice.
40
+
31
41
  ## Reference implementations
32
42
 
33
43
  ### Bash one-liner (matches `skills/_shared/bootstrap-gate.md` style)
@@ -2,6 +2,7 @@
2
2
 
3
3
  > Single source of truth for the parallel-session-detection preamble.
4
4
  > Referenced by: autopilot, session-start, session-plan, wave-executor, session-end (5 orchestrator entry-points).
5
+ > Design history: the 2026-05-26 parallel-aware-sessions spec is superseded on lock liveness (heartbeat, not PID) — see `docs/README.md` § Superseded design notes.
5
6
 
6
7
  ## Purpose
7
8
 
@@ -70,7 +70,7 @@ Lifecycle: `spawn_agent` (launch, one per wave task) → `list_agents` (enumerat
70
70
  - **session-reviewer** — quality review; spawn as a separate agent rather than reviewing in the main session
71
71
 
72
72
  **On Cursor IDE:**
73
- No native Agent() tool or typed agent roles inside the Composer session — wave tasks run sequentially there, and `agents-per-wave` is ignored. This is a statement about the IDE session only: coordinator-side foreign dispatch to Cursor models via the headless `cursor-agent` CLI does exist (`scripts/lib/wave-executor/foreign-dispatch.mjs`, #1150 — detached worktree, filesystem-measured result, `orchestrator.foreign_dispatch.completed` telemetry, mandatory Claude review, `NEVER_FOREIGN_ROLES` lock). Measured 2026-08-25 on `cursor-agent 2026.08.11-e8db854`. Slash commands live in `.cursor/commands/`. There is no Skill tool — Read `skills/<name>/SKILL.md` instead.
73
+ No native Agent() tool or typed agent roles inside the Composer session — wave tasks run sequentially there, and `agents-per-wave` is ignored. This is a statement about the IDE session only: coordinator-side foreign dispatch to Cursor models via the headless `cursor-agent` CLI does exist (`scripts/lib/wave-executor/foreign-dispatch.mjs`, #1150 — detached worktree, filesystem-measured result, `orchestrator.foreign_dispatch.completed` telemetry, mandatory Claude review, `NEVER_FOREIGN_ROLES` lock in `scripts/lib/wave-executor/dispatch-common.mjs`). Measured 2026-08-25 on `cursor-agent 2026.08.11-e8db854`. Slash commands live in `.cursor/commands/`. There is no Skill tool — Read `skills/<name>/SKILL.md` instead.
74
74
 
75
75
  **On Pi:**
76
76
  No Session Orchestrator Pi subagent dispatcher exists in v1. Execute wave tasks sequentially in the active Pi session. Treat `agents-per-wave` as advisory until the SDK-based dispatcher lands.
@@ -147,7 +147,7 @@ This is the wave-plan-time analog of PSA-007 (subagents never race the shared gi
147
147
 
148
148
  The rule above deconflicts writers *inside one wave*. The same working copy is also shared across SESSIONS, and `<state-dir>/wave-scope.json` is the one control artefact that constrains writes rather than describing them. It lives in the working copy, not in the session — so before #1123 a manifest written by session A governed session B's every Edit. Measured 2026-08-22 (#1082): a Discovery wave's `allowedPaths: []` — prescribed for every Discovery wave — denied all writes of an unrelated parallel session, with a deny reason that could only tell it to fix a wave plan it does not own.
149
149
 
150
- **The manifest is SESSION-BOUND since #1123.** The coordinator that writes it names itself in two optional fields, `session` (raw `session_id`) and `semantic_session`, both from ONE `sessionAttribution(repoRoot)` call (`scripts/lib/events.mjs`) — see `skills/wave-executor/wave-loop.md` § Scope Manifest 1. `hooks/enforce-scope.mjs` Gate 3b classifies the manifest with `readProcessLocalSessionIds()` + `classifyManifestSession()` (`scripts/lib/session-identity/own-session.mjs`) — process-local tiers only (hook payload, `CLAUDE_CODE_SESSION_ID`), never the repo-global `session.lock`, which is shared by every session in the checkout and made a peer's manifest read `own` (#1194):
150
+ **The manifest is SESSION-BOUND since #1123.** The coordinator that writes it names itself in two optional fields, `session_id` (the raw harness session id) and `semantic_session_id` (renamed from `session` / `semantic_session` in #1153 P2 to match `session.lock` and `current-session.json`; the legacy pair is still READ until the next minor release), both from ONE `sessionAttribution(repoRoot)` call (`scripts/lib/events.mjs`) — see `skills/wave-executor/wave-loop.md` § Scope Manifest 1. `hooks/enforce-scope.mjs` Gate 3b classifies the manifest with `readProcessLocalSessionIds()` + `classifyManifestSession()` (`scripts/lib/session-identity/own-session.mjs`) — process-local tiers only (hook payload, `CLAUDE_CODE_SESSION_ID`), never the repo-global `session.lock`, which is shared by every session in the checkout and made a peer's manifest read `own` (#1194):
151
151
 
152
152
  - **`foreign`** (ids present, none of them ours) → the gate ALLOWS the write and emits one `orchestrator.scope.foreign_session_ignored` event. A foreign manifest is somebody else's wave plan; it never had authority here, and the event keeps the skip counted rather than silent.
153
153
  - **`own`** → enforce, unchanged.
@@ -30,7 +30,9 @@ Key principles (see [LANGUAGE.md](LANGUAGE.md) for the full list):
30
30
  - **The interface is the test surface.**
31
31
  - **One adapter = hypothetical seam. Two adapters = real seam.**
32
32
 
33
- This skill is _informed_ by the project's domain model — `CONTEXT.md` and any `docs/adr/`. The domain language gives names to good seams; ADRs record decisions the skill should not re-litigate. See [CONTEXT-FORMAT.md](../domain-model/CONTEXT-FORMAT.md) and [ADR-FORMAT.md](../domain-model/ADR-FORMAT.md).
33
+ This skill is _informed_ by the project's domain model — `CONTEXT.md` and any `docs/adr/`. The domain language gives names to good seams; ADRs record decisions the skill should not re-litigate. See [references/CONTEXT-FORMAT.md](./references/CONTEXT-FORMAT.md) and [references/ADR-FORMAT.md](./references/ADR-FORMAT.md).
34
+
35
+ **When to load [references/domain-model.md](./references/domain-model.md):** before this skill's first pass on a repo with no `CONTEXT.md` yet, or one too thin to name the seams below — run its grilling interview to establish the domain vocabulary first. Skip it once `CONTEXT.md` already reflects the domain.
34
36
 
35
37
  ## Process
36
38
 
@@ -45,7 +47,7 @@ If any of these files don't exist, proceed silently — don't flag their absence
45
47
 
46
48
  **Optional pre-pass — export inventory injection (mapper-supported projects only):**
47
49
 
48
- Before dispatching the Explore subagent, call `extractSemanticSlices(filePath, { type: 'exports' })` from `scripts/lib/language-mappers/index.mjs` on known entry-point files (e.g., `index.ts`, `src/index.ts`, main export barrel). If the mapper returns a non-empty result, format the export list as structured context and inject it into the Explore subagent prompt. This gives the subagent an immediate map of the codebase's public surface without requiring it to grep manually.
50
+ Before dispatching the Explore subagent, read each known entry-point file (e.g. `index.ts`, `src/index.ts`, the main export barrel) and pass its contents to `extractSemanticSlices(filePath, content)` from `scripts/lib/language-mappers/index.mjs`. The signature is `(filePath, content, options?)` — the second argument is the raw file text, **not** an options object, and there is no slice-kind filter parameter. It resolves to an array of `SemanticSlice` records (`{ kind, name, exported, … }`); keep the ones with `exported === true`, format them as structured context, and inject that into the Explore subagent prompt. This gives the subagent an immediate map of the codebase's public surface without requiring it to grep manually.
49
51
 
50
52
  ```
51
53
  ## Export inventory (auto-generated)
@@ -53,7 +55,7 @@ Before dispatching the Explore subagent, call `extractSemanticSlices(filePath, {
53
55
  - src/lib/bar.ts: exports `processBar`
54
56
  ```
55
57
 
56
- This pre-pass is OPTIONAL — only activate when the entry-point file is mapper-supported (TypeScript/JavaScript). For unsupported file types, `extractSemanticSlices` returns an empty array and the pre-pass is silently skipped. Never block Explore dispatch on a mapper failure.
58
+ This pre-pass is OPTIONAL — only activate when the entry-point file is mapper-supported (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.md`, `.mdx`, `.swift`, `.py` — the `EXT_TO_LANG` map in `index.mjs` is the source of truth). For any other extension `extractSemanticSlices` **throws** rather than returning an empty array, so wrap the call in a try/catch and skip the pre-pass silently on failure. Never block Explore dispatch on a mapper error.
57
59
 
58
60
  Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction:
59
61
 
@@ -86,7 +88,7 @@ Once the user picks a candidate, drop into a grilling conversation. Walk the des
86
88
 
87
89
  Side effects happen inline as decisions crystallize:
88
90
 
89
- - **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as `/domain-model` (see [CONTEXT-FORMAT.md](../domain-model/CONTEXT-FORMAT.md)). Create the file lazily if it doesn't exist.
91
+ - **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as the domain-model grilling reference (see [references/CONTEXT-FORMAT.md](./references/CONTEXT-FORMAT.md)). Create the file lazily if it doesn't exist.
90
92
  - **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there.
91
- - **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [ADR-FORMAT.md](../domain-model/ADR-FORMAT.md).
93
+ - **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [references/ADR-FORMAT.md](./references/ADR-FORMAT.md).
92
94
  - **Want to explore alternative interfaces for the deepened module?** See [INTERFACE-DESIGN.md](INTERFACE-DESIGN.md).
@@ -1,12 +1,12 @@
1
- ---
2
- name: domain-model
3
- description: Use when the user wants to stress-test a plan against the existing domain model and documented decisions. Grilling session that interviews the user one question at a time, sharpens fuzzy terminology inline, updates CONTEXT.md lazily, and offers ADRs sparingly under a 3-criteria gate. Reads docs/adr/ and CONTEXT.md if present.
4
- model: inherit
5
- disable-model-invocation: true
6
- derived-from: mattpocock/skills@90ea8ee
7
- license: MIT
8
- upstream-url: https://github.com/mattpocock/skills/tree/main/domain-model
9
- ---
1
+ # Domain-Model Grilling (reference)
2
+
3
+ > Reference material for the `architecture` skill **not a skill of its own.**
4
+ > Merged here in v4.0.0 from the former `skills/domain-model/` (retired: zero
5
+ > telemetry, zero fleet invocations, reference-only usage — see
6
+ > `docs/audits/2026-09-06-360-audit.md` § 5A). Invoke it by reading this file
7
+ > from `/architecture`, not via a slash command.
8
+ >
9
+ > Adapted from [`mattpocock/skills@90ea8ee`](https://github.com/mattpocock/skills/tree/main/domain-model), MIT — Copyright (c) 2026 Matt Pocock. See the repo-root `NOTICE`.
10
10
 
11
11
  Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
12
12
 
@@ -377,24 +377,10 @@ identically by readers per the v1 schema additive convention).
377
377
 
378
378
  ## Configuration
379
379
 
380
- The `autopilot` block in Session Config (`CLAUDE.md` / `AGENTS.md`) accepts the following fields. All fields are optional; omitting a field applies the documented default.
381
-
382
- ```yaml
383
- autopilot:
384
- bg-isolation: worktree # worktree | none (default: worktree) — see #431
385
- ```
386
-
387
- ### bg-isolation
388
-
389
- **Type:** `worktree` | `none` — **Default:** `worktree`
390
-
391
- Controls whether `autopilot --multi-story` creates a per-story git worktree before spawning sub-sessions.
392
-
393
- `worktree` (default): Each story pipeline receives its own isolated git worktree via `EnterWorktree`. Parallel writes are safe because every agent edits a private working copy. Cost: disk space proportional to the number of concurrent stories plus the latency of worktree creation at story-start.
394
-
395
- `none` (opt-in): No worktrees are created. Sub-sessions spawn directly in the main working tree. Useful for monorepos where worktree creation is impractical due to large `node_modules`, sparse-checkout setups, or build caches that must be shared. **Requires file-scope discipline:** when `max-stories > 1`, every story must edit a disjoint set of files. If two stories touch the same file simultaneously, edits will collide silently. To enforce acknowledgement of this discipline, `autopilot-multi` requires `--deconflict-paths=<glob>` whenever `bg-isolation: none` AND `max-stories > 1`; omitting the flag is a hard error (exit 1). See `.claude/rules/parallel-sessions.md` PSA-001/002/003.
396
-
397
- **Operator-awareness note:** CC 2.1.133 silently flipped `worktree.baseRef` default from `head` to `origin/<default>`, breaking users who relied on unpushed commits being included in their worktree base. The same class of upstream change can affect `bg-isolation` semantics in a future CC release. Treat CC changelog entries related to worktree or `--bg` session behaviour as requiring a re-read of this section before upgrading.
380
+ Single-story `/autopilot` takes no Session Config block. Multi-story
381
+ (`autopilot.bg-isolation`, `--multi-story`, `--deconflict-paths`) was removed in
382
+ 4.0.0 — see `docs/migration-v4.md` and `docs/session-config-reference.md` §
383
+ "Autopilot Multi-Story (#431) — removed".
398
384
 
399
385
  ## References
400
386
 
@@ -39,7 +39,7 @@ PHASE 1 IMPLEMENTED (2026-04-19). Session-end opt-in quality gate. Upstream of `
39
39
  | 4 | `session-file-existence` | `50-sessions/YYYY-MM-DD-*.md` references anywhere in scope | `existsSync(vault/50-sessions/<file>)` |
40
40
  | 5 | `command-count` | "N commands" / "N /commands" claims in prose | compare to `ls commands/*.md \| wc -l`; skipped if no `commands/` dir |
41
41
  | 6 | `session-config-parity` | Top-level keys under `## Session Config` in `CLAUDE.md` / `AGENTS.md` | diff against `docs/session-config-template.md`; a missing MANDATORY (minimal-baseline) key is an error, a missing OPT-IN-baseline-only key is a warning |
42
- | 7 | `vault-dir-parity` | `vault-integration.vault-dir` in BOTH `CLAUDE.md` AND `AGENTS.md` | reuse `_parseVaultIntegration`; flag when the two files disagree |
42
+ | 7 | `vault-dir-parity` | `vault-integration.vault-dir` in BOTH `CLAUDE.md` AND `AGENTS.md` | reuse `_parseVaultIntegration`; flag when the two files disagree. Short-circuits to PASS when the two are the SAME file by construction (symlink / same inode / byte-identical generated copy) |
43
43
  | 8 | `generated-rule-staleness` *(WARN only)* | `.claude/rules/*.md` with `auto-generated: true` frontmatter | extract `learning-key`; WARN when the key is absent from `.orchestrator/metrics/learnings.jsonl` or its learning's `expires_at` is in the past; skipped silently when no auto-generated rules exist |
44
44
  | 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | five probes: `paths:` frontmatter (error), cited-but-missing rule citations (error), zero-match `globs:` patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped); skipped silently when `.claude/rules/` is absent |
45
45
  | 10 | `docs-parity` | `docs/components.md` count-claims, Session Config key parity (`docs/session-config-template.md` vs `docs/session-config-reference.md`), `.claude/metrics/` stale-path references in `docs/*.md` / `docs/examples/*.md` | three sub-checks (a/b/c) — see below; skipped silently when `docs/components.md` is absent |
@@ -61,6 +61,10 @@ A local CLAUDE.md / AGENTS.md that omits any of these now surfaces a `session-co
61
61
 
62
62
  Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruction files rather than the single alias-resolved one. The alias rule (CLAUDE.md wins ties, AGENTS.md is the Codex alias) means `resolveInstructionFile()` picks exactly one — so a repo carrying both files can silently let `AGENTS.md` drift out of sync with `CLAUDE.md`. A sibling project ran for weeks with a correct `vault-integration.vault-dir` in `CLAUDE.md` and a dead path in `AGENTS.md`. Check 7 reads `vault-integration.vault-dir` from each file (reusing the `_parseVaultIntegration` parser from `scripts/lib/config/vault-integration.mjs` — no hand-rolled YAML) and flags a `vault-dir-parity` error when the two values diverge (the error is attributed to `AGENTS.md`, the secondary alias, and names both values). The check skips gracefully when only one instruction file is present (nothing to compare), when neither file declares a `vault-integration:` block, or when explicitly disabled via `--skip-vault-dir-parity`. Two files that both omit `vault-dir` (both unset) agree and pass.
63
63
 
64
+ **Alias-by-construction short-circuit.** Since this repo's root `AGENTS.md` became a GENERATED byte-identical copy of `CLAUDE.md` (`scripts/generate-agents-skills.mjs`), Check 7 no longer skips here — it RUNS on every invocation. Two files that cannot disagree have nothing to diff, so the check reports parity as satisfied without deriving one: `instructionFilesAreAliased()` recognises a symlink resolving to the same path, the same inode/device (hardlink or already-resolved symlink), and byte-identical content. Branch order is load-bearing: the "neither file has a `vault-integration:` block" skip is evaluated FIRST, because an absent subject is a different (and more informative) outcome than "the files cannot disagree". The error class the check exists for is untouched — two INDEPENDENT files that declare different `vault-dir` values still produce the `vault-dir-parity` error attributed to `AGENTS.md`.
65
+
66
+ The same predicate guards Check 9's probe 2a, which iterates `['CLAUDE.md', 'AGENTS.md']`: with an aliased `AGENTS.md` it scans `CLAUDE.md` only, because otherwise every dangling `.claude/rules/*.md` citation would be reported TWICE — one defect, two errors, and in `--mode strict` a doubled blocking surface against a file the operator is not allowed to edit.
67
+
64
68
  Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Four probes: **(1) paths-presence** — a top-level `paths:` frontmatter key is not a key `rule-loader.mjs` recognises (it only reads `globs:`), so a rule with `paths:` silently loads ALWAYS-ON regardless of intended file scope; flagged as an error. **(2) cited-but-missing** — `(a)` `.claude/rules/<name>.md` citations inside `CLAUDE.md`/`AGENTS.md` that don't resolve to a file on disk, and `(b)` bare `<name>.md` tokens in a rule's own `## See Also` footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. `../../skills/_shared/state-ownership.md`, are cross-directory references and explicitly out of scope); both flagged as errors. **(3) zero-match-globs** — a `globs:` pattern matching zero files in `git ls-files` (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules (this repo alone carries ~37 by design — Swift/Next.js/Supabase rules with no matching files in a pure-Node-ESM codebase). **(4) foreign-glob** — a glob pattern containing a PascalCase product-like token (regex `[A-Z][a-z]+[A-Z]`, e.g. `WalkAITalkieTests`) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. Glob matching reuses the same picomatch-with-inline-fallback resolution `scripts/lib/rule-loader.mjs` uses (`parseGlobsFrontmatter` is imported directly; the picomatch resolution itself is duplicated locally since `rule-loader.mjs` does not export a public matcher function). The check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `.claude/rules/` is absent, or explicitly via `--skip-rule-scoping`.
65
69
 
66
70
  Check 10 (`docs-parity`, issue #780) validates the public docs surface against live repository state via three sub-checks, all reported under the single `docs-parity` check id: **(a) count-claims** — `docs/components.md`'s own heading counts ("## Skills (N user-facing)", "## Commands (N)", "## Agents (N typed sub-agents)", "## Hook event types (N)") are compared against the SAME actual on-disk derivation the surface-count family (Check 5) uses (`countSkills`/the commands-dir listing/`countAgents`/`readHookCounts`) — NOT Check 5's `claimRe` regexes, which are tuned for CLAUDE.md/README prose phrasing and verifiably do not match `components.md`'s own heading convention; a dedicated set of regexes tailored to the doc's actual authored structure is used instead. **(b) config-block-parity** — top-level Session Config keys documented in `docs/session-config-template.md` (opt-in baseline, via the Check 6 'last'-occurrence extractor) are compared against `docs/session-config-reference.md`; a key is considered documented in the reference when it appears in a `yaml` fence (any indent), a `##`/`###`/`####` heading, or the first cell of a markdown table row (`` | `key-name` | ... ``) — the reference's dominant documentation shape is one table row per field, so a naive fence-only extraction under-counts what it actually documents. Missing keys surface as `docs-parity` errors attributed to `docs/session-config-reference.md`. **(c) metrics-path-liveness** — any `.claude/metrics/` path reference (stale; canonical is `.orchestrator/metrics/`) found in root `docs/*.md` or `docs/examples/*.md` surfaces as an error at its exact file/line. The whole check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `docs/components.md` is absent, or explicitly via `--skip-docs-parity`.
@@ -28,7 +28,7 @@
28
28
  * rule-loader.mjs uses); otherwise pure Node stdlib.
29
29
  */
30
30
 
31
- import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
31
+ import { readFileSync, readdirSync, existsSync, statSync, lstatSync, realpathSync } from 'node:fs';
32
32
  import { join, relative, resolve } from 'node:path';
33
33
  import { execFileSync } from 'node:child_process';
34
34
  import { createRequire } from 'node:module';
@@ -294,6 +294,45 @@ function readVaultIntegration(filePath) {
294
294
  return { present, vaultDir: parsed['vault-dir'] };
295
295
  }
296
296
 
297
+ /**
298
+ * Are the two instruction files the SAME file by construction?
299
+ *
300
+ * Since the cross-harness portable surface landed, this repo's root `AGENTS.md`
301
+ * is GENERATED from `CLAUDE.md` (`scripts/generate-agents-skills.mjs`) as a
302
+ * byte-identical copy, and a consumer repo may instead symlink it. Three shapes
303
+ * therefore count as aliased: a symlink resolving to the same path, the same
304
+ * inode/device (symlink already resolved, or a hardlink), and byte-identical
305
+ * content.
306
+ *
307
+ * This matters because Check 7 exists to catch TWO INDEPENDENT files that
308
+ * disagree. Files that cannot disagree have nothing to diff — running the
309
+ * comparison on them would turn a check that skipped for years into a source of
310
+ * spurious findings the moment AGENTS.md appears. Check 9's probe 2a uses the
311
+ * same predicate to avoid reporting every CLAUDE.md defect twice.
312
+ *
313
+ * @param {string} pathA
314
+ * @param {string} pathB
315
+ * @returns {boolean}
316
+ */
317
+ function instructionFilesAreAliased(pathA, pathB) {
318
+ if (!existsSync(pathA) || !existsSync(pathB)) return false;
319
+ try {
320
+ if (lstatSync(pathA).isSymbolicLink() || lstatSync(pathB).isSymbolicLink()) {
321
+ if (realpathSync(pathA) === realpathSync(pathB)) return true;
322
+ }
323
+ const a = statSync(pathA);
324
+ const b = statSync(pathB);
325
+ if (a.ino !== 0 && a.ino === b.ino && a.dev === b.dev) return true;
326
+ } catch {
327
+ // stat/realpath failure (race, permissions) — fall back to bytes
328
+ }
329
+ try {
330
+ return readFileSync(pathA).equals(readFileSync(pathB));
331
+ } catch {
332
+ return false;
333
+ }
334
+ }
335
+
297
336
  function classifySection(heading) {
298
337
  if (!heading) return null;
299
338
  if (BACKWARD_HEADING_RE.test(heading)) return 'backward';
@@ -881,7 +920,19 @@ function main() {
881
920
  const claudeVi = readVaultIntegration(claudePath);
882
921
  const agentsVi = readVaultIntegration(agentsPath);
883
922
  if (!claudeVi.present && !agentsVi.present) {
923
+ // Nothing to compare — the SUBJECT of the check is absent from both
924
+ // files. This branch precedes the alias short-circuit on purpose: an
925
+ // absent vault-integration block is a different (and more informative)
926
+ // outcome than "the files cannot disagree".
884
927
  checksSkipped.push('vault-dir-parity: neither file has a vault-integration: block');
928
+ } else if (instructionFilesAreAliased(claudePath, agentsPath)) {
929
+ // Identical by construction (generated copy, symlink, or hardlink) —
930
+ // parity is SATISFIED, so the check RAN and found nothing. Reporting
931
+ // this as a skip would be wrong too: the invariant Check 7 guards is
932
+ // actively held here, it is simply held mechanically rather than by
933
+ // agreement, and re-deriving a diff between one file and itself can
934
+ // only manufacture noise.
935
+ vaultDirParityRan = true;
885
936
  } else {
886
937
  vaultDirParityRan = true;
887
938
  const claudeDir = claudeVi.vaultDir;
@@ -1182,7 +1233,16 @@ function main() {
1182
1233
  }
1183
1234
 
1184
1235
  // --- Probe 2a: cited-but-missing (CLAUDE.md / AGENTS.md citations) → errors[] ---
1185
- for (const instrName of ['CLAUDE.md', 'AGENTS.md']) {
1236
+ // AGENTS.md is scanned as a SECOND file only when it is genuinely
1237
+ // independent. When it is an alias of CLAUDE.md (this repo generates it
1238
+ // byte-identically), scanning both would report every dangling citation
1239
+ // TWICE — one defect, two errors, and in `--mode strict` a doubled exit
1240
+ // surface for a file the operator cannot edit.
1241
+ const aliasedInstruction = instructionFilesAreAliased(
1242
+ join(vaultDir, 'CLAUDE.md'), join(vaultDir, 'AGENTS.md'),
1243
+ );
1244
+ const instrNames = aliasedInstruction ? ['CLAUDE.md'] : ['CLAUDE.md', 'AGENTS.md'];
1245
+ for (const instrName of instrNames) {
1186
1246
  const filePath = join(vaultDir, instrName);
1187
1247
  if (!existsSync(filePath) || !statSync(filePath).isFile()) continue;
1188
1248
  const fcontent = readFileSync(filePath, 'utf8');
@@ -32,6 +32,34 @@ PEAK_DIFF_SIZE=$(jq -s 'map(.diff_size) | max' <<< "$WAVE_HISTORY_JSON")
32
32
  SD_VALUE=$(echo "scale=3; $WAVE_DIFF_SIZE / $PEAK_DIFF_SIZE" | bc)
33
33
  ```
34
34
 
35
+ ### Live monitor input (`scripts/lib/convergence-monitor.mjs`)
36
+
37
+ The `shrinking_diff` NDJSON signal compares `files_changed` across two
38
+ consecutive `orchestrator.wave.completed` records. Until 2026-09-05 **no
39
+ producer wrote that key** — 144 such records in
40
+ `.orchestrator/metrics/events.jsonl`, none carrying a measurement key, so the
41
+ signal was structurally dead. The emitter is now
42
+ `hooks/post-tool-batch-wave-signal.mjs` (#980): at an N→N+1 wave transition its
43
+ `orchestrator.wave.completed` for wave N carries
44
+
45
+ - `files_changed` — the DEDUPED count of `git diff --name-only <wave_start_sha>`
46
+ ∪ `git ls-files --others --exclude-standard`, and
47
+ - `files_changed_source: 'worktree-vs-wave-start-sha'` — how it was measured.
48
+
49
+ `wave_start_sha` is the `git rev-parse HEAD` the same hook persists into
50
+ `.orchestrator/current-session.json` when wave N is opened. The comparison is
51
+ against the WORKING TREE, not `<sha>..HEAD`, because the coordinator commits at
52
+ session close rather than per wave.
53
+
54
+ **Both keys are optional, and absent is not zero.** Any git failure, a 1.5 s
55
+ timeout, or a missing `wave_start_sha` omits them; the monitor reads the absence
56
+ as `null` and `shrinking_diff` simply does not fire. Two records carry no
57
+ measurement by construction: the final wave's completion from
58
+ `hooks/on-session-end.mjs` (no wave-open transition ran there, so it has no
59
+ start sha) and any completion emitted from an explicit injected `wave_signal`.
60
+ The git/jq recipe above is the SKILL-level computation and is independent of the
61
+ monitor.
62
+
35
63
  ### Thresholds
36
64
 
37
65
  | SD value | Interpretation |
@@ -99,6 +127,22 @@ PR_PRIOR=$(echo "$PASS_RATES" | jq '.[length - 2] // 0')
99
127
  If `quality.incremental` events are absent (test runner not configured), set
100
128
  `PR_confidence = 0.0` and trend = `plateau` (neutral; do not block on missing data).
101
129
 
130
+ ### Live monitor input (`scripts/lib/convergence-monitor.mjs`)
131
+
132
+ The `pass_rate_plateau` NDJSON signal reads the **quality-gate envelope**:
133
+ `orchestrator.quality_gate.passed` / `.failed` records carrying BOTH
134
+ `wave_number` and a well-formed `counts` object. It folds `counts.passed` into
135
+ the wave's pass count (and `counts.failed` alongside it), then fires when two
136
+ consecutive waves report the identical pass count. Measured 2026-09-05: 33 such
137
+ records in `.orchestrator/metrics/events.jsonl`; the flat `test.passed` key the
138
+ reader also accepts has **0** producers and is kept only as an alias for a future
139
+ direct emitter.
140
+
141
+ The `wave_number` + `counts` pair is a type-AND-shape gate, not a prefix
142
+ widening: a session-level gate run (no `wave_number`) must stay invisible to the
143
+ monitor, because admitting it would instantiate a `WaveSummary` per gate run and
144
+ burn the once-per-wave emit keys before the real wave record arrives (#966).
145
+
102
146
  ### Thresholds
103
147
 
104
148
  | PR value | Interpretation |
@@ -160,6 +204,17 @@ VEL_LINES=$(git diff --shortstat "$WAVE_START_REF" "$WAVE_END_REF" \
160
204
  VEL_LINES_PRIOR=<read from events.jsonl for wave N-1>
161
205
  ```
162
206
 
207
+ ### Live monitor input (`scripts/lib/convergence-monitor.mjs`)
208
+
209
+ The `velocity_drop` NDJSON signal counts **one `orchestrator.agent.stopped`
210
+ record per dispatched agent** (wave read from `wave` or `wave_number`) and fires
211
+ when the later wave stopped fewer agents than the earlier one. Measured
212
+ 2026-09-05 over `.orchestrator/metrics/events.jsonl`: 11,754
213
+ `orchestrator.agent.stopped` records versus **0** for the `agent.dispatched` type
214
+ the reader also accepts — `agent.stopped` is the only per-agent record this repo
215
+ actually emits with a wave number. The git-based `VEL_COMMITS` / `VEL_LINES`
216
+ recipe above is the SKILL-level computation and is independent of the monitor.
217
+
163
218
  ### Thresholds
164
219
 
165
220
  | Condition | Interpretation |