session-orchestrator 3.21.0 → 3.22.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 (117) 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/000-session-orchestrator.mdc +3 -2
  5. package/.cursor/rules/040-discovery.mdc +6 -6
  6. package/.cursor/rules/050-plan.mdc +8 -8
  7. package/CHANGELOG.md +101 -0
  8. package/README.md +10 -10
  9. package/agents/memory-proposal-collector.md +6 -4
  10. package/commands/eli5.md +33 -0
  11. package/commands/release.md +5 -3
  12. package/commands/test.md +2 -2
  13. package/docs/components.md +6 -5
  14. package/docs/scope-collision-guard.md +3 -3
  15. package/docs/session-config-reference.md +31 -8
  16. package/hooks/_lib/lock-bootstrap.mjs +19 -13
  17. package/hooks/hooks-codex.json +1 -1
  18. package/hooks/hooks.json +11 -1
  19. package/hooks/on-session-end.mjs +24 -92
  20. package/hooks/on-session-start.mjs +195 -104
  21. package/hooks/pre-auq-clarity.mjs +787 -0
  22. package/hooks/pre-bash-issue-budget.mjs +17 -18
  23. package/package.json +3 -1
  24. package/pi/prompts/eli5.md +12 -0
  25. package/scripts/auq-audit.mjs +825 -0
  26. package/scripts/autopilot.mjs +7 -8
  27. package/scripts/lib/auq/clarity.mjs +1314 -0
  28. package/scripts/lib/auq/parse.mjs +1006 -0
  29. package/scripts/lib/auq/schema.mjs +1457 -0
  30. package/scripts/lib/ci-status-banner.mjs +63 -57
  31. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
  32. package/scripts/lib/config/vault-integration.mjs +12 -1
  33. package/scripts/lib/dispatcher/rank.mjs +4 -7
  34. package/scripts/lib/gates/gate-full.mjs +3 -3
  35. package/scripts/lib/gates/gate-helpers.mjs +17 -6
  36. package/scripts/lib/io.mjs +239 -0
  37. package/scripts/lib/issue-budget.mjs +63 -9
  38. package/scripts/lib/owner-interview.mjs +78 -32
  39. package/scripts/lib/peer-discovery.mjs +73 -22
  40. package/scripts/lib/project-hygiene.mjs +64 -4
  41. package/scripts/lib/reconcile/renderer.mjs +17 -4
  42. package/scripts/lib/resource-probe/evaluate.mjs +330 -149
  43. package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
  44. package/scripts/lib/resource-probe.mjs +18 -2
  45. package/scripts/lib/spiral-carryover.mjs +23 -2
  46. package/scripts/lib/state-md/mission-status.mjs +147 -50
  47. package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
  48. package/scripts/lib/validate/check-hooks-symmetry.mjs +30 -0
  49. package/scripts/lib/validate/check-rules.mjs +153 -9
  50. package/scripts/lib/vault-backfill/glab.mjs +91 -58
  51. package/scripts/lib/vault-backfill/manifest.mjs +28 -8
  52. package/scripts/lib/vcs-repo-spec.mjs +182 -13
  53. package/scripts/lib/wave-resource-gate.mjs +67 -73
  54. package/scripts/materialize-wave-scope.mjs +281 -0
  55. package/scripts/release.mjs +443 -122
  56. package/scripts/run-quality-gate.mjs +14 -0
  57. package/scripts/validate-plugin.mjs +3 -0
  58. package/scripts/validate-wave-scope.mjs +6 -1
  59. package/scripts/vault-backfill.mjs +32 -5
  60. package/skills/_shared/parallel-aware-auq.md +30 -24
  61. package/skills/_shared/parallel-aware-preamble.md +31 -2
  62. package/skills/_shared/state-ownership.md +32 -6
  63. package/skills/bootstrap/SKILL.md +2 -1
  64. package/skills/brainstorm/SKILL.md +18 -18
  65. package/skills/brainstorm/soul.md +12 -0
  66. package/skills/discovery/SKILL.md +28 -24
  67. package/skills/eli5/SKILL.md +43 -0
  68. package/skills/evolve/SKILL.md +8 -9
  69. package/skills/gitlab-ops/SKILL.md +30 -26
  70. package/skills/grill/SKILL.md +6 -6
  71. package/skills/grill/soul.md +16 -0
  72. package/skills/memory-cleanup/SKILL.md +2 -2
  73. package/skills/npm-publish/SKILL.md +4 -4
  74. package/skills/peekaboo-driver/SKILL.md +3 -3
  75. package/skills/plan/SKILL.md +18 -16
  76. package/skills/plan/mode-feature.md +1 -1
  77. package/skills/plan/mode-new.md +35 -23
  78. package/skills/plan/soul.md +12 -0
  79. package/skills/reconcile/SKILL.md +3 -3
  80. package/skills/session-end/SKILL.md +53 -20
  81. package/skills/session-end/phase-3-6-tail.md +37 -2
  82. package/skills/session-start/SKILL.md +69 -35
  83. package/skills/session-start/phase-2-5-docs-planning.md +8 -8
  84. package/skills/session-start/phase-4-5-resource-health.md +82 -19
  85. package/skills/session-start/soul.md +110 -0
  86. package/skills/test-runner/SKILL.md +2 -2
  87. package/skills/using-orchestrator/SKILL.md +1 -1
  88. package/skills/wave-executor/wave-loop.md +27 -5
  89. package/skills/write-executable-plan/SKILL.md +6 -6
  90. package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
  91. package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
  92. package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
  93. package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
  94. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
  95. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
  96. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
  97. package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
  98. package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
  99. package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
  100. package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
  101. package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
  102. package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
  103. package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
  104. package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
  105. package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
  106. package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
  107. package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
  108. package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
  109. package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
  110. package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
  111. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
  112. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
  113. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
  114. package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
  115. package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
  116. package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
  117. package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "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.",
10
- "version": "3.21.0"
10
+ "version": "3.22.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": "3.21.0",
4
+ "version": "3.22.0",
5
5
  "description": "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.",
6
6
  "author": {
7
7
  "name": "Bernhard Goetzendorfer",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "session-orchestrator",
3
- "version": "3.21.0+codex.20260819195349",
3
+ "version": "3.22.0+codex.20260822193811",
4
4
  "description": "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.",
5
5
  "keywords": [
6
6
  "session",
@@ -33,10 +33,11 @@ You have Session Orchestrator capabilities. This plugin provides structured sess
33
33
  **No AskUserQuestion tool**: Present choices as numbered Markdown lists:
34
34
  ```
35
35
  Choose one:
36
- 1. Option A — description
37
- 2. Option B — description
36
+ 1. Option A (Recommended) what it does, and why it is the safe default here.
37
+ 2. Option B — when B applies instead, and what it costs.
38
38
  Reply with the number of your choice.
39
39
  ```
40
+ Two rules the example above follows. `(Recommended)` goes on option 1, in the label — an operator skims labels, so a recommendation hidden in the description is a recommendation he never sees. And every option names a reason, a cost, or a consequence: a recommendation without one is a claim he can only believe or ignore.
40
41
 
41
42
  **No TaskCreate/Update**: Use plain-text checklists to track progress:
42
43
  ```
@@ -186,9 +186,9 @@ Options:
186
186
  1. [title] — [file_path]:[line] ([severity])
187
187
 
188
188
  Options:
189
- 1. Accept all (Recommended) — create issues for all
190
- 2. Review individually
191
- 3. Dismiss all
189
+ 1. Accept all (Recommended) — one issue per finding. Fastest, and you can still close any of them later.
190
+ 2. Review individually — decide finding by finding. Costs about a minute per item.
191
+ 3. Dismiss all — no issues created, and the findings are gone when this session ends.
192
192
  ```
193
193
 
194
194
  ### Step 4: Batch Confirmation
@@ -196,9 +196,9 @@ Options:
196
196
  ```
197
197
  Ready to create [N] issues? ([X] critical, [Y] high, [Z] medium, [W] low)
198
198
 
199
- 1. Create all [N] issues (Recommended)
200
- 2. Review list first
201
- 3. Cancel
199
+ 1. Create all [N] issues (Recommended) — the list is already filtered, so nothing here is noise. You can close any of them later.
200
+ 2. Review list first — read the titles before anything is written. Costs one extra step.
201
+ 3. Cancel — nothing is written, and this run's findings are not kept.
202
202
  ```
203
203
 
204
204
  ## Phase 5: Issue Creation
@@ -29,9 +29,9 @@ If no mode specified, ask via numbered list:
29
29
  ```
30
30
  Which planning mode?
31
31
 
32
- 1. new (Recommended) — Project kickoff (full PRD, repo setup, issue creation)
33
- 2. feature — Feature PRD (compact scope, acceptance criteria, issues)
34
- 3. retro — Retrospective (metrics analysis, reflection, improvement actions)
32
+ 1. new (Recommended) — Project kickoff: full PRD, repo setup, issue creation. Fits a repo that has no PRD yet.
33
+ 2. feature — One feature: compact scope, acceptance criteria, issues. Cheaper, but assumes the project context already exists.
34
+ 3. retro — Retrospective on finished work: metrics, reflection, improvement actions. Defines no new scope.
35
35
  ```
36
36
 
37
37
  ## Phase 2: Q&A Engine (Shared Core)
@@ -154,9 +154,9 @@ If any criterion fails, revise and re-review. Maximum **3 iterations**. After 3
154
154
  The PRD reviewer flagged these remaining issues:
155
155
  [list]
156
156
 
157
- 1. Accept as-is (Recommended) — issues are minor, proceed
158
- 2. Manual edit — I'll edit the PRD myself before continuing
159
- 3. Re-run review — try one more revision round
157
+ 1. Accept as-is (Recommended) — the flagged issues are minor, so the PRD is usable now. You can still edit it later.
158
+ 2. Manual edit — you edit the PRD yourself before continuing. Costs a round-trip.
159
+ 3. Re-run review — one more revision round. Costs another review pass and may surface nothing new.
160
160
  ```
161
161
 
162
162
  Then present the saved PRD path and ask for final approval:
@@ -164,8 +164,8 @@ Then present the saved PRD path and ask for final approval:
164
164
  ```
165
165
  PRD saved to [path]. Ready for your review.
166
166
 
167
- 1. Approve PRD (Recommended) — proceed to issue creation
168
- 2. Request changes — describe what to change
167
+ 1. Approve PRD (Recommended) — proceed to issue creation. You can still edit the PRD afterwards; the issues link to it rather than copy it.
168
+ 2. Request changes — describe what to change. Costs one more revision round.
169
169
  ```
170
170
 
171
171
  ## Phase 5: Issue Creation
package/CHANGELOG.md CHANGED
@@ -7,6 +7,107 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.22.0] - 2026-08-22
11
+
12
+ Twenty commits (12 `fix`, 5 `docs`, 2 `feat`, 1 `chore`; 150 files, +17,312/−2,575), no
13
+ `BREAKING CHANGE:` footer and no `!` subject. One strand runs through all of it:
14
+ **an instrument that reported confidently and measured the wrong quantity.** Not a
15
+ threshold set too tight, not a rule too strict — the wrong quantity, reported with the
16
+ same certainty as the right one. The host-resource warning fired on **99.0% of 1,477
17
+ measured session starts** and nobody could falsify it, because the two fields it was
18
+ computed from were never persisted. A hygiene probe announced "37 files (11 MB)" while
19
+ sizing the entire directory instead of the 37 — factor ~18 — and proposed deleting
20
+ version-controlled source files, because it never asked git. And the questions this tool
21
+ puts to its operator had never been measured at all: **21 of 72 passed** on first
22
+ measurement, and the single dominant cause was a 12-codepoint header limit that silently
23
+ truncated 30 of them.
24
+
25
+ ### Added
26
+
27
+ - **The operator's questions are now measured before he reads them (#1107).** `scripts/auq-audit.mjs`
28
+ scores every `AskUserQuestion` block and every prose fallback list against eight criteria
29
+ (K1–K8) plus two hard hurdles, all thresholds sourced from one registry — no hard-coded
30
+ number in the scorer. The census found **three populations, not one**: 40 Claude Code blocks,
31
+ 10 Codex prose lists, and 17 that every prior count had missed (9 `.cursor/rules/*.mdc` with
32
+ 10 choice blocks, plus 6 runtime questions inside `.mjs`). Baseline **21 of 72 (29%)**, after
33
+ the pass **72 of 72**. The dominant cause was structural: `header` caps at 12 codepoints and
34
+ the tool truncates silently — **26 of 42 headers exceeded it**, and one NFD-composed header
35
+ measured 12 visible characters as 14, destroying the question. Population A — the operator's
36
+ own path — ran 62% failing against 0% for the Codex and Cursor forms, which carry no header
37
+ at all.
38
+ - **A guard on the guard (#1122 groundwork).** The `AskUserQuestion` PreToolUse hook denies only
39
+ the two hard hurdles and reports the rest to stderr; `check-auq-clarity` is wired into
40
+ `validate-plugin` and blocks on H1/H2 with a measured 0% false-positive rate. Its own wiring is
41
+ proven by fake regression, not asserted.
42
+ - **`/eli5`** — say the last answer again in plain words, same facts, in the order the operator
43
+ needs them.
44
+
45
+ ### Fixed
46
+
47
+ - **The warning that fired on 99.0% of all session starts (#1089).** Measured over **1,477
48
+ `orchestrator.session.started` events across 18 repos**: `ram_free_gb < 2` fired on 84.0%,
49
+ `claude_processes >= 5` on 93.6%, any of the three on **99.0%** — against 4,884 stop events
50
+ with zero OOM markers. Three independent measurement errors, no threshold among them.
51
+ (a) `os.freemem()` on Darwin reports only `Pages free` — median **0.4 GB** on hosts with
52
+ 24–128 GB — so memory is now judged on `memory_pressure_pct_free` and a better signal
53
+ *replaces* a worse one rather than suppressing it. (b) `concurrent-sessions-warn` is
54
+ denominated in sessions and was compared against a process count; measured ratio **6.0:1**.
55
+ Same threshold, right denominator: 93.6% → **4.2%**. (c) One noisy axis could cap a wave;
56
+ now two independent soft signals must agree. The verdict had reached `sessions.jsonl` for
57
+ **15 of 1,734 sessions (0.9%)**, which is why the false alarm survived four months while six
58
+ repos independently wrote it into their learnings store — one at confidence **1.0**.
59
+ Codified as `.claude/rules/host-resources.md` (HR-101..106).
60
+ - **A standing condition is not a second opinion (#1089 follow-up).** The first live run of the
61
+ rebuilt verdict returned `warn | cap 2 | soft: ["cpu","zombies"]`. Zombie processes are idle
62
+ by definition — they cannot cause the load they are paired with — and are always present
63
+ (6, 13 and 9 in three readings minutes apart). They report; they never count toward the
64
+ two-signal rule.
65
+ - **A probe that sized the wrong set and never asked git.** `checkStaleArtifacts` computed its
66
+ byte figure over the whole of `.orchestrator/` instead of the aged subset it names —
67
+ the 37 files weighed **0.68 MB against 11 MB reported**. Independently, it consulted git
68
+ nowhere and therefore proposed pruning seven tracked, runtime-read source files, among them
69
+ the policy file `pre-bash-templates-first.mjs` reads and the two steering documents
70
+ session-start loads. The git exclusion is fail-**silent**, not fail-open: falling back to
71
+ "nothing is tracked" *is* the defect it closes.
72
+ - **Only the raw session ID releases a lock (#1085).** One session carried three identities —
73
+ STATE.md said `session-2`, the lock said `session-3`, issue-budget counted against a third
74
+ UUID — and `findPeers` reported the session's own presence as a foreign peer. The semantic
75
+ ID is now attribution metadata only; a release requires exact raw-ID equality, and a
76
+ semantic hint resolves to a raw ID only against a verified local binding. The companion fix
77
+ to issue-budget removed a data-loss path in which an identity-less invocation wrote its
78
+ fresh state into the shared counter and erased a live session's parked overflow.
79
+ - **The npm receipt is the boundary — and a `throw` lay behind it (#1088).** `publish()` threw
80
+ *after* a successful `npm publish`, during a 12-second registry-propagation poll: published,
81
+ not tagged, not pushed, reported as "failed, retryable". Propagation now returns a typed
82
+ outcome, each post-receipt step gets exactly one attempt, and everything else is collected as
83
+ `post-publish-reconciliation`. Leak detection decides on an extracted path rather than a regex
84
+ over `npm notice` prose (`contest` ≠ `tests`), and `ensureGithubRelease` became tri-state so
85
+ an auth or network answer no longer reads as "no release exists".
86
+ - **`--silent` leaked into the children, and the gate discarded its own diagnosis (a4f93cf).**
87
+ - **The empty sidecar path skipped the collision gate with exit 0 (#1083).** A coordinator that
88
+ wrote only the aggregate form of the two-shape scope declaration ran **six waves and ~27
89
+ dispatches with zero `FILE-SCOPE` injection** — and the absence looked identical to a clean
90
+ run. `scripts/materialize-wave-scope.mjs` is now the canonical writer of both shapes.
91
+ - **The GitLab target comes from the explicit path (#1065).** `glab repo view --output json`
92
+ materialised a full project response to read one field; on 2026-08-17 that response carried a
93
+ runner registration credential into a tool transcript. Project identity is now
94
+ `(host, URL-encoded namespace/project)` with traversal and double-encoding rejection, and the
95
+ same minimisation reached the vault-backfill path.
96
+ - **The restore froze after its first run (#state-md).** Also: a `gitleaks` allowlist that grew
97
+ by one proper name per incident now matches on a word boundary instead.
98
+ - **Four count claims where the checker could only see two (#docs-parity).** `docs/components.md`
99
+ claimed 46 skills and 26 commands against 47 and 27 on disk. The drift checker reported two —
100
+ its patterns are `^##`-anchored — while a census of the same two surfaces in the same file
101
+ found four; the two inside the Mermaid diagram were already self-contradictory before the fix.
102
+
103
+ ### Notes
104
+
105
+ `.claude/rules/host-resources.md` is new and always-on. `.claude/rules/bash-harness-pitfalls.md`
106
+ gained no rule text this line, but two measurement traps were recorded against it in the
107
+ learnings store: `rg -rn` is not `grep -rn` (`-r` is `--replace` and silently substitutes every
108
+ match), and zsh does not word-split an unquoted variable, so a multi-path `git log -- $paths`
109
+ reports zero matches with exit 0 — both produce a plausible wrong number without erroring.
110
+
10
111
  ## [3.21.0] - 2026-08-19
11
112
 
12
113
  Twenty-one commits (12 `fix`, 4 `feat`, 4 `docs`, 1 `chore`; 209 files, +28,906/−2,893), no
package/README.md CHANGED
@@ -1,7 +1,7 @@
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.21.0-blue.svg)](CHANGELOG.md)
4
+ [![Version](https://img.shields.io/badge/version-3.22.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
  [![Tests](https://img.shields.io/badge/tests-12%2C000%2B-brightgreen.svg)](docs/telemetry/telemetry-claims.md)
7
7
 
@@ -131,18 +131,18 @@ The system is markdown-driven config plus a thin Node runtime — skills, comman
131
131
  - **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
132
  - **VCS dual support, no lock-in.** Auto-detects GitLab or GitHub from your remote and drives the full lifecycle for both.
133
133
 
134
- ## Recent highlights (v3.21.0)
134
+ ## Recent highlights (v3.22.0)
135
135
 
136
- Every release is additive and backward-compatible. Highlights of the v3.21.0 line:
136
+ Every release is additive and backward-compatible. Highlights of the v3.22.0 line:
137
137
 
138
- - **The site that proves its own numbers (#1043–#1046)** — the live page had served v3.19.0 against v3.20.0 in the repo for six days, all five legal paths were 404, and the `Measured` block the honesty argument of the whole page carried five wrong numbers. It is now generated: `scripts/site-numbers.mjs` reads **13 metrics** from the repo into `data-metric` spans, and its very first run caught three real errors, one of them the coordinator's own (`skills: page says 47, repo says 46`) and one a contract defect that would have shipped `vv3.20.0` at the next release. Deploy config moved from a dashboard into a versioned `vercel.json`. Rendered height fell to **52.4%** of the old page (10,350 5,428px), and mobile navigation previously `display:none` with no hamburger — exists.
139
- - **The documented Claude Code install path was guaranteed broken** — `claude plugin dir` is not a subcommand; it exits 1, the fallback path does not exist, `cd` fails, `npm install` never runs, and every hook afterwards cannot find `zx`. Measured on Claude Code 2.1.235. That is the cause behind external issues #62/#63, whose reporters described the symptom and **all four issues opened by people outside this repo are install or environment failures, not one a feature request**. The replacement was extracted from the shipped HTML, entity-decoded and executed verbatim.
140
- - **The scope guard shipped the instructions for its own disarmament (#1057)** — the stale-manifest denial ended on ``remove it with `rm -f <path>` ``, and that string travels in `permissionDecisionReason` into the context of the agent just denied. `rm -f` is not blocked (14 rules, only `rm-rf-destructive` bites), and with the manifest gone both allowedPaths **and** project-root containment fall. A parallel session in the same working copy received this suggestion for the live manifest of this one, and declined on an indicator rather than on knowledge.
141
- - **Two dead instruments, found by measuring rather than reading** — the v2 vault renderer's branch condition was **structurally false from its first commit** and matched **0 of 253** session records; the obvious fix would have been worse than the bug, because `agents` is polymorphic (210× a number, 14× an array over 599 wave objects) and a naive `??` would have written `[object Object]` into the vault. And `scanBacklog` read 50 of 89 open issues while reporting the window nowhere: `critical 0, high 10, stale 0` where the truth was `1 / 18 / 5`.
142
- - **Cross-session messaging is wired (Epic #1048, waves 1–2)** — a new always-on rule with CSM-001..005, and a peer-inform branch hung **below** the sibling check in the PSA decision tree so it can mask neither that branch nor the PSA-002 pause. The moat was never "peer sessions cannot talk to each other" they demonstrably can it is that they share one working copy, and what crosses the channel is information, never isolation.
143
- - **Guards that were green without biting** the owner-leakage scanner did not read `.html` at all; admitting the class immediately found a live `U+00AD` breaking a word visibly on a legally required page. `harness-audit` category 6 awarded **2/2** outside a git repository. Six consecutive CI pipelines were red because a test depended on untracked ledger files: locally 23/23, on CI `23 tests | 23 skipped`. Each is now closed by a mechanism, and the review panel found further holes inside the fixes themselves — including one fail-open state inside the fix that was closing fail-open.
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.
144
144
 
145
- Previous line (v3.20.0): the memory pipeline 233 sessions of learnings that reached no agent (#1014), agent-authored text neutralised at the render point (#1015), and a learning store made durable after 11 of 13 provenance pointers resolved to nothing (#1017).
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.
146
146
 
147
147
  Full version history: [CHANGELOG.md](CHANGELOG.md).
148
148
 
@@ -135,8 +135,8 @@ and `{{options}}` at render time.
135
135
  ```
136
136
  AskUserQuestion({
137
137
  questions: [{
138
- header: "Memory — Confirm Proposals (Batch {{N}} of {{M}})",
139
- question: "Select the learnings you want to store permanently. Unselected proposals will be archived as declined.",
138
+ header: "Memory",
139
+ question: "Batch {{N}} of {{M}} — which learnings should be stored permanently? Unselected ones are archived as declined.",
140
140
  options: [
141
141
  // one entry per proposal in this batch — see label format below
142
142
  { label: "{{label}}", description: "{{description}}" },
@@ -147,12 +147,14 @@ AskUserQuestion({
147
147
  })
148
148
  ```
149
149
 
150
- **When there is only one batch** (`M === 1`), omit the batch suffix:
150
+ **When there is only one batch** (`M === 1`), omit the batch prefix from the question:
151
151
 
152
152
  ```
153
- header: "Memory Confirm Proposals"
153
+ question: "Which learnings should be stored permanently? Unselected ones are archived as declined."
154
154
  ```
155
155
 
156
+ `header` stays `"Memory"` in both cases: the field is cut off after 12 characters, so anything longer never reaches the operator.
157
+
156
158
  ### Option label format
157
159
 
158
160
  Locked by D3 (Wave 1 decision):
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: Say the last answer again in plain words — same facts, in the order the operator needs them. Optional topic argument.
3
+ argument-hint: "[topic]"
4
+ ---
5
+
6
+ # eli5
7
+
8
+ Invokes the `eli5` skill (`skills/eli5/SKILL.md`). Restates my last substantial output — or the named topic — for someone who knows this project but did not watch the last ten minutes of it.
9
+
10
+ ## Argument Validation
11
+
12
+ The optional argument is a topic, in prose. If absent, the target is my own last substantial output in this conversation; if there is none yet, say so rather than picking a topic for him.
13
+
14
+ Examples:
15
+ - `/eli5` — restate what I just said
16
+ - `/eli5 warum ist der Regel-Korpus voll?` — explain that, grounded in what this session measured
17
+
18
+ ## Behavior
19
+
20
+ 1. **Resolve the target** — last output, or `$ARGUMENTS`.
21
+ 2. **Ground it** — prefer what this session already measured over recall, and name where it came from. Never measured here → say so.
22
+ 3. **Restate** — consequence first (*must I act, and what if I don't?*), then the facts in the order he needs them.
23
+ 4. **Check before sending** — every greppable token from the original still present; every noun the system does not contain gone.
24
+
25
+ ## The limit that outranks the command
26
+
27
+ **Simplifying removes words, never facts.** A dropped path, number, error code, identifier, or instruction to act is data loss, not simplification — `skills/session-start/soul.md` § "Never traded for brevity" outranks brevity here as everywhere. And no invented pictures: say what happens, never what it is "like".
28
+
29
+ ## Related
30
+
31
+ - `skills/eli5/SKILL.md` — the skill, in full
32
+ - `.claude/rules/ask-via-tool.md` § AUQ-006 — plain words, real things
33
+ - `skills/session-start/soul.md` § Register — the canonical register statement
@@ -32,13 +32,13 @@ The same reading shows the other half: the GitHub releases for 3.15/3.18/3.19/3.
32
32
  4. **Preflight.** `node scripts/release.mjs --check --json` — every row green. This runs *after* step 2, never before: `--check` derives its target from `package.json`, so on the pre-bump version the registry- and tag-collision rows are red by construction.
33
33
  5. **Gate, commit, push.** Full quality gate, then commit and push to **both** remotes.
34
34
  6. **CI green — on the commit that will be published.** Not on its predecessor. A green pipeline from before step 5's commit is evidence about a different tree.
35
- 7. **Publish.** `node scripts/release.mjs --publish` — publishes, verifies the registry, tags **after** the verified publish, pushes `main` + tag to both remotes, then polls the live site. Add the GitHub release for the new tag (`gh release create`) as part of this step, not "later" — "later" is what produced the three-second backfill above.
35
+ 7. **Publish.** `node scripts/release.mjs --publish` — the target-confirmed npm receipt is the irreversible boundary. Before that receipt, any failure aborts normally. After it, never rerun `--publish`: registry propagation timeout/query/wait failures are reconciliation while the script still tags, pushes `main` + tag to both remotes, handles the GitHub release, and polls the live site. If tag/push fails after the receipt, the dependent GitHub-release and site steps are skipped and the script returns structured reconciliation guidance instead. Add the GitHub release for the new tag (`gh release create`) as part of this step, not "later" — "later" is what produced the three-second backfill above.
36
36
 
37
37
  Steps 2–7 are one continuous act. A release left parked between step 5 and step 7 is exactly the `3.18.0` state: every surface says released, the registry disagrees.
38
38
 
39
39
  ## Abort criteria
40
40
 
41
- Stop and report. Do not work around, do not "fix it after the publish" — an npm publish is not revocable.
41
+ **Before a target-confirmed npm publish receipt:** stop and report. Do not work around, do not "fix it after the publish" — an npm publish is not revocable.
42
42
 
43
43
  | Signal | Why it stops the release |
44
44
  |---|---|
@@ -51,7 +51,9 @@ Stop and report. Do not work around, do not "fix it after the publish" — an np
51
51
 
52
52
  ## After
53
53
 
54
- `--publish` prints the remaining manual items (token rotation, async gallery indexing). Rotate the npm token write tokens are short-lived by policy, and a token that transited a log or a chat is burned.
54
+ `--publish` reports either **Release complete** or **Post-publish reconciliation required**. The latter means npm accepted the target release but registry propagation, tag/push, GitHub-release handling, or the live-site check still needs repair; a tag/push failure explicitly skips its dependent GitHub-release and site steps. **Do not rerun `--publish`**: reconcile the listed state directly, because a second publish cannot replace the immutable version. Exit `1` means a preflight/check failure or post-publish reconciliation is required; exit `2` remains a system/usage failure before the receipt.
55
+
56
+ After a complete release, `--publish` prints the remaining manual items (token rotation, async gallery indexing). Rotate the npm token — write tokens are short-lived by policy, and a token that transited a log or a chat is burned.
55
57
 
56
58
  ## See Also
57
59
 
package/commands/test.md CHANGED
@@ -59,9 +59,9 @@ Present the user with a structured choice via `AskUserQuestion`:
59
59
  AskUserQuestion({
60
60
  questions: [{
61
61
  question: "Which test profile should be run?",
62
- header: "Test Command: profile selection",
62
+ header: "Profil",
63
63
  options: [
64
- { label: "smoke (Recommended)", description: "Quick sanity pass — key flows, axe critical/serious, console errors." },
64
+ { label: "smoke (Recommended)", description: "Quick sanity pass — key flows, axe critical/serious, console errors. The fastest of the four; run it first." },
65
65
  { label: "full", description: "All checks at full depth — slower, used before release." },
66
66
  { label: "a11y", description: "Accessibility-focused pass — axe-core exhaustive scan." },
67
67
  { label: "onboarding", description: "Onboarding step-count + Liquid Glass conformance checks." }
@@ -7,8 +7,8 @@ Detailed component inventory and architecture reference for Session Orchestrator
7
7
  ```mermaid
8
8
  flowchart LR
9
9
  USER([Operator]) -->|invokes /session| COORD[Coordinator]
10
- COORD -->|reads| SK[Skills<br/>46 user-facing]
11
- COORD -->|invokes| CMD[Commands<br/>25 slash-cmds]
10
+ COORD -->|reads| SK[Skills<br/>47 user-facing]
11
+ COORD -->|invokes| CMD[Commands<br/>27 slash-cmds]
12
12
  COORD -->|dispatches| AG[Agents<br/>15 typed sub-agents]
13
13
  AG -.->|parallel waves| W1[code-implementer]
14
14
  AG -.-> W2[test-writer]
@@ -18,7 +18,7 @@ flowchart LR
18
18
  COORD -->|writes| METRIC[.orchestrator/metrics/<br/>sessions · learnings · events]
19
19
  ```
20
20
 
21
- ## Skills (46 user-facing)
21
+ ## Skills (47 user-facing)
22
22
 
23
23
  - **Lifecycle:** `session-start`, `session-plan`, `wave-executor`, `session-end`, `quality-gates`, `using-orchestrator`
24
24
  - **Authoring:** `skill-creator`, `mcp-builder`, `hook-development`, `frontmatter-guard`, `contract-version-bump`
@@ -29,11 +29,12 @@ flowchart LR
29
29
  - **Ecosystem:** `bootstrap`, `gitlab-ops`, `gitlab-portfolio`, `ecosystem-health`, `mode-selector`, `autopilot`, `dispatcher`, `spinout`, `npm-publish`
30
30
  - **Testing:** `test-runner`, `playwright-driver`, `peekaboo-driver`
31
31
  - **Content review:** `persona-panel`
32
+ - **Operator ergonomics:** `eli5` (plain-language restatement of the last answer)
32
33
  - **Visualization:** `tmux-layout` (opt-in operator side-channel — [ADR-0007](adr/0007-tmux-visualization-substrate.md))
33
34
 
34
- ## Commands (25)
35
+ ## Commands (27)
35
36
 
36
- `/session`, `/go`, `/close`, `/discovery`, `/plan`, `/evolve`, `/bootstrap`, `/harness-audit`, `/autopilot`, `/autopilot-multi`, `/repo-audit`, `/test`, `/memory-cleanup`, `/portfolio`, `/brainstorm`, `/debug`, `/persona-panel`, `/grill`, `/sunset-review`, `/templates-ack`, `/dispatcher`, `/reconcile`, `/spinout`, `/eval`, `/contract-version-bump`.
37
+ `/session`, `/go`, `/close`, `/discovery`, `/plan`, `/evolve`, `/bootstrap`, `/harness-audit`, `/autopilot`, `/autopilot-multi`, `/repo-audit`, `/test`, `/memory-cleanup`, `/portfolio`, `/brainstorm`, `/debug`, `/persona-panel`, `/grill`, `/sunset-review`, `/templates-ack`, `/dispatcher`, `/reconcile`, `/spinout`, `/eval`, `/release`, `/contract-version-bump`, `/eli5`.
37
38
 
38
39
  ## Agents (15 typed sub-agents)
39
40
 
@@ -1,7 +1,7 @@
1
1
  # Scope-Collision Guard — Pre-Dispatch File-Scope Deconfliction
2
2
 
3
3
  > Reference for the mechanism that stops a wave from handing the SAME file to two agents (issue #1020).
4
- > Four moving parts: the per-agent scope files, `scripts/validate-wave-scope.mjs` (`--assert-disjoint` / `--union`), `findScopeCollisions()` + `unionFileScopes()` in [`scripts/lib/scope-gate.mjs`](../scripts/lib/scope-gate.mjs), and the `PreToolUse` hook [`hooks/pre-task-scope-disjoint.mjs`](../hooks/pre-task-scope-disjoint.mjs).
4
+ > Five moving parts: `scripts/materialize-wave-scope.mjs` (the canonical declaration writer), the per-agent scope files, `scripts/validate-wave-scope.mjs` (`--assert-disjoint` / `--union`), `findScopeCollisions()` + `unionFileScopes()` in [`scripts/lib/scope-gate.mjs`](../scripts/lib/scope-gate.mjs), and the `PreToolUse` hook [`hooks/pre-task-scope-disjoint.mjs`](../hooks/pre-task-scope-disjoint.mjs).
5
5
  > The coordinator-side **runbook** is `skills/wave-executor/wave-loop.md` § Scope Manifest 3.1–3.3 — this document does not restate it. What lives here instead: how the mechanism works, how it fails, what it deliberately does not see, and how to debug it.
6
6
 
7
7
  ## 1. What the pre-existing gates could not see
@@ -17,8 +17,8 @@ Two things follow, and both are the point of #1020:
17
17
 
18
18
  | # | Step | Artefact | Mechanism |
19
19
  |---|------|----------|-----------|
20
- | 1 | Declare | `<state-dir>/filescopes/wave-<N>/<agent-id>.json` (one per agent, plus `coordinator.json`) | written verbatim from the session plan |
21
- | 2 | Assert disjointness | the sidecar array `[{id, files}, …]` | `validate-wave-scope.mjs --assert-disjoint` → `findScopeCollisions()` |
20
+ | 1 | Materialize declarations | `<state-dir>/filescopes/wave-<N>/<agent-id>.json` (one per agent, plus `coordinator.json`) and `<state-dir>/filescopes/wave-<N>.scopes.json` | one canonical `[{id, files}, …]` stdin array → `materialize-wave-scope.mjs` |
21
+ | 2 | Assert disjointness | the materialized sidecar array `[{id, files}, …]` | `validate-wave-scope.mjs --assert-disjoint` → `findScopeCollisions()` |
22
22
  | 3 | Compute the union | stdout of `--union` → `allowedPaths` | `expandTestSiblings(unionFileScopes(scopes), { role })` |
23
23
  | 4 | Inject | `FILE-SCOPE — exactly these:` + a fenced block in each agent prompt | the per-agent file from step 1 |
24
24
  | 5 | Dispatch | `.orchestrator/wave-dispatch-scopes.json` (ledger) | `hooks/pre-task-scope-disjoint.mjs`, `PreToolUse` matcher `Agent` |
@@ -423,7 +423,7 @@ Introduced by Epic #157 / issue #166. Lets session-start sense the host (RAM, CP
423
423
  |-------|------|---------|-------------|
424
424
  | `resource-awareness` | boolean | `true` | Master toggle for the env-aware runtime. When `false`, skips Phase 4.5 adaptive wave sizing and the host banner. |
425
425
  | `enable-host-banner` | boolean | `true` | Whether `hooks/on-session-start.mjs` emits the host + resource banner at the top of every session. Set `false` to silence. |
426
- | `resource-thresholds` | object | see below | Numeric thresholds that drive Phase 4.5 adaptive rules. Unset sub-keys fall back to defaults. Sub-keys: `ram-free-min-gb`, `ram-free-critical-gb`, `cpu-load-max-pct`, `concurrent-sessions-warn`, `ssh-no-docker`, `zombie-threshold-min`. |
426
+ | `resource-thresholds` | object | see below | Numeric thresholds that drive Phase 4.5 adaptive rules. Unset sub-keys fall back to the single canonical default set (`DEFAULT_RESOURCE_THRESHOLDS` in `scripts/lib/resource-probe/evaluate.mjs`). Sub-keys: `ram-free-min-gb`, `ram-free-critical-gb`, `cpu-load-max-pct`, `concurrent-sessions-warn`, `ssh-no-docker`, `zombie-threshold-min`. |
427
427
 
428
428
  ### resource-thresholds
429
429
 
@@ -431,17 +431,40 @@ Sub-key defaults:
431
431
 
432
432
  ```yaml
433
433
  resource-thresholds:
434
- ram-free-min-gb: 4 # below this, cap agents-per-wave at 2
435
- ram-free-critical-gb: 2 # below this, recommend coordinator-direct
436
- cpu-load-max-pct: 80 # sustained above this, cap agents-per-wave at 2
437
- concurrent-sessions-warn: 5 # warn when host has this many Claude sessions
434
+ ram-free-min-gb: 4 # soft memory signal (see precedence below)
435
+ ram-free-critical-gb: 2 # hard memory signal coordinator-direct
436
+ cpu-load-max-pct: 90 # soft CPU signal, judged on min(1m, 5m)
437
+ concurrent-sessions-warn: 5 # soft signal at this many live peer SESSIONS
438
438
  ssh-no-docker: true # when session is over SSH, steer the plan away from Docker-based tests
439
439
  zombie-threshold-min: 30 # age (minutes) above which an idle Claude/Node process is a zombie candidate
440
440
  ```
441
441
 
442
- **`zombie-threshold-min`** (default: `30`): When set, the resource probe runs a secondary `ps` pass that counts Claude and Node processes older than this many minutes **and** with CPU% ≤ 1%. These are "zombie candidates" — stale sessions or orphaned workers that still hold RAM. The probe exposes them via `zombie_processes_count` in the snapshot. The evaluator escalates the verdict to at least `warn` when `zombie_processes_count >= 1` **and** `claude_processes_count > 0` (i.e., there are active Claude processes alongside the zombies). The reason string surfaces the threshold and count so the session-start banner gives actionable context. Set to `0` to disable zombie detection entirely (the field is omitted from the default snapshot when absent from config).
443
-
444
- Rationale: originated from the 2026-04-19 incident where 8 parallel Claude sessions on one Mac caused a hard freeze. The adaptive rules cap concurrent agent load when the host is under pressure, before a wave ever spawns subagents. See Epic #157 and Sub-Epic #158.
442
+ **No single threshold caps a wave (#1089).** A cap requires either one *hard*
443
+ signal (→ `critical`, coordinator-direct) or **two independent soft signals**
444
+ agreeing (→ `warn`, cap 2). One soft signal alone is reported and acted on by
445
+ nobody. Rationale and the measured firing rates are in
446
+ [`.claude/rules/host-resources.md`](../.claude/rules/host-resources.md); the full
447
+ rule table is in `skills/session-start/phase-4-5-resource-health.md`.
448
+
449
+ **What the memory thresholds are compared against** is chosen by precedence, not
450
+ by configuration: `memory_pressure_pct_free` (macOS, hard `<15%` / soft `<30%`)
451
+ outranks `ram_available_gb`, which outranks `ram_free_gb`. The two GB-denominated
452
+ keys above therefore apply to *available* RAM on macOS and to `os.freemem()` on
453
+ Linux/Windows, where it is accurate. They are never compared against Darwin's
454
+ `Pages free`, whose median across 1477 measured session starts was **0.4 GB** on
455
+ hosts with 24-128 GB installed — gating on it fired `ram-free-critical-gb` on
456
+ 84.0% of all starts.
457
+
458
+ **`concurrent-sessions-warn` counts SESSIONS, not processes.** The live count
459
+ comes from the session registry (`detectPeers()` — self excluded,
460
+ heartbeat-fresh). Until #1089 it was compared against `claude_processes_count`, a
461
+ measured 6x unit error (median processes:sessions = 6.0) that fired the threshold
462
+ on 93.6% of starts instead of 4.2%. When the registry is unreadable the probe
463
+ falls back to the process count rescaled by that same factor.
464
+
465
+ **`zombie-threshold-min`** (default: `30`): When set, the resource probe runs a secondary `ps` pass that counts Claude and Node processes older than this many minutes **and** with CPU% ≤ 1%. These are "zombie candidates" — stale sessions or orphaned workers that still hold RAM. The probe exposes them via `zombie_processes_count` in the snapshot. Since #1089 this is a *soft* signal: it is reported when `zombie_processes_count >= 1` **and** there is a live peer/process context, but on its own it caps nothing — sweeping stale sessions is housekeeping advice, not a reason to shrink a wave. The reason string surfaces the threshold and count so the session-start banner gives actionable context. Set to `0` to disable zombie detection entirely (the field is omitted from the default snapshot when absent from config).
466
+
467
+ Rationale: originated from the 2026-04-19 incident where 8 parallel Claude sessions on one Mac caused a hard freeze. That hazard is real and the rules still escalate for it — what #1089 changed is that they now recognise it, instead of reporting it on 99.0% of session starts where it was not happening. See Epic #157, Sub-Epic #158, and `.claude/rules/host-resources.md`.
445
468
 
446
469
  ### isolation graduation
447
470
 
@@ -15,8 +15,8 @@
15
15
  *
16
16
  * Schema v2 (Epic #583 D4 #587):
17
17
  * {
18
- * session_id: string, // semantic OR UUID whatever resolveSessionId returned
19
- * semantic_session_id: string, // ALWAYS the semantic form (closes D4)
18
+ * session_id: string, // native raw identity OR generated UUID; sole live lock/registry ownership key
19
+ * semantic_session_id: string, // attribution/history label only; never ownership
20
20
  * started_at: ISO,
21
21
  * last_heartbeat: ISO, // basis for liveness; replaces PID-liveness checks
22
22
  * mode: string, // "deep"|"feature"|"housekeeping"|"session"|...
@@ -47,10 +47,12 @@ import { writeJsonAtomicSync } from '../../scripts/lib/io.mjs';
47
47
  *
48
48
  * @param {object} opts
49
49
  * @param {string} opts.repoRoot — absolute path to the repository root.
50
- * @param {string} opts.sessionId — the resolved session id (semantic OR UUID).
51
- * @param {string} [opts.semanticSessionId] the semantic form, ALWAYS surfaced
52
- * even when sessionId is a UUID (closes D4 issue #587). When omitted, the
53
- * field is populated by mirroring sessionId.
50
+ * @param {string} opts.sessionId — the physical raw session id from the native
51
+ * harness, or a generated UUID when no trustworthy raw id exists. This is the
52
+ * only live lock/registry ownership key.
53
+ * @param {string} [opts.semanticSessionId] the semantic attribution/history
54
+ * label, surfaced separately and never used for ownership. When omitted, the
55
+ * field is populated by mirroring sessionId for backward-compatible display only.
54
56
  * @param {string} opts.mode — session mode (e.g. "deep", "feature").
55
57
  * @param {number} [opts.ttlHours=4] — lock TTL in hours.
56
58
  * @param {Function} [opts._acquireImpl] — DI for tests (defaults to importing acquire from session-lock.mjs).
@@ -88,9 +90,10 @@ export async function bootstrapLock({
88
90
 
89
91
  // Step 1: try to acquire. If a fresh acquire succeeds, we are done.
90
92
  // If a stale-PID-dead/-alive lock exists, force-overwrite it (the prior
91
- // session has died; we own the worktree now).
92
- // If the existing lock has the same sessionId, force-overwrite so the
93
- // last_heartbeat gets refreshed.
93
+ // session has died; the current raw owner can take the worktree).
94
+ // Only an exact match of the existing physical raw sessionId permits the
95
+ // same-session force-refresh. semantic_session_id, STATE.md `session`, and
96
+ // an owner proof never make a different raw id the same owner.
94
97
  let acquireResult;
95
98
  try {
96
99
  // quiet: true suppresses the unknown-mode stderr WARN in acquire() (#592 MED-2).
@@ -169,9 +172,10 @@ export async function bootstrapLock({
169
172
  // last_heartbeat is the basis for liveness — set to started_at on bootstrap
170
173
  // so an immediate liveness check (< ttl_hours from now) succeeds.
171
174
  last_heartbeat: startedAt,
172
- // semantic_session_id is ALWAYS the semantic form, even when session_id is
173
- // a UUID-v4 (closes D4 #587). Fallback to mirroring session_id if no
174
- // semantic was provided.
175
+ // semantic_session_id is an attribution/history label, normally semantic
176
+ // even when the physical session_id is a UUID-v4. Fall back to mirroring
177
+ // session_id only for backward-compatible display when no label was provided;
178
+ // it never changes the raw ownership key.
175
179
  semantic_session_id:
176
180
  typeof semanticSessionId === 'string' && semanticSessionId.length > 0
177
181
  ? semanticSessionId
@@ -190,7 +194,9 @@ export async function bootstrapLock({
190
194
  // Step 2b (#987 Part 1): persist the durable ownership proof at lock
191
195
  // genesis. `enriched` is byte-identical to the on-disk lock at this point
192
196
  // (the v2 overlay never touches pid/host/started_at), so the proof written
193
- // here will verify via isLockOwnedByProof() against any later re-read.
197
+ // here will verify via isLockOwnedByProof() against any later re-read. The
198
+ // proof is supplementary evidence only: it never bridges a raw-id mismatch
199
+ // through semantic_session_id or STATE.md `session` equality.
194
200
  // This single call covers BOTH the plain-acquire and the forceAcquire
195
201
  // branch — both flow through the enriched write above. Best-effort like
196
202
  // the surrounding breadcrumb writes: writeOwnerProof() is no-throw by
@@ -7,7 +7,7 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "echo '🎯 Session Orchestrator v3.21.0 — /session [housekeeping|feature|deep] | /plan [new|feature|retro] | /discovery [scope] | /evolve [analyze|review|list]'",
10
+ "command": "echo '🎯 Session Orchestrator v3.22.0 — /session [housekeeping|feature|deep] | /plan [new|feature|retro] | /discovery [scope] | /evolve [analyze|review|list]'",
11
11
  "async": false
12
12
  },
13
13
  {