session-orchestrator 3.24.0 → 4.0.1

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 (435) 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 +3 -2
  47. package/.codex-plugin/skills/architecture/SKILL.md +20 -0
  48. package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
  49. package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
  50. package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
  51. package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
  52. package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
  53. package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
  54. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
  55. package/.codex-plugin/skills/close/SKILL.md +21 -0
  56. package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
  57. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
  58. package/.codex-plugin/skills/debug/SKILL.md +21 -0
  59. package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
  60. package/.codex-plugin/skills/discovery/SKILL.md +21 -0
  61. package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
  62. package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
  63. package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
  64. package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
  65. package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
  66. package/.codex-plugin/skills/eli5/SKILL.md +21 -0
  67. package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
  68. package/.codex-plugin/skills/eval/SKILL.md +21 -0
  69. package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
  70. package/.codex-plugin/skills/evolve/SKILL.md +21 -0
  71. package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
  72. package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
  73. package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
  74. package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
  75. package/.codex-plugin/skills/go/SKILL.md +22 -0
  76. package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
  77. package/.codex-plugin/skills/grill/SKILL.md +21 -0
  78. package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
  79. package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
  80. package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
  81. package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
  82. package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
  83. package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
  84. package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
  85. package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
  86. package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
  87. package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
  88. package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
  89. package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
  90. package/.codex-plugin/skills/plan/SKILL.md +22 -0
  91. package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
  92. package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
  93. package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
  94. package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
  95. package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
  96. package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
  97. package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
  98. package/.codex-plugin/skills/release/SKILL.md +22 -0
  99. package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
  100. package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
  101. package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
  102. package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
  103. package/.codex-plugin/skills/session/SKILL.md +21 -0
  104. package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
  105. package/.codex-plugin/skills/session-end/SKILL.md +22 -0
  106. package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
  107. package/.codex-plugin/skills/session-start/SKILL.md +22 -0
  108. package/.codex-plugin/skills/spinout/SKILL.md +21 -0
  109. package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
  110. package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
  111. package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
  112. package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
  113. package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
  114. package/.codex-plugin/skills/test/SKILL.md +21 -0
  115. package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
  116. package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
  117. package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
  118. package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
  119. package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
  120. package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
  121. package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
  122. package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
  123. package/.cursor/commands/autopilot.md +2 -2
  124. package/.cursor/commands/bootstrap.md +1 -1
  125. package/.cursor/commands/brainstorm.md +1 -1
  126. package/.cursor/commands/debug.md +1 -1
  127. package/.cursor/commands/discovery.md +1 -1
  128. package/.cursor/commands/dispatcher.md +2 -2
  129. package/.cursor/commands/eli5.md +2 -2
  130. package/.cursor/commands/eval.md +2 -2
  131. package/.cursor/commands/evolve.md +1 -1
  132. package/.cursor/commands/go.md +1 -1
  133. package/.cursor/commands/grill.md +2 -2
  134. package/.cursor/commands/memory-cleanup.md +2 -2
  135. package/.cursor/commands/persona-panel.md +1 -1
  136. package/.cursor/commands/plan.md +1 -1
  137. package/.cursor/commands/portfolio.md +1 -1
  138. package/.cursor/commands/reconcile.md +2 -2
  139. package/.cursor/commands/release.md +2 -2
  140. package/.cursor/commands/session.md +2 -2
  141. package/.cursor/commands/spinout.md +2 -2
  142. package/.cursor/commands/sunset-review.md +2 -2
  143. package/.cursor/commands/templates-ack.md +2 -2
  144. package/.cursor/commands/test.md +2 -2
  145. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  146. package/.cursor/skills/eval/SKILL.md +1 -1
  147. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  148. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  149. package/.cursor-plugin/plugin.json +30 -0
  150. package/.orchestrator/policy/blocked-commands.json +121 -0
  151. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  152. package/.orchestrator/policy/quality-gates.example.json +16 -0
  153. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  154. package/.orchestrator/policy/templates-policy.json +27 -0
  155. package/.orchestrator/policy/test-profiles.json +47 -0
  156. package/AGENTS.md +225 -0
  157. package/CHANGELOG.md +1314 -2
  158. package/NOTICE +11 -6
  159. package/README.md +135 -94
  160. package/agents/eval-judge.md +1 -1
  161. package/agents/skill-applied-judge.md +1 -1
  162. package/assets/wave-lifecycle.svg +98 -0
  163. package/commands/release.md +6 -3
  164. package/commands/session.md +18 -3
  165. package/docs/README.md +4 -0
  166. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  167. package/docs/baseline.md +67 -0
  168. package/docs/ci-setup.md +108 -62
  169. package/docs/codex-setup.md +107 -29
  170. package/docs/components.md +38 -16
  171. package/docs/cursor-setup.md +6 -2
  172. package/docs/events-schema.md +9 -6
  173. package/docs/instruction-delivery.md +69 -0
  174. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  175. package/docs/migration-v4.md +365 -0
  176. package/docs/pi-setup.md +6 -1
  177. package/docs/plugin-architecture-v3.md +1 -1
  178. package/docs/rule-authoring.md +85 -19
  179. package/docs/scope-collision-guard.md +5 -5
  180. package/docs/session-config-reference.md +57 -56
  181. package/docs/session-config-template.md +6 -29
  182. package/docs/telemetry.md +157 -3
  183. package/docs/vault-docs-architecture.md +50 -11
  184. package/hooks/_lib/hook-import-set.json +1488 -0
  185. package/hooks/_lib/subagent-transcript.mjs +562 -0
  186. package/hooks/config-protection.mjs +2 -2
  187. package/hooks/cwd-change-restore.mjs +2 -2
  188. package/hooks/enforce-commands.mjs +69 -0
  189. package/hooks/hooks-codex.json +1 -1
  190. package/hooks/hooks-cursor.json +10 -0
  191. package/hooks/hooks-pi.json +5 -0
  192. package/hooks/hooks.json +6 -1
  193. package/hooks/loop-guard.mjs +3 -3
  194. package/hooks/on-session-end.mjs +2 -2
  195. package/hooks/on-session-start.mjs +103 -2
  196. package/hooks/on-stop.mjs +60 -14
  197. package/hooks/operator-steer.mjs +2 -2
  198. package/hooks/post-bash-write-verify.mjs +85 -0
  199. package/hooks/post-edit-import-probe.mjs +344 -0
  200. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  201. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  202. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  203. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  204. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  205. package/hooks/skill-invocation-telemetry.mjs +17 -5
  206. package/hooks/subagent-telemetry.mjs +13 -4
  207. package/monitors/monitors.json +3 -3
  208. package/package.json +9 -1
  209. package/pi/prompts/session.md +2 -2
  210. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  211. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  212. package/scripts/dialectic-deriver.mjs +73 -8
  213. package/scripts/export-hw-learnings.mjs +113 -1
  214. package/scripts/generate-agents-skills.mjs +378 -0
  215. package/scripts/generate-codex-skills.mjs +246 -0
  216. package/scripts/generate-cursor-adapter.mjs +45 -8
  217. package/scripts/generate-hook-import-set.mjs +292 -0
  218. package/scripts/lib/agent-status.mjs +13 -2
  219. package/scripts/lib/auto-dream.mjs +38 -36
  220. package/scripts/lib/autonomy/suitability.mjs +6 -0
  221. package/scripts/lib/autopilot/loop.mjs +2 -2
  222. package/scripts/lib/ci-status-banner.mjs +220 -75
  223. package/scripts/lib/codex/plugin-contract.mjs +88 -6
  224. package/scripts/lib/config/auto-dream.mjs +2 -1
  225. package/scripts/lib/config/block-header.mjs +8 -0
  226. package/scripts/lib/config/block-preprocess.mjs +177 -0
  227. package/scripts/lib/config/broken-window.mjs +2 -1
  228. package/scripts/lib/config/cold-start.mjs +2 -1
  229. package/scripts/lib/config/config-protection.mjs +22 -2
  230. package/scripts/lib/config/context-coverage.mjs +2 -1
  231. package/scripts/lib/config/cross-repo.mjs +2 -1
  232. package/scripts/lib/config/custom-phases.mjs +2 -1
  233. package/scripts/lib/config/dialectic.mjs +2 -1
  234. package/scripts/lib/config/discovery-validator.mjs +2 -1
  235. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  236. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  237. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  238. package/scripts/lib/config/docs-staleness.mjs +2 -1
  239. package/scripts/lib/config/drift-check.mjs +2 -1
  240. package/scripts/lib/config/eval.mjs +2 -1
  241. package/scripts/lib/config/events-rotation.mjs +2 -1
  242. package/scripts/lib/config/evolve.mjs +8 -2
  243. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  244. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  245. package/scripts/lib/config/handover-gate.mjs +2 -1
  246. package/scripts/lib/config/health-endpoints.mjs +7 -2
  247. package/scripts/lib/config/host-paths.mjs +20 -4
  248. package/scripts/lib/config/issue-budget.mjs +2 -1
  249. package/scripts/lib/config/loop-guard.mjs +2 -1
  250. package/scripts/lib/config/memory.mjs +2 -1
  251. package/scripts/lib/config/moc-staleness.mjs +2 -1
  252. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  253. package/scripts/lib/config/private-config-dir.mjs +67 -0
  254. package/scripts/lib/config/reconcile.mjs +2 -1
  255. package/scripts/lib/config/remote-hosts.mjs +2 -1
  256. package/scripts/lib/config/section-extractor.mjs +7 -1
  257. package/scripts/lib/config/skill-evolution.mjs +2 -1
  258. package/scripts/lib/config/slopcheck.mjs +2 -1
  259. package/scripts/lib/config/state-md-lock.mjs +2 -1
  260. package/scripts/lib/config/templates-first.mjs +2 -1
  261. package/scripts/lib/config/test.mjs +2 -1
  262. package/scripts/lib/config/vault-integration.mjs +7 -1
  263. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  264. package/scripts/lib/config/vault-staleness.mjs +2 -1
  265. package/scripts/lib/config/vault-sync.mjs +2 -1
  266. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  267. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  268. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  269. package/scripts/lib/convergence-monitor.mjs +82 -16
  270. package/scripts/lib/dispatcher/rank.mjs +124 -48
  271. package/scripts/lib/ecosystem-health.mjs +16 -2
  272. package/scripts/lib/eval/engine.mjs +9 -1
  273. package/scripts/lib/eval/session-resolve.mjs +23 -4
  274. package/scripts/lib/events.mjs +22 -6
  275. package/scripts/lib/frontmatter-guard.mjs +131 -13
  276. package/scripts/lib/gates/gate-full.mjs +30 -0
  277. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  278. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  279. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  280. package/scripts/lib/host-identity.mjs +50 -11
  281. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  282. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  283. package/scripts/lib/learnings/io.mjs +60 -6
  284. package/scripts/lib/memory-proposals/store.mjs +30 -22
  285. package/scripts/lib/owner-config-banner.mjs +41 -6
  286. package/scripts/lib/owner-config-loader.mjs +21 -10
  287. package/scripts/lib/owner-interview.mjs +3 -3
  288. package/scripts/lib/owner-yaml.mjs +215 -15
  289. package/scripts/lib/platform.mjs +108 -15
  290. package/scripts/lib/plugin-update-banner.mjs +414 -0
  291. package/scripts/lib/project-hygiene.mjs +38 -2
  292. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  293. package/scripts/lib/quality-gate.mjs +133 -44
  294. package/scripts/lib/reconcile/emitter.mjs +68 -6
  295. package/scripts/lib/reconcile/engine.mjs +51 -11
  296. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  297. package/scripts/lib/reconcile/writer.mjs +40 -18
  298. package/scripts/lib/session-close-backfill.mjs +67 -9
  299. package/scripts/lib/session-id.mjs +12 -23
  300. package/scripts/lib/session-identity/own-session.mjs +125 -10
  301. package/scripts/lib/session-lock-shape.mjs +43 -0
  302. package/scripts/lib/session-lock.mjs +5 -10
  303. package/scripts/lib/session-registry.mjs +25 -9
  304. package/scripts/lib/session-schema/constants.mjs +64 -3
  305. package/scripts/lib/session-schema/validator.mjs +38 -4
  306. package/scripts/lib/session-start-probes.mjs +30 -1
  307. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  308. package/scripts/lib/skill-health/join.mjs +17 -4
  309. package/scripts/lib/state-md.mjs +78 -0
  310. package/scripts/lib/sunset/walker.mjs +6 -0
  311. package/scripts/lib/telemetry/schema.mjs +202 -9
  312. package/scripts/lib/telemetry/sync.mjs +368 -12
  313. package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
  314. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  315. package/scripts/lib/validate/check-agents.mjs +3 -3
  316. package/scripts/lib/validate/check-codex-skills.mjs +191 -0
  317. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  318. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  319. package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
  320. package/scripts/lib/validate/check-skill-links.mjs +193 -0
  321. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  322. package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
  323. package/scripts/lib/validate/check-unwired-features.mjs +163 -15
  324. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  325. package/scripts/lib/validate/confidential-names.mjs +95 -30
  326. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  327. package/scripts/lib/validate/repo-files.mjs +48 -14
  328. package/scripts/lib/vault-backfill/template.mjs +63 -6
  329. package/scripts/lib/vault-mirror/process.mjs +165 -42
  330. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  331. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  332. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  333. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  334. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  335. package/scripts/lib/wave-resource-gate.mjs +8 -2
  336. package/scripts/lib/wave-sizing.mjs +4 -1
  337. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  338. package/scripts/materialize-wave-scope.mjs +12 -5
  339. package/scripts/memory-propose.mjs +19 -5
  340. package/scripts/migrate-cold-start-seed.mjs +4 -1
  341. package/scripts/parse-config.mjs +60 -3
  342. package/scripts/release.mjs +430 -31
  343. package/scripts/repair-invalid-sessions.mjs +3 -3
  344. package/scripts/run-quality-gate.mjs +128 -11
  345. package/scripts/site-numbers.mjs +344 -8
  346. package/scripts/sweep-expired-learnings.mjs +90 -0
  347. package/scripts/sync-vault-schema.mjs +3 -1
  348. package/scripts/telemetry.mjs +2 -2
  349. package/scripts/validate-plugin.mjs +164 -0
  350. package/scripts/validate-wave-scope.mjs +28 -8
  351. package/scripts/wave-scope-binding.mjs +215 -0
  352. package/skills/_shared/instruction-file-resolution.md +10 -0
  353. package/skills/_shared/parallel-aware-preamble.md +1 -0
  354. package/skills/_shared/platform-tools.md +1 -1
  355. package/skills/_shared/state-ownership.md +1 -1
  356. package/skills/architecture/SKILL.md +7 -5
  357. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  358. package/skills/autopilot/SKILL.md +4 -18
  359. package/skills/claude-md-drift-check/SKILL.md +5 -1
  360. package/skills/claude-md-drift-check/checker.mjs +62 -2
  361. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  362. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  363. package/skills/discovery/probes-arch.md +20 -18
  364. package/skills/dispatcher/SKILL.md +3 -2
  365. package/skills/evolve/SKILL.md +65 -26
  366. package/skills/frontmatter-guard/SKILL.md +11 -5
  367. package/skills/npm-publish/SKILL.md +1 -1
  368. package/skills/reconcile/SKILL.md +33 -0
  369. package/skills/remote-offload/SKILL.md +1 -1
  370. package/skills/session-end/SKILL.md +18 -905
  371. package/skills/session-end/phase-3-6-tail.md +10 -3
  372. package/skills/session-end/plan-verification.md +221 -155
  373. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  374. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  375. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  376. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  377. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  378. package/skills/session-end/references/session-summary-template.md +62 -0
  379. package/skills/session-plan/SKILL.md +49 -0
  380. package/skills/session-start/SKILL.md +22 -904
  381. package/skills/session-start/phase-8-5-express-path.md +1 -1
  382. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  383. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  384. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  385. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  386. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  387. package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
  388. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  389. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  390. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  391. package/skills/vault-sync/SKILL.md +10 -0
  392. package/skills/vault-sync/validator.mjs +21 -27
  393. package/skills/wave-executor/SKILL.md +15 -1
  394. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  395. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  396. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  397. package/skills/wave-executor/wave-loop.md +14 -1309
  398. package/templates/_shared/journey-manifest.md +10 -6
  399. package/.cursor/commands/autopilot-multi.md +0 -14
  400. package/.cursor/commands/contract-version-bump.md +0 -14
  401. package/.cursor/commands/journey-audit.md +0 -14
  402. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  403. package/.cursor/skills/daily/SKILL.md +0 -12
  404. package/.cursor/skills/domain-model/SKILL.md +0 -13
  405. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  406. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  407. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  408. package/commands/autopilot-multi.md +0 -74
  409. package/commands/contract-version-bump.md +0 -28
  410. package/commands/journey-audit.md +0 -43
  411. package/pi/prompts/autopilot-multi.md +0 -12
  412. package/pi/prompts/contract-version-bump.md +0 -12
  413. package/pi/prompts/journey-audit.md +0 -12
  414. package/scripts/autopilot-multi.mjs +0 -885
  415. package/scripts/backfill-learnings-expires.mjs +0 -196
  416. package/scripts/backfill-learnings.mjs +0 -203
  417. package/scripts/fleet-instruction-scan.mjs +0 -141
  418. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  419. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  420. package/scripts/lib/webhook-url.mjs +0 -105
  421. package/scripts/lifecycle-sim-v6.mjs +0 -347
  422. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  423. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  424. package/scripts/upload-social-preview.mjs +0 -316
  425. package/skills/_shared/model-selection.md +0 -64
  426. package/skills/contract-version-bump/SKILL.md +0 -219
  427. package/skills/daily/SKILL.md +0 -222
  428. package/skills/daily/generate.sh +0 -92
  429. package/skills/daily/templates/daily.md.tpl +0 -36
  430. package/skills/journey-audit/SKILL.md +0 -270
  431. package/skills/skill-creator/SKILL.md +0 -168
  432. package/skills/ubiquitous-language/SKILL.md +0 -97
  433. package/skills/vault-sync/package-lock.json +0 -40
  434. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  435. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * dispatch-common.mjs — shared base for the two foreign-dispatch adapters:
3
+ * `foreign-dispatch.mjs` (Cursor channel, `cursor-agent`) and
4
+ * `remote-dispatch.mjs` (remote-host channel, `offload`). Two adapters, one
5
+ * base (#1204).
6
+ *
7
+ * These six symbols moved here because each one is an enforcement point or a
8
+ * budget constant that must NOT diverge between the two channels: the
9
+ * `never_foreign` role lock, the run-id path-segment validator, the
10
+ * SIGTERM→SIGKILL child runner, and the two dispatch-budget constants. Before
11
+ * this module existed, `remote-dispatch.mjs` imported all five from
12
+ * `foreign-dispatch.mjs` directly — a Cursor-specific file acting as a shared
13
+ * base for a channel that has nothing to do with Cursor. Both adapters now
14
+ * import from here instead, so neither exports something the other must reach
15
+ * through.
16
+ *
17
+ * Revisit-Trigger: if a THIRD dispatch channel ever needs a different budget
18
+ * default or a different never-foreign list, split the constant per-channel
19
+ * at that point rather than overriding it at the call site — a call-site
20
+ * override would silently diverge from the one enforcement point this module
21
+ * exists to be.
22
+ */
23
+
24
+ /**
25
+ * Roles that may NEVER run on a foreign model. Copied verbatim from
26
+ * account-switch `tools/routing/routing.yaml` § never_foreign (read 2026-08-25).
27
+ * Keep in sync by hand — the YAML lives in a different repo and is parsed there
28
+ * by awk, not by a shared library.
29
+ * @type {readonly string[]}
30
+ */
31
+ export const NEVER_FOREIGN_ROLES = Object.freeze([
32
+ 'impl-core',
33
+ 'security-review',
34
+ 'migration',
35
+ 'release',
36
+ 'secrets',
37
+ 'incident',
38
+ 'refactor-crosscut',
39
+ ]);
40
+
41
+ /** Default wall-clock budget for one foreign dispatch, in seconds.
42
+ * This is a FLOOR, not a suggestion: measured 2026-08-23, `cursor-grok-4.6-high`
43
+ * ran 2 of 3 hard-test tasks past a 540 s cap (recorded as DNF). Lowering this
44
+ * manufactures timeouts and reads as model failure. */
45
+ export const DEFAULT_TIMEOUT_SEC = 900;
46
+
47
+ /**
48
+ * Grace period between SIGTERM and SIGKILL, in ms.
49
+ *
50
+ * `child.kill('SIGTERM')` is a REQUEST: a child that installs a handler and
51
+ * ignores it never emits `close`, and this module's only resolve paths are
52
+ * `close` and `error` — so without escalation a wedged `cursor-agent` hangs
53
+ * `dispatchForeign` forever and takes the whole wave with it. SIGKILL is not
54
+ * catchable, so the escalation always terminates.
55
+ *
56
+ * Named ceiling (BV-004): 10 s is enough for a cooperative child to flush its
57
+ * stream-json tail and exit. Revisit if a well-behaved `cursor-agent` is ever
58
+ * measured needing longer than that to shut down — raise the constant, never
59
+ * drop the escalation.
60
+ */
61
+ export const DEFAULT_KILL_GRACE_MS = 10_000;
62
+
63
+ /** A runId names a directory and a log file. Anything outside this alphabet —
64
+ * or the two relative-path literals the alphabet happens to admit — can escape
65
+ * the parent it is joined to. */
66
+ const SAFE_RUN_ID = /^[A-Za-z0-9._-]+$/;
67
+
68
+ /**
69
+ * @param {unknown} runId
70
+ * @returns {boolean} true when the id is safe to use as a path segment.
71
+ */
72
+ export function isSafeRunId(runId) {
73
+ const s = String(runId ?? '');
74
+ if (s === '.' || s === '..') return false; // admitted by SAFE_RUN_ID, still an escape
75
+ return SAFE_RUN_ID.test(s);
76
+ }
77
+
78
+ /**
79
+ * @param {string} role
80
+ * @returns {boolean} true when the role is locked to Claude.
81
+ */
82
+ export function isNeverForeignRole(role) {
83
+ return NEVER_FOREIGN_ROLES.includes(String(role || '').trim());
84
+ }
85
+
86
+ /**
87
+ * Run the child process, capturing stdout+stderr merged, with a hand-rolled
88
+ * SIGTERM timeout that ESCALATES to SIGKILL. `spawnFn` is the DI seam so tests
89
+ * never touch the real `cursor-agent` binary.
90
+ *
91
+ * The escalation is the difference between a bounded dispatch and a hung wave:
92
+ * this function's only resolve paths are the child's `close` and `error`
93
+ * events, so a child that ignores SIGTERM never lets the promise settle. See
94
+ * {@link DEFAULT_KILL_GRACE_MS}.
95
+ *
96
+ * @returns {Promise<{capture: string, exitCode: number|null, signal: string|null,
97
+ * timerFired: boolean, killSignals: string[], errorCode: string|null}>}
98
+ */
99
+ export function runChild(cmd, args, options, { spawnFn, timeoutMs, killGraceMs }) {
100
+ return new Promise((resolve) => {
101
+ /** @type {string[]} */
102
+ const killSignals = [];
103
+ let child;
104
+ try {
105
+ child = spawnFn(cmd, args, options);
106
+ } catch (err) {
107
+ resolve({
108
+ capture: `spawn failed: ${err && err.message}`,
109
+ exitCode: null,
110
+ signal: null,
111
+ timerFired: false,
112
+ killSignals,
113
+ errorCode: (err && err.code) || null,
114
+ });
115
+ return;
116
+ }
117
+
118
+ let capture = '';
119
+ let timerFired = false;
120
+ let settled = false;
121
+ let errorCode = null;
122
+ /** @type {ReturnType<typeof setTimeout>|null} */
123
+ let graceTimer = null;
124
+
125
+ const onChunk = (chunk) => {
126
+ capture += chunk.toString();
127
+ };
128
+ child.stdout?.on('data', onChunk);
129
+ child.stderr?.on('data', onChunk);
130
+
131
+ const send = (signal) => {
132
+ killSignals.push(signal);
133
+ try {
134
+ child.kill(signal);
135
+ } catch {
136
+ /* already gone */
137
+ }
138
+ };
139
+
140
+ const timer = setTimeout(() => {
141
+ timerFired = true;
142
+ send('SIGTERM');
143
+ // SIGTERM is a request; SIGKILL is not. A child that installs a handler
144
+ // and declines to exit would otherwise hang this promise forever.
145
+ graceTimer = setTimeout(() => send('SIGKILL'), killGraceMs);
146
+ graceTimer.unref?.();
147
+ }, timeoutMs);
148
+
149
+ const finish = (exitCode, signal) => {
150
+ if (settled) return;
151
+ settled = true;
152
+ clearTimeout(timer);
153
+ if (graceTimer) clearTimeout(graceTimer);
154
+ resolve({ capture, exitCode, signal, timerFired, killSignals, errorCode });
155
+ };
156
+
157
+ child.on('error', (err) => {
158
+ errorCode = (err && err.code) || null;
159
+ capture += `\nchild error: ${err && err.message}`;
160
+ finish(null, null);
161
+ });
162
+ child.on('close', (code, signal) => finish(code, signal ?? null));
163
+ });
164
+ }
@@ -54,45 +54,13 @@ import path from 'node:path';
54
54
 
55
55
  import { emitEvent, sessionAttribution } from '../events.mjs';
56
56
  import { isPathInside } from '../path-utils.mjs';
57
-
58
- /**
59
- * Roles that may NEVER run on a foreign model. Copied verbatim from
60
- * account-switch `tools/routing/routing.yaml` § never_foreign (read 2026-08-25).
61
- * Keep in sync by hand — the YAML lives in a different repo and is parsed there
62
- * by awk, not by a shared library.
63
- * @type {readonly string[]}
64
- */
65
- export const NEVER_FOREIGN_ROLES = Object.freeze([
66
- 'impl-core',
67
- 'security-review',
68
- 'migration',
69
- 'release',
70
- 'secrets',
71
- 'incident',
72
- 'refactor-crosscut',
73
- ]);
74
-
75
- /** Default wall-clock budget for one foreign dispatch, in seconds.
76
- * This is a FLOOR, not a suggestion: measured 2026-08-23, `cursor-grok-4.6-high`
77
- * ran 2 of 3 hard-test tasks past a 540 s cap (recorded as DNF). Lowering this
78
- * manufactures timeouts and reads as model failure. */
79
- export const DEFAULT_TIMEOUT_SEC = 900;
80
-
81
- /**
82
- * Grace period between SIGTERM and SIGKILL, in ms.
83
- *
84
- * `child.kill('SIGTERM')` is a REQUEST: a child that installs a handler and
85
- * ignores it never emits `close`, and this module's only resolve paths are
86
- * `close` and `error` — so without escalation a wedged `cursor-agent` hangs
87
- * `dispatchForeign` forever and takes the whole wave with it. SIGKILL is not
88
- * catchable, so the escalation always terminates.
89
- *
90
- * Named ceiling (BV-004): 10 s is enough for a cooperative child to flush its
91
- * stream-json tail and exit. Revisit if a well-behaved `cursor-agent` is ever
92
- * measured needing longer than that to shut down — raise the constant, never
93
- * drop the escalation.
94
- */
95
- export const DEFAULT_KILL_GRACE_MS = 10_000;
57
+ import {
58
+ DEFAULT_KILL_GRACE_MS,
59
+ DEFAULT_TIMEOUT_SEC,
60
+ isNeverForeignRole,
61
+ isSafeRunId,
62
+ runChild,
63
+ } from './dispatch-common.mjs';
96
64
 
97
65
  /**
98
66
  * argv prefix for every foreign dispatch, ahead of `--model`/`--output-format`.
@@ -156,29 +124,6 @@ export const FOREIGN_ENV_ALLOWLIST = Object.freeze([
156
124
  * directory named this way. */
157
125
  export const FOREIGN_TMP_PREFIX = 'so-foreign';
158
126
 
159
- /** A runId names a directory and a log file. Anything outside this alphabet —
160
- * or the two relative-path literals the alphabet happens to admit — can escape
161
- * the parent it is joined to. */
162
- const SAFE_RUN_ID = /^[A-Za-z0-9._-]+$/;
163
-
164
- /**
165
- * @param {unknown} runId
166
- * @returns {boolean} true when the id is safe to use as a path segment.
167
- */
168
- export function isSafeRunId(runId) {
169
- const s = String(runId ?? '');
170
- if (s === '.' || s === '..') return false; // admitted by SAFE_RUN_ID, still an escape
171
- return SAFE_RUN_ID.test(s);
172
- }
173
-
174
- /**
175
- * @param {string} role
176
- * @returns {boolean} true when the role is locked to Claude.
177
- */
178
- export function isNeverForeignRole(role) {
179
- return NEVER_FOREIGN_ROLES.includes(String(role || '').trim());
180
- }
181
-
182
127
  /**
183
128
  * Build the allowlisted child environment.
184
129
  * @param {Record<string, string|undefined>} [source]
@@ -317,86 +262,6 @@ export function parseCursorStream(raw) {
317
262
  */
318
263
  export const MEASUREMENT_EXCLUDES = Object.freeze(['node_modules']);
319
264
 
320
- /**
321
- * Run the child process, capturing stdout+stderr merged, with a hand-rolled
322
- * SIGTERM timeout that ESCALATES to SIGKILL. `spawnFn` is the DI seam so tests
323
- * never touch the real `cursor-agent` binary.
324
- *
325
- * The escalation is the difference between a bounded dispatch and a hung wave:
326
- * this function's only resolve paths are the child's `close` and `error`
327
- * events, so a child that ignores SIGTERM never lets the promise settle. See
328
- * {@link DEFAULT_KILL_GRACE_MS}.
329
- *
330
- * @returns {Promise<{capture: string, exitCode: number|null, signal: string|null,
331
- * timerFired: boolean, killSignals: string[], errorCode: string|null}>}
332
- */
333
- export function runChild(cmd, args, options, { spawnFn, timeoutMs, killGraceMs }) {
334
- return new Promise((resolve) => {
335
- /** @type {string[]} */
336
- const killSignals = [];
337
- let child;
338
- try {
339
- child = spawnFn(cmd, args, options);
340
- } catch (err) {
341
- resolve({
342
- capture: `spawn failed: ${err && err.message}`,
343
- exitCode: null,
344
- signal: null,
345
- timerFired: false,
346
- killSignals,
347
- errorCode: (err && err.code) || null,
348
- });
349
- return;
350
- }
351
-
352
- let capture = '';
353
- let timerFired = false;
354
- let settled = false;
355
- let errorCode = null;
356
- /** @type {ReturnType<typeof setTimeout>|null} */
357
- let graceTimer = null;
358
-
359
- const onChunk = (chunk) => {
360
- capture += chunk.toString();
361
- };
362
- child.stdout?.on('data', onChunk);
363
- child.stderr?.on('data', onChunk);
364
-
365
- const send = (signal) => {
366
- killSignals.push(signal);
367
- try {
368
- child.kill(signal);
369
- } catch {
370
- /* already gone */
371
- }
372
- };
373
-
374
- const timer = setTimeout(() => {
375
- timerFired = true;
376
- send('SIGTERM');
377
- // SIGTERM is a request; SIGKILL is not. A child that installs a handler
378
- // and declines to exit would otherwise hang this promise forever.
379
- graceTimer = setTimeout(() => send('SIGKILL'), killGraceMs);
380
- graceTimer.unref?.();
381
- }, timeoutMs);
382
-
383
- const finish = (exitCode, signal) => {
384
- if (settled) return;
385
- settled = true;
386
- clearTimeout(timer);
387
- if (graceTimer) clearTimeout(graceTimer);
388
- resolve({ capture, exitCode, signal, timerFired, killSignals, errorCode });
389
- };
390
-
391
- child.on('error', (err) => {
392
- errorCode = (err && err.code) || null;
393
- capture += `\nchild error: ${err && err.message}`;
394
- finish(null, null);
395
- });
396
- child.on('close', (code, signal) => finish(code, signal ?? null));
397
- });
398
- }
399
-
400
265
  /** Read `git` output as text, returning '' on failure (a detached worktree with
401
266
  * no commits still answers, but a broken invocation must not throw here). */
402
267
  function gitText(args, execFn) {
@@ -6,10 +6,10 @@
6
6
  * Relationship to `foreign-dispatch.mjs`: same contract, different channel.
7
7
  * That module sends a task to a foreign MODEL on this machine; this one sends
8
8
  * a task to Claude on ANOTHER machine. Both share the parts that must not
9
- * diverge and are therefore IMPORTED, never copied: the `never_foreign` role
10
- * lock ({@link isNeverForeignRole}), the run-id path-segment validator
11
- * ({@link isSafeRunId}), the SIGTERM→SIGKILL child runner ({@link runChild}),
12
- * and the two budget constants.
9
+ * diverge and are therefore IMPORTED from the common base (`dispatch-common.mjs`,
10
+ * #1204), never copied: the `never_foreign` role lock ({@link isNeverForeignRole}),
11
+ * the run-id path-segment validator ({@link isSafeRunId}), the SIGTERM→SIGKILL
12
+ * child runner ({@link runChild}), and the two budget constants.
13
13
  *
14
14
  * Three properties are load-bearing here and each has a test:
15
15
  *
@@ -40,9 +40,7 @@ import {
40
40
  isNeverForeignRole,
41
41
  isSafeRunId,
42
42
  runChild,
43
- } from './foreign-dispatch.mjs';
44
-
45
- export { DEFAULT_KILL_GRACE_MS, DEFAULT_TIMEOUT_SEC };
43
+ } from './dispatch-common.mjs';
46
44
 
47
45
  /** The `offload` binary. Resolved on PATH; a missing binary surfaces as ENOENT
48
46
  * and is classified as a CHANNEL failure, never as a model failure. */
@@ -8,7 +8,6 @@
8
8
  */
9
9
 
10
10
  import { probe, evaluate } from './resource-probe.mjs';
11
- import { isNeverForeignRole } from './wave-executor/foreign-dispatch.mjs';
12
11
 
13
12
  /**
14
13
  * Wave roles that may run on a declared remote host, mapped to the
@@ -147,7 +146,14 @@ async function applyOffloadDecision(result, opts) {
147
146
  if (!Array.isArray(hosts) || hosts.length === 0) return result;
148
147
 
149
148
  const role = String(waveRole ?? '').trim().toLowerCase();
150
- if (isNeverForeignRole(role)) return result;
149
+ // No isNeverForeignRole() check here (D8 #1204 LOW-1): the invariant is
150
+ // enforced STRUCTURALLY by this mapping table, not by an extra guard.
151
+ // NEVER_FOREIGN_ROLES {impl-core, security-review, migration, release,
152
+ // secrets, incident, refactor-crosscut} and OFFLOADABLE_WAVE_ROLES keys
153
+ // {quality, test, ui, perf} have ZERO overlap — measured by inspection of
154
+ // both literal lists — so a never-foreign role always falls through to
155
+ // `mappedRole === undefined` below regardless. A redundant call here was
156
+ // dead code that a lookup-table edit could not make live again on its own.
151
157
  const mappedRole = OFFLOADABLE_WAVE_ROLES[role];
152
158
  if (mappedRole === undefined) return result;
153
159
 
@@ -3,7 +3,10 @@
3
3
  * Learning: coordinator-over-worktree-on-shared-files caused back-to-back regressions.
4
4
  */
5
5
 
6
- const VALID_SESSION_TYPES = ['housekeeping', 'feature', 'deep'];
6
+ // 'unknown' is accepted because scripts/lib/session-close-backfill.mjs writes it for records whose
7
+ // type it could not measure (4.0.0). It is NOT a mode anyone selects — isolation resolves as for
8
+ // 'deep' (the conservative end). SSOT for the ledger enum: scripts/lib/session-schema/constants.mjs.
9
+ const VALID_SESSION_TYPES = ['housekeeping', 'feature', 'deep', 'unknown'];
7
10
  const VALID_COLLISION_RISKS = ['low', 'medium', 'high'];
8
11
  const VALID_CONFIG_ISOLATIONS = ['auto', 'worktree', 'none'];
9
12
  const VALID_ENFORCEMENTS = ['strict', 'warn', 'off'];
@@ -96,9 +96,107 @@ const RE_EMIT_EVERY = 10;
96
96
 
97
97
  /**
98
98
  * git subcommands that mutate the shared index, the stash stack, or remote
99
- * history — the PSA-007 prohibition list, verbatim.
99
+ * history — the PSA-007 prohibition list, verbatim. Matching the subcommand
100
+ * LITERAL is only the first half: the arguments decide whether the invocation
101
+ * writes anything at all (#1215).
102
+ *
103
+ * Group 1 is the subcommand, group 2 the remaining argument string.
104
+ *
105
+ * The global-flag run has TWO alternatives on purpose (W4/F2). A bare
106
+ * `(?:-[^\s]+\s+)*` cannot absorb a VALUE-taking global flag: in
107
+ * `git -C /tmp stash` the run eats `-C ` and then meets `/tmp`, which is not a
108
+ * subcommand, so the whole invocation read as NOT a git write — measured 2026-09-04,
109
+ * `isGitWrite('git -C /tmp stash')` and `isGitWrite('git -c user.name=x commit -m y')`
110
+ * both returned false. `-C`/`-c`/`--git-dir`/`--work-tree`/`--namespace`/
111
+ * `--exec-path`/`--config-env` are the value-taking globals; the space-separated
112
+ * form is matched first, the `--flag=value` form falls through to the generic
113
+ * alternative unchanged.
114
+ *
115
+ * `command git …` and `env VAR=x git …` prefixes are absorbed too — both are
116
+ * ordinary agent shapes that hid a write behind one token.
117
+ *
118
+ * DELIBERATE — `-C <dir>` is NOT treated as fixture context. `FIXTURE_CONTEXT_RE`
119
+ * keys on `cd /tmp` / `mktemp`, the shapes agents actually write around a test
120
+ * fixture; teaching it `-C` would hand back the exact one-token evasion this fix
121
+ * closes (`git -C /tmp/../<repo> commit`). A genuine `git -C "$TMPDIR" commit`
122
+ * therefore reports — a false alarm on an advisory detector, never a missed breach.
123
+ *
124
+ * CEILING (BV-004): the prefix list is enumerated, not general. `sudo git …`,
125
+ * `nice -n 5 git …` and any other wrapper still hide the write; and a git write
126
+ * inside `$(…)` or a quoted string was never detected (see `isGitWrite`).
127
+ * Revisit if a real PSA-007 breach is ever missed through a wrapper.
100
128
  */
101
- const GIT_WRITE_RE = /^\s*git\s+(?:-[^\s]+\s+)*(add|commit|stash|push|mv|rm|reset|checkout\s+--)\b/;
129
+ const GIT_WRITE_RE =
130
+ /^\s*(?:command\s+|env\s+(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)+)*git\s+(?:(?:-C|-c|--git-dir|--work-tree|--namespace|--exec-path|--config-env)\s+\S+\s+|-[^\s]+\s+)*(add|commit|stash|push|mv|rm|reset|checkout)\b(.*)$/;
131
+
132
+ /**
133
+ * `git stash` sub-subcommands that actually touch the stash stack. Bare
134
+ * `git stash` (no sub-subcommand) is `stash push`, so it counts too; anything
135
+ * else — `list`, `show`, an unknown word — is a read.
136
+ */
137
+ const STASH_WRITE_SUBS = new Set([
138
+ 'push',
139
+ 'save',
140
+ 'pop',
141
+ 'apply',
142
+ 'drop',
143
+ 'clear',
144
+ 'branch',
145
+ 'create',
146
+ 'store',
147
+ ]);
148
+
149
+ /**
150
+ * Argument-aware PSA-007 classification. Decision table (#1215):
151
+ *
152
+ * | invocation | verdict | why |
153
+ * |-----------------------------------|---------|----------------------------------------|
154
+ * | `git stash` / `stash push|save` | write | pushes onto the shared stash stack |
155
+ * | `stash pop|apply|drop|clear` | write | mutates / consumes the stack |
156
+ * | `stash branch|create|store` | write | creates refs from the stack |
157
+ * | `git stash list` / `stash show` | READ | capability/inspection probe |
158
+ * | `--help` / `-h` / `--version` | READ | capability probe, any subcommand |
159
+ * | `--dry-run` (any subcommand) | READ | prints, never writes |
160
+ * | `-n` on add / rm / mv / push | READ | `-n` IS `--dry-run` there |
161
+ * | `-n` on commit | WRITE | `-n` is `--no-verify` — a REAL commit |
162
+ * | `git checkout -- <path>` | write | discards sibling work (destructive) |
163
+ * | `git checkout <branch>` | READ | no index/worktree content destroyed |
164
+ * | `git reset` (any form) | write | destructive, kept unconditional |
165
+ * | `git add|commit|push|mv|rm` else | write | the PSA-007 list, verbatim |
166
+ *
167
+ * The `-n` split is measured, not assumed — `git commit -h` prints
168
+ * `-n, --no-verify` while `git add -h` / `git rm -h` / `git push -h` all print
169
+ * `-n, --[no-]dry-run` (measured 2026-09-04, git in this repo's PATH).
170
+ * Treating `commit -n` as a read would blind the detector to
171
+ * `git commit --no-verify`, which `parallel-sessions.md` § PSA-007 names as an
172
+ * anti-pattern verbatim.
173
+ *
174
+ * Ceiling: flags are matched as whole tokens after quoted spans are stripped,
175
+ * so `git commit -m "fix -h"` stays a write. Bundled short flags (`-nv`) and
176
+ * flags hidden in `$(…)` are not decoded — they read as writes, a false alarm,
177
+ * never a missed breach. Revisit if agents start writing bundled git flags.
178
+ *
179
+ * @param {string} sub — the matched git subcommand
180
+ * @param {string} rawArgs — everything after it in the segment
181
+ * @returns {boolean}
182
+ */
183
+ function gitSegmentWrites(sub, rawArgs) {
184
+ // Strip quoted spans so a commit message can never supply a read flag.
185
+ const args = String(rawArgs || '').replace(/"[^"]*"|'[^']*'/g, ' ');
186
+ const tokens = args.split(/\s+/).filter(Boolean);
187
+
188
+ for (const t of tokens) {
189
+ if (t === '--help' || t === '-h' || t === '--version' || t === '--dry-run') return false;
190
+ if (t === '-n' && sub !== 'commit') return false;
191
+ }
192
+
193
+ if (sub === 'checkout') return tokens.includes('--');
194
+ if (sub === 'stash') {
195
+ const first = tokens.find((t) => !t.startsWith('-'));
196
+ return first === undefined || STASH_WRITE_SUBS.has(first);
197
+ }
198
+ return true;
199
+ }
102
200
 
103
201
  /**
104
202
  * STATUS literals that all report as pattern `status-partial`.
@@ -283,9 +381,22 @@ export function classifyErrorClass(text) {
283
381
  * `/private/tmp`, `/var/folders`, `$TMPDIR`). A fixture repo created somewhere
284
382
  * else is still reported — a false alarm, never a missed breach. Revisit if
285
383
  * agents start seeding fixtures outside these paths.
384
+ *
385
+ * #1172: the temp path must also match BARE — the reported false alarm was
386
+ * `cd /tmp && git init && git commit -m x`, where the old pattern required a
387
+ * trailing `/` after `/tmp` and therefore saw nothing. The terminator class
388
+ * (`/`, quote, whitespace, `;`, `&`, `|`, end of string) is what keeps
389
+ * `cd /tmpfoo` — a real directory that is NOT the temp dir — reported.
390
+ *
391
+ * The destructive guard's own temp allowlist (`hooks/pre-bash-destructive-guard.mjs`
392
+ * `isSafeRmTarget`) is deliberately NOT reused: it exports nothing
393
+ * (`grep -n "^export" hooks/pre-bash-destructive-guard.mjs` → 0 matches,
394
+ * measured 2026-09-04) and resolves paths on the filesystem against a loaded
395
+ * policy, whereas this is a lexical test on a transcript string with no repo
396
+ * context. Same intent, different substrate.
286
397
  */
287
398
  const FIXTURE_CONTEXT_RE =
288
- /\bmktemp\b|\bcd\s+["']?(?:\/private)?\/(?:tmp|var\/folders)\/|\bcd\s+["']?\$\{?(?:TMPDIR|TMP|SCRATCH)/;
399
+ /\bmktemp\b|\bcd\s+["']?(?:\/private)?\/(?:tmp|var\/folders)(?:["'\s;&|/]|$)|\bcd\s+["']?\$\{?(?:TMPDIR|TMP|SCRATCH)/;
289
400
 
290
401
  /**
291
402
  * True when the Bash command contains a git INDEX/HISTORY write in ANY
@@ -301,7 +412,10 @@ const FIXTURE_CONTEXT_RE =
301
412
  export function isGitWrite(command) {
302
413
  const text = String(command || '');
303
414
  if (FIXTURE_CONTEXT_RE.test(text)) return false;
304
- return text.split(/&&|\|\||;|\||\n/).some((seg) => GIT_WRITE_RE.test(seg));
415
+ return text.split(/&&|\|\||;|\||\n/).some((seg) => {
416
+ const m = GIT_WRITE_RE.exec(seg);
417
+ return m ? gitSegmentWrites(m[1], m[2]) : false;
418
+ });
305
419
  }
306
420
 
307
421
  /**
@@ -27,6 +27,10 @@ import { resolve } from 'node:path';
27
27
  import { fileURLToPath } from 'node:url';
28
28
  import { writeJsonAtomicSync } from './lib/io.mjs';
29
29
  import { isPeerRecordId } from './lib/scope-gate.mjs';
30
+ import {
31
+ MANIFEST_SESSION_KEYS,
32
+ manifestSessionBinding,
33
+ } from './lib/session-identity/own-session.mjs';
30
34
 
31
35
  const HELP = `Usage: node scripts/materialize-wave-scope.mjs --state-dir <dir> --wave <positive-int> [--json]
32
36
 
@@ -220,7 +224,8 @@ export function validateScopeRecords(value) {
220
224
  /**
221
225
  * Read the session ids the sibling manifest `<state-dir>/wave-scope.json`
222
226
  * declares for this state directory (#1123 wrote both `session` — the raw
223
- * `session_id` — and its human-readable twin `semantic_session`).
227
+ * `session_id` — and its human-readable twin `semantic_session_id`; the
228
+ * pre-#1153 spellings `session` / `semantic_session` are still read).
224
229
  *
225
230
  * A caller may legitimately hold either spelling, so BOTH are returned and a
226
231
  * match against either proves ownership. Any failure to read or parse the
@@ -239,10 +244,12 @@ export function manifestSessionIds(stateDir, readFile = readFileSync) {
239
244
  return [];
240
245
  }
241
246
  if (!isRecord(manifest)) return [];
242
- return ['session', 'semantic_session']
243
- .map((key) => manifest[key])
244
- .filter((value) => typeof value === 'string' && value.trim().length > 0)
245
- .map((value) => value.trim());
247
+ // #1153 P2 — the key spellings live in own-session.mjs, which also reads the
248
+ // pre-#1153 `session`/`semantic_session` names for one transition release.
249
+ const binding = manifestSessionBinding(manifest);
250
+ return MANIFEST_SESSION_KEYS.current
251
+ .map((key) => binding[key])
252
+ .filter((value) => typeof value === 'string' && value.length > 0);
246
253
  }
247
254
 
248
255
  /**
@@ -46,7 +46,7 @@ import { dirname, join, isAbsolute } from 'node:path';
46
46
  import { fileURLToPath } from 'node:url';
47
47
 
48
48
  import { isWaveAgentContext, WAVE_AGENT_ENV_VAR, WAVE_AGENT_ENV_VALUE } from './lib/wave-context.mjs';
49
- import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
49
+ import { readProcessLocalSessionIds, manifestSessionBinding } from './lib/session-identity/own-session.mjs';
50
50
  import { readLockDetailed } from './lib/session-lock.mjs';
51
51
 
52
52
  // ---------------------------------------------------------------------------
@@ -369,7 +369,7 @@ if (!dryRun && (currentWaveRaw === undefined || currentWaveRaw === null || curre
369
369
  // trusted (#1177 FX1) — since #1123 both writers stamp the binding, so a
370
370
  // binding-less manifest is a peer's or a stale artefact.
371
371
  //
372
- // The session binding compares `manifest.semantic_session` — the SEMANTIC label
372
+ // The session binding compares the manifest's `semantic_session_id` — the SEMANTIC label
373
373
  // — against the semantic id looked up in `session.lock`, and that lookup is only
374
374
  // honoured when this PROCESS's own raw session id equals the lock's raw
375
375
  // `session_id` (#1188). STATE.md's `session` field is NOT a witness here: it is
@@ -382,14 +382,18 @@ function resolveRunningWaveId() {
382
382
  const manifestPath = join(stateDir, 'wave-scope.json');
383
383
  if (existsSync(manifestPath)) {
384
384
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
385
- const boundSession = manifest?.semantic_session ?? manifest?.session;
385
+ // #1153 P2 key spellings resolved in ONE place (own-session.mjs),
386
+ // which also reads the pre-#1153 `session` / `semantic_session` names
387
+ // for one transition release. Absent slots come back as `undefined`.
388
+ const binding = manifestSessionBinding(manifest);
389
+ const boundSession = binding.semantic_session_id ?? binding.session_id;
386
390
  const unbound =
387
391
  boundSession === undefined || boundSession === null || boundSession === '';
388
392
  // BOTH sides must be non-empty strings. `undefined === undefined` is
389
393
  // true, so a STATE.md WITHOUT a `session` key previously "matched" any
390
394
  // manifest whose `semantic_session` was also absent-but-present-in-the
391
395
  // -comparison — adopting a foreign coordinator's wave number as its own.
392
- const mineSide = manifest?.semantic_session;
396
+ const mineSide = binding.semantic_session_id;
393
397
  // #1188 — STATE.md is a working-copy artefact of the LOCK OWNER, not a
394
398
  // process-local witness: when a peer holds the lock the peer wrote BOTH
395
399
  // STATE.md and the manifest, so the two "independent" sides agree about
@@ -403,6 +407,16 @@ function resolveRunningWaveId() {
403
407
  // Cursor) resolves no process-local id -> binding unprovable ->
404
408
  // current-wave + 1 fallback with a stderr line. REVISIT if that fallback
405
409
  // rate is measured non-trivial in events.jsonl.
410
+ //
411
+ // #1207 — kept hand-rolled rather than calling `attributionForRecord()`
412
+ // (scripts/lib/events.mjs): that function's contract is `{}`-on-any-
413
+ // mismatch with no detail, but the stderr diagnostics below (raw id,
414
+ // lock status, process-local id list) are load-bearing for debugging a
415
+ // wrongly-bucketed proposal, and attributionForRecord() has no lock-
416
+ // status channel to source them from (it swallows `readLock()` errors
417
+ // silently by design). `readLockDetailed()` is used here specifically
418
+ // for that `.status` field `readLock()` (attributionForRecord's own
419
+ // primitive) does not return.
406
420
  const lock = readLockDetailed({ repoRoot: process.cwd() });
407
421
  const processLocal = readProcessLocalSessionIds();
408
422
  const lockRaw =
@@ -431,7 +445,7 @@ function resolveRunningWaveId() {
431
445
  // bucket. Unbound → say so once on stderr and fall back.
432
446
  if (unbound) {
433
447
  process.stderr.write(
434
- 'memory-propose: wave-scope.json unbound (no semantic_session) — ignored\n',
448
+ 'memory-propose: wave-scope.json unbound (no semantic_session_id) — ignored\n',
435
449
  );
436
450
  }
437
451
  if (mine) {
@@ -143,7 +143,10 @@ if (applyFlag && dryRunFlag) {
143
143
  process.exit(1);
144
144
  }
145
145
 
146
- // Default is dry-run (matches scripts/migrate-learnings-jsonl.mjs convention).
146
+ // Default is dry-run: a migration script writes only when `--apply` is passed, so an
147
+ // invocation with no flags can never mutate a store. Repo-wide convention for every
148
+ // `scripts/migrate-*` / `scripts/backfill-*` CLI; live example:
149
+ // `scripts/migrate-sessions-jsonl.mjs` (`args.apply = false` default, `--apply` opts in).
147
150
  const dryRun = !applyFlag;
148
151
 
149
152
  // `--repos` is kept as the raw comma-separated string here (split later, at