session-orchestrator 3.22.0 → 3.24.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 (316) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/commands/autopilot-multi.md +14 -0
  5. package/.cursor/commands/autopilot.md +14 -0
  6. package/.cursor/commands/bootstrap.md +14 -0
  7. package/.cursor/commands/brainstorm.md +14 -0
  8. package/.cursor/commands/close.md +13 -0
  9. package/.cursor/commands/contract-version-bump.md +14 -0
  10. package/.cursor/commands/debug.md +14 -0
  11. package/.cursor/commands/discovery.md +14 -0
  12. package/.cursor/commands/dispatcher.md +14 -0
  13. package/.cursor/commands/eli5.md +14 -0
  14. package/.cursor/commands/eval.md +14 -0
  15. package/.cursor/commands/evolve.md +14 -0
  16. package/.cursor/commands/go.md +14 -0
  17. package/.cursor/commands/grill.md +14 -0
  18. package/.cursor/commands/harness-audit.md +13 -0
  19. package/.cursor/commands/journey-audit.md +14 -0
  20. package/.cursor/commands/memory-cleanup.md +14 -0
  21. package/.cursor/commands/persona-panel.md +14 -0
  22. package/.cursor/commands/plan.md +14 -0
  23. package/.cursor/commands/portfolio.md +14 -0
  24. package/.cursor/commands/reconcile.md +14 -0
  25. package/.cursor/commands/release.md +14 -0
  26. package/.cursor/commands/repo-audit.md +13 -0
  27. package/.cursor/commands/session.md +14 -0
  28. package/.cursor/commands/spinout.md +14 -0
  29. package/.cursor/commands/sunset-review.md +14 -0
  30. package/.cursor/commands/templates-ack.md +14 -0
  31. package/.cursor/commands/test.md +14 -0
  32. package/.cursor/hooks.json +60 -0
  33. package/.cursor/rules/000-session-orchestrator.mdc +8 -0
  34. package/.cursor/rules/010-session-workflow.mdc +9 -1
  35. package/.cursor/rules/020-quality-gates.mdc +1 -1
  36. package/.cursor/rules/030-wave-execution.mdc +1 -1
  37. package/.cursor/rules/050-plan.mdc +2 -2
  38. package/.cursor/rules/070-gitlab-ops.mdc +73 -57
  39. package/.cursor/rules/080-ecosystem-health.mdc +7 -7
  40. package/.cursor/skills/architecture/SKILL.md +13 -0
  41. package/.cursor/skills/autopilot/SKILL.md +12 -0
  42. package/.cursor/skills/bootstrap/SKILL.md +12 -0
  43. package/.cursor/skills/brainstorm/SKILL.md +13 -0
  44. package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
  45. package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
  46. package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
  47. package/.cursor/skills/daily/SKILL.md +12 -0
  48. package/.cursor/skills/debug/SKILL.md +13 -0
  49. package/.cursor/skills/discovery/SKILL.md +13 -0
  50. package/.cursor/skills/dispatcher/SKILL.md +13 -0
  51. package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
  52. package/.cursor/skills/domain-model/SKILL.md +13 -0
  53. package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
  54. package/.cursor/skills/eli5/SKILL.md +13 -0
  55. package/.cursor/skills/eval/SKILL.md +12 -0
  56. package/.cursor/skills/evolve/SKILL.md +13 -0
  57. package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
  58. package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
  59. package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
  60. package/.cursor/skills/grill/SKILL.md +13 -0
  61. package/.cursor/skills/hook-development/SKILL.md +13 -0
  62. package/.cursor/skills/journey-audit/SKILL.md +13 -0
  63. package/.cursor/skills/mcp-builder/SKILL.md +13 -0
  64. package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
  65. package/.cursor/skills/mode-selector/SKILL.md +13 -0
  66. package/.cursor/skills/npm-publish/SKILL.md +12 -0
  67. package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
  68. package/.cursor/skills/persona-panel/SKILL.md +12 -0
  69. package/.cursor/skills/plan/SKILL.md +13 -0
  70. package/.cursor/skills/playwright-driver/SKILL.md +13 -0
  71. package/.cursor/skills/quality-gates/SKILL.md +13 -0
  72. package/.cursor/skills/reconcile/SKILL.md +12 -0
  73. package/.cursor/skills/remote-offload/SKILL.md +13 -0
  74. package/.cursor/skills/repo-audit/SKILL.md +13 -0
  75. package/.cursor/skills/session-end/SKILL.md +13 -0
  76. package/.cursor/skills/session-plan/SKILL.md +13 -0
  77. package/.cursor/skills/session-start/SKILL.md +13 -0
  78. package/.cursor/skills/skill-creator/SKILL.md +13 -0
  79. package/.cursor/skills/spinout/SKILL.md +12 -0
  80. package/.cursor/skills/sunset-review/SKILL.md +13 -0
  81. package/.cursor/skills/test-runner/SKILL.md +13 -0
  82. package/.cursor/skills/tmux-layout/SKILL.md +13 -0
  83. package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
  84. package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
  85. package/.cursor/skills/vault-mirror/SKILL.md +13 -0
  86. package/.cursor/skills/vault-sync/SKILL.md +13 -0
  87. package/.cursor/skills/wave-executor/SKILL.md +13 -0
  88. package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
  89. package/.mcp.json +4 -1
  90. package/CHANGELOG.md +446 -0
  91. package/README.md +22 -17
  92. package/agents/AGENTS.md +23 -4
  93. package/agents/code-implementer.md +2 -1
  94. package/agents/db-specialist.md +2 -2
  95. package/agents/docs-writer.md +3 -1
  96. package/agents/eval-judge.md +1 -1
  97. package/agents/session-reviewer.md +7 -1
  98. package/agents/test-writer.md +2 -1
  99. package/agents/ui-developer.md +2 -1
  100. package/commands/bootstrap.md +2 -2
  101. package/commands/close.md +3 -1
  102. package/commands/go.md +1 -1
  103. package/commands/journey-audit.md +43 -0
  104. package/docs/USER-GUIDE.md +2 -2
  105. package/docs/ci-setup.md +194 -25
  106. package/docs/codex-setup.md +64 -0
  107. package/docs/components.md +7 -7
  108. package/docs/cursor-setup.md +26 -47
  109. package/docs/events-schema.md +120 -10
  110. package/docs/github-mirror-protection.md +197 -0
  111. package/docs/pi-setup.md +2 -0
  112. package/docs/rule-authoring.md +3 -1
  113. package/docs/scope-collision-guard.md +49 -2
  114. package/docs/session-config-reference.md +89 -9
  115. package/docs/session-config-template.md +38 -7
  116. package/docs/telemetry/telemetry-claims.md +11 -10
  117. package/docs/telemetry.md +52 -1
  118. package/hooks/_lib/atomic-json.mjs +111 -0
  119. package/hooks/_lib/lock-bootstrap.mjs +8 -4
  120. package/hooks/_lib/subagent-paths.mjs +143 -0
  121. package/hooks/_lib/vcs-create-matcher.mjs +397 -38
  122. package/hooks/cwd-change-restore.mjs +9 -29
  123. package/hooks/enforce-scope.mjs +93 -0
  124. package/hooks/hooks-codex.json +1 -1
  125. package/hooks/hooks-cursor.json +201 -20
  126. package/hooks/hooks-pi.json +1 -1
  127. package/hooks/hooks.json +2 -2
  128. package/hooks/on-session-end.mjs +486 -19
  129. package/hooks/on-session-start.mjs +263 -12
  130. package/hooks/on-stop.mjs +392 -24
  131. package/hooks/post-bash-write-verify.mjs +104 -4
  132. package/hooks/post-subagent-discovery-validator.mjs +182 -21
  133. package/hooks/post-tool-batch-wave-signal.mjs +165 -42
  134. package/hooks/post-tool-failure-corrective-context.mjs +9 -32
  135. package/hooks/pre-bash-issue-budget.mjs +117 -4
  136. package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
  137. package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
  138. package/hooks/pre-bash-staging-fence.mjs +4 -0
  139. package/hooks/pre-task-scope-disjoint.mjs +368 -35
  140. package/hooks/skill-invocation-telemetry.mjs +21 -10
  141. package/hooks/subagent-telemetry.mjs +11 -26
  142. package/monitors/monitors.json +6 -0
  143. package/package.json +1 -1
  144. package/pi/prompts/journey-audit.md +12 -0
  145. package/rules/_index.md +9 -1
  146. package/rules/always-on/ask-via-tool.md +62 -0
  147. package/rules/always-on/bash-harness-pitfalls.md +168 -0
  148. package/rules/always-on/build-value.md +47 -0
  149. package/rules/always-on/cross-session-messaging.md +59 -0
  150. package/rules/always-on/loop-and-monitor.md +221 -0
  151. package/rules/always-on/parallel-sessions.md +142 -12
  152. package/rules/always-on/receiving-review.md +108 -0
  153. package/rules/always-on/test-value.md +40 -0
  154. package/rules/always-on/verification-before-completion.md +77 -0
  155. package/scripts/archive-closed-prds.mjs +258 -18
  156. package/scripts/autopilot.mjs +31 -12
  157. package/scripts/backfill-abandoned-sessions.mjs +80 -11
  158. package/scripts/backfill-evidence-digest.mjs +376 -0
  159. package/scripts/cursor-install.mjs +89 -48
  160. package/scripts/emit-event.mjs +10 -2
  161. package/scripts/export-hw-learnings.mjs +143 -2
  162. package/scripts/express-path.mjs +299 -0
  163. package/scripts/generate-cursor-adapter.mjs +253 -0
  164. package/scripts/github-protection-audit.mjs +358 -0
  165. package/scripts/lib/auq/parse.mjs +5 -29
  166. package/scripts/lib/auto-dialectic.mjs +68 -0
  167. package/scripts/lib/autopilot/worktree-pipeline.mjs +318 -18
  168. package/scripts/lib/build-live-signals.mjs +49 -27
  169. package/scripts/lib/ci-status-banner.mjs +158 -11
  170. package/scripts/lib/cold-start-detector.mjs +23 -14
  171. package/scripts/lib/command-blocker.mjs +70 -0
  172. package/scripts/lib/config/block-header.mjs +55 -0
  173. package/scripts/lib/config/discovery-validator.mjs +7 -2
  174. package/scripts/lib/config/health-endpoints.mjs +383 -0
  175. package/scripts/lib/config/reconcile.mjs +79 -4
  176. package/scripts/lib/config/remote-hosts.mjs +233 -0
  177. package/scripts/lib/config/section-extractor.mjs +235 -36
  178. package/scripts/lib/config-schema.mjs +9 -1
  179. package/scripts/lib/config.mjs +87 -8
  180. package/scripts/lib/convergence-monitor.mjs +13 -2
  181. package/scripts/lib/cursor-hook-bridge.mjs +443 -0
  182. package/scripts/lib/dispatcher/cli.mjs +2 -2
  183. package/scripts/lib/dispatcher/enumerate.mjs +2 -17
  184. package/scripts/lib/events-schema.mjs +48 -0
  185. package/scripts/lib/events.mjs +238 -5
  186. package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
  187. package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
  188. package/scripts/lib/express-path.mjs +327 -0
  189. package/scripts/lib/file-lock.mjs +22 -4
  190. package/scripts/lib/gates/gate-full.mjs +81 -8
  191. package/scripts/lib/gates/gate-helpers.mjs +76 -15
  192. package/scripts/lib/git-config-drift.mjs +134 -5
  193. package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
  194. package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
  195. package/scripts/lib/host-identity.mjs +247 -2
  196. package/scripts/lib/instruction-budget-guard.mjs +31 -1
  197. package/scripts/lib/issue-budget.mjs +229 -30
  198. package/scripts/lib/learnings/io.mjs +55 -10
  199. package/scripts/lib/learnings/schema.mjs +95 -28
  200. package/scripts/lib/lock-reaper.mjs +7 -1
  201. package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
  202. package/scripts/lib/locks/state-md-lock.mjs +8 -1
  203. package/scripts/lib/memory-banner.mjs +25 -10
  204. package/scripts/lib/memory-paths.mjs +15 -6
  205. package/scripts/lib/mode-selector/scoring.mjs +53 -6
  206. package/scripts/lib/peer-discovery.mjs +20 -2
  207. package/scripts/lib/platform.mjs +72 -9
  208. package/scripts/lib/plugin-root.mjs +143 -19
  209. package/scripts/lib/project-hygiene.mjs +43 -3
  210. package/scripts/lib/quality-gate.mjs +271 -13
  211. package/scripts/lib/reconcile/emitter.mjs +87 -19
  212. package/scripts/lib/reconcile/engine.mjs +517 -18
  213. package/scripts/lib/reconcile/idempotency.mjs +102 -1
  214. package/scripts/lib/reconcile/renderer.mjs +148 -3
  215. package/scripts/lib/reconcile/sanitize.mjs +40 -17
  216. package/scripts/lib/reconcile/writer.mjs +415 -84
  217. package/scripts/lib/rule-loader.mjs +37 -2
  218. package/scripts/lib/rules-sync.mjs +51 -8
  219. package/scripts/lib/scope-gate.mjs +126 -0
  220. package/scripts/lib/session-close-backfill.mjs +427 -37
  221. package/scripts/lib/session-discovery.mjs +69 -5
  222. package/scripts/lib/session-end/phase-skip.mjs +38 -5
  223. package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
  224. package/scripts/lib/session-id.mjs +30 -14
  225. package/scripts/lib/session-identity/own-session.mjs +220 -0
  226. package/scripts/lib/session-lock.mjs +85 -30
  227. package/scripts/lib/session-schema/normalizer.mjs +70 -3
  228. package/scripts/lib/session-schema/validator.mjs +40 -0
  229. package/scripts/lib/session-start-probes.mjs +608 -0
  230. package/scripts/lib/session-transition.mjs +277 -0
  231. package/scripts/lib/sessions-canonical.mjs +446 -0
  232. package/scripts/lib/sessions-staleness-banner.mjs +124 -57
  233. package/scripts/lib/spiral-carryover.mjs +90 -9
  234. package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
  235. package/scripts/lib/state-md/mission-status.mjs +350 -52
  236. package/scripts/lib/state-md/yaml-parser.mjs +145 -16
  237. package/scripts/lib/state-md.mjs +12 -2
  238. package/scripts/lib/telemetry/schema.mjs +74 -8
  239. package/scripts/lib/telemetry/sync.mjs +91 -16
  240. package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
  241. package/scripts/lib/validate/check-agents.mjs +66 -0
  242. package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
  243. package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
  244. package/scripts/lib/validate/check-doc-cli-commands.mjs +25 -65
  245. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
  246. package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -63
  247. package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
  248. package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
  249. package/scripts/lib/validate/check-skill-script-paths.mjs +436 -0
  250. package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
  251. package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
  252. package/scripts/lib/validate/check-unwired-features.mjs +333 -32
  253. package/scripts/lib/validate/check-validator-registration.mjs +248 -0
  254. package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
  255. package/scripts/lib/validate/markdown-fences.mjs +196 -0
  256. package/scripts/lib/validate/repo-files.mjs +275 -0
  257. package/scripts/lib/validate-vendored-rules.mjs +229 -7
  258. package/scripts/lib/vault-mirror/process.mjs +99 -43
  259. package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
  260. package/scripts/lib/vault-staleness-banner.mjs +76 -6
  261. package/scripts/lib/vault-status/board-lock.mjs +185 -0
  262. package/scripts/lib/vault-status/board-writer.mjs +381 -141
  263. package/scripts/lib/vault-status/narrative-mirror.mjs +190 -27
  264. package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
  265. package/scripts/lib/wave-executor/remote-dispatch.mjs +504 -0
  266. package/scripts/lib/wave-resource-gate.mjs +127 -7
  267. package/scripts/lib/wave-transcript-tail.mjs +889 -0
  268. package/scripts/materialize-wave-scope.mjs +228 -15
  269. package/scripts/mcp-server.sh +11 -2
  270. package/scripts/memory-propose.mjs +132 -8
  271. package/scripts/parse-config.mjs +65 -0
  272. package/scripts/promote-vault-strict.mjs +4 -15
  273. package/scripts/site-numbers.mjs +36 -4
  274. package/scripts/token-audit.sh +9 -2
  275. package/scripts/validate-plugin.mjs +29 -0
  276. package/scripts/validate-wave-scope.mjs +67 -0
  277. package/scripts/vault-consolidate.mjs +3 -11
  278. package/scripts/vault-integration-watcher.mjs +2 -4
  279. package/scripts/vault-mirror.mjs +305 -51
  280. package/skills/_shared/monitor-patterns.md +31 -5
  281. package/skills/_shared/parallel-aware-auq.md +31 -2
  282. package/skills/_shared/parallel-aware-preamble.md +19 -4
  283. package/skills/_shared/platform-tools.md +11 -5
  284. package/skills/_shared/state-ownership.md +29 -2
  285. package/skills/autopilot/SKILL.md +5 -1
  286. package/skills/bootstrap/SKILL.md +3 -3
  287. package/skills/bootstrap/_shared-template.md +18 -10
  288. package/skills/bootstrap/deep-template.md +10 -6
  289. package/skills/bootstrap/fast-template.md +15 -8
  290. package/skills/bootstrap/standard-template.md +10 -6
  291. package/skills/claude-md-drift-check/checker.mjs +39 -11
  292. package/skills/contract-version-bump/SKILL.md +1 -1
  293. package/skills/dispatcher/SKILL.md +1 -1
  294. package/skills/ecosystem-health/SKILL.md +4 -1
  295. package/skills/ecosystem-health/wizard.md +5 -0
  296. package/skills/evolve/SKILL.md +38 -1
  297. package/skills/journey-audit/SKILL.md +270 -0
  298. package/skills/peekaboo-driver/SKILL.md +15 -3
  299. package/skills/persona-panel/SKILL.md +1 -1
  300. package/skills/reconcile/SKILL.md +46 -3
  301. package/skills/remote-offload/SKILL.md +89 -0
  302. package/skills/session-end/SKILL.md +17 -4
  303. package/skills/session-end/metrics-collection.md +7 -4
  304. package/skills/session-end/phase-3-6-tail.md +20 -9
  305. package/skills/session-end/phase-3-7a-recommendations.md +16 -2
  306. package/skills/session-plan/SKILL.md +6 -1
  307. package/skills/session-plan/wave-template.md +1 -0
  308. package/skills/session-start/SKILL.md +54 -17
  309. package/skills/session-start/phase-7-5-mode-selector.md +15 -3
  310. package/skills/session-start/phase-8-5-express-path.md +77 -12
  311. package/skills/vault-sync/validator.mjs +31 -0
  312. package/skills/wave-executor/SKILL.md +5 -3
  313. package/skills/wave-executor/circuit-breaker.md +34 -9
  314. package/skills/wave-executor/wave-loop.md +143 -22
  315. package/templates/_shared/journey-manifest.md +110 -0
  316. package/templates/_shared/rules/parallel-sessions.md +0 -77
package/CHANGELOG.md CHANGED
@@ -7,6 +7,452 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.24.0] - 2026-09-02
11
+
12
+ Five commits since v3.23.0 (4 `feat`, 1 `docs`; 152 files, +14,596/−942), no
13
+ `BREAKING CHANGE:` footer and no `!` subject.
14
+
15
+ One `docs` commit (`c3ab4801`) adds ADR-0013, naming the ownership-check-before-deregister
16
+ ordering that closed Wave 4 of the previous session. One `feat` commit (`a019d5a4`, deep
17
+ session, 5 waves, 37 agents + 3 reviewers + 3 panel) closes the GH#67 peer-filter class
18
+ across every affected site, plus #1170, #1176, #1177, #1166, #1167, #1174, #1180 and #1175.
19
+ One `feat` commit (`936dae8a`) closes a Fleet-Mining v2 instrument audit across 18 repos
20
+ (1,587 learnings, 144k events, 347 telemetry records) with five repairs to the fleet's own
21
+ measurement instruments (#1189, #1190, #1191, #1192, #1193).
22
+
23
+ Two further `feat` commits (`2ccea0f2`, `3b352d78`) are Waves 2 and 3 of a new session (11
24
+ agents each; Opus for guards/identity/protocol, Sonnet for consolidation/polish). The
25
+ through-line: identity and scope boundaries are now drawn at the PROCESS, not the working
26
+ copy — a peer session's manifest in the same checkout is `foreign`, a dispatched subagent's
27
+ raw session id belongs to its coordinator, and four duplicated helper implementations
28
+ collapse to one canonical each.
29
+
30
+ ### Added
31
+
32
+ - **`orchestrator.reconcile.completed` (#1192).** Wrapper around `runReconcile` covering all
33
+ three return paths (empty corpus, normal, engine-error) with `trigger`
34
+ (`skill|session-end|phase-skip|unknown`), `targets` (allowlisted `repo-local|baseline`),
35
+ counters including measured zeros, `duration_ms`, and `aborted`+`reason` on the catch path.
36
+ Skipped with a stderr WARN when no `repoRoot` is passed, so test runs never write the fleet
37
+ ledger. First live record: `eligible 102 / proposals 10 / rejected 154 / capped 72 /
38
+ already_materialized 20`.
39
+ - **`docs/audits/2026-09-02-fleet-instruments.md`** — the public scrubbed extract of the
40
+ Fleet-Mining v2 instrument audit: 18 repos, 1,587 learnings, 143,967 events, 347 telemetry
41
+ records (6 `anon_id`), 13 instrument verdicts and 12 orchestrator candidates (K1–K12).
42
+ - **A new blocking validator catches dead script paths cited in skill/command/agent prose
43
+ (#1176).** `scripts/lib/validate/check-skill-script-paths.mjs` scans `skills/`, `commands/`
44
+ and `agents/` for `scripts/**.mjs` citations that neither exist nor carry a same-line/
45
+ line-above `<!-- path-check: planned #<iid> | historical | example -->` marker; fenced code
46
+ blocks are skipped as illustrative shape, not a claim. Measured 2026-09-02 @ `c3ab480`: 237
47
+ distinct citations, 7 dead, 4 of the 7 inside fences.
48
+ - **`events.jsonl` records are now schema-versioned and validated before they are written
49
+ (#1177).** `emitEvent()` stamps every record `schema_version: 1` (never overwriting a
50
+ caller-supplied value) and runs `validateEventRecord()` BEFORE the append and BEFORE any
51
+ webhook POST — an invalid record throws `EventValidationError` and produces neither.
52
+ `scripts/emit-event.mjs` maps that to exit `1` (I/O failures keep exit `2`);
53
+ `scripts/lib/tmux-layout/telemetry.mjs` stamps + validates synchronously and drops invalid
54
+ lines. The read path stays lenient — the 33k+ pre-#1177 records without the field remain
55
+ valid.
56
+ - **A canonical reader collapses `sessions.jsonl`'s append-only duplication (#1167).**
57
+ `scripts/lib/sessions-canonical.mjs` applies newest-wins per `session_id`, drops any record a
58
+ later `supersedes` pointer refutes, and narrowly collapses the systemic double-stub class
59
+ (two `abandoned` records sharing an exact `started_at`/`completed_at` pair, one synthetic).
60
+ The startup/CLI backfill path (`scripts/backfill-abandoned-sessions.mjs`) now also emits
61
+ `orchestrator.session.backfill_completed` per record — previously only the SessionEnd hook
62
+ did — and stamps `raw_session_id`; `hooks/on-session-end.mjs`'s backfill gained a second
63
+ UUID→semantic bridge via the `session.ended` event for sessions that lost the
64
+ lock-acquire race.
65
+ - **A vault-scoped lock protects the shared live-status board's read-modify-write (#1180).**
66
+ `scripts/lib/vault-status/board-lock.mjs` wraps `sweepBoard()`'s merge in a cross-repo mutex
67
+ (mtime-based staleness — the vault dir can be synced cross-host, so a recorded pid isn't
68
+ probeable); fail-open on acquire timeout or fs-error (one stderr WARN, then runs unlocked)
69
+ since a board update is best-effort telemetry. `.gitignore` gained `state.lock` and
70
+ `rules.lock`.
71
+ - **A GitLab Project Access Token recipe for the schema-drift CI job (#1175).**
72
+ `docs/ci-setup.md` documents Option A (Project Access Token, recommended) and Option B
73
+ (personal PAT) for `SCHEMA_DRIFT_TOKEN`. Activation is blocked on the documented
74
+ vendor-ahead drift tracked in #531: the token is provisioned but the CI/CD variable is not
75
+ yet set upstream.
76
+ - **A `remote-hosts:` config block routes wave work to another machine over SSH (#1160).**
77
+ `scripts/lib/config/remote-hosts.mjs` adds the block (opt-in template + reference doc) and
78
+ an `ssh:<alias>` channel in `agent-mapping`; the wave-resource gate adds an `offload`
79
+ decision AFTER the HR-004 heavy-repo cap, gated on an injected readiness witness
80
+ (`remoteReady` map or async `probeFn`) — the gate never probes the network itself, so with
81
+ no witness the decision stays local. `dispatchRemote()`/`remoteDoctor()`/
82
+ `remoteReadyProbe()` wrap the host-side `offload` CLI (prompt travels on stdin, never argv;
83
+ exit codes 1–8 mapped to typed refusal reasons; an empty returned patch counts as failure,
84
+ not success). Emits `orchestrator.remote_dispatch.completed` on every attempt AND every
85
+ refusal (`ok:false`, `exit_code: null`) — the payload carries the configured alias, never a
86
+ hostname or IP. `skills/remote-offload/SKILL.md` documents the channel generically for any
87
+ consuming repo (skill count measured 48 → 49); wave-3 added the fourth wave-executor
88
+ dispatch branch (`ssh:<alias>` via `dispatchRemote()`) to `wave-loop.md` alongside the
89
+ existing three.
90
+ - **A Peer-Scope-Union protocol lets two coordinator sessions in the same working copy share
91
+ scope without racing (#1195).** `skills/_shared/parallel-aware-auq.md` documents the
92
+ four-step handshake plus a message template; `skills/wave-executor/wave-loop.md` §
93
+ Scope Manifest 3.1a adds a `peer-session-<id>` record (rolled over each wave, cleared at
94
+ session-end) that is included in the disjoint check but excluded from the coordinator's own
95
+ file-scope union; a new branch in the `parallel-sessions.md` decision tree routes to it; and
96
+ `hooks/post-bash-write-verify.mjs` now partitions a peer's writes from genuine scope
97
+ violations instead of flagging both alike.
98
+ - **Session-start now writes its own identity before anything can read it wrong (#1199).**
99
+ Phase 1.05 adds a self-report block (`session_id: null` before Phase 1.2 resolves it) so
100
+ downstream consumers see an explicit unresolved state instead of inferring one.
101
+ - **`/evolve` and the auto-dialectic nudge now emit their own completion events (#1200).**
102
+ `orchestrator.evolve.completed` and `dialectic.completed` are emitted via `emit-event.mjs`
103
+ from inside the skill; `decideAndRecordAutoDialectic()` emits `dialectic.nudge_decided`
104
+ mechanically rather than leaving the decision only in skill prose, consumed by
105
+ `phase-3-6-tail.md`.
106
+
107
+ ### Changed
108
+
109
+ - **BREAKING (behavior): `discovery-validator.enabled` defaults to `false` again (#1191).**
110
+ Reverts the #690 flip of 2026-06-25. Fleet measurement (2026-09-02, 18 repos): 6,946
111
+ `discovery_validator_violation` events had accumulated in repos that never declared the
112
+ block, and a scope-adjusted n=60 sample put real precision at 0/60 — the hook was scanning
113
+ the MAIN transcript on `SubagentStop`, not the subagent's own. It now reads the subagent
114
+ transcript (`agent_transcript_path` when the harness sends it, else
115
+ `<dir>/<base>/subagents/agent-<agent_id>.jsonl` — no fallback to the main transcript), and
116
+ the violation event now carries `agent_id`. Repos that relied on the silent default lose the
117
+ signal until they set `enabled: true` explicitly.
118
+ - **Scope enforcement now treats every identity as process-local, including your own
119
+ (#1194).** `enforce-scope.mjs` reads session ids via a new `readProcessLocalSessionIds()`
120
+ instead of `readOwnSessionIds()` — a peer's manifest sitting in the SAME working copy now
121
+ classifies as `foreign`, not `own`. Two existing tests had asserted the inverted behaviour
122
+ and were rewritten with a named ceiling (a harness with no session id, e.g. bare
123
+ Codex/Cursor, resolves to `unknown` → enforce, never skip); `scope-collision-guard.md` and
124
+ `state-ownership.md` were updated to match.
125
+ - **`memory.propose`'s lock check is now a raw→semantic lookup, not a trust boundary
126
+ (#1188).** The lock is consulted only to translate the caller's raw id into its semantic
127
+ session id, authorised by a process-local match against the RAW id — measured: a dispatched
128
+ subagent carries the COORDINATOR's raw UUID in `CLAUDE_CODE_SESSION_ID`, never its own.
129
+ `sessions-canonical.mjs` and the backfill path now distinguish silent `ENOENT` from loud
130
+ `EACCES`/`EISDIR` in two separate ledger readers instead of treating every read failure the
131
+ same.
132
+ - **Four duplicated helper implementations collapse to one each.**
133
+ `scripts/lib/validate/markdown-fences.mjs` (#1181) replaces 4 line-start-only fence
134
+ scanners with one line-start-AND-end-anchored automaton shared by all 4 callers
135
+ (`validate-plugin` output byte-identical before/after, 203/0). `expandTilde` (#1182)
136
+ replaces 8 copies of home-directory expansion with the existing `common.mjs`
137
+ implementation — one of the deleted copies (`gitlab-portfolio/cli.mjs`) mis-expanded
138
+ `~user` (a different user's home, not the caller's). `hooks/_lib/subagent-paths.mjs`
139
+ (#1196) replaces 4 divergent sidecar-path derivations with one carrying the strictest rules
140
+ from any of them (agent-id length `{1,64}`, `'unknown'` rejected,
141
+ `agent_transcript_path` confined inside the transcript directory).
142
+ `hooks/_lib/atomic-json.mjs` (#1197) replaces 4 byte-identical copies with one whose
143
+ read-modify-write default only replaces a genuinely MISSING file (`ENOENT`) — an
144
+ unparsable or unreadable file is no longer silently overwritten with the default.
145
+ - **A block-header matcher gained an indent+inline-value form, additively (#1185).**
146
+ `matchBlockHeaderDetailed` is now shared so `health-endpoints.mjs` no longer maintains its
147
+ own `HEADER_RE`.
148
+ - **Five readers now go through the canonical `sessions.jsonl` collapse instead of counting
149
+ raw lines (#1186).** `telemetry/sync.mjs` and `build-live-signals.mjs` (wave 2), plus
150
+ `site-numbers.mjs` and `vault-mirror.mjs --kind session` (wave 3), now read
151
+ newest-per-`session_id`; on this repo the session count measured 289 lines → 278 sessions
152
+ (11 duplicate lines collapsed). Two tests pinning the old raw-line count were removed as no
153
+ longer testing real behaviour.
154
+ - **Wave-completion refusals are now named events, not silent skips (#1201).**
155
+ `KNOWN_TRIGGERS` plus three explicit wrapper functions (`runReconcileFromSkill`/
156
+ `AtSessionEnd`/`FromPhaseSkip`) replace an implicit trigger string — an unrecognised
157
+ trigger now throws instead of silently defaulting. `emitFinalWaveCompleted` emits
158
+ `orchestrator.wave.final_refused` for all six refusal paths instead of leaving them
159
+ unrecorded — closing the same class of instrument gap named in
160
+ `.claude/rules/host-resources.md` § HR-105.
161
+
162
+ ### Fixed
163
+
164
+ - **`orchestrator.agent.stopped` carried `agent: ""` in 86.7% of fleet records (#1190).**
165
+ 89,991 of 103,763 historical records carried an empty `agent` because
166
+ `input?.agent_type ?? 'unknown'` never fires on an empty string — the harness's actual
167
+ shape. `agent` is now omitted when empty (never `'unknown'`, never a fabricated `0`/`false`).
168
+ Seven optional sidecar-derived fields were added — `agent_id`, `transcript_found`,
169
+ `tool_use_id`, `agent_type_meta`, `duration_ms` + `duration_source` (`meta-birthtime`), and
170
+ `status` (`done|partial|blocked|failed|no-tests-needed`, from the last line-anchored
171
+ `STATUS:` marker in the final 64 KiB of the agent transcript) — each independently omitted
172
+ when unmeasured, each wrapped in its own try/catch, with a charset guard on `agent_id`
173
+ before path interpolation. `CURRENT_SCHEMA_VERSION` is unchanged (additive). Measured
174
+ `status` coverage: 61.7% (71 done / 3 partial / 46 absent) over 120 completed sidecars.
175
+ - **`commands[]` was empty in every usage ping (#1189).** `classifyInvocationName` routes
176
+ plugin-prefixed command names (`session-orchestrator:session`) into `commands[]` under the
177
+ bare name — the prefix is required, so a bare foreign name never becomes one of our
178
+ commands — and `.command`-field names are prefixed before classification, since that field
179
+ is itself the "this is one of ours" provenance signal a bare `.skill` arrival lacks.
180
+ - **The final `orchestrator.wave.completed` of every session was never emitted (#1193).**
181
+ Fleet-wide: 1,018 `started` vs. 722 `completed`, a gap of exactly 296 — one per wave run.
182
+ `hooks/on-session-end.mjs` now emits it at SessionEnd, idempotent via a
183
+ `last_wave_completed` high-water mark written monotonically by both emitters, gated on
184
+ ownership of `current-session.json` via the RAW stdin session id (not the resolved one), and
185
+ skipped on `reason: clear`. `hooks/post-tool-batch-wave-signal.mjs` gained the same
186
+ raw-stdin-id ownership check, so it can no longer write wave keys into a peer session's
187
+ record either.
188
+ - **`orchestrator.session.stopped.duration_ms` was a literal `0` in 8,127 of 8,127 fleet
189
+ records.** The harness never sends `start_ms`, so the old expression fell back to a
190
+ fabricated zero — indistinguishable from a real instant turn. It is now derived from an
191
+ OWNED `.orchestrator/session.lock` (`duration_source: 'session-lock'`, session-elapsed
192
+ measured at turn end) or omitted entirely, never `0`.
193
+ - **`resolveSession()`'s ownership predicate was self-fulfilling** when the stdin `session_id`
194
+ was absent or non-UUID: it fell back to the recorded id and then compared the fallback
195
+ against itself. Fixed at the root (`rawStdinId !== null && rawStdinId === recordedId`) in
196
+ `hooks/on-session-end.mjs` for `duration_ms`, `semantic_session_id` and the new wave-completed
197
+ emit, plus the sibling site `resolveSessionId()` in `hooks/on-stop.mjs`.
198
+ - **A live local `session.lock` no longer hides a same-working-copy registry peer (GH#67).**
199
+ Registry-sourced entries in `scripts/lib/session-discovery.mjs` / `peer-discovery.mjs` now
200
+ carry additive `registryOnly` / `lockSuperseded` / `lockOwnerId` annotations instead of
201
+ being filtered out — `lockSuperseded: true` is a HINT (the lock is advisory, so the entry
202
+ may still be a live session that lost the acquire race), never a verdict. Only
203
+ PROMOTION_OFFER decisions downgrade such a peer to advisory; peer-count and display
204
+ consumers keep it. Lock-sourced sessions carry none of the three fields, so their shape
205
+ stays byte-identical to pre-GH#67.
206
+ - **Worktree-promotion teardown is now mechanical, not skill prose (#1170).**
207
+ `enterWorktree()` takes an optional `rawSessionId`; when given, it calls `leaveSourceRoot()`
208
+ on the OLD root after the destination provably exists and records the outcome on
209
+ `result.left`. Before #1170, four promotion call sites relied on coordinator prose to
210
+ release the source root — measured zero `deregisterSelf`/`release(` call sites across the
211
+ affected skill docs, so the abandoned registry entry advertised a phantom peer for up to an
212
+ hour.
213
+ - **`memory.propose` filed wave-N+1 proposals into the wave-N quota bucket (#1166).**
214
+ `scripts/memory-propose.mjs` now resolves the RUNNING wave from `<state-dir>/wave-scope.json`
215
+ first (when present and unbound or bound to this session), falling back to `STATE.md
216
+ current-wave + 1` only when no usable manifest exists — `current-wave` itself always
217
+ records the JUST-COMPLETED wave.
218
+ - **`health-endpoints` silently dropped the wizard's own nested-block output (#1174).** The
219
+ key was read off the flat KV map via `_coerceList`, which bails to `null` on any `{` and
220
+ cannot see a nested YAML block at all. `scripts/lib/config/health-endpoints.mjs` now parses
221
+ it content-scoped, accepting inline object arrays, the nested block form (top-level or
222
+ under `ecosystem-health:`), and a bare URL list; `ecosystem-health`'s own valueless
223
+ block-header form is now read as a fallback when the scalar key is absent.
224
+ - **`emitEvent()`'s automatic session/semantic-session fill now requires a process-local
225
+ witness (#1177).** `CLAUDE_CODE_SESSION_ID` (or the hook-input session id) must exactly
226
+ equal the lock's raw `session_id` before `session_id`/`semantic_session_id` are
227
+ auto-filled — STATE.md is never treated as a witness. `wave` is filled the same way, only
228
+ from a `wave-scope.json` manifest bound to this same session, and is coerced to an integer.
229
+ See `docs/events-schema.md` § Correlation keys.
230
+ - **The memory banner's "sessions ever" stat now counts distinct sessions, not JSONL lines
231
+ (#1167).** `sessions.jsonl` is append-only, so one physical session can occupy two lines;
232
+ on this repo the stat drops from 286 lines to 275 sessions. Five readers now go through
233
+ the canonical collapse: cold-start detection, the memory banner, harness-audit Category 1,
234
+ evolve's autopilot-effectiveness/verdict, and autopilot's id-set guard.
235
+ - **`check-skill-script-paths` no longer goes blind past an unbalanced code fence (#1176).**
236
+ An unclosed/unbalanced fence now reports as its own `unbalanced-fence` finding instead of
237
+ silently absorbing the rest of the file as "inside a fence"; a fence opened inside a
238
+ blockquote is now recognised as a fence too.
239
+ - **`sessions-canonical.mjs`'s `supersedes` collapse now requires an attestable join key
240
+ (#1167).** A `supersedes` pointer on an abandoned record is honoured only when it can be
241
+ matched to the record it claims to supersede; rule ordering was fixed so a superseded
242
+ record's synthetic double-stub twin is collapsed too. A new `keepUnidentified` option lets
243
+ callers retain records the canonicalizer could not confidently classify instead of
244
+ dropping them.
245
+ - **`memory.propose` no longer trusts an unbound `wave-scope.json` (#1177).** A manifest
246
+ without a `semantic_session` binding is a peer's or a stale artefact, not this session's
247
+ own — it is now ignored with a stderr note instead of adopted; an unreadable/malformed
248
+ manifest also WARNs on stderr before falling back to `current-wave + 1`, rather than
249
+ failing silently.
250
+ - **The vault-status board lock's stale-override is now surfaced, not silent (#1180).**
251
+ `onLockOutcome` reports a `staleOverride` reason when the lock was force-acquired past a
252
+ stale holder, and `sweepBoard()`'s board event now records it too — distinguishing "the
253
+ mutex worked as designed" from "a slow host looked like a crash".
254
+ - **`check-hooks-emit-event-guard` and `check-validator-registration` both went live in
255
+ `validate-plugin` (#1183, #1184).** `check-hooks-emit-event-guard.mjs` (#1183) parses hooks
256
+ via `@babel/parser` (AST, not regex) to catch a hook that mutates state without emitting a
257
+ corresponding event; 5 pre-existing sites are grandfathered into an explicit WARN baseline
258
+ with a reason and stale-detection instead of silently passing. `check-validator-registration.mjs`
259
+ (#1184) confirms every validator module is actually wired into `validate-plugin.mjs`
260
+ (measured 33 of 33 wired, using comment-stripped source so a commented-out registration
261
+ doesn't count as live).
262
+ - **`check-skill-script-paths`'s new `--strict-sh` mode found its own premise refuted
263
+ (#1187).** Extending the dead-path scan to shell-script citations, only 1 of 27 `.sh`
264
+ references sits inside the tool's own scan directories (`skills/`, `commands/`, `agents/`)
265
+ — the other 26 are in `docs/`, alongside 7 dead `.mjs` citations inside `docs/adr/` the same
266
+ run surfaced. Shipped as a WARN-only opt-in flag; the `docs/` sweep is a follow-up.
267
+ - **The discovery-validator's dedupe key, gate-summary skip, and masking order all landed
268
+ together (#1198).** A dedupe key (`session_id`, `agent_id`, `sha256(claim)`) collapses
269
+ repeated flags of the identical claim; gate-summary recap lines (the coordinator's own
270
+ restatement of a subagent's findings, not a fresh claim) are now skipped entirely; claim
271
+ masking now runs before every pattern is checked, not a subset. Measured against the
272
+ flagged corpus: 186 of 400 flagged claims were false positives. Two claims from this
273
+ session's own discovery briefing were checked against the fix and refuted.
274
+ - **A pre-push scratch-vs-publish check now reads the two arguments git already passes it.**
275
+ `.husky/pre-push` had never read `$1` (remote name/URL) or `$2` (remote URL) — confirmed by
276
+ `rg -n '\$1|\$2' .husky/pre-push` finding only a local function's own arguments — so every
277
+ push ran the full quality gate regardless of destination. It now skips the gate for a raw,
278
+ unconfigured scratch URL (e.g. the remote-offload sync target) while continuing to gate
279
+ real publish remotes; a named ceiling covers a real but unlisted third publish host, which
280
+ still reads as scratch.
281
+ - **`check-untracked-test-deps` misclassified `.git` as an untracked file inside a linked
282
+ worktree.** In a normal checkout `.git` is a directory and was already excluded; in a
283
+ `git worktree add` checkout `.git` is a FILE containing `gitdir: <path>`, which satisfied
284
+ the same `isFile()` check the untracked-candidate branch uses. `.git` is now explicitly
285
+ exempted regardless of which shape it takes — measured 62 of 62 tests passing across the 5
286
+ affected test files inside a linked worktree checkout.
287
+
288
+ ## [3.23.0] - 2026-08-28
289
+
290
+ Two commits today (1 `feat`, 1 `fix`; 64 files, +4,971/−184) close Waves 3 and 4 of the
291
+ resumed session, plus eight items carried forward from Wave 2 (`36aa605`, 2026-08-26,
292
+ never previously changelogged). No `BREAKING CHANGE:` footer and no `!` subject. The
293
+ pattern repeats across both waves: a guard, sanitizer, or census that already existed but
294
+ checked the wrong population or stopped short of the surface it needed to reach — a parity
295
+ guard sampling five rule files by name instead of reading the directory, a sanitizer whose
296
+ findings never reached stderr, a byte-count drifting 338 lines from the line it cited, a
297
+ tracked directory hiding an untracked hook beside it.
298
+
299
+ One `feat` commit (session-2, `30940cb`, 2026-08-28; 39 files, +2,475/−405) closes Wave 2
300
+ with three GitHub-mirror fixes and a session-identity cluster at the process boundary. No
301
+ `BREAKING CHANGE:` footer and no `!` subject. The through-line: identifiers the platforms
302
+ mint (UUIDv7, semantic session ids) were rejected or dropped by readers built for UUIDv4
303
+ only — every resume looked foreign — and call sites that needed a repo root passed
304
+ `undefined` instead, so fail-open paths ran on every invocation.
305
+
306
+ ### Added
307
+
308
+ - **Eight core rules ship as sanitized copies, with a report-only leak scanner (#1098).**
309
+ `rules/always-on/` holds sanitized copies of `ask-via-tool`, `build-value`,
310
+ `loop-and-monitor` and five more; `scanVendoringLeaks()` now reaches stderr as one line
311
+ per finding in both CLIs, folded into `validate-vendored-rules` with a rule-ID census.
312
+ - **Session Config fails loud on an unparsable line (#1097).** `collectUnparsableLines`
313
+ gates warn/strict via one `htmlCommentSkipper` shared by extractor and classifier — a
314
+ commented-out `enforcement: strict` had been reading as live config.
315
+ - **A read-only protection-audit CLI, with a runbook naming the order (#1079).** States
316
+ the required sequence; never flips a setting itself.
317
+ - **The scope guard now emits `orchestrator.wave_dispatch.scope_checked` (#1092).** Closes
318
+ the ledger half that never recorded a signal-free ALLOW.
319
+ - **`/journey-audit` — a user-facing product audit as a repeatable skill (#1161).** Skill,
320
+ command and manifest template; skill/command counts measured at 48/28.
321
+ - **A Cursor `.mdc` parity guard that reads the directory, not a fixed list (#1093).**
322
+ Catches bare `PID=`/`${VAR}` IDs a name-sampling guard had missed; `priority::` swept
323
+ across all 5 files.
324
+ - **The old root now formally leaves before the new one claims it, wired at all four
325
+ promotion sites (#1069).** `scripts/lib/session-transition.mjs` → `leaveSourceRoot()`:
326
+ `deregisterSelf()` → lock-release (reusing `on-session-end`'s ownership rules) →
327
+ `orchestrator.session.root_left` (carries `source_root_hash`/`source_root_basename`,
328
+ never an absolute path — the payload also travels over the optional webhook). Review
329
+ found the Phase-1.2.1 promotion site had been missing the call; it is now wired at
330
+ session-start Phase 0.5, Phase 1.2.1, and both `parallel-aware-*` docs. A real
331
+ `git worktree` two-root integration test goes phantom-peer red → green.
332
+
333
+ ### Fixed
334
+
335
+ - **Six onboarding contradictions + a `cursor-install` TARGET guard (#1078).** First
336
+ production diff authored by a foreign model (composer-2.5) under mandatory Claude review.
337
+ - **Amber is now a documented normal state (#1062).** `docs/ci-setup.md` names it instead
338
+ of leaving it an unexplained CI color.
339
+ - **A tracked directory no longer hides an untracked hook beside it (#1158).**
340
+ `hooksPathIsTracked` now requires a GIT-tracked file directly under `core.hooksPath`.
341
+ - **`probe-stale` collapses to one vocabulary term (#1159).** A record older than 7 days
342
+ is `probe-stale`, severity `warn`, never a live finding; the registry remap is gone.
343
+ - **The scope-cap saw operators, not shell keywords (#1145).** `splitSegments` now splits
344
+ on keywords too, so `for t in a b c; do glab issue create; done` no longer slips a
345
+ compound-statement head past the cap.
346
+ - **A missing `.claude/rules/` was misdiagnosed as a loop (#1132).** `err.code` now
347
+ separates silent `ENOENT`/`ENOTDIR` from loud `EACCES`; the repeat was caller fan-out.
348
+ - **Two probes measured the wrong population (#1143/#1148).** One counted a whole file and
349
+ cited a line that had already drifted 338 lines from the finding; both scanners read the
350
+ filesystem instead of the git index, censoring 5 gitignored files as documentation.
351
+ - **Generated rules now self-verify offline (#1101).** Each carries a sha256 seal over its
352
+ own Evidence block instead of a learning-key into a gitignored file; 23 rules sealed,
353
+ fresh-clone warnings 23 → 0.
354
+ - **`reconcile.targets` had zero consumers (#1099).** Now wired through with a closed enum,
355
+ visible rejection of unknowns, and per-target path confinement.
356
+ - **Orphaned wave-scope files survived re-materialization (#1103).** Reconciled by
357
+ ownership proof now, never a blind directory wipe — two parallel sessions can share one
358
+ wave-N directory (PSA-003).
359
+ - **Two rival vendoring paths wrote the same rule file (#1060).** `rules/` is now sole SSOT
360
+ for PSA-001..007; `templates/_shared/rules/` is deleted.
361
+ - **`parseSessionId` accepted only UUIDv4; Codex mints v7 (#66/#1091).**
362
+ `scripts/lib/session-id.mjs` now accepts RFC-9562 v1–8 (`UUID_RE`, additive `version`
363
+ field — see `### Removed` below for the alias this superseded). Every SessionStart had
364
+ minted a fresh v4; a
365
+ resumed/compacted thread read its own lock as a foreign session. `hooks/on-stop.mjs` and
366
+ `hooks/on-session-end.mjs` now apply the writer's acceptance rule, closing a lock-leak via
367
+ non-UUID stdin ids.
368
+ - **phase-3-7a called `updateFrontmatterFieldsOnDisk(undefined, …)` (#65/#1036).**
369
+ `skills/session-end/phase-3-7a-recommendations.md` requires a repo root; every `/close`
370
+ took the fail-open path — 0 of 5 recommendation fields were ever written. The snippet now
371
+ binds `repoRoot` via `git rev-parse --show-toplevel`; the catch logs the cause.
372
+ - **Codex MCP launch resolved to `/scripts/mcp-server.sh` from non-git cwd (#64).** Codex
373
+ copies a marketplace plugin to
374
+ `~/.codex/plugins/cache/<marketplace>/session-orchestrator/<version>/` and starts the MCP
375
+ child with no plugin-root env var. `.mcp.json` and `scripts/lib/plugin-root.mjs` gained a
376
+ plugin-cache scan tier (name-matched `package.json`, newest by mtime). Codex snapshots
377
+ `.mcp.json` at install time — existing installs need a reinstall.
378
+ - **`scoring.mjs` read a flat `completion_rate` 0 of 281 records carry (#1071).** The value
379
+ lives under `effectiveness`, so the high-completion bonus was unreachable.
380
+ `resolveMemoryDir`/`buildLiveSignals` now take an explicit `repoRoot` instead of
381
+ `process.cwd()`.
382
+ - **mission-status recovery aborted on the first non-canonical body line (#1104).**
383
+ `scripts/lib/state-md/mission-status.mjs` now skips per line, reports skipped lines
384
+ (`recoverFrontmatterMissionStatusDetailed`). `setMissionStatus` refuses ids outside the
385
+ grammar with a stderr WARN from the on-disk wrapper.
386
+ - **`enterWorktree` failed with `already used by worktree` when the source branch was
387
+ checked out (#1067).** `scripts/lib/autopilot/worktree-pipeline.mjs` now creates
388
+ `so/<sessionId>` from that branch and returns `branch`/`promotedFrom`.
389
+ - **Phase 4a auto-promoted-worktree cleanup was dead under the #1069 process boundary.**
390
+ Since the promoted worktree runs as a brand-new session with its own id,
391
+ `detectAutoPromotedWorktree()`'s basename match against the CURRENT session id could
392
+ never fire post-#1069. `enterWorktree()` now writes `.orchestrator/promoted-from.json`
393
+ (source-root hash + basename, source session id, branch) at creation time;
394
+ `detectAutoPromotedWorktree()` tries this marker FIRST (`source: 'marker'`), falling back
395
+ to the legacy basename match (`source: 'basename'`) for pre-marker worktrees.
396
+ `isWorktreeClean()` discounts exactly the marker's own untracked line so the marker never
397
+ reads as dirty by itself. Re-promoting onto an existing `so/<sessionId>` branch now reuses
398
+ it (`reusedBranch: true`) or reports a typed refusal, instead of failing outright. See
399
+ ADR-0013.
400
+ - **`.mcp.json`'s bash bootstrap had silently drifted from `resolvePluginRoot()`'s tier
401
+ order.** The shell mirror now matches the same five env tiers in the same order, tests
402
+ `package.json`'s `name` field via `node -e JSON.parse(...)` instead of a substring `grep`
403
+ (which had matched `session-orchestrator-fork`), and ties the newest-by-mtime cache scan
404
+ to the same strict `-nt` comparison as the JS side. Two drift tests pin JS and shell
405
+ staying in sync.
406
+ - **`ci-status-banner.mjs` escaped control bytes in the JSON preview but not in the
407
+ parse-error message.** `JSON.stringify()` alone is not enough — V8 quotes the raw
408
+ offending input inside `SyntaxError.message`, carrying control bytes straight to the
409
+ terminal. `escapeControlBytes()` now covers both the preview AND `err.message`;
410
+ `parseCliJson()` also gained a shape-gate against `null`/`[]`/a bare `"ok"` string, all of
411
+ which parse as valid JSON but are not the object/array shape callers expect.
412
+ - **A refused mission-status write warned on only 1 of 5 call paths.**
413
+ `setMissionStatusDetailed()` now reports the refusal reason (e.g. `id-grammar`) to every
414
+ caller, and the on-disk wrapper emits the stderr WARN on all 5 paths instead of the one it
415
+ happened to cover before.
416
+ - **`check-unwired-features` exempted itself from its own edge census via its allowlist
417
+ string, hiding a real orphan behind it.** The `SELF_REL` self-reference correctly
418
+ contributes zero edges by design, but the same code path was masking that
419
+ `scripts/lib/webhook-url.mjs` has zero callers repo-wide — genuinely unreachable, not a
420
+ false positive. Follow-up issue pending to wire it in or remove it.
421
+ - **Three load-sensitive tests hardened with margin instead of being disabled.**
422
+ `tests/scripts/validate-plugin.test.mjs`'s child-process timeout moved from 30s to 120s (a
423
+ killed child returns `status: null`, which had read as a different failure than a real
424
+ one) plus a matching `hookTimeout` bump, and two further spawn-timeout assertions gained
425
+ the same discriminating margin between contention and a genuine failure.
426
+
427
+ ### Changed
428
+
429
+ - **Host-registry census counts `semantic_session_id` (#1066).** `hooks/on-session-start.mjs`
430
+ falls back to `session_id` only when it is itself semantic. Before, only UUIDs were
431
+ projected — the n-increment discards them — so the registry contributed nothing to
432
+ numbering.
433
+ - **Session end events carry `semantic_session_id`; backfill event + supersede (#1068).**
434
+ `orchestrator.session.ended` / `.stopped` carry `semantic_session_id` (omitted when
435
+ unknown). A new `orchestrator.session.backfill_completed` event records each backfill outcome. A
436
+ completed record may now supersede an `abandoned` stub (append-only, `supersedes` marker).
437
+ Readers preferring the newest record are a follow-up.
438
+ - **`.semgrep.yml` regains two taint-mode rules (#1129).** `json-parse-untrusted-input` and
439
+ `prototype-pollution-object-assign` aimed at this repo's real trust boundary (hook stdin,
440
+ child-process stdout); 27 rules total. `unsafe-llm-output-rendering` stays excluded with a
441
+ measured reason (no DOM code).
442
+
443
+ ### Removed
444
+
445
+ - **`UUID_V4_RE` removed** from `scripts/lib/session-id.mjs` — deprecated alias of
446
+ `UUID_RE` with zero importers repo-wide (measured @ 7daa3d2; hits in
447
+ `tests/telemetry/anon-id.test.mjs` and `server/ingest/validate.mjs` are unrelated local
448
+ constants of the same name). Use `UUID_RE`, any RFC 9562 version 1–8.
449
+
450
+ ### Notes
451
+
452
+ Two of this session's diffs were authored by foreign models under the #1150 adapter —
453
+ #1078 by composer-2.5, #1093 by grok-4.6 — each passing mandatory Claude review before
454
+ landing. First production use of the foreign-dispatch path.
455
+
10
456
  ## [3.22.0] - 2026-08-22
11
457
 
12
458
  Twenty commits (12 `fix`, 5 `docs`, 2 `feat`, 1 `chore`; 150 files, +17,312/−2,575), no
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Session Orchestrator
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
- [![Version](https://img.shields.io/badge/version-3.22.0-blue.svg)](CHANGELOG.md)
4
+ [![Version](https://img.shields.io/badge/version-3.24.0-blue.svg)](CHANGELOG.md)
5
5
  [![npm](https://img.shields.io/npm/v/session-orchestrator.svg)](https://www.npmjs.com/package/session-orchestrator)
6
- [![Tests](https://img.shields.io/badge/tests-12%2C000%2B-brightgreen.svg)](docs/telemetry/telemetry-claims.md)
6
+ [![Tests](https://img.shields.io/badge/tests-15%2C000%2B-brightgreen.svg)](docs/telemetry/telemetry-claims.md)
7
7
 
8
8
  Loop engineering for AI coding agents — turn ad-hoc sessions into a repeatable research → plan → wave-execute → close loop with verification gates. Runs on **Claude Code, Codex CLI, Cursor, and [Pi](docs/pi-setup.md)**.
9
9
 
@@ -52,6 +52,8 @@ That is the whole loop. `/plan` and `/evolve` extend it (see [Lifecycle](#lifecy
52
52
 
53
53
  ## Quick Start
54
54
 
55
+ Run `/bootstrap` in your project repo first — it writes `.orchestrator/bootstrap.lock`, which session-start requires before `/session` will run.
56
+
55
57
  Add a `## Session Config` section to your project's `CLAUDE.md` (Claude Code and Cursor IDE) or `AGENTS.md` (Codex CLI and Pi) — see [instruction-file-resolution](skills/_shared/instruction-file-resolution.md) for which file each platform reads. The smallest valid config is seven fields:
56
58
 
57
59
  ```yaml
@@ -70,11 +72,11 @@ Everything else is opt-in. See [`docs/session-config-template.md`](docs/session-
70
72
 
71
73
  ## What you get
72
74
 
73
- - **45 skills** for the session lifecycle (start, plan, execute, close, evolve), discovery, vault sync, MCP authoring, debugging, brainstorming, plan grilling, persona panels, cross-repo dispatch, learning→rule reconciliation, session-process eval, audits, and more
74
- - **24 slash commands** (`/session`, `/go`, `/close`, `/discovery`, `/plan`, `/grill`, `/evolve`, `/autopilot`, `/dispatcher`, `/reconcile`, `/eval`, `/test`, `/debug`, …)
75
+ - **49 skills** for the session lifecycle (start, plan, execute, close, evolve), discovery, vault sync, MCP authoring, debugging, brainstorming, plan grilling, persona panels, cross-repo dispatch, learning→rule reconciliation, session-process eval, audits, and more
76
+ - **28 slash commands** (`/session`, `/go`, `/close`, `/discovery`, `/plan`, `/grill`, `/evolve`, `/autopilot`, `/dispatcher`, `/reconcile`, `/eval`, `/test`, `/debug`, …)
75
77
  - **15 typed subagents** (code-implementer, test-writer, security-reviewer, session-reviewer, qa-strategist, architect-reviewer, …)
76
78
  - **10 hook event types** enforcing scope, blocking destructive commands, gating templates-first, capturing telemetry — full on Claude Code; experimental, post-hoc, or bridged on the other platforms ([Platform support](#platform-support))
77
- - **10,000+ vitest tests** run on every commit ([telemetry methodology](docs/telemetry/telemetry-claims.md))
79
+ - **15,000+ vitest tests** run on every commit ([telemetry methodology](docs/telemetry/telemetry-claims.md))
78
80
 
79
81
  Full component inventory: [`docs/components.md`](docs/components.md).
80
82
 
@@ -127,22 +129,25 @@ The system is markdown-driven config plus a thin Node runtime — skills, comman
127
129
  - **Five typed waves, not one big batch.** Discovery first, so implementers start with shared context. Impl-Core before Impl-Polish, so architecture lands before integrations. Quality runs a *simplification pass* on AI-generated code **before** tests are written — otherwise tests pin the AI patterns into place.
128
130
  - **Inter-wave reviews, not just end-of-session.** Catching regressions between waves — not only at the end — stops a bad pattern from propagating into later work; the confidence floor filters speculative criticism so only high-signal findings reach you.
129
131
  - **State persists across crashes.** `STATE.md` records wave progress and deviations; the next `/session` offers to resume from the last completed wave.
130
- - **Hooks enforce, not just warn.** A pre-Bash guard blocks destructive shell commands, and pre-Edit scope enforcement blocks writes outside an agent's allowed paths — in main sessions and subagent waves alike (specifics in [Safety](#safety)). This hard enforcement is full on Claude Code; it degrades to experimental / post-hoc / bridged on Codex CLI, Cursor IDE, and Pi (see [Platform support](#platform-support)).
132
+ - **Hooks enforce, not just warn.** A pre-Bash guard blocks destructive shell commands, and pre-Edit scope enforcement blocks writes outside an agent's allowed paths — in main sessions and subagent waves alike (specifics in [Safety](#safety)). This hard enforcement is full on Claude Code; Cursor and Pi reach it through payload bridges; Codex is still pending a real `apply_patch` adapter (see [Platform support](#platform-support)).
131
133
  - **Cross-session learning is opt-in and inspectable.** Every session writes a record; after 5+ sessions `/evolve analyze` extracts confidence-scored patterns you can read and prune. Nothing is hidden.
132
134
  - **VCS dual support, no lock-in.** Auto-detects GitLab or GitHub from your remote and drives the full lifecycle for both.
133
135
 
134
- ## Recent highlights (v3.22.0)
136
+ ## Recent highlights (v3.24.0)
135
137
 
136
- Every release is additive and backward-compatible. Highlights of the v3.22.0 line:
138
+ Every release is additive and backward-compatible. Highlights of the v3.24.0 line: parallel-session identity moves from repo-scoped to process-local, remote work gets a declared host channel, and four duplicated-copy classes collapse to one module each:
137
139
 
138
- - **The warning that fired on 99.0% of all session starts (#1089)** — measured over **1,477 session-start events across 18 repos**, against 4,884 stop events with zero OOM markers. Three independent measurement errors, not one threshold among them: `os.freemem()` on Darwin reports only `Pages free` (median **0.4 GB** on hosts with 24–128 GB installed), a threshold named for *sessions* was compared against a *process* count (measured ratio **6.0:1** — same number, right denominator: 93.6% **4.2%**), and one noisy axis could cap a wave alone. The verdict had reached the ledger for **15 of 1,734 sessions (0.9%)**, which is why the false alarm survived four months while six repos independently wrote it into their learnings store one at confidence **1.0**. A warning that fires on almost everything changes nothing except how fast you learn to ignore it.
139
- - **The questions this tool asks its operator had never been measured (#1107)** — `scripts/auq-audit.mjs` now scores every choice block against eight criteria and two hard hurdles, all thresholds from one registry. The census found **three populations where every prior count had seen one**: 40 Claude Code blocks, 10 Codex prose lists, and 17 nobody had counted. Baseline **21 of 72 (29%)**, after the pass **72 of 72**. The dominant cause was structural rather than editorial — `header` caps at 12 codepoints and truncates silently, **26 of 42 exceeded it**, and one NFD-composed header measured 12 visible characters as 14 and destroyed its own question.
140
- - **A probe that sized the wrong set and never asked git** `checkStaleArtifacts` announced "37 files (11 MB)" while computing the megabytes over the entire directory instead of the 37 it names; they weigh **0.68 MB**, a factor of ~18. Separately it proposed pruning seven **tracked, runtime-read** source files, including the policy file a hook reads on every Bash call. The git exclusion is fail-**silent**, not fail-open: falling back to "nothing is tracked" *is* the defect it closes.
141
- - **Only the raw session ID releases a lock (#1085)** — one session carried three identities (STATE.md, the lock, and issue-budget each held a different one) and `findPeers` reported the session's own presence as a foreign peer. The companion issue-budget fix removed a data-loss path where an identity-less invocation erased a live session's parked overflow.
142
- - **The npm receipt is the boundary, and a `throw` lay behind it (#1088)** — `publish()` threw *after* a successful publish during a 12-second propagation poll: published, not tagged, not pushed, reported as "failed, retryable". Each post-receipt step now gets exactly one attempt and the rest is collected as reconciliation. Leak detection decides on an extracted path, not a regex over `npm notice` prose — `contest` is not `tests`.
143
- - **Six waves ran with zero scope injection, and it looked exactly like a clean run (#1083)** — the scope declaration has two shapes; a coordinator that writes only the aggregate one degrades the chain to signal-free ALLOW. `scripts/materialize-wave-scope.mjs` is now the canonical writer of both.
140
+ - **Parallel-session identity moves from repo-scoped to process-local (#1194 / #1188)** — `enforce-scope` now reads only process-local session ids (`readProcessLocalSessionIds`), so a peer's manifest in the same working copy is treated as foreign, not as your own; `memory-propose` authorizes off a process-local match of the raw session id rather than trusting the lock file alone. A peer's declared scope also gets its own place in the wave manifest now, as a `peer-session-<id>` record instead of a silent gap (#1195).
141
+ - **Remote-offload: declare hosts, route heavy roles to a ready host instead of halving the wave (#1160)** — a `remote-hosts:` config block, an `ssh:<alias>` dispatch channel, and an offload decision wired into the resource gate right after the HR-004 cap (readiness is injected, never measured inside the gate itself). `skills/remote-offload/SKILL.md` is now the generic entry point, with adapters around the `offload` CLI.
142
+ - **Four copy-classes collapsed to one module each (#1181 / #1182 / #1196 / #1197)** atomic-JSON handling, subagent sidecar-path derivation, `expandHome`, and markdown-fence parsing each had 4–8 divergent copies; consolidating `atomic-json.mjs` also closed a data-loss path where a read-modify-write default silently replaced an unparsable or unreadable file.
143
+ - **Refusals are now events, not silence (#1200 / #1201)** — `emitFinalWaveCompleted` writes `orchestrator.wave.final_refused` for six refusal paths, evolve and dialectic completions emit their own events, and reconcile's trigger set now lives in code (`KNOWN_TRIGGERS`) instead of being asserted in prose.
144
+ - **Canonical session counting, fleet-wide (#1186)** — `site-numbers` and `vault-mirror --kind session` now count the same way everywhere: 289 raw lines in this repo's own ledger collapsed to 278 real sessions once double-counts were removed.
145
+ - **Discovery-validator false positives cut 186 of 400 measured hits were the harness's own gate-summary lines (#1198)** — a dedupe key and a gate-summary skip removed the noise; two brief claims about the masking order were also measured and refuted along the way.
146
+ - **Three new mechanical guards land in `validate-plugin` (#1183 / #1184 / #1187)** — `emitEvent` try/catch coverage, validator-registration parity (every check actually wired in), and dead `.sh` citations in docs.
147
+ - **The pre-push gate distinguishes a publish push from a scratch push; linked-worktree gates run green (15,829/0 on the remote host)** — a `.git` file (not a directory) in a linked worktree was previously misread as an untracked candidate, flagging 13 tracked files as missing.
148
+ - **The AUQ clarity guard's delivery is proven live, not asserted (#1122)** — `check-auq-clarity` is wired into `validate-plugin` and blocks on the two hard hurdles at a measured 0% false-positive rate; its own wiring closes with a fake-regression proof, not a claim.
144
149
 
145
- Previous line (v3.21.0): guard mechanics, the public site, and cross-session messaging which turned out to be one strand: a fact maintained in two places, and a guard that is green without biting.
150
+ Previous line (v3.23.0): three external bug reports fixed end-to-end (Codex session-id versions, missing `/close` fields, plugin-root resolution), the worktree-promotion process boundary, and three measurement fixes (host-registry numbering, the mode-selector's mis-shaped read, two regained Semgrep rules).
146
151
 
147
152
  Full version history: [CHANGELOG.md](CHANGELOG.md).
148
153
 
@@ -165,10 +170,10 @@ The design goal is engineering quality: every wave exits verified, every unfinis
165
170
 
166
171
  | Feature | Claude Code | Codex CLI | Cursor IDE | Pi |
167
172
  |---|---|---|---|---|
168
- | All 24 commands | Native slash commands | Native plugin commands | Rules-based (.mdc) | Prompt templates |
173
+ | All 28 commands | Native slash commands | Native plugin commands | Native `.cursor/commands` slash commands | Prompt templates |
169
174
  | Parallel agents | Agent tool | Multi-agent roles | Sequential only | Sequential (parallel planned) |
170
175
  | Session persistence | `.claude/STATE.md` | `.codex/STATE.md` | `.cursor/STATE.md` | `.pi/STATE.md` |
171
- | Scope enforcement | PreToolUse hooks | Unavailable — pending a real `apply_patch` adapter | `afterFileEdit` (post-hoc) | `tool_call` bridge |
176
+ | Scope enforcement | PreToolUse hooks | Unavailable — pending a real `apply_patch` adapter | `preToolUse` + `beforeShellExecution` via cursor-hook-bridge; `afterFileEdit` post-hoc | `tool_call` bridge |
172
177
  | AskUserQuestion | Native tool | Numbered-list fallback | Numbered-list fallback | Numbered-list fallback |
173
178
  | Quality gates | Full | Full | Full | Full |
174
179
 
package/agents/AGENTS.md CHANGED
@@ -73,7 +73,7 @@ tools: Read, Grep, Glob, Bash # comma-separated string OR JSON array (bo
73
73
  **Body conventions** (from Anthropic's `plugins/plugin-dev/agents/*` reference set):
74
74
  - Sections: `**Your Core Responsibilities:**` → `**[X] Process:**` → `**Quality Standards:**` → `**Output Format:**` → `**Edge Cases:**`.
75
75
  - Length: 500–3000 words is the recommended range. Below 500 reads as under-specified; above 3000 reads as bloated.
76
- - Read-only reviewer agents: tools `Read, Grep, Glob, Bash` (no Edit/Write). Implementer agents: `Read, Edit, Write, Glob, Grep, Bash`.
76
+ - Read-only reviewer agents: tools `Read, Grep, Glob, Bash` (no Edit/Write). Implementer agents: `Read, Edit, Write, Glob, Grep, Bash`. Agents on the escalation allowlist append `SendMessage` to either baseline — see § Escalation Channel (#1051) for the six that do and why the rest do not.
77
77
 
78
78
  ## Model Selection & Cost Routing (#768)
79
79
 
@@ -95,6 +95,25 @@ Every agent definition with `repo-write` sandbox-tier (i.e. `Edit`/`Write` prese
95
95
 
96
96
  This is deliberately more explicit than a bare "Do NOT commit" — the git index and stash are SHARED resources across concurrently-dispatched sibling agents in the same wave, and `git stash`/`git add`/`git mv`/`git rm` are index-mutating even when scoped to the agent's own files. `docs-writer.md` had NO git-write restriction at all until #724 closed the gap (its `repo-write` siblings at least carried a bare "Do NOT commit" line) — when adding a new repo-write agent, copy the ban line verbatim rather than re-deriving a weaker phrasing so this gap does not recur. See `.claude/rules/parallel-sessions.md` § PSA-007 for the full rationale and fleet evidence.
97
97
 
98
+ ## Escalation Channel (#1051)
99
+
100
+ Six agents opt into `SendMessage` so a wave-blocking obstacle can reach the coordinator **while the wave is still running**, instead of surfacing only in the final report after the wave has ended. The allowlist is exactly:
101
+
102
+ `code-implementer` · `db-specialist` · `ui-developer` · `test-writer` · `docs-writer` · `session-reviewer`
103
+
104
+ **The Nicht-Liste is deliberate, not an oversight.** `analyst`, `qa-strategist`, `architect-reviewer`, `security-reviewer`, `ux-evaluator`, `eval-judge`, `skill-applied-judge` and `dialectic-deriver` do NOT get the tool. Their output is a judgment over a whole corpus — a finding at minute three is provisional, and the analysis is complete only at the end. An early message from one of them would carry an unfinished verdict the coordinator cannot act on without re-reading the final report anyway, so the channel would add interrupts without adding information. The six above are different: each one can hit a state where its *task* becomes unfulfillable (the file it must edit does not exist, the migration target is missing, the diff under review is absent) — an obstacle, not a verdict.
105
+
106
+ **The contract every escalation-enabled agent carries** (see `.claude/rules/cross-session-messaging.md` CSM-001/004/005):
107
+
108
+ - **One message, upward only.** Exactly ONE `SendMessage` to `main` per obstacle. Never to a sibling agent — agents send upward, they cannot discover sideways.
109
+ - **Never wait for a reply.** Silence is neither refusal nor consent (CSM-004); the agent keeps working in its scope or ends `blocked`.
110
+ - **Degrade silently.** Where `SendMessage` is unavailable (disabled telemetry, non-Anthropic provider, native Windows — CSM-005), the obstacle goes in the final report instead. No code path may assume delivery happened.
111
+ - **Payload:** agent role, declared file scope, the obstacle. The send is noted in the agent's own report so the operator can see the hand-off.
112
+
113
+ **Mandate for new agents** — same shape as the git-write ban convention above: when adding a repo-write agent that belongs on this allowlist, copy the escalation block verbatim from `code-implementer.md` (adjusting only the role name) rather than re-deriving a weaker phrasing. A paraphrase that drops "never wait for a reply" or "upward only" re-opens exactly the failure modes CSM-004 and CSM-001 exist to close.
114
+
115
+ `SendMessage` does NOT lift an agent out of `read-only`: it is in `READ_ONLY_TOOLS` (`scripts/lib/validate/tier-inference.mjs`, #1049), so `session-reviewer` keeps `sandbox-tier: read-only` and every `sandbox-tier` in this directory is unchanged by the opt-in.
116
+
98
117
  ## Color Allocation Strategy (#443)
99
118
 
100
119
  `color` is an **operator side-channel**, not a cosmetic field. In a `/tmux-layout` or multi-pane session, the per-agent color lets the operator tell co-running agents apart at a glance. With only a 9-color palette (`blue | cyan | green | yellow | purple | orange | pink | red | magenta`) and more than 9 agents in this directory, some colors are **deliberately shared** — but never carelessly.
@@ -124,12 +143,12 @@ Agents MAY declare their sandbox permission tier. Valid values:
124
143
 
125
144
  | Value | Meaning | Typical tools |
126
145
  |---|---|---|
127
- | `read-only` | observes only; no file writes, no network | `Read, Grep, Glob, Bash` |
128
- | `repo-write` | may create or modify files | `Read, Edit, Write, Glob, Grep, Bash` |
146
+ | `read-only` | observes only; no file writes, no network | `Read, Grep, Glob, Bash` (+ `Skill`, `SendMessage`, `ListAgents`) |
147
+ | `repo-write` | may create or modify files | `Read, Edit, Write, Glob, Grep, Bash` (+ `Skill`, `SendMessage`, `ListAgents`) |
129
148
  | `network-allowed` | may make outbound network calls (future) | — |
130
149
  | `dangerous` | may run destructive shell commands (future) | — |
131
150
 
132
- Inference rule (backward-compat): agents without `sandbox-tier:` infer their tier from tools — `Edit` or `Write` present → `repo-write`; only `Read/Grep/Glob/Bash/Skill` → `read-only`. The validator emits **WARN**, not FAIL, when the field is absent, so existing agents continue to work during migration. Bash appears in all tiers — fine-grained Bash control is handled by `hooks/pre-bash-destructive-guard.mjs`, not by tier.
151
+ Inference rule (backward-compat): agents without `sandbox-tier:` infer their tier from tools — `Edit` or `Write` present → `repo-write`; only `Read/Grep/Glob/Bash/Skill/SendMessage/ListAgents` → `read-only`. `SendMessage` and `ListAgents` are pure agent↔coordinator communication surfaces with no filesystem write path, so they do not lift an agent out of `read-only` (`scripts/lib/validate/tier-inference.mjs` `READ_ONLY_TOOLS`, #1049) — that is what lets `session-reviewer` opt into the escalation channel while staying `read-only`. The validator emits **WARN**, not FAIL, when the field is absent, so existing agents continue to work during migration. Bash appears in all tiers — fine-grained Bash control is handled by `hooks/pre-bash-destructive-guard.mjs`, not by tier.
133
152
 
134
153
  Example:
135
154