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
@@ -73,6 +73,7 @@ import path from 'node:path';
73
73
  import { readFileSync, existsSync, statSync } from 'node:fs';
74
74
  import { execFileSync } from 'node:child_process';
75
75
  import { warn } from './lib/common.mjs';
76
+ import { MANIFEST_SESSION_KEYS } from './lib/session-identity/own-session.mjs';
76
77
  import {
77
78
  assertFileScopeSubset,
78
79
  assertTestSiblingCoverage,
@@ -289,10 +290,13 @@ function validateOptionalSessionId(obj, key, errors) {
289
290
  }
290
291
 
291
292
  /**
292
- * Validate the OPTIONAL session binding (#1123): `session` (the raw
293
- * `session_id` of the session that WROTE this manifest) and its human-readable
294
- * twin `semantic_session`. Both come from one `sessionAttribution(repoRoot)`
295
- * call — see `skills/wave-executor/wave-loop.md` § Scope Manifest.
293
+ * Validate the OPTIONAL session binding (#1123): `session_id` (the raw session
294
+ * id of the session that WROTE this manifest) and its human-readable twin
295
+ * `semantic_session_id`. Both come from one `sessionAttribution(repoRoot)`
296
+ * call — see `skills/wave-executor/wave-loop.md` § Scope Manifest. The
297
+ * pre-#1153 spellings `session` / `semantic_session` are still ACCEPTED here
298
+ * (read side only, until the next minor release) — key names come from
299
+ * `MANIFEST_SESSION_KEYS` so the writer and every reader share one list.
296
300
  *
297
301
  * Deliberately NOT part of {@link validateRequired}, and that is a compatibility
298
302
  * constraint rather than a preference: `wave-scope.json` is a shared
@@ -306,9 +310,25 @@ function validateOptionalSessionId(obj, key, errors) {
306
310
  * @param {string[]} errors
307
311
  * @param {string[]} warnings
308
312
  */
309
- function validateSession(obj, errors, warnings) {
310
- const present = validateOptionalSessionId(obj, 'session', errors);
311
- validateOptionalSessionId(obj, 'semantic_session', errors);
313
+ function validateSessionBinding(obj, errors, warnings) {
314
+ let present = false;
315
+ MANIFEST_SESSION_KEYS.current.forEach((key, i) => {
316
+ const legacyKey = MANIFEST_SESSION_KEYS.legacy[i];
317
+ const hasCurrent = validateOptionalSessionId(obj, key, errors);
318
+ const hasLegacy = validateOptionalSessionId(obj, legacyKey, errors);
319
+ // Both spellings of the SAME slot, disagreeing, is the one case the reader
320
+ // cannot resolve honestly: it silently prefers `key` and drops the other
321
+ // id, so a manifest that names two different sessions would classify as
322
+ // `own` for one of them. Name it here rather than let the preference decide.
323
+ if (hasCurrent && hasLegacy && obj[key] !== obj[legacyKey]) {
324
+ errors.push(
325
+ `${key} and legacy ${legacyKey} are both present with DIFFERENT values — ` +
326
+ 'a manifest binds to exactly one session; drop the legacy key (accepted on ' +
327
+ 'the read side only, until the next minor release, #1153)',
328
+ );
329
+ }
330
+ if (i === 0) present = hasCurrent || hasLegacy;
331
+ });
312
332
  if (!present) {
313
333
  warnings.push(
314
334
  'no session field — manifest is not session-bound (legacy, #1123), so every session sharing this ' +
@@ -749,7 +769,7 @@ function validate(
749
769
  const warnings = [];
750
770
 
751
771
  validateRequired(obj, errors);
752
- validateSession(obj, errors, warnings);
772
+ validateSessionBinding(obj, errors, warnings);
753
773
  validateAllowedPaths(obj, errors, warnings);
754
774
  validateBlockedCommands(obj, errors);
755
775
  validateGates(obj, errors);
@@ -84,10 +84,9 @@
84
84
 
85
85
  import { promises as fs } from 'node:fs';
86
86
  import path from 'node:path';
87
- import os from 'node:os';
88
87
  import { pathToFileURL } from 'node:url';
89
88
 
90
- import { die, utcTimestamp } from './lib/common.mjs';
89
+ import { die, utcTimestamp, expandTilde } from './lib/common.mjs';
91
90
  import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
92
91
  import {
93
92
  SCRIPT_NAME,
@@ -150,13 +149,6 @@ Exit codes:
150
149
  process.stderr.write(usage);
151
150
  }
152
151
 
153
- function expandHome(p) {
154
- if (typeof p !== 'string' || p.length === 0) return p;
155
- if (p === '~') return os.homedir();
156
- if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
157
- return p;
158
- }
159
-
160
152
  async function isDir(p) {
161
153
  try {
162
154
  const st = await fs.stat(p);
@@ -222,8 +214,8 @@ async function main() {
222
214
  const isDryRun = !isApply;
223
215
  const isJson = parsed.values.json === true;
224
216
 
225
- const sourceRoot = path.resolve(expandHome(parsed.values.source ?? DEFAULT_SOURCE));
226
- const canonicalRoot = path.resolve(expandHome(parsed.values.canonical ?? DEFAULT_CANONICAL));
217
+ const sourceRoot = path.resolve(expandTilde(parsed.values.source ?? DEFAULT_SOURCE));
218
+ const canonicalRoot = path.resolve(expandTilde(parsed.values.canonical ?? DEFAULT_CANONICAL));
227
219
 
228
220
  // Parse --resolve flags into a Map<relPath, "src"|"dst"|"skip">
229
221
  const resolutions = new Map();
@@ -31,11 +31,10 @@
31
31
  */
32
32
 
33
33
  import { spawnSync } from 'node:child_process';
34
- import { join } from 'node:path';
35
- import { homedir } from 'node:os';
36
34
  import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-repo.mjs';
37
35
  import { validatePathInsideProject } from './lib/path-utils.mjs';
38
36
  import { resolveRepoSpec, redactUrlCredentials } from './lib/vcs-repo-spec.mjs';
37
+ import { expandTilde } from './lib/common.mjs';
39
38
 
40
39
  // ── Argument parsing ──────────────────────────────────────────────────────────
41
40
 
@@ -332,9 +331,8 @@ async function main() {
332
331
  // Load the config-driven repo list before doing any glab work
333
332
  const watcherRoot = getConfinementRoot();
334
333
  // Home-expand each entry before confinement, matching sibling scripts (W4-Q2 LOW).
335
- const expandHome = (p) => (p.startsWith('~/') ? join(homedir(), p.slice(2)) : p);
336
334
  const flipRepos = (await getCrossRepoProjects()).filter((r) => {
337
- const guard = validatePathInsideProject(expandHome(r), watcherRoot);
335
+ const guard = validatePathInsideProject(expandTilde(r), watcherRoot);
338
336
  if (!guard.ok) {
339
337
  process.stderr.write(
340
338
  `vault-integration-watcher: WARN rejecting confined-path violation for ${JSON.stringify(r)} (reason: ${guard.reason})\n`
@@ -63,6 +63,7 @@ import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
63
63
  import { resolveRepoNamespace } from './lib/vault-mirror/namespace.mjs';
64
64
  import { resolveCanonicalSuffixes } from './lib/named-vault-resolver.mjs';
65
65
  import { loadOwnerConfig } from './lib/owner-yaml.mjs';
66
+ import { canonicalizeSessions } from './lib/sessions-canonical.mjs';
66
67
 
67
68
  // ── Canonical-vault helpers (#600 D2 / #607 D2) ────────────────────────────────
68
69
  // These are module-level (above the CLI bootstrap) so the module is import-safe
@@ -489,24 +490,24 @@ async function main() {
489
490
  qualityMinConfidence,
490
491
  };
491
492
 
492
- for (const line of lines) {
493
- lineNum++;
494
- const trimmed = line.trim();
495
- if (!trimmed) continue;
496
- runState.total++;
497
-
498
- let entry;
499
- try {
500
- entry = JSON.parse(trimmed);
501
- } catch (err) {
502
- process.stderr.write(`vault-mirror: malformed JSON on line ${lineNum}: ${err.message}\n`);
503
- // Close the run out BEFORE exiting: `process.exit` runs no `finally`, so
504
- // without this the abort is the one outcome that leaves no run record —
505
- // exactly the shape reserved for a broken emitter.
506
- await finishRun('malformed-json');
507
- process.exit(1);
508
- }
509
-
493
+ /**
494
+ * Dispatch one already-parsed entry to its processor and account for the
495
+ * result. Extracted (#1186c) so the `--kind session` path below can call it
496
+ * AFTER a whole-file dedup pass instead of once per raw line; every branch
497
+ * is byte-identical to the pre-#1186c per-line loop body.
498
+ * @param {unknown} entry — parsed JSONL value (usually an object; a bare
499
+ * `null`/primitive line is a real shape this must keep handling, see the
500
+ * #1186c session branch below for why it is never filtered out here).
501
+ * @param {number} entryLineNum — 1-based JSONL line number, or (for the
502
+ * `--kind session` dedup path) the line of the record that WON the
503
+ * collapse. `_lineNum` is read ONLY for telemetry (process.mjs
504
+ * `emitEntryAction` `line:` on the per-entry ledger event), never to
505
+ * derive content process.mjs itself tolerates a non-finite value by
506
+ * suppressing just that one ledger record, but every call site here
507
+ * always supplies a real line number.
508
+ * @returns {Promise<void>}
509
+ */
510
+ async function dispatchEntry(entry, entryLineNum) {
510
511
  try {
511
512
  // Both processors return the `action` string they emitted (every one of
512
513
  // their exit paths is an `emitAction` call), so the tally needs no second
@@ -514,8 +515,8 @@ async function main() {
514
515
  // stale the first time a branch is added.
515
516
  const action =
516
517
  kind === 'learning'
517
- ? await processLearning(entry, lineNum, ctx)
518
- : await processSession(entry, lineNum, ctx);
518
+ ? await processLearning(entry, entryLineNum, ctx)
519
+ : await processSession(entry, entryLineNum, ctx);
519
520
  tally(action);
520
521
  } catch (err) {
521
522
  // Validation errors (missing required fields) → per-entry skip, not a global failure
@@ -530,13 +531,13 @@ async function main() {
530
531
  await emitMirrorEvent({
531
532
  action: 'skipped-invalid',
532
533
  kind,
533
- line: lineNum,
534
+ line: entryLineNum,
534
535
  recordId: entryId,
535
536
  skipClass: 'validation',
536
537
  reason: err.message,
537
538
  dryRun,
538
539
  });
539
- continue;
540
+ return;
540
541
  }
541
542
  // #718: discriminate genuine filesystem/system errors (which must still
542
543
  // abort the whole run — a partially-written vault is worse than a loud
@@ -552,7 +553,7 @@ async function main() {
552
553
  (typeof err.code === 'string' && err.code.length > 0) || Boolean(err.syscall);
553
554
  if (!isSystemError) {
554
555
  process.stderr.write(
555
- `vault-mirror: mapper crash on line ${lineNum} (${err.message}) — record skipped\n`,
556
+ `vault-mirror: mapper crash on line ${entryLineNum} (${err.message}) — record skipped\n`,
556
557
  );
557
558
  const entryId = entry?.id ?? entry?.session_id ?? null;
558
559
  process.stdout.write(
@@ -569,21 +570,105 @@ async function main() {
569
570
  await emitMirrorEvent({
570
571
  action: 'skipped-invalid',
571
572
  kind,
572
- line: lineNum,
573
+ line: entryLineNum,
573
574
  recordId: entryId,
574
575
  skipClass: 'mapper-crash',
575
576
  reason: err.message,
576
577
  dryRun,
577
578
  });
578
- continue;
579
+ return;
579
580
  }
580
581
  // Unexpected filesystem errors → fatal
581
- process.stderr.write(`vault-mirror: filesystem error on line ${lineNum}: ${err.message}\n`);
582
+ process.stderr.write(`vault-mirror: filesystem error on line ${entryLineNum}: ${err.message}\n`);
582
583
  await finishRun('filesystem-error');
583
584
  process.exit(2);
584
585
  }
585
586
  }
586
587
 
588
+ // #1186c: for `--kind session`, every parsed entry is buffered here instead
589
+ // of dispatched inline — the dedup pass below needs the WHOLE file before it
590
+ // can tell which of several same-`session_id` lines is the winner. `--kind
591
+ // learning` is unaffected: it still dispatches per line, inline, immediately
592
+ // below (a malformed line further down the file must not undo an already-
593
+ // dispatched learning — pinned by the existing `total: 2, created: 1` abort
594
+ // test in tests/unit/vault-mirror.test.mjs).
595
+ const sessionEntries = [];
596
+ const sessionLineNums = [];
597
+
598
+ for (const line of lines) {
599
+ lineNum++;
600
+ const trimmed = line.trim();
601
+ if (!trimmed) continue;
602
+ runState.total++;
603
+
604
+ let entry;
605
+ try {
606
+ entry = JSON.parse(trimmed);
607
+ } catch (err) {
608
+ process.stderr.write(`vault-mirror: malformed JSON on line ${lineNum}: ${err.message}\n`);
609
+ // Close the run out BEFORE exiting: `process.exit` runs no `finally`, so
610
+ // without this the abort is the one outcome that leaves no run record —
611
+ // exactly the shape reserved for a broken emitter.
612
+ await finishRun('malformed-json');
613
+ process.exit(1);
614
+ }
615
+
616
+ if (kind === 'session') {
617
+ sessionEntries.push(entry);
618
+ sessionLineNums.push(lineNum);
619
+ continue;
620
+ }
621
+
622
+ await dispatchEntry(entry, lineNum);
623
+ }
624
+
625
+ if (kind === 'session') {
626
+ // Only an OBJECT entry carrying a non-empty `session_id` is eligible for
627
+ // the dedup collapse — the same predicate canonicalizeSessions itself uses
628
+ // internally (sessions-canonical.mjs `isRecordObject` + `isNonEmptyString`,
629
+ // not exported, so re-stated here rather than reached into). Everything
630
+ // else — a bare `null`/primitive JSONL line, or a legacy record with no
631
+ // `session_id` field — is dispatched EXACTLY as before: unaffected, in
632
+ // original file order, through the SAME validation/mapper-crash paths
633
+ // process.mjs already has for those shapes. Two regression-guard tests in
634
+ // tests/unit/vault-mirror.test.mjs depend on this (a bare `null` line and a
635
+ // legacy `session`-keyed record both still reach processSession() and its
636
+ // existing error handling, never silently vanish into the collapse).
637
+ const isIdentifiable = (e) =>
638
+ e !== null &&
639
+ typeof e === 'object' &&
640
+ !Array.isArray(e) &&
641
+ typeof e.session_id === 'string' &&
642
+ e.session_id.length > 0;
643
+ const identifiable = sessionEntries.filter(isIdentifiable);
644
+ // canonicalizeSessions never clones — the survivors are the SAME object
645
+ // references as in `sessionEntries`, so reference identity below is exact,
646
+ // never a guess (scripts/lib/sessions-canonical.mjs header, "RULE ORDER").
647
+ const survivors = new Set(canonicalizeSessions(identifiable));
648
+
649
+ for (let i = 0; i < sessionEntries.length; i++) {
650
+ const entry = sessionEntries[i];
651
+ if (isIdentifiable(entry) && !survivors.has(entry)) {
652
+ // A losing duplicate: an earlier line whose `session_id` a LATER line
653
+ // in this same batch supersedes or overwrites (crash-recovery
654
+ // re-append, #1068 stub/supersede pair). No dispatch, no stdout line,
655
+ // no tally for it — the winning occurrence (dispatched below, at its
656
+ // own position) already produces the ONE note this physical session
657
+ // gets. BV-004 ceiling: `runState.total` still counts this raw line,
658
+ // so `created+updated+skipped+failed` no longer partitions `total`
659
+ // for a `--kind session` run that collapsed at least one duplicate —
660
+ // no test pins that invariant for session kind (only for `learning`,
661
+ // where duplicates are not collapsed), and a partially-written vault
662
+ // from a batch that could not be fully deduped is the worse failure
663
+ // mode. Revisit with a dedicated telemetry action if an operator ever
664
+ // needs to name WHICH lines were collapsed, not just how many notes
665
+ // were written.
666
+ continue;
667
+ }
668
+ await dispatchEntry(entry, sessionLineNums[i]);
669
+ }
670
+ }
671
+
587
672
  // ── Run close-out (#1147) ───────────────────────────────────────────────────
588
673
  //
589
674
  // The happy tail. Both roll-ups live in `finishRun` above, which every abort
@@ -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)
@@ -101,10 +101,39 @@ The two slots are `parallelPeer.mode` and `parallelPeer.pid`; `<ageHours>` is th
101
101
 
102
102
  ### Outcome handling
103
103
 
104
- - **Worktree anlegen + starten** → invoke `enterWorktree({ basePath, sessionId, branch, repoRoot })` from `scripts/lib/autopilot/worktree-pipeline.mjs`. The helper creates a sibling worktree at `<basePath>/<repo-name>-<sessionId>/`, runs idempotency + boundary checks, and logs a WARN line to stderr on fresh creation. When `<branch>` is already checked out by another worktree — the normal case, since Phase 0.5 passes the current HEAD — the worktree lands on a fresh `so/<sessionId>` branch created at `<branch>` and the helper returns `{ branch: 'so/<sessionId>', promotedFrom: '<branch>' }` (#1067); the new worktree's STATE.md `branch` MUST record `so/<sessionId>` and note `promoted from <branch>@<repoRoot>`. Once the worktree exists, and BEFORE exiting the preamble flow, call `leaveSourceRoot({ repoRoot, sessionId: <the raw physical session_id owning this root's lock/registry entry>, semanticSessionId, reason: 'worktree-promotion' })` from `scripts/lib/session-transition.mjs`. **Read that `sessionId` from this root's `.orchestrator/session.lock` via `readLock({ repoRoot })` never the semantic label, and never `current-session.json`, which may describe a peer session (#863); a wrong id aborts the teardown with `ok: false, reason: 'lock-session-mismatch:<owner>'` and removes nothing.** In detail: the promotion is a PROCESS BOUNDARY, not a live migration (#1069) — the old root is deregistered and its `session.lock` released BEFORE the new worktree's own Phase 1.2 acquires, so the two roots never both own a live claim at once. The call never throws; on `{ ok: false }` emit a stderr WARN `parallel-aware: leaveSourceRoot: <reason>` and continue (the destination worktree already exists — aborting here would leave the two-live-roots state the call prevents). Then exit the current preamble flow — the new worktree's own session-start runs from scratch (Phase 1 onwards). On failure (`WorktreeBoundaryError` or `git worktree add` non-zero exit), emit a stderr warning `parallel-aware: enterWorktree failed: <error>; falling back to Manuell` and proceed via the Manuell path.
104
+ - **Worktree anlegen + starten** → invoke `enterWorktree({ basePath, sessionId, branch, repoRoot, rawSessionId, reason: 'worktree-promotion' })` from `scripts/lib/autopilot/worktree-pipeline.mjs`. The helper creates a sibling worktree at `<basePath>/<repo-name>-<sessionId>/`, runs idempotency + boundary checks, and logs a WARN line to stderr on fresh creation. When `<branch>` is already checked out by another worktree — the normal case, since Phase 0.5 passes the current HEAD — the worktree lands on a fresh `so/<sessionId>` branch created at `<branch>` and the helper returns `{ branch: 'so/<sessionId>', promotedFrom: '<branch>' }` (#1067); the new worktree's STATE.md `branch` MUST record `so/<sessionId>` and note `promoted from <branch>@<repoRoot>`. Since #1170, `enterWorktree` releases the source root ITSELF once the destination worktree provably exists — it calls `leaveSourceRoot({ repoRoot, sessionId: rawSessionId, semanticSessionId: sessionId, reason })` from `scripts/lib/session-transition.mjs` internally, on BOTH success exits, so this AUQ handler makes no separate `leaveSourceRoot` call. `rawSessionId` — **read from this root's `.orchestrator/session.lock` via `readLock({ repoRoot })`, never the semantic label, and never `current-session.json`, which may describe a peer session (#863)** — is the RAW physical `session_id` owning this root's lock/registry entry; a wrong id aborts the teardown with `left.ok: false, reason: 'lock-session-mismatch:<owner>'` and removes nothing. In detail: the promotion is a PROCESS BOUNDARY, not a live migration (#1069) — the old root is deregistered and its `session.lock` released BEFORE the new worktree's own Phase 1.2 acquires, so the two roots never both own a live claim at once. `enterWorktree`'s return value carries the outcome as `left: { ok, steps, reason? }`; `leaveSourceRoot()` never throws, so on `left.ok !== true` `enterWorktree` itself emits the stderr WARN `enterWorktree: leaveSourceRoot: <reason>` and the promotion continues regardless (the destination worktree already exists — aborting here would leave the two-live-roots state the call prevents). Then exit the current preamble flow — the new worktree's own session-start runs from scratch (Phase 1 onwards). On failure (`WorktreeBoundaryError` or `git worktree add` non-zero exit), emit a stderr warning `parallel-aware: enterWorktree failed: <error>; falling back to Manuell` and proceed via the Manuell path.
105
105
  - **Manuell** → append a Deviation via `appendDeviationOnDisk()`:
106
106
  `Worktree-Auto-Promotion declined; running in-place alongside session_id=<peer.sessionId>, mode=<peer.mode>, pid=<peer.pid>. PSA-001/PSA-002/PSA-004 discipline applies.`
107
- Continue Phase-0.
107
+ Continue Phase-0 — and run the **Peer-Scope-Union** protocol below before the first write.
108
+
109
+ ### Peer-Scope-Union (Manuell only, #1195)
110
+
111
+ In-place beside a peer is survivable when the two scopes are DECLARED to each other rather than discovered by collision. Measured 2026-09-02 in a consumer repo: a 4-subagent session ran beside a deep session (wave 4, `enforcement: strict`) in ONE checkout, no worktree, zero collisions — the peer's paths were carried in the deep session's `allowedPaths` union (19 → 41, `--assert-subset` green) across a wave rollover.
112
+
113
+ Four steps, in order. Steps 1 and 2 are the protocol; 3 and 4 are what keeps it honest.
114
+
115
+ 1. **Declare the COMPLETE path list.** The arriving session sends the peer every path it will write — including the ones a script produces (fixtures, snapshots, result files, temp helpers), not only the ones it plans to edit by hand. A path omitted here is a path the peer's guard reports as a violation. In the same message it adopts the peer's resource rules.
116
+ 2. **The peer unions.** The receiving coordinator adds those paths to its `allowedPaths` as ONE record `peer-session-<id>` in the wave's scope manifest, re-asserts subset/disjointness, and re-materializes them on every wave rollover (`skills/wave-executor/wave-loop.md` § Scope Manifest).
117
+ 3. **Probe with ONE real write.** Before dispatching any agent, make one small PLANNED Edit from the declared list. A denial here costs one edit; the same denial found after a fan-out costs the wave.
118
+ 4. **Announce before committing.** The arriving session sends its final file list, the peer sequences its own push behind it, and the SHAs come back. The git index is shared (PSA-007) — sequencing it is the only thing that makes two sessions in one checkout committable.
119
+
120
+ Message template for step 1 (`SendMessage`, first line self-contained per `.claude/rules/cross-session-messaging.md`):
121
+
122
+ ```
123
+ Scope-union request: I will write exactly these paths in <repo> — please add them to your allowedPaths.
124
+
125
+ Paths (complete, incl. files my scripts write):
126
+ - <path>
127
+ - <path>
128
+
129
+ Resource rules I adopt from you: no build, no dev-server ports, no service stop/restart,
130
+ commit only via `git commit --only <my files>` after announcing, no push, no tag.
131
+
132
+ I will probe with ONE planned edit before dispatching, announce my final file list before
133
+ committing, and send you the SHAs afterwards.
134
+ ```
135
+
136
+ Delivery is never guaranteed (CSM-004): an unanswered request establishes nothing. Without a confirmed union, do not write beside the peer — take the worktree instead.
108
137
  - **Abbrechen** → exit Phase-0 immediately. No file writes.
109
138
 
110
139
  ## Always-OK Pass-Through (no AUQ)