session-orchestrator 5.4.0 → 5.6.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 (151) 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/rules/060-evolve.mdc +5 -5
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +115 -3
  7. package/README.md +30 -15
  8. package/docs/ci-setup.md +120 -23
  9. package/docs/components.md +7 -7
  10. package/docs/events-schema.md +17 -14
  11. package/docs/install.md +14 -2
  12. package/docs/migration-v4.md +24 -17
  13. package/docs/plugin-architecture-v3.md +1 -1
  14. package/docs/scope-collision-guard.md +19 -15
  15. package/docs/session-config-reference.md +7 -7
  16. package/docs/vault-docs-architecture.md +8 -4
  17. package/hooks/_lib/hook-import-set.json +17 -8
  18. package/hooks/_lib/lock-bootstrap.mjs +118 -26
  19. package/hooks/config-protection.mjs +1 -1
  20. package/hooks/enforce-commands.mjs +28 -13
  21. package/hooks/enforce-scope.mjs +201 -23
  22. package/hooks/hooks-codex.json +1 -1
  23. package/hooks/hooks.json +1 -1
  24. package/hooks/on-session-start.mjs +4 -2
  25. package/hooks/on-stop.mjs +77 -171
  26. package/hooks/post-bash-write-verify.mjs +219 -24
  27. package/hooks/post-edit-validate.mjs +15 -5
  28. package/hooks/post-tool-batch-wave-signal.mjs +41 -156
  29. package/hooks/pre-bash-memory-propose-audit.mjs +28 -11
  30. package/hooks/pre-bash-staging-fence.mjs +29 -8
  31. package/hooks/pre-task-scope-disjoint.mjs +603 -241
  32. package/hooks/wave-scope-commit-guard.mjs +235 -73
  33. package/package.json +9 -8
  34. package/scripts/apply-session-learnings.mjs +35 -14
  35. package/scripts/archive-closed-prds.mjs +38 -11
  36. package/scripts/autopilot.mjs +8 -1
  37. package/scripts/backfill-abandoned-sessions.mjs +80 -5
  38. package/scripts/backfill-learnings-from-vault.mjs +49 -22
  39. package/scripts/codex-install.mjs +3 -0
  40. package/scripts/cursor-install.mjs +46 -2
  41. package/scripts/emit-session.mjs +5 -3
  42. package/scripts/export-hw-learnings.mjs +18 -7
  43. package/scripts/fleet-checkin.mjs +26 -3
  44. package/scripts/lib/autopilot/loop.mjs +25 -10
  45. package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
  46. package/scripts/lib/blocked-commands-policy.mjs +40 -0
  47. package/scripts/lib/common.mjs +41 -0
  48. package/scripts/lib/config/vault-integration.mjs +151 -16
  49. package/scripts/lib/config.mjs +5 -1
  50. package/scripts/lib/eval/engine.mjs +13 -0
  51. package/scripts/lib/events-retention-banner.mjs +393 -0
  52. package/scripts/lib/events-rotation.mjs +21 -6
  53. package/scripts/lib/events.mjs +51 -3
  54. package/scripts/lib/fleet-protocol.mjs +20 -2
  55. package/scripts/lib/hardening.mjs +1 -0
  56. package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
  57. package/scripts/lib/instruction-budget-guard.mjs +8 -0
  58. package/scripts/lib/learnings/expiry-sweep.mjs +387 -20
  59. package/scripts/lib/learnings/io.mjs +125 -16
  60. package/scripts/lib/lock-reaper.mjs +38 -14
  61. package/scripts/lib/maintenance-due-banner.mjs +23 -8
  62. package/scripts/lib/named-vault-resolver.mjs +48 -5
  63. package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
  64. package/scripts/lib/orphan-reaper/reaper-audit.mjs +101 -69
  65. package/scripts/lib/orphan-reaper/scan-throttle.mjs +79 -27
  66. package/scripts/lib/orphan-reaper/scan.mjs +4 -0
  67. package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
  68. package/scripts/lib/orphan-reaper.mjs +5 -1
  69. package/scripts/lib/platform.mjs +206 -2
  70. package/scripts/lib/plugin-update-banner.mjs +3 -1
  71. package/scripts/lib/process-group.mjs +83 -22
  72. package/scripts/lib/scope-baseline.mjs +116 -25
  73. package/scripts/lib/scope-echo.mjs +7 -0
  74. package/scripts/lib/scope-gate.mjs +201 -27
  75. package/scripts/lib/session-close-backfill.mjs +202 -4
  76. package/scripts/lib/session-end/tail-runner.mjs +10 -5
  77. package/scripts/lib/session-lock-shape.mjs +6 -3
  78. package/scripts/lib/session-lock.mjs +320 -67
  79. package/scripts/lib/session-registry.mjs +41 -16
  80. package/scripts/lib/session-start-probes.mjs +242 -68
  81. package/scripts/lib/session-token-rollup.mjs +39 -34
  82. package/scripts/lib/sessions-staleness-banner.mjs +3 -1
  83. package/scripts/lib/tail-window.mjs +27 -3
  84. package/scripts/lib/telemetry/pricing.mjs +5 -2
  85. package/scripts/lib/telemetry/sync.mjs +17 -0
  86. package/scripts/lib/tests-src-ratio.mjs +77 -2
  87. package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
  88. package/scripts/lib/validate/check-test-value-bans.mjs +320 -32
  89. package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
  90. package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
  91. package/scripts/lib/vault-mirror/namespace.mjs +47 -26
  92. package/scripts/lib/vault-mirror/process.mjs +350 -62
  93. package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
  94. package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
  95. package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
  96. package/scripts/lib/vault-mirror/telemetry.mjs +18 -11
  97. package/scripts/lib/vault-status/board-writer.mjs +5 -1
  98. package/scripts/lib/vcs-repo-spec.mjs +8 -0
  99. package/scripts/materialize-wave-scope.mjs +7 -4
  100. package/scripts/release.mjs +1 -0
  101. package/scripts/run-migrate-v2-cross-repo.mjs +14 -2
  102. package/scripts/run-quality-gate.mjs +8 -5
  103. package/scripts/self-update.mjs +229 -0
  104. package/scripts/sweep-expired-learnings.mjs +560 -28
  105. package/scripts/validate-wave-scope.mjs +60 -6
  106. package/scripts/vault-backfill.mjs +50 -37
  107. package/scripts/vault-mirror.mjs +170 -33
  108. package/skills/_shared/fleet-protocol.md +74 -71
  109. package/skills/_shared/parallel-aware-auq.md +1 -1
  110. package/skills/_shared/state-ownership.md +38 -14
  111. package/skills/autopilot/SKILL.md +1 -1
  112. package/skills/bootstrap/SKILL.md +5 -3
  113. package/skills/bootstrap/_shared-template.md +96 -1
  114. package/skills/bootstrap/fast-template.md +3 -0
  115. package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
  116. package/skills/bootstrap/standard-template.md +2 -0
  117. package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
  118. package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
  119. package/skills/discovery/probes/vault-staleness.mjs +9 -3
  120. package/skills/docs-orchestrator/SKILL.md +12 -5
  121. package/skills/docs-orchestrator/audience-mapping.md +5 -3
  122. package/skills/evolve/SKILL.md +11 -2
  123. package/skills/evolve/references/evolve-analyze-mode.md +37 -17
  124. package/skills/session-end/learning-patterns.md +3 -3
  125. package/skills/session-end/metrics-collection.md +1 -1
  126. package/skills/session-end/phase-3-2-docs-verification.md +7 -6
  127. package/skills/session-end/plan-verification.md +25 -9
  128. package/skills/session-end/references/phase-3-documentation-updates.md +11 -1
  129. package/skills/session-end/session-metrics-write.md +45 -10
  130. package/skills/session-start/SKILL.md +3 -3
  131. package/skills/session-start/references/operations-contract.md +5 -3
  132. package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
  133. package/skills/session-start/references/phase-4-ssot-environment-check.md +8 -1
  134. package/skills/vault-mirror/SKILL.md +21 -11
  135. package/skills/wave-executor/SKILL.md +1 -1
  136. package/skills/wave-executor/circuit-breaker.md +1 -1
  137. package/skills/wave-executor/references/wave-executor-state-init.md +1 -1
  138. package/skills/wave-executor/references/wave-loop-dispatch.md +8 -0
  139. package/skills/wave-executor/references/wave-loop-review.md +2 -2
  140. package/skills/wave-executor/references/wave-loop-scope-manifest.md +14 -6
  141. package/.agents/skills/navigator/SKILL.md +0 -22
  142. package/.agents/skills/navigator/agents/openai.yaml +0 -5
  143. package/.codex-plugin/skills/navigator/SKILL.md +0 -22
  144. package/.codex-plugin/skills/navigator/agents/openai.yaml +0 -5
  145. package/.cursor/commands/navigator.md +0 -15
  146. package/.cursor/skills/navigator/SKILL.md +0 -13
  147. package/pi/prompts/navigator.md +0 -12
  148. package/skills/navigator/SKILL.md +0 -193
  149. package/skills/navigator/references/account-check.sh +0 -87
  150. package/skills/navigator/references/fleet-decisions.mjs +0 -231
  151. package/skills/navigator/references/slot-check.sh +0 -114
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "A repeatable Plan, Go, Close workflow for AI coding sessions: /session reads your repo and agrees the scope, /go runs the work in waves with a quality gate between each, /close verifies and commits. Runs on Claude Code, Codex CLI, Cursor and Pi.",
10
- "version": "5.4.0"
10
+ "version": "5.6.0"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "session-orchestrator",
4
- "version": "5.4.0",
4
+ "version": "5.6.0",
5
5
  "description": "A repeatable Plan, Go, Close workflow for AI coding sessions: /session reads your repo and agrees the scope, /go runs the work in waves with a quality gate between each, /close verifies and commits. Runs on Claude Code, Codex CLI, Cursor and Pi.",
6
6
  "author": {
7
7
  "name": "Bernhard Goetzendorfer",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "session-orchestrator",
3
- "version": "5.4.0+codex.20261001191535",
3
+ "version": "5.6.0+codex.20261003103620",
4
4
  "description": "A repeatable Plan, Go, Close workflow for AI coding sessions: /session reads your repo and agrees the scope, /go runs the work in waves with a quality gate between each, /close verifies and commits. Runs on Claude Code, Codex CLI, Cursor and Pi.",
5
5
  "keywords": [
6
6
  "session",
@@ -97,11 +97,11 @@ If zero patterns were extracted, report: "No patterns found in session history."
97
97
  ### Step 5: Write Confirmed Learnings
98
98
 
99
99
  Write through the archive-safe pipeline — never a shell redirect (#1017, #1446):
100
- 1. Read ALL existing lines from `learnings.jsonl` into memory
100
+ 1. Snapshot the store into the sidecar: `node scripts/sweep-expired-learnings.mjs --prune --snapshot .orchestrator/tmp/learnings-next.jsonl`. It writes every record of `learnings.jsonl` behind a line-1 `{"_store_generation": "sha256:…", "_store_path": "…"}` header naming the exact store state and the store they came from (#1486). Work on the SIDECAR, never on a separate read of `learnings.jsonl`. On a non-default store pass the same `--file` / `--archive` here as to the apply below. Exit `1` here (a PATH that would replace a ledger) wrote nothing: report it and stop. A malformed (unparseable) store line does not stop the snapshot: it prints a stderr WARN with the line numbers and a `malformed` count, and the apply below keeps those lines verbatim — report the count, never hand-edit the store; the one sanctioned repair is `node scripts/sweep-expired-learnings.mjs --drop-malformed --line N` (dry run by default: previews plus the store generation), then `--drop-malformed --line N --apply --generation <TOKEN> --repo-root "$(pwd)"` once the operator confirmed the previews (`--accept-embedded` for a line still holding a readable record; a changed store exits `3`)
101
101
  2. Apply confidence updates for confirmed existing learnings (+0.15, cap at 1.0; reset `expires_at` via `deriveExpiresAt(now, type)`)
102
102
  3. Apply confidence decrements for contradicted learnings (-0.2; `expires_at` unchanged)
103
103
  4. Add new learnings with confidence 0.5 in the canonical shape (see Learning JSONL Format below)
104
- 5. Write that full next generation as JSONL to a sidecar via the file-write tool at `.orchestrator/tmp/learnings-next.jsonl` (under `tmp/`, never `metrics/`: a metrics-dir sidecar makes the cleanup `rm` a blocked command, #1453), then let the `/evolve` helper prune (`expires_at` < now OR `confidence` <= 0.0), consolidate duplicates (same `type` + `subject`, highest confidence wins), archive every removed record and rewrite the store with a `.bak-<ISO>` snapshot. The `--apply` call is also `/evolve`'s only `orchestrator.evolve.completed` emit (#1206): export `N` (new learnings from item 4), `M` (existing learnings confirmed in item 2) and `DURATION_MS` (elapsed ms since this `/evolve` run started) first — without them the event records zero counters. Preview first, then apply:
104
+ 5. Edit the sidecar `.orchestrator/tmp/learnings-next.jsonl` into that full next generation via the file-write or edit tool, **keeping line 1 — the `_store_generation` header — unchanged and first** (under `tmp/`, never `metrics/`: a metrics-dir sidecar makes the cleanup `rm` a blocked command, #1453). A peer session may append to the store while you edit; the helper compares the store with the header under its lock and refuses on any difference, because such a record is absent from your sidecar and would otherwise be archived `pruned`. Then let the `/evolve` helper prune (`expires_at` < now OR `confidence` <= 0.0), consolidate duplicates (same `type` + `subject`, highest confidence wins), archive every removed record and rewrite the store with a `.bak-<ISO>` snapshot. The `--apply` call is also `/evolve`'s only `orchestrator.evolve.completed` emit (#1206): export `N` (new learnings from item 4), `M` (existing learnings confirmed in item 2) and `DURATION_MS` (elapsed ms since this `/evolve` run started) first — without them the event records zero counters. Preview first, then apply:
105
105
 
106
106
  ```bash
107
107
  NEXT=".orchestrator/tmp/learnings-next.jsonl"
@@ -111,7 +111,7 @@ Write through the archive-safe pipeline — never a shell redirect (#1017, #1446
111
111
  --repo-root "$(pwd)" && rm -f "$NEXT"
112
112
  ```
113
113
 
114
- Exit `1` = the sidecar is absent, malformed or empty; store and archive untouched.
114
+ Exit `1` = the sidecar is absent, malformed, empty, lacks its line-1 header, or was snapshotted from another store than `--file`; `2` = the prune failed inside the helper. On `1` and `2`, report the error and stop — store and archive untouched. Exit `3` = the store changed after step 1, nothing written. Retry once: snapshot into a FRESH path (`--prune --snapshot .orchestrator/tmp/learnings-next-retry.jsonl`, same `--file` / `--archive` as the apply), re-apply this run's decisions to the records in that file, and run the block above with `NEXT` set to it; a second consecutive `3`: stop and report. Never move the fresh header onto the old sidecar's records — they lack the peer change the header protects. Never delete `$NEXT` by hand: the `&&` keeps it on failure as the record of this run's decisions.
115
115
 
116
116
  Report: "Saved N new learnings, updated M existing. Total active: K."
117
117
 
@@ -148,7 +148,7 @@ Present as a numbered Markdown list (see Cursor Adaptation above):
148
148
 
149
149
  ### Step 3: Apply Changes
150
150
 
151
- Same pipeline as analyze mode Step 5: read all, modify the selected entries (a deleted entry is simply left out of the sidecar — the helper archives it), write the next generation to the sidecar, then run `node scripts/sweep-expired-learnings.mjs --prune --dry-run --json --entries "$NEXT" --repo-root "$(pwd)"` followed by `--prune --apply --json --entries "$NEXT" --repo-root "$(pwd)"`.
151
+ Same pipeline as analyze mode Step 5, exit codes included: snapshot the store into the sidecar with `--prune --snapshot "$NEXT"`, modify the selected entries in the sidecar keeping its line-1 header (a deleted entry is simply left out of the sidecar — the helper archives it), then run `node scripts/sweep-expired-learnings.mjs --prune --dry-run --json --entries "$NEXT" --repo-root "$(pwd)"` followed by `--prune --apply --json --entries "$NEXT" --repo-root "$(pwd)"`.
152
152
 
153
153
  Report: "Updated N learnings. Total active: K."
154
154
 
@@ -241,7 +241,7 @@ Each line in `.orchestrator/metrics/learnings.jsonl` is a JSON object:
241
241
  - NEVER write learnings without user confirmation
242
242
  - ALWAYS use uuid-v4 for new learning IDs (generate via `uuidgen`)
243
243
  - ALWAYS derive `expires_at` per type via `deriveExpiresAt()` unless the candidate supplies one
244
- - ALWAYS write through `scripts/sweep-expired-learnings.mjs --prune` (read all, modify, sidecar, prune) — NEVER a shell rewrite of the store
244
+ - ALWAYS write through `scripts/sweep-expired-learnings.mjs --prune` (snapshot, modify the sidecar keeping its header line, prune) — NEVER a shell rewrite of the store
245
245
  - ALWAYS cap confidence at 1.0
246
246
  - NEVER fabricate patterns -- only extract from actual session data with verifiable evidence
247
247
  - NEVER append to `learnings.jsonl` with `>>` -- the only write is the prune helper's atomic tmp-file + rename, which keeps a `.bak-<ISO>` snapshot
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "session-orchestrator",
3
- "version": "5.4.0",
3
+ "version": "5.6.0",
4
4
  "description": "A repeatable Plan, Go, Close workflow for AI coding sessions: /session reads your repo and agrees the scope, /go runs the work in waves with a quality gate between each, /close verifies and commits. Runs on Claude Code, Codex CLI, Cursor and Pi.",
5
5
  "author": {
6
6
  "name": "Bernhard Goetzendorfer"
package/CHANGELOG.md CHANGED
@@ -7,7 +7,119 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [5.4.0] - 2026-10-01
10
+ ## [5.6.0] - 2026-10-03
11
+
12
+ ### Added
13
+
14
+ - **`validate-wave-scope --assert-disjoint --no-manifest`** (#1510 f): runs the disjointness check on the per-wave sidecar alone, without a manifest on stdin, so `/plan` no longer fabricates a dummy manifest. Without the flag an empty stdin stays an error (fail-closed); combining it with `--union`, `--assert-subset`, `--expand-test-siblings` or a positional manifest is rejected.
15
+
16
+ ### Changed
17
+
18
+ - **Wave git facts follow the session root** (`hooks/post-tool-batch-wave-signal.mjs`, `hooks/on-stop.mjs`, `hooks/pre-bash-memory-propose-audit.mjs`, #1511 a–c): in a session that entered a worktree, the wave's start SHA, changed-file count, HEAD, the stop record's branch/commit and the audit record's `cwd` now describe the tree the session works in. `current-session.json`, `session.lock` and the heartbeat stay at the launch dir, where `on-session-start` writes them.
19
+ - **The commit guard also judges the session-root manifest** (`hooks/wave-scope-commit-guard.mjs`, #1511 d): a session launched in a repo subdirectory has its own manifest checked in addition to the repo-root manifest, which is judged exactly as before; either one blocking blocks the commit. Staged paths outside the session root are violations without pattern matching, and the path is resolved with `realpathSync.native`.
20
+ - **One scope-entry spelling rule** (`scripts/lib/scope-gate.mjs` `scopeEntrySpelling`, now exported, #1510 g): `pre-task-scope-disjoint` drops its drifted copy (`scripts/././lib` stayed unpromoted there, so a sibling claim compared as disjoint).
21
+ - **Close-backfill reads ledger gaps inside the session's own window** (`scripts/lib/session-close-backfill.mjs`, #1512): a recoverable `unindexed-archive` gap overlapping the session defers the candidate; a pruned `missing-archive` gap writes the record and lists `session_type`/`session_profile` (and `completed_at` when the gap runs past the last event) in `_backfill_incomplete_fields`. Gaps outside the window, or without bounds, no longer matter.
22
+ - **Website, README and npm description** lead with the problem the plugin solves and group the features by benefit (site EN/DE, guide, `llms.txt`, `llms-full.txt`).
23
+
24
+ ### Fixed
25
+
26
+ - **A sidecar changed under a standing manifest no longer downgrades violations** (`hooks/post-bash-write-verify.mjs`, #1504 point 5): the sidecar hash is bound into the hook's trusted snapshot; on a mismatch `peer-session-*` records are ignored and one notice per mismatching hash is shown. A mid-wave Peer-Scope-Union must re-run the full scope sequence including the skeleton rewrite (`skills/_shared/parallel-aware-auq.md`).
27
+ - **Destructive-command guard counts on the site** read 11 blocking of 15 rules; the policy has 12 blocking and 4 warning rules of 16.
28
+ - **`.phase-result` on the site** loses its coloured side stripe (#1357, first half).
29
+
30
+ ## [5.5.0] - 2026-10-03
31
+
32
+ ### Added
33
+
34
+ - **`events-retention` session-start probe** (`scripts/lib/events-retention-banner.mjs`, #1401 part 3, variant a). Each reader of the rotated events ledger declares `REQUIRED_EVENTS_WINDOW_DAYS` next to its call (`telemetry/sync` = 1 day, a chosen ceiling; the eval engine and `telemetry-stats` = `null`, they have no finite window). The probe warns only when the archive ring is full and covers less than the largest finite window, judged on archive start times; an unmeasured or unlistable source never records as clean. When the oldest archive's rotation record names a predecessor that was pruned (a `max-backups` raised after a prune), coverage starts at that archive, and a future-dated archive is unmeasurable rather than a negative coverage. No new config key. The probe family has 20 members.
35
+ - **`excludedScope.shell` in the tests:src envelope** (`scripts/lib/tests-src-ratio.mjs`, #1488, variant B). Shell code (`.sh`/`.bash`/`.bats`) still counts in neither bucket, so the ratio is unchanged, but its volume (split into test and src shell) is now reported. Additive; the schema stays `tests-src-ratio/2`.
36
+ - **`orchestrator.scope.refused_grant_allowed`** (`hooks/enforce-scope.mjs`, #1485 point 1): the first Gate-5b WARN of a wave (an absolute grant the wave-scope validator would refuse) emits one event, deduplicated per session and wave.
37
+ - **`isDeny` predicate** in `tests/_helpers/hook-decision.mjs` (#1027 Nachtrag 8), sharing one contract with `expectDeny` / `expectAllow` (the `isAllow` twin was removed again in the same cycle: zero consumers, #1497).
38
+ - **Probe telemetry `measure`** (`scripts/lib/session-start-probes.mjs`, #1401): a probe may declare `telemetry(result)`; its output is persisted on that probe's entry in `orchestrator.probes.completed` (at most 8 snake_case keys, finite numbers or null — the event travels the webhook unredacted). `events-retention` persists `{coverage_days, archives, max_backups, required_days}` on every measured answer, so a probe that never fires stays falsifiable (HR-105).
39
+ - **`reaper_trigger` on Stop / SubagentStop records** (`hooks/on-stop.mjs`, #1489): the orphan-scan trigger outcome is recorded as an optional key on the existing records — absent while the reaper is disabled, no new event type.
40
+ - **`reclaimed` on `orchestrator.session.lock.acquired`** (`hooks/_lib/lock-bootstrap.mjs`): `stale-heartbeat` or `corrupt` when a SessionStart took over a FOREIGN stale lock or an unparseable one (a session re-acquiring its own stale lock is a refresh and carries no key) — the bootstrap's stderr WARN is discarded by the harness, so takeovers were uncountable.
41
+ - **`navigator_adresse` in `scripts/fleet-checkin.mjs` output** (#1491): the validated lease address (or `null`) that session-start Phase 7.6 sends its optional hint to.
42
+ - **`tokens_unknown_sessions`** in the autopilot loop state, return value and `autopilot.jsonl` (#1487 item 4): an iteration whose record carries no token figure is counted instead of reading as a measured 0. The token-budget kill-switch still fires on the known lower bound.
43
+ - **`redactHomeDir(text)`** in `scripts/lib/common.mjs` (#1490): replaces the home directory with `~` in free text, path-boundary aware (`/Users/bob` never touches `/Users/bobby`).
44
+ - **`npm run update:local` — one command updates every local install** (`scripts/self-update.mjs`). Fast-forwards the clone (`git merge --ff-only origin/main`, `npm ci` when HEAD moved), updates the Claude Code marketplace and plugin, reads `claude plugin list --json` back and runs `npm ci --omit=dev` in the reported `installPath` when runtime dependencies are missing, and re-runs `scripts/codex-install.mjs` only when the plugin is already installed in Codex. `--dry-run` prints the mutating commands, `--skip-pull` leaves the clone alone, `--json` reports per step; old cache folders are never deleted because the running session loads them until the restart. The `release.mjs --publish` checklist names it as step 3, the Claude Code update banner points to it.
45
+ - **`vault-mirror --repo-root`** (`scripts/vault-mirror.mjs`, #1503): names the repo whose ledger is mirrored; without it the root is `<X>` of a `<X>/.orchestrator/metrics/<file>` source, else the git repo containing `--source` (realpath'd). A root whose namespace differs from the source's aborts before writing (exit 2, `aborted: source-repo-mismatch`). Notes gain a quoted `source-record` frontmatter field (learning id; `raw_session_id`, else `started_at`).
46
+ - **`sweep-expired-learnings --drop-malformed --line N`** (#1500): the sanctioned way to remove an unparseable learnings line. Dry run by default; `--apply` needs the store `--generation` and `--repo-root`, re-reads under the store lock, refuses a changed store or a line that is not malformed, refuses a line carrying a readable record (BOM or fused after a torn prefix) unless `--accept-embedded`, snapshots the store first and emits `orchestrator.learnings.malformed_dropped`.
47
+ - **`fallback_due_at` in `scripts/fleet-checkin.mjs` output** (#1501): check-in `zeit` + `CHECKIN_FALLBACK_MIN` while a navigator lease is active, else `zeit`; session-start Phase 7.6 reads it instead of a hard-coded 10 minutes. The check-in file schema is unchanged.
48
+ - **`commitlint.ci.config.mjs`** (#1477 item 4): the CI lint uses the repo config with `defaultIgnores: false` and first-line-only ignores for the merge and revert subjects git or GitLab write themselves, so `fixup!`, semver-only headers and body-line merges no longer slip through. The husky hook keeps the permissive config; `config-protection` warns on both files.
49
+ - **`history_gaps` in the `backfill-abandoned-sessions` summary** (#1498): counts pruned archives, so a backfill over a pruned history says it planned from an incomplete record.
50
+
51
+ ### Changed
52
+
53
+ - **Subagent worktrees branch from the session HEAD** (`.claude/settings.json` `worktree.baseRef: "head"`, #1485 point 2). Per the Claude Code docs this setting also governs `--worktree` and background sessions in this repo.
54
+ - **The worktree base check denies only a measured stale base** (`hooks/pre-task-scope-disjoint.mjs`, #1485). It reads `worktree.baseRef` in precedence order (local → project → user) at the session root, records the deciding layer as `base_ref_source`, denies an `isolation: "worktree"` dispatch only when `git merge-base --is-ancestor HEAD origin/HEAD` exits 1, skips shallow clones, unknown bases and a cached remote ref the harness would refresh first (`FETCH_HEAD` older than 24 h or absent, `skipped: 'stale-remote-ref'`), and writes no scope-ledger claim for a denied dispatch. The STATE.md `session-start-ref` prediction it replaced was wrong per the docs.
55
+ - **The `/evolve` prune flow snapshots first** (`scripts/sweep-expired-learnings.mjs --prune --snapshot`, `skills/evolve/**`, `.cursor/rules/060-evolve.mdc`, #1486). The sidecar's first line carries the store generation and path; `--entries` needs that header.
56
+ - **One orphan-scan trigger** (`scripts/lib/orphan-reaper/trigger.mjs`, #1489): `hooks/on-stop.mjs` and `hooks/post-tool-batch-wave-signal.mjs` import it instead of carrying byte-identical copies; the hardened no-follow append is shared too (`no-follow-append.mjs`).
57
+ - **Every bootstrap tier ignores the store-lock artefacts** (`skills/bootstrap/_shared-template.md` § store-lock-ignore, #1489 item 17, #1487 item 11): one idempotent snippet runs from Fast Step 3 (inherited by Standard and Deep), Upgrade Flow and Refresh-Lock Flow, so repos bootstrapped before 5.4.0 get the lines on their next refresh. It adds a missing trailing newline first (an append onto `.env` without one had un-ignored it), treats `pattern\r` in a CRLF file as present, and skips without a repository root or with an unreadable `.gitignore`.
58
+ - **The MR pipeline lints the MR title when the merge request will squash** (`.gitlab-ci.yml` `commitlint`, #1477 item 1): only when `CI_MERGE_REQUEST_SQUASH_ON_MERGE` is `"true"`, after stripping GitLab's Draft prefixes. Merge-commit MRs keep free-form titles. `docs/ci-setup.md` lists the remaining gaps.
59
+ - **`vault-backfill` reads `vault-integration` through the canonical parser and resolves `vault-dir` host-locally** (`scripts/vault-backfill.mjs`, `scripts/lib/config/vault-integration.mjs`, #1094): the inline-object, dash-bullet and bold-bullet forms are now read (19 of 56 instruction files on the measuring host returned no vault-dir before), a block inside an HTML comment is ignored, `SO_VAULT_DIR` and `owner.yaml` win over the committed path, `~` is expanded, and `gitlab-groups: null` means unset.
60
+ - **Scope drift counts uncommitted and untracked files** (`scripts/lib/scope-baseline.mjs`, #1027 item 2): the numerator is the committed range ∪ the working tree vs HEAD ∪ untracked files. The plugin's own writes are excluded (`.orchestrator/metrics/**`, untracked paths under `.orchestrator/`, `<state-dir>/filescopes/**` and `<state-dir>/worktrees/**`); tracked deliverables such as `.orchestrator/steering/` and `bootstrap.lock` count. Each query is bounded (10 s, 64 MiB); a failing query skips the measurement instead of reporting a partial numerator. Files already dirty at session start, or a same-checkout peer's files, can over-report (named ceiling).
61
+ - **A session spanning the v1/v2 token-schema boundary is reported as unpriced** — the rollup returns `total_cost_usd: null` and `sessions.jsonl` omits the field (`scripts/lib/session-token-rollup.mjs`, #1487 item 1, owner decision 2026-10-02): a v1 record is an unpriced cost candidate, so a partial sum no longer reads as the whole bill (`cost_records_priced < cost_records_total` says why).
62
+ - **Scope hooks read the session's own working copy** (`scripts/lib/platform.mjs` `resolveSessionRoot`, #1492): `enforce-scope`, `enforce-commands`, `post-bash-write-verify` and `pre-task-scope-disjoint` resolve their control files from the repo root of the payload cwd instead of `CLAUDE_PROJECT_DIR`, so a session that entered a worktree enforces that worktree's manifest. The root never climbs above the launch directory (`CLAUDE_PROJECT_DIR`; on the Cursor/Pi bridges their `*_PROJECT_DIR`, i.e. the payload cwd as before #1492), so a session launched in a monorepo package keeps its manifest; harness `agent-<hex>` worktrees lift to their parent repo, and the auto-memory carve-out still keys on the launch directory. Named ceiling: `isolation: worktree` agents of a session that entered a worktree lift to the launch checkout and stay unenforced.
63
+ - **One dispatch-scope ledger per session** (`hooks/pre-task-scope-disjoint.mjs`, #1493): `.orchestrator/wave-dispatch-scopes.<session>.json` (+ `.lock`, path-safe id) replaces the shared file, so two sessions in one checkout no longer erase each other's claims; a legacy shared ledger is adopted read-only when its wave key names this session. Wave step 7 and session-end 3.0 delete only this session's file.
64
+ - **The commit guard reads ownership, enforcement and role** (`hooks/wave-scope-commit-guard.mjs`, #1493): it stands down on a foreign session's manifest, skips under `enforcement: off`, notes a Discovery manifest, reports a stale one, and treats an empty writing-role union as an incomplete `--union` (warn reports, strict blocks). Out-of-scope staged paths under a non-empty manifest keep blocking at every other level.
65
+ - **Session-end diffs use `resolveBaselineRange`** (`skills/session-end/plan-verification.md`, docs-orchestrator, #1493): `SESSION_RANGE` is `<pinned sha>..HEAD`, else `<resolved base>...HEAD`, with explicit `no-baseline-ref` / `unresolvable-ref` skips instead of a hard two-dot `origin/main..HEAD`; the pinned ref must be hex.
66
+ - **Consumer repos ignore every plugin runtime artefact** (`skills/bootstrap/_shared-template.md` § store-lock-ignore, #1495): the snippet appends the session lock and its temps, state/rules/dispatch/transcript-tail locks, `current-session.json`, `host.json`, the staging fence, `.orchestrator/tmp/`, rewrite backups (`*.jsonl.bak-*`, legacy `*.jsonl.bak.*`) and 16 more paths a census found missing — among them `pending-dream.md` (a private MEMORY.md draft), `metrics/test-runs/` and `metrics/ux-grill/` (screenshots) and `metrics/verification-failures/`. A census test checks every `.orchestrator/` line of this repo's own `.gitignore` against the snippet; ledgers, `bootstrap.lock`, `policy/`, `steering/` and `peers/` stay versioned.
67
+ - **The SessionStart backfill bounds its archive reads** (`scripts/lib/session-close-backfill.mjs`, `scripts/backfill-abandoned-sessions.mjs`, #1498): a session whose start rotated into `_archive/` is read from the rotated ledger, under one shared 500 ms deadline per SessionStart; a candidate left unread is deferred (`skipped-history-unread`) instead of being written with now/now times. CLI and SessionEnd stay unbounded.
68
+ - **Telemetry answers that read "clean" while unmeasured** (#1498): `events-retention` reports a stampless ledger as unmeasurable (`ran-warn`); the autopilot run summary prints the token sum and `tokens_unknown_sessions` as a lower-bound note; `readGateProcessLedger` returns `read` / `absent` / `unreadable`, so a refused or dangling-symlink ledger is no longer silent; the orphan-scan trigger reads an unresolvable project dir as `disabled`.
69
+ - **B5 date-bomb scanner** (`scripts/lib/validate/check-test-value-bans.mjs`, #1499): all-caps clock constants (`NOW`, `FIXED_NOW`, `NOW_MS` …) count as clock names; only `setSystemTime(<date>)` or `useFakeTimers({ now: <date> })` freeze the date (a bare `useFakeTimers()` does not); a real-clock name bound at collection time is no longer exempted by a freeze. Corpus: 0 findings in 722 files before and after.
70
+ - **`cursor-install` prunes dead plugin links** (`scripts/cursor-install.mjs`, #1501): a `.cursor/{rules,commands,skills}` entry is removed only when it is a dangling symlink resolving inside the plugin's own `.cursor/<kind>/`; a broken link is reported as dangling instead of "symlink exists".
71
+ - **The commitlint job survives force-pushed branches and says how to repair main** (`.gitlab-ci.yml`, #1477, #1502): a non-default-branch push whose before-sha is gone lints from the merge-base with the default branch (fail-closed); a red default-branch run prints the revert remedy; the squash-title step names why a title is not linted. `docs/ci-setup.md` documents the owner settings (`suggestion_commit_message` template, `squash_option: never`) and the cancelled-pipeline ceiling.
72
+ - **Test fakes are no longer freshly written executables** (`tests/_helpers/executable-fixture.mjs` `installScriptCli`, `tests/fixtures/bin/node-script-launcher`, #1497): macOS's first-launch check flaked the suite under load (fresh node-shebang fake median 207 ms vs 34 ms).
73
+
74
+ ### Deprecated
75
+
76
+ - **`findScopeFile()`** (`scripts/lib/scope-gate.mjs`, re-exported by `scripts/lib/hardening.mjs`): it returns the first `wave-scope.json` without checking whose it is, which is the #1504 item 6 defect. No production caller is left; use `findOwnScopeFile()`. Kept for one minor cycle (`.claude/rules/development.md` § Package Lifecycle).
77
+
78
+ ### Removed
79
+
80
+ - **`/session-orchestrator:navigator` skill and command** (#1491, owner decision): the 5.4.0 package shipped fleet internals (a private host alias, internal references, calls to a CLI other users do not have). `skills/_shared/fleet-protocol.md` remains as a generic file contract; `scripts/fleet-checkin.mjs`, `scripts/lib/fleet-protocol.mjs` and the SessionStart navigator banner stay. **Not a breaking change (owner decision 2026-10-02):** a skill or slash command is not an API export under `.claude/rules/development.md` § Package Lifecycle, and this command only worked with a private CLI that other users do not have — so it ships in a minor release without a migration guide.
81
+ - **The v1 session-lock fallback** (`scripts/lib/session-lock.mjs`, harness-audit category 4, #595; sunset target was 2026-08-25): a lock without `last_heartbeat` is still visible but never live: `acquire()` reports it as `stale-heartbeat` and the SessionStart bootstrap reclaims it. Measured 2026-10-02: 8 of 8 locks and 10 of 10 registry entries on the measuring host carry the field.
82
+ - **The pre-#1404 staging-fence fallback** (`hooks/wave-scope-commit-guard.mjs`, #1487 item 16): its own revisit trigger named 5.4.0. The only fence writer, `hooks/pre-bash-staging-fence.mjs`, has written `paths` + `command_hash` and never `command` since 5.3.0, and no other hook or bridge writes fence files; an entry without `paths` is now skipped.
83
+
84
+ ### Fixed
85
+
86
+ - **vault-mirror no longer overwrites another project's notes on a slug collision** (`scripts/vault-mirror.mjs`, `scripts/lib/vault-mirror/`, #1503): a run started from another repo's checkout took the namespace from the cwd and overwrote three tracked notes of a different project. The namespace now follows the `--source` ledger (see Added; a monorepo package keeps its own namespace via the `<X>/.orchestrator/metrics/` convention), and an existing note whose `source-record` is not a key of the writing ledger is `skipped-foreign-owner` — which also stops a different-id session note from being overwritten and reported as `created`. A re-learned learning goes to the `<slug>-<uuid8>` disambiguation instead of freezing, and a note that still leaks a secret is healed over the guard.
87
+ - **Only one session wins a stale-lock takeover** (`scripts/lib/session-lock.mjs`, `hooks/_lib/lock-bootstrap.mjs`, #1494): `forceAcquire({ onlyIfNotLive })` moves the old lock to a unique tombstone and replaces it via create-or-fail, so two SessionStarts can no longer both believe they hold it; the winner keeps its enrichment through a racer's window. A `last_heartbeat` more than 5 minutes in the future is not live (mirrored in harness-audit category 4); an unreadable lock path is reported (`reason: 'unreadable'`, stderr WARN, `read_anomaly`); the `active` result carries `ageHours`.
88
+ - **vault-backfill and archive-closed-prds honour the vault-integration off switch** (#1496): neither writes into a vault while `vault-integration` is off (env `SO_VAULT_INTEGRATION`, `owner.yaml` or committed config) — an explicit `vault-backfill --vault-dir` still wins, as the run's own request; `gitlab-groups: "null"` is the group name in block and inline form alike; scheme-less strings containing `://` are redacted in logs.
89
+ - **A valid JSON line that is not an object no longer becomes an empty learning** (`scripts/lib/learnings/io.mjs`, #1500): `null`, numbers, arrays and strings count as malformed and are kept verbatim.
90
+ - **Scope bookkeeping false alarms** (#1493, #1027): `scope-echo --verify` no longer reports `injection-missing` for `coordinator.json`, `agent_count` excludes it, and `post-bash-write-verify` no longer calls `<state-dir>/filescopes/` writes violations. In a session launched from a repo subdirectory the hook rebases porcelain paths onto the session root, and a `../` path is never in scope or a "peer write".
91
+ - **A prune no longer archives learnings appended after the snapshot** (#1486). `--prune --entries --apply` compares the store generation inside the store lock and refuses on drift (exit 3, the current token is not printed); applying a sidecar to a different store, a sidecar without a header, and a `--snapshot` onto the store or archive (also via a symlinked directory or a different letter case) all exit 1. A store with unparseable lines is snapshotted with a WARN (the lines are kept verbatim on every rewrite). The Cursor `/evolve` rule had not been migrated and is now.
92
+ - **The Agent-dispatch guard keys its state on the repository the dispatch runs in** (`hooks/pre-task-scope-disjoint.mjs`, #1489): ledger, lock, wave key and events follow the git toplevel of the payload cwd, then the nearest `.git` ancestor, then `$CLAUDE_PROJECT_DIR` — after a `cd` into a subdirectory its records had landed in `<root>/sub/.orchestrator/`. All its subprocess calls share one 1500 ms budget (one hanging call had killed the hook and lost the verdict; an exhausted budget allows and records why); a stale-base deny is recorded as `deny-stale-base`, with the value it replaces in `collision_result`.
93
+ - **A pruned events history no longer reads as "never"** (`scripts/lib/events.mjs`, #1401, #1489): every reader of the rotated ledger declares its retention window (census over `scripts/`, `hooks/`, `skills/`), `scanEventsBackwards` reports gaps from a rotation record on any line, and maintenance-due answers *undeterminable*.
94
+ - **The orphan reaper's gate process ledger refuses FIFOs and symlinks** (`scripts/lib/process-group.mjs`, #1489): a FIFO planted at the ledger path no longer hangs the gate, and an inode replaced mid-append is reported as unlinked, not as a hard link.
95
+ - **Unparseable learnings-store lines survive every rewrite** (`scripts/lib/learnings/`, #1489 item 4): the expiry sweep, the prune and `export-hw-learnings` (which deleted them) write them back verbatim — `rewriteLearnings` keeps the store's own malformed lines by default — and count them as `malformed: N` (also on `orchestrator.learnings.sweep_applied`). `apply-session-learnings --apply` no longer exits 2 on such a store (it blocked the learnings write on every `/close` of that repo); it WARNs with line numbers and proceeds. A record appended after a torn last line is readable, and whitespace-only or CRLF blank lines no longer count as malformed.
96
+ - **A corrupt `.orchestrator/session.lock` no longer blocks every new session** (`scripts/lib/session-lock.mjs`, `hooks/_lib/lock-bootstrap.mjs`): `acquire()` reports it as `corrupt` and the SessionStart bootstrap reclaims it with one stderr WARN; an absent or unreadable lock keeps the conservative `active`. The lock reaper, the autopilot stall sampler and the vault board no longer show `started_at` as a heartbeat. `buildLock` always writes the lock shape (`mode` falls back to `unknown`, a non-positive or non-numeric `ttl_hours` to 4), so a lock from a caller passing `mode: undefined` is never mistaken for a corrupt one. Two SessionStarts forcing the same stale or corrupt lock no longer stamp one session's label and owner proof onto the other's lock.
97
+ - **The session-start probe runner never rejects on a throwing probe** (`scripts/lib/session-start-probes.mjs`): a throwing `severityOf`/`render` or an unprintable thrown value records the probe as `error` (`severity-threw` / `render-threw` / `probe-threw-unprintable`) instead of losing every banner line and the `orchestrator.probes.completed` record of that start; a throwing telemetry read drops only `measure`.
98
+ - **pseudonym-map warnings no longer print the home directory or map content** (`scripts/lib/vault-mirror/pseudonym-map.mjs`, #1490): V8's JSON parse error quoted the map's content.
99
+ - **Vault and home paths in logs show as `~`, and `ssh://` logins are redacted like the scp form** (`scripts/lib/vault-mirror.mjs`, `auto-commit.mjs`, `archive-closed-prds.mjs`, the `/close` metrics diagnostics, `export-hw-learnings` errors and the learnings-store WARN, #1490, #1487 items 9–10).
100
+ - **B5 reads clock bindings and fake timers from the shared setup** (`scripts/lib/validate/check-test-value-bans.mjs`, #1489 item 16): a module-level `const now = Date.now()` passed as control is caught, a `setSystemTime` in `beforeEach` no longer raises a false finding. A setup fake timer exempts only the blocks of its own or an enclosing `describe`, and only installers (`useFakeTimers`, `setSystemTime`) count.
101
+ - **The ci-status banner resolves its SHAs inside the probe budget** (`scripts/lib/session-start-probes.mjs`, #1396); the renderer no longer spawns git.
102
+ - **Orphan reaper file safety** (`scripts/lib/orphan-reaper/`, `scripts/lib/tail-window.mjs`, #1487 items 5–7): the audit append refuses symlinks and hard links; the reader opens with `O_NOFOLLOW|O_NONBLOCK` (a planted FIFO no longer hangs scan children) and is capped at 1 MiB; the throttle marker is read without following symlinks, a future mtime fails toward a scan, and a symlinked marker is replaced by a regular file on the next stamp (tmp + rename) instead of switching the throttle off for good (#1489 item 9); the false-alarm window reads N decisions, not N lines.
103
+ - **Vault logs no longer print absolute vault paths or scp login users** (`scripts/lib/vault-mirror/process.mjs`, `scripts/lib/named-vault-resolver.mjs`, #1487 items 9–10), including bracketed IPv6 remotes; the vault-relative helper matches the root by whole path segment.
104
+ - **B5 no longer counts a real-clock value as clock control** (`scripts/lib/validate/check-test-value-bans.mjs`, #1487 item 8).
105
+ - **The vault learnings backfill checks and appends under the store lock** (`scripts/backfill-learnings-from-vault.mjs`, #1487 item 12); bootstrap Step 3b ignores the lock artefacts in consumer repos (#1487 item 11).
106
+ - **`codex-install.mjs` failed with `ENOBUFS` on hosts with several Codex marketplaces** — `codex plugin list --available --json` returned 2.56 MB against `spawnSync`'s 1 MiB default (measured 2026-10-02); the buffer is now 64 MiB.
107
+ - **Claude Code skipped the plugin's dependency install** — Claude Code 2.1.287 refuses to install dependencies for a package that sets `overrides` and reports it only as `noteDetails: dependencies-refused`, leaving the 5.4.0 cache without `node_modules` (hooks still ran; skill scripts and the MCP server did not). The `overrides` block (vite, fast-uri, brace-expansion) is removed: without it npm resolves the same or newer versions and `npm audit` reports 0 vulnerabilities, full and `--omit=dev` (measured 2026-10-02); the lockfile is unchanged. `docs/install.md` § Upgrade documents the check and the manual fallback.
108
+ - **A peer session's wave manifest no longer switches this session's scope guards off** (`scripts/lib/scope-gate.mjs`, #1504 item 6): the guards took the first `wave-scope.json` in harness order (`.pi` > `.cursor` > `.codex` > `.claude`); when that one belonged to another session, `enforce-scope`, `enforce-commands`, `post-bash-write-verify` and `wave-scope-commit-guard` stood down and never read this session's own `.claude` manifest. `findOwnScopeFile()` checks ownership per candidate and skips only a provably foreign one; unbound, corrupt or unidentifiable manifests still decide. The wave-number readers (`post-edit-validate`, `post-tool-batch-wave-signal`, `pre-bash-memory-propose-audit`, `on-stop`, `run-quality-gate`) use it too, and so does the dispatch-ledger key of `pre-task-scope-disjoint` — a peer's manifest no longer supplies this session's wave number, splits its in-flight dispatch records, or toggles `gates.post-edit-validate`.
109
+ - **Wave readers, the staging fence and the orphan reaper resolve the session root** (`hooks/`, `scripts/lib/platform.mjs`, #1504 items 4 and 8, #1492 item 2): they used the launch dir while the scope guards use the session root, so a worktree session read its wave and reaped in the wrong tree. The fence is written at the git toplevel of the hook's cwd, where the commit guard reads it. The launch-dir clamp compares native realpaths, so a lowercase `/users/…` spelling on APFS no longer skips it. A nested repo or entered worktree that hides an active wave manifest records `orchestrator.scope.manifest_hidden_by_session_root` once per wave (#1504 item 3; the write is still allowed).
110
+ - **`blockedCommands` carries only command patterns** (`scripts/lib/blocked-commands-policy.mjs`, `skills/wave-executor/references/wave-loop-scope-manifest.md`, #1509): the manifest recipe copied every block rule, including the typed `redirect-truncate` rules (pattern `>`) and the `path-delete` label, so `enforce-commands` denied redirects inside `bash -c`/`sh -c`/`eval` payloads. `blockedCommandPatterns()` keeps only untyped rules; the destructive guard still enforces the typed ones.
111
+ - **Scope entries compare and grant in one spelling** (`scripts/lib/scope-gate.mjs`, `scripts/validate-wave-scope.mjs`, #1026 items 3–5): `./x`, `a//b` and `a/./b` collide with `x`/`a/b` in the disjointness check, the `allowedPaths` union is written in that canonical spelling (a `./x` entry used to be unmatchable, so the agent's own write was denied), and a schema-invalid manifest no longer hides a collision report.
112
+ - **A future-stamped registry entry or lock is never "young"** (`scripts/lib/session-registry.mjs`, `scripts/lib/lock-reaper.mjs`, #1505 items 1–2): it counted as a fresh peer forever, was never swept, and the reaper emitted a negative `age_hours`. Beyond the shared 5-minute tolerance the age is unmeasurable (`null`, never fresh); where an age only decides deletion, the file's mtime decides.
113
+ - **Lock leftovers of a killed reclaim are swept** (`scripts/lib/session-lock.mjs`, #1505 item 3): `acquire()` removes `.session.lock.tmp.*`, `.session.lock.create.tmp.*` and `.session.lock.reclaim.*` files older than one hour (regular files only; a reclaim tombstone only when it is not a live lock).
114
+ - **The close backfill no longer treats an unreadable events archive as a clean scan** (`scripts/lib/session-close-backfill.mjs`, #1505 item 5): such a candidate is deferred as `skipped-history-unread`.
115
+ - **`run-migrate-v2-cross-repo` exits 2 on read/write errors** as its help documents (#1487 item 15).
116
+ - **vault-mirror checks the generator marker on every write path** (`scripts/lib/vault-mirror/process.mjs`, #1506 item 3): the disambiguation branch overwrote a note another generator owned.
117
+ - **`export-hw-learnings` honours the vault-integration off switch** for its default output (#1506 item 4); `apply-session-learnings` resolves symlinks before pinning its write event to the repo.
118
+ - **The vault staleness probes expand `~`** (`skills/discovery/probes/vault-staleness.mjs`, `vault-narrative-staleness.mjs`): the committed `~/Projects/vault` resolved to `<cwd>/~/…` and reported a false all-clear. `ssot-code-diff` counts all 27 slash commands (`commands/*.md` + user-invocable skills) instead of 2.
119
+ - **commitlint in CI accepts merge and revert headers only from real merges and reverts** (`commitlint.ci.config.mjs`, `.gitlab-ci.yml`, #1507 item 1): an ordinary commit titled `Merge branch …` or `Revert "…"` skipped every rule. The job lints `--no-merges` and `--merges` in two passes; a revert needs git's or GitLab's `This reverts …` body line.
120
+ - **Docs match the code**: the memory-proposal quota is per wave, shared by all agents of that wave (#1027); `docs/ci-setup.md` records the GitLab settings `squash_option: never` and the conventional `suggestion_commit_message` applied 2026-10-03 (#1507 item 5); the evolve docs name the `--drop-malformed` repair.
121
+
122
+ ## [5.4.0] - 2026-10-02
11
123
 
12
124
  ### Added
13
125
 
@@ -19,7 +131,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
131
  - **Fleet check-in: session-start Phase 7.6, operations-contract peer preflight, CLI and event** (#1462). `scripts/fleet-checkin.mjs` reads the check-in JSON from stdin, stamps `zeit` (`date -u +%FT%TZ` form), validates, writes `checkin/<session_id>.json` atomically with mode 0600 (directories 0700) and emits `orchestrator.fleet.checkin` (payload `session`, `repo`, `modus`, `kandidaten`, `navigator_state`; catalogued in `docs/events-schema.md`); exit 0 written, 2 invalid input (error list on stderr, no file), 1 write error. The new Phase 7.6 in `skills/session-start/SKILL.md` (+6 lines / +1 038 B against the +12 / +1 200 B ceiling) and one sentence in `references/operations-contract.md` (+4 lines / +318 B against +4 / +400 B) place the check-in before the first writing action; a hint to the navigator is optional and never awaited (CSM-004); after 10 min without `auflagen/<session_id>.json` the reference's Standard-Auflagen apply. Test isolation: `tests/setup/navigator-dir-guard.mjs` points `NAVIGATOR_CONFIG_DIR` at a tmp dir per vitest worker, so no test reads or writes the operator's real directory.
20
132
  - **Registry field `role`** (#1462). `heartbeat(sessionId, { role })` in `scripts/lib/session-registry.mjs` accepts `'navigator'` or `null`, throws `TypeError` on anything else before touching the file, and the patch-less call `heartbeat(id)` (`hooks/on-stop.mjs`) preserves the field; `_validEntry` drops an entry whose `role` is neither `null` nor a string. Display only, never ownership — the navigator lease stays authoritative (`skills/_shared/state-ownership.md`). `registerSelf()` does not set it and `classifyMode` is untouched; this change ships no production writer (the `/navigator` skill is a later cut).
21
133
  - **Navigator banner line** (#1462). `hooks/on-session-start.mjs` appends exactly one line after the peer lines, on every harness that runs the hook: `🧭 Navigator aktiv: <adresse ?? session_id>, Check-in nach ~/.config/navigator/checkin/`, `🧭 Navigator: keiner nachweisbar (keine gültige Lease)` or `🧭 Navigator: Lease unlesbar, Status nicht nachweisbar` — never more than 120 bytes UTF-8 (a long label is cut, measured with a 128-character `adresse`; a 200-character id is refused by the lease reader and renders the "unlesbar" line), never blocking. 🧭 joins the curated emoji set in `scripts/lib/validate/check-unicode-safety.mjs`.
22
- - **Skill `navigator` — the fleet-coordinator persona as a user-invocable skill** (#1462 B5, Epic #1461). `skills/navigator/SKILL.md` (`/session-orchestrator:navigator ticker|status|start|fenster|handover`, `disable-model-invocation: true` because a coordinator role runs only on operator command; description 344 bytes against the ≤ 400 B ceiling of PRD 4.5) describes what the navigator measures, writes and refrains from: it grants no approvals („Freigaben erteile ich keine, maßgeblich ist dein Owner-Auftrag"), writes `auflagen/<session_id>.json` only through `auflagenPath()` after `isSafeSessionId()`, treats a merge "Zeichen" as its own measurement (MR opened + mergeable, head = pipeline = gate SHA, job list, main free, file overlap), starts headless runs only behind a slot gate and a busy gate, carries the verbatim foreground wait pattern, the offload path rule (worktree path, alias `m5-remote` only), the Codex rules, the account watch and the AI-budget rule, and sets `role: 'navigator'` via `heartbeat()` per ticker round — the first production writer of that registry field. Lease acquisition, renewal and handover stay S4 (agents/navigator#36): the skill writes no lease, so the session-start banner keeps reporting "keiner nachweisbar" until S4 lands. Schemas are not repeated; `skills/_shared/fleet-protocol.md` stays the single reference (its obsolete `path-check: planned` marker for the PRD is removed and it now links the skill). Generated mirrors under `.agents/`, `.codex-plugin/`, `.cursor/` and `pi/`; README and `docs/components.md` counts move to 52 skills / 28 slash commands / 26 user-invocable skills.
134
+ - **Skill `navigator` — the fleet-coordinator persona as a user-invocable skill** (#1462 B5, Epic #1461). `skills/navigator/SKILL.md` (`/session-orchestrator:navigator ticker|status|start|fenster|handover`, `disable-model-invocation: true` because a coordinator role runs only on operator command; description 344 bytes against the ≤ 400 B ceiling of PRD 4.5) describes what the navigator measures, writes and refrains from: it grants no approvals („Freigaben erteile ich keine, maßgeblich ist dein Owner-Auftrag"), writes `auflagen/<session_id>.json` only through `auflagenPath()` after `isSafeSessionId()`, treats a merge "Zeichen" as its own measurement (MR opened + mergeable, head = pipeline = gate SHA, job list, main free, file overlap), starts headless runs only behind a slot gate and a busy gate, carries the verbatim foreground wait pattern, the offload path rule (worktree path, the configured offload alias only), the Codex rules, the account watch and the AI-budget rule, and sets `role: 'navigator'` via `heartbeat()` per ticker round — the first production writer of that registry field. Lease acquisition, renewal and handover stay S4 (tracked in the navigator repository): the skill writes no lease, so the session-start banner keeps reporting "keiner nachweisbar" until S4 lands. Schemas are not repeated; `skills/_shared/fleet-protocol.md` stays the single reference (its obsolete `path-check: planned` marker for the PRD is removed and it now links the skill). Generated mirrors under `.agents/`, `.codex-plugin/`, `.cursor/` and `pi/`; README and `docs/components.md` counts move to 52 skills / 28 slash commands / 26 user-invocable skills.
23
135
  - **`skills/navigator/references/` — pure decision functions plus measurement-only shell wrappers** (#1462 B5). `fleet-decisions.mjs` exports `slotDecision` (free-memory bands 4 / 3 / 0 runs at ≥ 50 % / ≥ 35 % / below, load1 > 40 closes everything, an unreadable value yields cap 0), `busyDecision` (a Codex rollout for this checkout younger than 30 min or a `session.lock` younger than 6 h means busy; missing counts mean busy) and `chooseAccount` (usable = 5h < 97 % and 7d < 97 %, earliest 7-day reset within 3 days first, far accounts by consumption, 1 800 s tie tolerance, a row without data is never usable), all fail-closed, none reading the environment, with a stdin-JSON CLI (`slot|busy|account`, exit 0 allowed / 1 refused / 2 bad input). `slot-check.sh` and `account-check.sh` only measure (`memory_pressure`, `vm.loadavg`, `pgrep` anchored on the `timeout -k 60` wrapper because one Codex run spawns three matching processes, `find -mmin`, the operator's `NAVIGATOR_ACCOUNT_CMD`) and hand the numbers to the module; `account-check.sh` prints the decision only, never the account rows (SEC-008), and `--apply` merely shows the switch command because a switch affects every session on the host. Tests: `tests/skills/navigator/fleet-decisions.test.mjs` (24 cases, each naming its bug, every rule probed red before its fix, including that no account alias ever reaches the wrapper's output).
24
136
  - **Session Config `offload-first` — placement before pressure** (#1465, navigator decision E4). New boolean key, default `false`; `false` or absent changes nothing. With `true`, `applyOffloadDecision()` in `scripts/lib/wave-resource-gate.mjs` fires on a `proceed` verdict too, not only on `reduce` / `coordinator-direct`: an offloadable role goes to the first declared `remote-hosts` entry whose `roles-allowed` carries it and that is witnessed ready, still under the HR-004 heavy-repo cap. The offloadable table (`OFFLOADABLE_WAVE_ROLES`: `quality`/`test` → `test`, `ui`, `perf`) is not extended — build, lint and audit ride on the Quality wave's `test` role. The gate still never probes the network, so the witness (`remoteReady` or `probeFn`) is mandatory for the switch to act. A wave that stays local names why: `offload-first: no ready host for '<role>' — staying local`, or `offload-first: true but no remote-hosts declared — staying local`; `validateSessionConfig()` adds the warn-level cross-field finding `offload-first-cross-field` for the second case and never flips `ok`. Named limits: `resource-awareness: false` stays a full opt-out (no probe, no cap, no placement); the `probe failed (ignored)` path does place, since placement needs no measurement; the key is committed-only — no env or `owner.yaml` precedence, because `resolveDispatcherAutonomy` is enum-specific and `owner.yaml` freezes its sections, so reuse would need a schema change.
25
137
 
@@ -31,7 +143,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
31
143
  - **The orphan reaper is six modules behind a stable entry** (`scripts/lib/orphan-reaper/`, #1437). `scripts/lib/orphan-reaper.mjs` (1588 → 316 lines) re-exports the same 21 names and keeps the CLI; the hooks import only `scan-throttle.mjs`. The audit is read through a bounded, growing tail window and pruned past 1 MiB to its newest 512 KiB; `DEFAULT_KILL_GRACE_MS` now lives in `process-group.mjs` (still re-exported from `dispatch-common.mjs`).
32
144
  - **tests:src ratio counts TypeScript and co-located tests** (`scripts/lib/tests-src-ratio.mjs`, #1451, envelope `tests-src-ratio/2`). `.ts/.tsx/.mts/.cts/.jsx` count, declaration files do not; the test side is `tests/` plus `*.test.*`/`*.spec.*` anywhere plus `__tests__/`. A TypeScript repo now gets a real number (navigator: 1.2847) instead of "not measurable". This repo moves 1.4006 → 1.4007 at the same tree.
33
145
  - **B5 proves a clock seam only from a named clock key** (`scripts/lib/validate/check-test-value-bans.mjs`, #1478): `SEAM_CLOCK_ARG` for pass 1, the wide `CONTROL_CLOCK_ARG` for pass 2; seamed pairs 192 → 172, B5 findings unchanged at 0.
34
- - **The offload threshold has one source** (`skills/_shared/fleet-protocol.md`, `skills/navigator/SKILL.md`, #1482): `navigator m5-ersatz --quiet` and its exit contract replace the prose numbers (< 12, `last_max`).
146
+ - **The offload threshold has one source** (`skills/_shared/fleet-protocol.md`, `skills/navigator/SKILL.md`, #1482): the navigator CLI's offload check and its exit contract replace the prose numbers (< 12, `last_max`).
35
147
  - **Dependency overrides:** `brace-expansion` ^5.0.12 (two high advisories in 5.0.9, #1481) and `fast-uri` ^3.1.8 (moderate GHSA-hrr3-gc8f-f4qj); `npm audit` reports 0 vulnerabilities.
36
148
  - **`vault-mirror`'s refusal path has a subprocess test for its ledger record** (#1450 follow-up 4). `tests/unit/vault-mirror.test.mjs` spawns the CLI against a vault without a git origin and pins exit 2, the stderr refusal, exactly one `orchestrator.vault.mirror_run_completed` record with `aborted: 'vault-not-canonical'` and `total: 0`, and an untouched vault directory. The exit/stderr half was already covered by `tests/scripts/vault-mirror-entry-point.test.mjs`; the ledger record and the no-write assertion were not.
37
149
  - **`board-writer.mjs` names the `vault-dirs:` cwd-match tier in its `hostPaths` JSDoc** (#1450 follow-up 2). The last precedence twin without the tier now matches `narrative-mirror.mjs`. Comment text only.
package/README.md CHANGED
@@ -1,15 +1,17 @@
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-5.4.0-blue.svg)](CHANGELOG.md)
4
+ [![Version](https://img.shields.io/badge/version-5.6.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
6
 
7
7
  **Give your agents a working rhythm.**
8
8
 
9
+ Your coding agents get an agreed plan, a check after each round of changes and a handover to the next session. On their own, coding agents drift from the plan, overwrite each other's files and call work done before it is checked; Session Orchestrator closes those gaps.
10
+
9
11
  You type three commands:
10
12
 
11
13
  - **`/session`** reads your repository, your open issues and the last session, proposes what to work on, and waits for your correction.
12
- - **`/go`** runs the agreed work in waves of parallel agents and runs your test, typecheck and lint commands between each wave. Work that fails a check goes back to be fixed before the next wave starts.
14
+ - **`/go`** runs the agreed work in waves of parallel agents. After each implementation wave your tests and typecheck run, and failures become fix tasks for the next wave. The full test, typecheck and lint gate runs before `/close` commits.
13
15
  - **`/close`** checks every planned item against what actually happened, commits, and files the rest as issues for next time.
14
16
 
15
17
  Session Orchestrator is a free, MIT-licensed workflow plugin for **Claude Code, Codex CLI, Cursor IDE, or [Pi](docs/pi-setup.md)**. It runs on your machine and writes plain text into your repository. No account, no server, nothing to sign up for.
@@ -59,7 +61,7 @@ persistence: true
59
61
  enforcement: warn
60
62
  ```
61
63
 
62
- The first three are the commands `/go` runs between waves and `/close` runs at the end — use whatever your project actually uses. Everything else is opt-in: [full template](docs/session-config-template.md) · [every key, its type and default](docs/session-config-reference.md).
64
+ The first three are the commands the quality gate runs: tests and typecheck after each implementation wave, all three after the Quality wave and before `/close` commits — use whatever your project actually uses. Everything else is opt-in: [full template](docs/session-config-template.md) · [every key, its type and default](docs/session-config-reference.md).
63
65
 
64
66
  **3. Run the loop.**
65
67
 
@@ -73,6 +75,20 @@ On Codex the same three are `$session-orchestrator:session feature`, `$session-o
73
75
 
74
76
  In headless Claude Code (`claude -p`), `/session` and `/plan` are reserved terminal-only built-in names and the bare form is refused; use `/session-orchestrator:session` and `/session-orchestrator:plan` there. Every other command keeps its bare form.
75
77
 
78
+ ## What you get
79
+
80
+ Each item: what it does for you, then how it works.
81
+
82
+ | You get | How |
83
+ |---|---|
84
+ | **Know what to work on.** Every session starts from the real state of your project, not from a prompt you rewrite. | `/session` reads git, open issues, recent commits and the last session record, recommends one scope and waits for your correction. `/plan`, `/brainstorm` and `/grill` help while an idea is still vague. |
85
+ | **Errors stop at the next check.** A broken test is found right after the work that caused it, not after more work is built on top. | `/go` checks each wave with your tests and typecheck and turns failures into fix tasks; lint and the full suite run before the commit. `/close` checks every planned item against what happened. `/debug` looks for the root cause before a fix. |
86
+ | **Agents keep to their files.** Parallel agents do not overwrite each other, and destructive commands do not slip through. | Each agent gets a declared list of files it may change; overlapping lists are rejected before dispatch. A guard blocks 12 destructive command patterns (hard reset, `rm -rf`, force-push, `DROP TABLE`, …) and warns on 4. Hooks on Claude Code, bridges on Cursor and Pi, instructions only on Codex. |
87
+ | **Pick up where you stopped.** A crash or a second session in the same folder does not cost you the work. | `STATE.md` (a progress file in your repo) records each finished wave, and the next `/session` offers to resume. A session lock with a heartbeat keeps two sessions apart in one checkout. `/close` files what is left as GitLab or GitHub issues. |
88
+ | **It improves with your sessions.** Patterns from your own work become rules, once you approve them. | Every session appends a record. `/evolve` proposes learnings with a confidence score, `/reconcile` drafts rules from them; you approve or delete each one. Optional mirror into a Markdown notes vault. |
89
+ | **Audit, test and review on demand.** Find problems you did not ask about, and show the app works. | A discovery scan at `/close` turns confirmed findings into issues. `/test` drives web and macOS flows end to end, `/ux-grill` audits a running web app, `/test-audit` removes tests that catch nothing. `/repo-audit` and `/harness-audit` score a repository against a checklist, and `/persona-panel` has several reviewer personas read the same output in parallel. |
90
+ | **Longer runs, more repositories.** | `/autopilot` chains sessions and stops on any of 10 kill-switches. `/dispatcher` recommends the next free repository and claims it once you confirm, and `/portfolio` summarises issues, MRs and CI across the repositories registered in your notes vault. |
91
+
76
92
  ## How it works
77
93
 
78
94
  When you type `/session feature`:
@@ -80,7 +96,7 @@ When you type `/session feature`:
80
96
  1. **It reads the project.** Git state, open issues, recent commits, documentation, host resources and the records of previous sessions become a Session Overview with one recommendation.
81
97
  2. **You correct the scope.** Nothing is implemented until you agree to the plan.
82
98
  3. **The work is split into waves.** The session type decides how many: housekeeping 1, feature 3, deep 5, the ultradeep profile 7. Each wave gets a purpose, a list of file paths it may write to, and a result that can be checked.
83
- 4. **`/go` runs it.** Agents whose file scopes do not overlap run at the same time on Claude Code and Codex; Cursor and Pi run them one after another. After each wave the quality gate runs, and anything it reports goes back for correction before the next wave starts.
99
+ 4. **`/go` runs it.** Agents whose file scopes do not overlap run at the same time on Claude Code and Codex; Cursor and Pi run them one after another. After each implementation wave your tests and typecheck run, and failures become fix tasks for the next wave. The full test, typecheck and lint gate runs after the Quality wave.
84
100
  5. **`/close` checks and records.** It compares the plan against what happened, runs the full quality gate, commits file by file, and opens issues for whatever was not finished.
85
101
 
86
102
  **What it writes into your repository**, and nothing else — all of it plain text, all of it local:
@@ -95,12 +111,12 @@ When you type `/session feature`:
95
111
  .claude/STATE.md # wave progress and deviations (harness-specific directory)
96
112
  ```
97
113
 
98
- The plugin is **52 skills, 28 slash commands, 14 typed subagents and 29 hook files across 10 event types**. A slash command is a skill whose frontmatter says `user-invocable: true` (26 of them) or one of the two remaining `commands/*.md` files (`/session`, `/templates-ack`) — one definition per name, so nothing is listed twice in the `/` picker. Skills, commands and agents are Markdown with YAML frontmatter; the code that dispatches, validates and records runs in `scripts/lib/*.mjs` and `hooks/*.mjs`. There is no build step and no compiled artifact — when a session does something you did not expect, you can open the file that decided it. Full inventory: [`docs/components.md`](docs/components.md).
114
+ The plugin is **51 skills, 27 slash commands, 14 typed subagents and 29 hook files across 10 event types**. A slash command is a skill whose frontmatter says `user-invocable: true` (25 of them) or one of the two remaining `commands/*.md` files (`/session`, `/templates-ack`) — one definition per name, so nothing is listed twice in the `/` picker. Skills, commands and agents are Markdown with YAML frontmatter; the code that dispatches, validates and records runs in `scripts/lib/*.mjs` and `hooks/*.mjs`. There is no build step and no compiled artifact — when a session does something you did not expect, you can open the file that decided it. Full inventory: [`docs/components.md`](docs/components.md).
99
115
 
100
116
  ## Why it is built this way
101
117
 
102
118
  - **The wave order is deliberate.** Discovery runs first so every implementer starts from the same picture of the code. Impl-Core runs before Impl-Polish so the structure exists before anything integrates against it. The Quality wave simplifies generated code *before* tests are written — write the tests first and they assert whatever the model produced, so changing it later means rewriting the tests too.
103
- - **Checks run between waves, not only at the end.** A mistake caught after wave 2 costs one wave. The same mistake found at `/close` has already been copied into every wave after it. Findings below the configured confidence threshold are not shown to you.
119
+ - **Checks run after each implementation wave, not only at the end.** A mistake caught after wave 2 costs one wave. The same mistake found at `/close` has already been copied into every wave after it. Findings below the configured confidence threshold are not shown to you.
104
120
  - **A crash does not lose the session.** `STATE.md` records which wave finished and what deviated from the plan. The next `/session` offers to continue from the last completed wave.
105
121
  - **Two sessions in one working copy is treated as a real risk.** Two people, or two of your own sessions, in the same checkout share one git index, one filesystem and one `STATE.md`, and neither can see the other's uncommitted work. A heartbeat session lock, per-agent file-scope manifests, and the PSA rules in [`.claude/rules/parallel-sessions.md`](https://github.com/Kanevry/session-orchestrator/blob/main/.claude/rules/parallel-sessions.md) exist for exactly that case.
106
122
  - **Guards run where the harness supports them, and the table below says where it does not.** A destructive-command policy — 12 rules block, 4 warn — and file-scope enforcement run as real hooks on Claude Code, as bridges on Cursor and Pi, and as instructions only on Codex. Details: [`docs/components.md`](docs/components.md#other-surfaces).
@@ -113,7 +129,7 @@ How this compares to other orchestrators, with measured results kept separate fr
113
129
 
114
130
  | Feature | Claude Code | Codex CLI | Cursor IDE | Pi |
115
131
  |---|---|---|---|---|
116
- | All 28 commands | Native slash commands | Generated skills (`$session-orchestrator:<name>`) | Native `.cursor/commands` slash commands | Prompt templates |
132
+ | All 27 commands | Native slash commands | Generated skills (`$session-orchestrator:<name>`) | Native `.cursor/commands` slash commands | Prompt templates |
117
133
  | Parallel agents | Agent tool | Multi-agent roles | Sequential only | Sequential (parallel planned) |
118
134
  | Session persistence | `.claude/STATE.md` | `.codex/STATE.md` | `.cursor/STATE.md` | `.pi/STATE.md` |
119
135
  | Scope enforcement | Active PreToolUse hook; blocking in `strict`, reporting in `warn` | Instructions only; no compatible `apply_patch` handler | `preToolUse` + `beforeShellExecution` bridge; scope blocking requires `strict`; `afterFileEdit` is post-hoc | `tool_call` bridge; scope blocking requires `strict` |
@@ -123,16 +139,15 @@ How this compares to other orchestrators, with measured results kept separate fr
123
139
 
124
140
  All four platforms share the same skills, commands and scripts; only the hooks differ, because each harness fires different events. Codex leaves its `PreToolUse` handlers empty because these guards do not yet match its tool names and edit payloads ([why](docs/codex-setup.md#why-our-pretooluse-guards-stay-unwired--the-reason-corrected)). Cursor and Pi have known event-coverage limits — see [`docs/cursor-setup.md`](docs/cursor-setup.md) and [`docs/pi-setup.md`](docs/pi-setup.md).
125
141
 
126
- ## Recent highlights (v5.4.0)
142
+ ## Recent highlights (v5.6.0)
127
143
 
128
- Highlights of the v5.4.0 line:
144
+ Highlights of the v5.6.0 line:
129
145
 
130
- - **Fleet protocol v1 and the `navigator` skill.** Parallel sessions now check in through files instead of chat messages: `scripts/lib/fleet-protocol.mjs` owns the layout under `~/.config/navigator/`, session-start writes a check-in (Phase 7.6), and the SessionStart banner shows in one line whether a navigator lease is active, absent or unreadable. `/navigator` runs the coordinator role on operator command only and grants no approvals (#1462).
131
- - **`offload-first` places work before pressure forces it.** With the new Session Config key set to `true`, offloadable wave roles go to a ready declared remote host on a `proceed` verdict too, not only when resources are short. Default `false` changes nothing (#1465).
132
- - **A `test-audit` skill for shrinking a suite without losing proof.** A four-question gate before a new test, a focused audit, and a lane-based campaign; every delete, merge or repair needs written evidence and a production mutation the remaining test catches.
133
- - **The sessions ledger and the vault writers are honest again.** Backfill stubs no longer hide real records, token totals are rolled up mechanically so the autopilot token-budget kill-switch fires again once `--max-tokens` is set (its default is now `0`, off), and the board writer and narrative mirror refuse a vault that is not the canonical one. `vault-dir` gains a per-directory tier in `owner.yaml` (#1443, #1436, #1450, #1448).
134
- - **Vendored rules load path-scoped again.** Library rules open their frontmatter on line 1 and carry `paths:` beside `globs:`, so consumers no longer load path-scoped rules on every turn; the loader, budget guard, drift check and validator now flag a displaced block (#1449).
135
- - **Session costs never read as complete when they are not.** Each subagent now counts once in the token and cost totals (a resumed agent used to be summed per stop), a session with any unpriced agent stores no dollar figure but its coverage counters, and mixed-model agents are priced per model. Every `learnings.jsonl` read-modify-write runs under one store lock, so a concurrent append is no longer lost.
146
+ - **Clearer about what you get.** The website, this README and the npm page now open with the problem the plugin solves and list the features by benefit: what each part does for you, then how it works.
147
+ - **Sessions in a worktree or a subdirectory report their own tree.** A wave's start commit, changed files, HEAD and the stop record's branch now describe the tree the session works in, and the commit guard also checks the manifest of a session launched in a subdirectory, never less strictly than before (#1511).
148
+ - **One rule for how scope paths are spelled.** The overlap check before dispatch and the scope tools now compare paths the same way, and `validate-wave-scope --assert-disjoint --no-manifest` checks a plan's file lists without a dummy manifest (#1510).
149
+ - **A changed scope sidecar can no longer hide a violation.** If the per-wave sidecar changes under a standing manifest, peer records stop counting and you get one notice (#1504).
150
+ - **Close-backfill reads ledger gaps only inside the session's own time window**, so one pruned archive no longer holds back every later session (#1512).
136
151
 
137
152
  Full changes and verification: [CHANGELOG.md](CHANGELOG.md).
138
153
 
package/docs/ci-setup.md CHANGED
@@ -421,40 +421,137 @@ do not make it `allow_failure`, which would restore the silent hole.
421
421
 
422
422
  ## `commitlint` job (#1458)
423
423
 
424
- Runs `commitlint` (rule set: `commitlint.config.mjs`, Conventional Commits with
425
- `header-max-length 120`) over the commits a pipeline introduces. CI is needed in
426
- addition to the husky `commit-msg` hook because the hook is inactive in a fresh
427
- clone until `npx husky` has run, absent for Codex / web edits, and skipped by
428
- `--no-verify`.
424
+ Runs `commitlint` over the commits a pipeline introduces, with the CI-only rule
425
+ set `commitlint.ci.config.mjs` (= `commitlint.config.mjs`, Conventional Commits
426
+ with `header-max-length 120`, plus stricter ignores). CI is needed in addition to
427
+ the husky `commit-msg` hook because the hook is inactive in a fresh clone until
428
+ `npx husky` has run, absent for Codex / web edits, and skipped by `--no-verify`.
429
429
 
430
430
  - **Range** `<from>..$CI_COMMIT_SHA`, first match wins: `CI_MERGE_REQUEST_DIFF_BASE_SHA`
431
431
  (MR pipelines), else `CI_COMMIT_BEFORE_SHA` (branch pipelines), else the first
432
- parent of the pipeline commit (a new branch has an all-zero before-sha). The
433
- job logs the range and the commits it lints.
432
+ parent of the pipeline commit (a new branch has an all-zero before-sha, so only
433
+ its tip is linted until its MR pipeline lints the whole range). The job logs
434
+ the range and the commits it lints.
435
+ - **Force-pushed branch without an open MR (#1477 item 3, closed).** The old
436
+ before-sha is not in the fresh clone. On a branch other than
437
+ `$CI_DEFAULT_BRANCH` the job fetches `origin/$CI_DEFAULT_BRANCH` and lints from
438
+ the merge-base instead, and says so in the log. Only plain branch pipelines can
439
+ hit this: a branch with an open MR runs MR pipelines only, and `main` is
440
+ protected against force-push, so a missing base on `main` stays a red job.
441
+ The all-zero before-sha of a new branch deliberately keeps the tip-only range:
442
+ a branch created at `main`'s tip (GitLab UI, `git push origin main:x`) would
443
+ otherwise lint an empty range and go red.
434
444
  - **Fail-closed.** `commitlint --from X --to Y` exits 0 over an *empty* range
435
445
  (measured 2026-09-29, `@commitlint/cli` 19.8.1), so the script itself fails
436
- when the range is empty or its base is missing from the clone. "Nothing was
446
+ when the range is empty, its base is missing from the clone, or the fallback
447
+ cannot fetch `origin/$CI_DEFAULT_BRANCH` or find a merge-base. "Nothing was
437
448
  checked" is never a pass.
438
449
  - **`GIT_DEPTH: "0"`.** The project default clone depth is 20, so the range base
439
450
  can be absent from a shallow clone. `semgrep` uses the same setting for its
440
451
  baseline commit.
441
- - **Skipped headers.** Merge commits, `Revert "..."`, `fixup!`/`squash!` and
442
- version-only headers are skipped by commitlint's built-in default ignores
443
- (`@commitlint/is-ignored`, not `config-conventional`; measured 2026-09-29), so
444
- GitLab's own merge commits pass. The husky hook applies the same ignores.
445
- - **Coverage gap (known, measured 2026-09-29).** An MR pipeline re-lints the
446
- whole MR range on every push. A direct push is linted per push only
447
- (`before-sha..sha`), and every job here is `interruptible`, so a push pipeline
448
- auto-cancelled by the next push can leave its commits unlinted when that push
449
- lands before the validate stage finishes. Of the last 100 `main` pipelines, 11
450
- were cancelled (7 of them with direct-push commits), and in all 11 every
451
- security and validate job had already finished — 0 were exposed. A squash
452
- merge's commit (the MR title) is never linted by the MR pipeline, only by the
453
- `main` pipeline after the merge, where a red job can no longer block it.
452
+ - **Stricter ignores in CI (#1477 item 4, closed).** commitlint's built-in default
453
+ ignores (`@commitlint/is-ignored` 19.8.1) skip far more than git's own merge and
454
+ revert messages: any message with a `Merge …` line *anywhere* (the pattern is
455
+ multiline, so one body line exempts the whole message), `revert <anything>`,
456
+ `Reapply <anything>`, `amend!`/`fixup!`/`squash!`, version headers (`v1.2.3`)
457
+ and `Merged …` / `Automatic merge…` / `Auto-merged … into …`. The CI config sets
458
+ `defaultIgnores: false` and accepts only first lines git or GitLab generate:
459
+ merges (`Merge branch 'x' into 'main'` from GitLab, and git's
460
+ `Merge branch 'main' into x`, `Merge remote-tracking branch 'origin/main' into x`,
461
+ `Merge branch 'main' of <remote>`), and `Revert "…"` / `Reapply "…"`. Both CI
462
+ call sites (range and squash title) pass `-g commitlint.ci.config.mjs`.
463
+ The first line alone does not prove git or GitLab wrote it (#1507 item 1): a
464
+ normal commit titled `Merge branch 'a' into b` or `Revert "x"` used to skip
465
+ every rule. So the job lints the range in two passes that together cover it
466
+ exactly: `--git-log-args=--no-merges` with `COMMITLINT_MERGE_COMMITS=0`, where
467
+ a merge-shaped subject is linted like any other, and `--git-log-args=--merges`
468
+ with `COMMITLINT_MERGE_COMMITS=1`, where git has counted two or more parents
469
+ and the merge shape is accepted. A hand-written message on a real merge is
470
+ still linted in the second pass. A revert or reapply shape is accepted only
471
+ with the body line git or GitLab writes under it: `This reverts commit <sha>`
472
+ (git, and GitLab's revert of a single commit) or `This reverts merge request
473
+ !<iid>` (GitLab's revert of a merged MR; taken from GitLab's source, not
474
+ measured on this instance). The squash-title call sets
475
+ `COMMITLINT_MERGE_COMMITS=0`: a squash commit has one parent, so a
476
+ merge-shaped or bare `Revert "…"` MR title fails. The
477
+ husky hook keeps the lenient defaults on purpose, so a local `git revert`,
478
+ `git commit --fixup` or `git pull` merge is not blocked mid-work; a `fixup!`
479
+ commit has to be autosquashed before its MR pipeline goes green.
480
+ History check (2026-10-02 @ `ff3e43b5`): the 200 most recent `main` commits
481
+ all pass the CI config (0 failures, as under the husky config). Over the full
482
+ history (1277 commits) it fails 10, 6 of which already failed under the old
483
+ config; the 4 new ones are hand-written merge subjects from 2026-04-30 to
484
+ 2026-06-11 (e.g. `Merge branch 'x' (MR !21, …)`). The job only lints new
485
+ ranges, so old history cannot turn a pipeline red.
486
+ - **MR title on a squash merge (#1477 item 1, closed).** A squash merge commits
487
+ the MR title (the project's squash commit template is unset, so GitLab uses
488
+ `%{title}`), which no linted range contains. When
489
+ `CI_MERGE_REQUEST_SQUASH_ON_MERGE` is `"true"` (GitLab renders
490
+ `squash_on_merge?`, so the project's `always`/`never` option is included),
491
+ the job also lints `CI_MERGE_REQUEST_TITLE`, after stripping GitLab's Draft
492
+ prefixes (`Draft:`, `[Draft]`, `(Draft)`, any case, repeated). An MR that will
493
+ not squash keeps a free-form title, because its commits are what lands. A red
494
+ title blocks the merge, since the project only merges with a green pipeline.
495
+ After fixing a title, run a new pipeline for the MR. When the title is not
496
+ linted the log says why (#1502): not a merge-request pipeline, the variable is
497
+ unset (GitLab did not provide it), or the MR does not squash.
498
+ - **A non-conventional commit that reaches `main` (#1502).** Two cases slip past
499
+ every MR pipeline: the squash box ticked after the last pipeline (the variable
500
+ is fixed when the pipeline is created, and neither ticking squash nor editing
501
+ the title starts a new one), and a squash or merge commit message edited in
502
+ the merge dialog. `main` push pipelines run this job over `before-sha..sha`,
503
+ which contains the squash commit as the merge's second parent (measured
504
+ 2026-10-02: job 152074, pipeline 16004, range `84c107a1..ff3e43b5`), so the
505
+ commit is caught after the fact. `main` cannot be force-pushed, so on the
506
+ default branch a red job prints the remedy: revert what landed via an MR
507
+ (`git revert -m 1 <sha>` for a merge commit, `git revert <from>..<sha>` for
508
+ directly pushed commits), then re-land the change from a new branch with a
509
+ conventional message (`git revert --no-commit <the revert commit>`, then
510
+ `git commit -m 'fix: …'`).
511
+ Closing these before the merge was an **owner decision** (project settings,
512
+ not changed by code):
513
+ - `squash_option: never` removes the squash case entirely. Cost: low,
514
+ measured 2026-10-02: 3 of 71 merged MRs were squashed, the newest on
515
+ 2026-05-22. **Set by the owner on 2026-10-03** (was `default_off`;
516
+ verified via `glab api projects/:id` at 2026-10-03T06:47Z, #1507 item 5).
517
+ The title lint above stays as the guard should the setting ever change.
518
+ - A push rule (`commit_message_regex`) would reject an edited message at
519
+ merge time, but push rules are not available on this instance's tier
520
+ (the push-rule API answers 404).
521
+ A merge-commit message hand-edited in the dialog stays detectable only after
522
+ the fact, as above.
523
+ - **Suggestion commits (#1477 item 2, owner setting, set).** With
524
+ `suggestion_commit_message` unset, GitLab commits "Apply 1 suggestion(s) to
525
+ 1 file(s)", which fails `type-empty` / `subject-empty`. A template that passes
526
+ (measured exit 0 against the CI config, 2026-10-02):
527
+ `chore: Apply %{suggestions_count} suggestion(s) to %{files_count} file(s)`.
528
+ **Set to exactly this template by the owner on 2026-10-03** (project setting
529
+ `suggestion_commit_message`, under the merge-request settings; verified via
530
+ `glab api projects/:id` at 2026-10-03T06:47Z, #1507 item 5). Keep
531
+ `%{file_paths}` out of it if it is ever edited: the template is the header,
532
+ and a few paths break `header-max-length 120`.
533
+ - **Cancelled push pipelines (#1477 item 5, accepted ceiling).** An MR pipeline
534
+ re-lints the whole MR range on every push. A direct push is linted per push
535
+ only (`before-sha..sha`), and every job here is `interruptible` with
536
+ `auto_cancel_pending_pipelines` enabled, so a push pipeline auto-cancelled by
537
+ the next push could leave its commits unlinted if that happened before the
538
+ validate stage finished. Measured: of the last 100 `main` pipelines, 11 were
539
+ cancelled (7 with direct-push commits), and in all 11 every security and
540
+ validate job had already finished (2026-09-29); re-measured 0 of 11 affected
541
+ on 2026-10-02. No CI change: making the job non-interruptible on `main` would
542
+ stop auto-cancel for every merge and cost a full 30–40 minute pipeline each on
543
+ the constrained runner. Remedy if it recurs: lint `main` from the previous
544
+ successful `main` pipeline's SHA (needs a token that can read the pipeline
545
+ list) or from the last release tag, instead of the before-sha, which
546
+ re-covers a cancelled predecessor.
547
+ **Revisit trigger:** a non-conventional commit is found on `main` that no
548
+ pipeline linted because its pipeline was cancelled. Check with
549
+ `npx --no -- commitlint -g commitlint.ci.config.mjs --from <sha> --to origin/main`.
550
+
454
551
  Two non-conforming commits reached `main` after `commitlint.config.mjs` was
455
552
  added and before this job (`c99f57d9`, a 126-character header; `c5252e68`, a
456
- `merge:` type). Revisit when a non-conforming commit lands on `main`
457
- after this job: `npx --no -- commitlint --from <sha that added this job> --to origin/main`.
553
+ `merge:` type). The same command from the commit that added this job shows
554
+ whether anything has slipped through since.
458
555
  - Hard-`needs`-ed by `pipeline-gate` and on the shared gate rules, so it runs on
459
556
  every non-scheduled pipeline. Behavioural tests: `tests/ci/commitlint.test.mjs`
460
557
  (they execute the committed script block against temp git repos).