session-orchestrator 5.3.0 → 5.4.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 (206) hide show
  1. package/.agents/skills/claude-md-drift-check/SKILL.md +1 -1
  2. package/.agents/skills/navigator/SKILL.md +22 -0
  3. package/.agents/skills/navigator/agents/openai.yaml +5 -0
  4. package/.agents/skills/test-audit/SKILL.md +21 -0
  5. package/.agents/skills/test-audit/agents/openai.yaml +5 -0
  6. package/.claude-plugin/marketplace.json +1 -1
  7. package/.claude-plugin/plugin.json +1 -1
  8. package/.codex-plugin/plugin.json +1 -1
  9. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
  10. package/.codex-plugin/skills/navigator/SKILL.md +22 -0
  11. package/.codex-plugin/skills/navigator/agents/openai.yaml +5 -0
  12. package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
  13. package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
  14. package/.cursor/commands/navigator.md +15 -0
  15. package/.cursor/commands/test-audit.md +14 -0
  16. package/.cursor/rules/060-evolve.mdc +27 -12
  17. package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
  18. package/.cursor/skills/navigator/SKILL.md +13 -0
  19. package/.cursor/skills/test-audit/SKILL.md +12 -0
  20. package/.cursor-plugin/plugin.json +1 -1
  21. package/.orchestrator/policy/blocked-commands.json +1 -1
  22. package/AGENTS.md +9 -31
  23. package/CHANGELOG.md +70 -0
  24. package/README.md +14 -10
  25. package/docs/agent-authoring.md +2 -2
  26. package/docs/baseline.md +10 -1
  27. package/docs/ci-setup.md +57 -4
  28. package/docs/components.md +12 -12
  29. package/docs/events-schema.md +11 -8
  30. package/docs/persona-panel.md +5 -4
  31. package/docs/rule-authoring.md +27 -12
  32. package/docs/scope-collision-guard.md +26 -4
  33. package/docs/session-config-reference.md +55 -6
  34. package/docs/session-config-template.md +11 -2
  35. package/hooks/_lib/hook-import-set.json +27 -23
  36. package/hooks/hooks-codex.json +1 -1
  37. package/hooks/hooks-cursor.json +5 -0
  38. package/hooks/hooks-pi.json +5 -0
  39. package/hooks/hooks.json +6 -1
  40. package/hooks/on-session-start.mjs +53 -0
  41. package/hooks/on-stop.mjs +3 -1
  42. package/hooks/post-tool-batch-wave-signal.mjs +3 -1
  43. package/hooks/pre-bash-memory-propose-audit.mjs +52 -3
  44. package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
  45. package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
  46. package/hooks/pre-task-scope-disjoint.mjs +559 -72
  47. package/hooks/subagent-telemetry.mjs +96 -19
  48. package/package.json +3 -3
  49. package/pi/prompts/navigator.md +12 -0
  50. package/pi/prompts/test-audit.md +12 -0
  51. package/rules/README.md +6 -1
  52. package/rules/_index.md +3 -3
  53. package/rules/always-on/bash-harness-pitfalls.md +7 -1
  54. package/rules/opt-in-domain/prompt-caching.md +5 -1
  55. package/rules/opt-in-stack/backend-data.md +9 -1
  56. package/rules/opt-in-stack/backend.md +7 -1
  57. package/rules/opt-in-stack/frontend.md +6 -1
  58. package/rules/opt-in-stack/security-web.md +7 -1
  59. package/rules/opt-in-stack/swift.md +5 -1
  60. package/scripts/apply-session-learnings.mjs +492 -0
  61. package/scripts/archive-closed-prds.mjs +2 -1
  62. package/scripts/autopilot.mjs +49 -53
  63. package/scripts/backfill-abandoned-sessions.mjs +1 -1
  64. package/scripts/baseline-archetypes.mjs +1 -1
  65. package/scripts/check-package-manager.mjs +62 -5
  66. package/scripts/cursor-install.mjs +1 -4
  67. package/scripts/emit-session.mjs +296 -2
  68. package/scripts/export-hw-learnings.mjs +22 -3
  69. package/scripts/fleet-checkin.mjs +147 -0
  70. package/scripts/lib/agent-frontmatter.mjs +3 -3
  71. package/scripts/lib/auto-dream.mjs +7 -6
  72. package/scripts/lib/autopilot/flags.mjs +6 -1
  73. package/scripts/lib/autopilot/loop.mjs +10 -6
  74. package/scripts/lib/baseline-archetypes.mjs +41 -22
  75. package/scripts/lib/config/host-paths.mjs +79 -0
  76. package/scripts/lib/config/persona-gate-wave.mjs +3 -3
  77. package/scripts/lib/config/reaper.mjs +7 -6
  78. package/scripts/lib/config-schema.mjs +13 -0
  79. package/scripts/lib/config.mjs +71 -10
  80. package/scripts/lib/events.mjs +21 -2
  81. package/scripts/lib/fleet-protocol.mjs +270 -0
  82. package/scripts/lib/gates/gate-baseline.mjs +13 -3
  83. package/scripts/lib/gates/gate-full.mjs +7 -2
  84. package/scripts/lib/gates/gate-helpers.mjs +93 -2
  85. package/scripts/lib/gates/gate-incremental.mjs +23 -22
  86. package/scripts/lib/gates/gate-per-file.mjs +16 -7
  87. package/scripts/lib/instruction-budget-guard.mjs +69 -11
  88. package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
  89. package/scripts/lib/learnings/io.mjs +154 -26
  90. package/scripts/lib/learnings/schema.mjs +163 -15
  91. package/scripts/lib/learnings/sizing-subject.mjs +2 -2
  92. package/scripts/lib/memory-proposals/schema.mjs +11 -5
  93. package/scripts/lib/named-baseline-resolver.mjs +53 -28
  94. package/scripts/lib/named-vault-resolver.mjs +104 -5
  95. package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
  96. package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
  97. package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
  98. package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
  99. package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
  100. package/scripts/lib/orphan-reaper/scan.mjs +506 -0
  101. package/scripts/lib/orphan-reaper.mjs +43 -1315
  102. package/scripts/lib/owner-yaml.mjs +104 -6
  103. package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
  104. package/scripts/lib/process-group.mjs +62 -11
  105. package/scripts/lib/project-hygiene.mjs +21 -1
  106. package/scripts/lib/quality-gate.mjs +45 -5
  107. package/scripts/lib/resource-probe.mjs +1 -1
  108. package/scripts/lib/rule-loader.mjs +100 -62
  109. package/scripts/lib/rules-sync.mjs +27 -12
  110. package/scripts/lib/scope-baseline.mjs +3 -4
  111. package/scripts/lib/session-record-repair.mjs +183 -26
  112. package/scripts/lib/session-registry.mjs +27 -2
  113. package/scripts/lib/session-schema/constants.mjs +30 -4
  114. package/scripts/lib/session-schema/validator.mjs +28 -1
  115. package/scripts/lib/session-token-rollup.mjs +245 -52
  116. package/scripts/lib/sessions-canonical.mjs +100 -9
  117. package/scripts/lib/sh-quote.mjs +29 -0
  118. package/scripts/lib/subagents-schema.mjs +30 -3
  119. package/scripts/lib/telemetry/pricing.mjs +118 -71
  120. package/scripts/lib/tests-src-ratio.mjs +140 -23
  121. package/scripts/lib/tmux-layout/layouts.mjs +1 -11
  122. package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
  123. package/scripts/lib/ux-grill/reconcile.mjs +1 -1
  124. package/scripts/lib/validate/check-agents.mjs +1 -1
  125. package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
  126. package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
  127. package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
  128. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  129. package/scripts/lib/validate/check-unwired-features.mjs +1 -1
  130. package/scripts/lib/validate-vendored-rules.mjs +206 -81
  131. package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
  132. package/scripts/lib/vault-status/board-writer.mjs +48 -11
  133. package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
  134. package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
  135. package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
  136. package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
  137. package/scripts/lib/wave-resource-gate.mjs +34 -8
  138. package/scripts/memory-propose.mjs +18 -5
  139. package/scripts/release-session-lock.mjs +2 -1
  140. package/scripts/release.mjs +10 -3
  141. package/scripts/repair-invalid-sessions.mjs +37 -4
  142. package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
  143. package/scripts/run-quality-gate.mjs +323 -302
  144. package/scripts/site-numbers.mjs +163 -5
  145. package/scripts/sweep-expired-learnings.mjs +56 -5
  146. package/scripts/sweep-expired-rules.mjs +2 -1
  147. package/scripts/validate-plugin.mjs +7 -0
  148. package/scripts/vault-integration-watcher.mjs +2 -1
  149. package/scripts/vault-mirror.mjs +72 -46
  150. package/scripts/wave-scope-binding.mjs +2 -1
  151. package/skills/_shared/config-reading.md +3 -3
  152. package/skills/_shared/fleet-protocol.md +131 -0
  153. package/skills/_shared/state-ownership.md +5 -0
  154. package/skills/autopilot/SKILL.md +25 -11
  155. package/skills/bootstrap/SKILL.md +1 -1
  156. package/skills/bootstrap/_shared-template.md +1 -1
  157. package/skills/bootstrap/fast-template.md +1 -1
  158. package/skills/bootstrap/private-contract.md +2 -1
  159. package/skills/bootstrap/public-fallback.md +6 -3
  160. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
  161. package/skills/claude-md-drift-check/SKILL.md +10 -10
  162. package/skills/claude-md-drift-check/checker.mjs +26 -18
  163. package/skills/evolve/SKILL.md +2 -2
  164. package/skills/evolve/references/evolve-analyze-mode.md +10 -6
  165. package/skills/memory-cleanup/SKILL.md +1 -1
  166. package/skills/navigator/SKILL.md +193 -0
  167. package/skills/navigator/references/account-check.sh +87 -0
  168. package/skills/navigator/references/fleet-decisions.mjs +231 -0
  169. package/skills/navigator/references/slot-check.sh +114 -0
  170. package/skills/persona-panel/SKILL.md +2 -2
  171. package/skills/persona-panel/persona-format.md +2 -2
  172. package/skills/persona-panel/presets/designer-lens.md +1 -1
  173. package/skills/persona-panel/presets/engineer-lens.md +1 -1
  174. package/skills/persona-panel/presets/pm-lens.md +1 -1
  175. package/skills/plan/mode-retro.md +19 -4
  176. package/skills/quality-gates/SKILL.md +6 -4
  177. package/skills/remote-offload/SKILL.md +4 -2
  178. package/skills/session-end/learning-patterns.md +22 -12
  179. package/skills/session-end/metrics-collection.md +5 -6
  180. package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
  181. package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
  182. package/skills/session-end/references/session-summary-template.md +1 -1
  183. package/skills/session-end/session-metrics-write.md +22 -60
  184. package/skills/session-plan/SKILL.md +0 -3
  185. package/skills/session-start/SKILL.md +6 -0
  186. package/skills/session-start/references/operations-contract.md +4 -0
  187. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  188. package/skills/session-start/references/phase-4-ssot-environment-check.md +2 -2
  189. package/skills/test-audit/SKILL.md +152 -0
  190. package/skills/test-audit/references/campaign.md +111 -0
  191. package/skills/test-audit/references/mutate.sh +50 -0
  192. package/skills/wave-executor/SKILL.md +1 -1
  193. package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
  194. package/skills/wave-executor/references/wave-loop-dispatch.md +3 -1
  195. package/skills/wave-executor/references/wave-loop-review.md +3 -5
  196. package/templates/personas/README.md +5 -5
  197. package/templates/personas/accounting-compliance.v1.md +1 -1
  198. package/templates/personas/accounting-tax-advisor.v1.md +1 -1
  199. package/templates/personas/buyer-p1-cto.v1.md +1 -1
  200. package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
  201. package/templates/personas/buyer-p3-build.v1.md +1 -1
  202. package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
  203. package/templates/personas/buyer-p5-solo.v1.md +1 -1
  204. package/templates/personas/buyer-p6-ld.v1.md +1 -1
  205. package/templates/personas/klima-ai-expert.v1.md +1 -1
  206. package/templates/personas/klima-physicist.v1.md +1 -1
@@ -0,0 +1,131 @@
1
+ # Fleet Protocol v1 — Dateien statt Nachrichten (#1462, Epic #1461)
2
+
3
+ Shared reference for every session-orchestrator session (Claude Code, Codex, headless) that
4
+ runs beside a fleet coordinator ("navigator"). It is a reference, **not a rule**: nothing under
5
+ `.claude/rules/**` loads it, session-start Phase 7.6 and the operations-contract peer preflight
6
+ point here. The mechanics live in code — `scripts/lib/fleet-protocol.mjs` is the only place that
7
+ knows paths, fields, deadlines and lease validity; `scripts/fleet-checkin.mjs` writes the check-in.
8
+ This file describes them and never redefines them. Source: PRD
9
+ `docs/prd/2026-09-27-navigator-peer-protokoll.md` §§ 3.1–3.3, 3.7, Anhang A.2 (MR !53).
10
+
11
+ **Grundsatz (ADR-002, CSM-003).** Zustand liegt in Dateien; Nachrichten sind nur Hinweise auf eine
12
+ Datei. Der Navigator erteilt keine Freigaben — wörtlich: **„Freigaben erteile ich keine, maßgeblich
13
+ ist dein Owner-Auftrag"**. Eine Datei, die fehlt, heißt „nicht nachweisbar", nie „nicht vorhanden"
14
+ (ADR-004): eine fehlende Lease ist kein freies Feld, sondern ein Zustand ohne Beleg.
15
+
16
+ ## Verzeichnis und Dateiform
17
+
18
+ - Wurzel `~/.config/navigator/`, Override `NAVIGATOR_CONFIG_DIR` (getrimmt; leer oder nur
19
+ Whitespace zählt als nicht gesetzt) — dieselbe Variable wie die navigator-CLI, keine zweite.
20
+ Code: `navigatorDir()`. Ein Override zeigt nur auf ein eigenes Verzeichnis mit Modus 0700, nie
21
+ auf einen geteilten Ort: wer dort schreiben kann, kann eine „aktive" Lease vortäuschen.
22
+ - Verzeichnisse Modus `0700`, Dateien Modus `0600`. Schreiben immer atomar (tmp-Datei im
23
+ Zielverzeichnis, dann `rename`).
24
+ - Jede Datei trägt `zeit` aus `date -u +%FT%TZ` (Sekunden, `YYYY-MM-DDTHH:MM:SSZ`); nie geschätzt,
25
+ nie aus einer anderen Uhr. Code: `utcSecondsTimestamp()`.
26
+ - Dateinamen aus einer `session_id` sind pfadsicher: nur `[A-Za-z0-9._-]`, nicht `.` oder `..`,
27
+ kein `/`. Code: `isSafeSessionId()`; `checkinPath()` und `auflagenPath()` werfen sonst.
28
+ - Tests berühren das echte Verzeichnis nie: `tests/setup/navigator-dir-guard.mjs` setzt
29
+ `NAVIGATOR_CONFIG_DIR` je vitest-Worker auf ein tmp-Verzeichnis.
30
+
31
+ ## `leases/navigator.json` — genau ein aktiver Navigator (PRD 3.1)
32
+
33
+ | Feld | Inhalt |
34
+ |---|---|
35
+ | `session_id` | Registry-ID des Navigators (Pflicht; pfadsicher wie oben, höchstens 128 Zeichen) |
36
+ | `plattform` | `claude` \| `codex` \| `kopflos` (Pflicht) |
37
+ | `adresse` | `ListAgents`-Name, wenn Claude (`[A-Za-z0-9._:@-]`, 1–128 Zeichen); sonst `null` |
38
+ | `seit` | Zeit des Erwerbs, UTC mit `Z`, Sekunden oder Millisekunden (Pflicht) |
39
+ | `laeuft_ab` | UTC mit `Z`; Erneuerung je Ticker-Runde, Ablauf 30 min nach der letzten (Pflicht) |
40
+ | `uebergabe_an` | `null` oder Session-ID des Nachfolgers |
41
+
42
+ **„Aktiv" heißt ausschließlich: gültige Lease.** `readNavigatorLease()` liefert genau eines von
43
+ `active` (Datei lesbar, Pflichtfelder da, `laeuft_ab` in der Zukunft), `none` (Datei fehlt oder
44
+ `laeuft_ab` überschritten) oder `unreadable` (kaputtes JSON, fehlende Pflichtfelder, Lesefehler, oder ein Feld außerhalb der
45
+ Tabellenform: unbekannte `plattform`, Steuerzeichen in `session_id`/`adresse`, eine Zeit ohne `Z`
46
+ oder mit Offset — sie würde in lokaler Zeit gelesen und könnte die Lease verlängern).
47
+ Unlesbar oder abgelaufen ist **nie** aktiv (fail-closed). Registry-Heartbeat (`role: navigator`,
48
+ `scripts/lib/session-registry.mjs`) und `ListAgents` sind Zusatzsignale, keine Lebendbeweise.
49
+ Erwerb und Übergabe der Lease sind S4 (agents/navigator#36), nicht Teil dieses Repos.
50
+
51
+ ## `checkin/<session_id>.json` — Session an Navigator (PRD 3.2)
52
+
53
+ **Schreibend** heißt: Änderung an Repo-Inhalt oder VCS-Zustand (Datei im Arbeitsbaum außerhalb
54
+ `.orchestrator/` und `.claude/`, Commit, Branch, Push, MR, Issue, Kommentar). SO-eigener
55
+ Sitzungszustand zählt nicht: Phase 1.1 (CLAUDE.md-Migration, einmalig), 1.2 `session.lock`, 1.5
56
+ `STATE.md`, 1.6 Metriken, 1.7 Live-Status-Board. Phase 1.1 ist eine Repo-Datei und wird deshalb
57
+ als Ausnahme im Check-in genannt (`vorab_geschrieben`). (`CLAUDE.md` heißt auf Codex CLI `AGENTS.md`, siehe
58
+ `skills/_shared/instruction-file-resolution.md`.)
59
+
60
+ Der Check-in liegt **vor der ersten schreibenden Aktion** (Phase 7.6, oder der Peer-Preflight der
61
+ Operations-Route). Schreiber: `node scripts/fleet-checkin.mjs` liest das JSON von stdin, ergänzt
62
+ `zeit`, validiert (`validateCheckin()`), schreibt atomar mit Modus 0600 und emittiert
63
+ `orchestrator.fleet.checkin` (Payload `session`, `repo`, `modus`, `kandidaten`, `navigator_state`).
64
+ Exit 0 geschrieben, 2 ungültige Eingabe (Fehlerliste auf stderr, keine Datei), 1 Schreibfehler.
65
+
66
+ | Feld | Inhalt |
67
+ |---|---|
68
+ | `session`, `plattform` | Registry-ID (aus `.orchestrator/current-session.json`); `claude` / `codex` / `kopflos` |
69
+ | `repo`, `repo_id`, `worktree` | Name; `repo_id` aus `navigator identity` oder `null`; Worktree `true`/`false` |
70
+ | `modus` | `housekeeping` / `feature` / `deep` / `operations` |
71
+ | `auftrag_ref` | Pfad der Auftragsdatei oder „Owner-Chat <zeit>" mit Wortlaut in einem Satz |
72
+ | `konto_slot`, `quota` | `account_slot` als Nummer (`navigator identity`) oder `null`; Quota aus `navigator quota` oder `nicht messbar` |
73
+ | `stand` | Objekt: main-, origin/main-, Prod-SHA (sonst `nicht messbar`), offene MRs |
74
+ | `kandidaten`, `schreibbereich` | Issue-Nummern; Verzeichnisse/Dateien |
75
+ | `bedarf` | Objekt: geplante volle Pipelines, m5-remote-Jobs, prozessstartende Agents |
76
+ | `vorab_geschrieben`, `rueckfall` | Ausnahmen nach der Definition oben; Verhalten ohne Auflagen (unten) |
77
+ | `zeit` | von der CLI gesetzt |
78
+
79
+ Eine `SendMessage` an den Navigator ist optional und nur ein Hinweis auf die Datei: höchstens eine,
80
+ nur wenn die Lease aktiv ist und eine Peer-Adresse `navigator-*` erreichbar ist; nie eine Antwort
81
+ erwarten, nie darauf warten (CSM-004).
82
+
83
+ ## `auflagen/<session_id>.json` — Navigator an Session (PRD 3.3)
84
+
85
+ Felder: Caps (prozessstartende / lesende Agents nach freiem Speicher des lokalen Hosts), volle
86
+ Pipelines je Repo, m5-remote-Regel (`m5-ersatz`-Exit, was lokal erlaubt ist), Merge-Protokoll, Zeitregel,
87
+ Flottenpriorität, Besitzkonflikte, Pflichtsatz „Freigaben erteile ich keine, maßgeblich ist dein
88
+ Owner-Auftrag". Die Session **liest** die Datei; sie wartet auf keine Nachricht.
89
+
90
+ **Standard-Auflagen (konservative Stufe).** Sie gelten, wenn `CHECKIN_FALLBACK_MIN` = 10 Minuten
91
+ nach `checkin.zeit` keine Auflagen-Datei liegt — und sofort, wenn kein Navigator aktiv ist
92
+ (PRD Anhang A.2, v2-Wortlaut):
93
+
94
+ - Caps: 2 prozessstartende + 2 lesende Agents, solange der lokale Host ≥ 50 % Speicher frei hat
95
+ (`memory_pressure | tail -1`).
96
+ - Pipelines: ≤ 2 volle je Repo; Nachzug-Pushes vorher im Check-in (`bedarf`) benennen.
97
+ - Offload: Heavy-Rollen (test, build, lint, audit) nur auf `m5-remote`, und nur wenn
98
+ `navigator m5-ersatz --quiet` unmittelbar davor mit Exit 0 endet. Exit-Vertrag: 0 nutzbar,
99
+ 3 Ersatzregel, 5 überlastet, 6 nicht messbar; jeder Exit außer 0 (auch ein fehlender Befehl)
100
+ heißt: kein neues Offload-Gate. Schwelle und Hysterese leben allein in diesem Befehl — hier und
101
+ in den Skills steht bewusst keine Lastzahl. Lokal nur `vitest` bzw. `eslint` je Datei mit `timeout`.
102
+ - Zeiten nur aus `date`.
103
+ - Kein Merge ohne Befugnis nach PRD 3.5: `navigator authority query <repo> merge --exit-code` mit
104
+ Exit 0 **oder** ein ausdrücklicher Owner-Auftrag, der Merge für genau dieses Repo nennt
105
+ (`auftrag_ref`). Ohne navigator-CLI ist jeder Merge `einzeln`.
106
+
107
+ Lesende Arbeit und Schreiben im eigenen Worktree laufen währenddessen weiter; der Rückfall ist ein
108
+ Dateistand mit Frist, kein Warten auf eine Antwort.
109
+
110
+ ## Verhalten ohne Navigator (PRD 3.7)
111
+
112
+ Lease `none` oder `unreadable`: die Check-in-Datei wird **trotzdem** geschrieben (sie ist der
113
+ zählbare Beleg), die Standard-Auflagen gelten sofort, es geht keine Nachricht ab. Kopflose Läufe
114
+ sind per Nachricht nicht erreichbar und nutzen dieselben Dateien; ihr Brief verlangt, vor jeder
115
+ Welle und vor jedem MR `auflagen/` und `merge/` zu lesen.
116
+
117
+ ## Vorschau: `merge/<repo_id>/*` (S2, #1463 — hier keine Mechanik)
118
+
119
+ Ansage `ansage-<mr>.json` (MR, gepinnte Head-SHA, Pipeline-ID, Jobliste, Gate-Log-SHA, Review-Stand,
120
+ Migration, `laeuft_ab` +20 min), Lease `lease-<mr>.json` (Navigator, +15 min), Meldung
121
+ `done-<mr>.json` (Merge-SHA, main-Pipeline-ID, Prod-SHA). Verfall bei Ablauf, MR nicht mehr
122
+ `opened` oder geändertem Head. Nichts davon ist in diesem Repo implementiert; bis S2 gilt PRD 3.4
123
+ Punkt 3: selbst messen und nach 3.5 mergen, oder `navigator ask --aktion merge`.
124
+
125
+ ## See Also
126
+
127
+ `skills/session-start/SKILL.md` Phase 7.6 · `skills/session-start/references/operations-contract.md`
128
+ (Peer-Preflight) · `skills/_shared/state-ownership.md` § Session Identity and Lock Ownership (`role`) ·
129
+ `docs/events-schema.md` (`orchestrator.fleet.checkin`) · `.claude/rules/cross-session-messaging.md`
130
+ (CSM-003/004) · `scripts/lib/fleet-protocol.mjs` · `scripts/fleet-checkin.mjs` ·
131
+ `skills/navigator/SKILL.md` (`/session-orchestrator:navigator`)
@@ -104,6 +104,11 @@ layer.
104
104
  contract requires a trusted native resume identifier and remains a follow-up.
105
105
  In particular, a host rotation that changes both raw and semantic values has
106
106
  no guaranteed continuity.
107
+ - **Registry `role` (`'navigator'`) is display, never ownership.** The
108
+ authoritative claim is the navigator lease (`leases/navigator.json` under
109
+ `~/.config/navigator/`, see `skills/_shared/fleet-protocol.md`); `registerSelf()`
110
+ does not carry the field across a repeated SessionStart, so the `/navigator`
111
+ skill re-sets it each ticker round via `heartbeat(id, { role })` (#1462).
107
112
 
108
113
  The peer-discovery and issue-budget procedures below apply these rules at their
109
114
  narrow surfaces; neither creates a second ownership model.
@@ -39,11 +39,19 @@ the invocation surface only:
39
39
  Headless requirements:
40
40
 
41
41
  - Anthropic API key configured for `claude -p` (Claude Code CLI auth).
42
- - `scripts/autopilot.mjs` reads `sessions.jsonl` tail after each child exit to construct
43
- the `sessionRunner` return shape — the spawned session must complete cleanly and append
44
- its record (session-end Phase 3.7 handles this).
45
- - `AUTOPILOT_RUN_ID` env var is propagated to the child so session-end stamps it onto the
46
- per-iteration `sessions.jsonl` record.
42
+ - `scripts/autopilot.mjs` starts each child with `--session-id <fresh uuid>` and, after it
43
+ exits, reads the one canonical `sessions.jsonl` record whose `raw_session_id` is that uuid
44
+ to construct the `sessionRunner` return shape — the spawned session must complete cleanly
45
+ and append its record (session-end Phase 3.7 handles this). No such record, or more than
46
+ one, stops the loop with `failed-wave` instead of reading the ledger tail (#1457). So does
47
+ a unique record that is only a backfill stub (no waves/agents/token totals — the child never
48
+ closed): its token usage is unknown, not 0, so it is never counted as a healthy 0-token
49
+ iteration (#1457 F2).
50
+ - `AUTOPILOT_RUN_ID` env var is set in the child's env, but **nothing reads it yet**
51
+ (measured 2026-10-01: `rg -n "AUTOPILOT_RUN_ID" scripts hooks skills` finds only the writer
52
+ in `scripts/autopilot.mjs` and this line). CLI-driven iterations therefore carry no
53
+ `autopilot_run_id` on their `sessions.jsonl` record yet; join them to `autopilot.jsonl` via
54
+ the `sessions` list there, not via that field.
47
55
 
48
56
  Do NOT re-implement loop logic inline — this skill and `scripts/lib/autopilot.mjs` are
49
57
  authoritative. Kill-switches are enforced by `scripts/lib/autopilot.mjs`, not inline by
@@ -111,7 +119,7 @@ intent for THIS run, not a standing policy.
111
119
  ## Command Surface
112
120
 
113
121
  ```
114
- /autopilot [--max-sessions=N] [--max-hours=H] [--confidence-threshold=0.X] [--dry-run]
122
+ /autopilot [--max-sessions=N] [--max-hours=H] [--confidence-threshold=0.X] [--max-tokens=N] [--dry-run]
115
123
  ```
116
124
 
117
125
  | Flag | Default | Bounds | Meaning |
@@ -119,6 +127,7 @@ intent for THIS run, not a standing policy.
119
127
  | `--max-sessions` | `5` | 1..50 | Iteration cap (graceful exit when reached) |
120
128
  | `--max-hours` | `4.0` | 0.5..24.0 | Wall-clock budget for entire loop |
121
129
  | `--confidence-threshold` | `0.85` | 0.0..1.0 | Minimum `selectMode` confidence for auto-execute |
130
+ | `--max-tokens` | `0` (off) | 0..10000000 | Token budget for `token-budget-exceeded`. Unit: cumulative **subagent OUTPUT** tokens, summed from each `sessions.jsonl` record's `total_token_output` (falling back to `total_tokens`, which is subagent-only as well) — coordinator tokens are not counted. Off by default until calibrated: one measured deep session's subagents produced 450 499 output tokens, so a six-figure budget stops the loop after about one deep session |
122
131
  | `--dry-run` | `false` | — | Print planned iterations without executing |
123
132
 
124
133
  Out-of-range values silently clamp to bounds. `--dry-run` exits after printing — never
@@ -136,7 +145,7 @@ WHILE state.iterations_completed < max-sessions:
136
145
  kill_switch := 'max-sessions-reached'; break
137
146
  IF (now() - state.started_at) > max-hours:
138
147
  kill_switch := 'max-hours-exceeded'; break
139
- IF cumulative_tokens_used >= max-tokens:
148
+ IF max-tokens > 0 AND cumulative_tokens_used >= max-tokens:
140
149
  kill_switch := 'token-budget-exceeded'; break
141
150
  IF resource_verdict() == 'critical' AND peer_count() > autopilot-peer-abort:
142
151
  kill_switch := 'resource-overload'; break
@@ -222,7 +231,7 @@ session — drives `runLoop` between manual `/session` invocations. The headless
222
231
  | Field | Signature | Source |
223
232
  |---|---|---|
224
233
  | `modeSelector` | `() => Promise<{mode, confidence, rationale?}>` | wraps `selectMode(await buildLiveSignals())` |
225
- | `sessionRunner` | `({mode, autopilotRunId}) => Promise<{session_id, agent_summary?, effectiveness?}>` | wraps a `/session <mode>` invocation; reads `sessions.jsonl` tail to construct return value |
234
+ | `sessionRunner` | `({mode, autopilotRunId}) => Promise<{session_id, agent_summary?, effectiveness?}>` | wraps a `/session <mode>` invocation; reads the child's own record (matched on `raw_session_id`) to construct return value |
226
235
  | `resourceEvaluator` | `() => {verdict}` | calls `evaluate(cachedProbeSnapshot, thresholds)` from `resource-probe.mjs` over a snapshot `peerCounter` refreshed on the prior iteration — never calls `probe()` itself, which is what keeps it synchronous |
227
236
  | `peerCounter` | `() => Promise<number>` | returns `peers.length` from `detectPeers({ sessionId, freshnessMin: 15 })` (a SESSION count, not a process count — see `host-resources.md` HR-103) while refreshing the cached `probe()` snapshot `resourceEvaluator` reads |
228
237
 
@@ -278,6 +287,10 @@ const sessionRunner = async ({ mode, autopilotRunId }) => {
278
287
  // The coordinator (Claude) invokes /session <mode> manually here. After the
279
288
  // session completes (/close runs, sessions.jsonl appended), this function
280
289
  // reads the tail entry and projects it into the runLoop return-shape.
290
+ // CAUTION: a tail read has the peer-append error class — a parallel session
291
+ // that appends after this one supplies ITS record (and token usage). A real
292
+ // run should read its own record by raw session id, as scripts/autopilot.mjs
293
+ // does (readOwnSession → raw_session_id match, #1457).
281
294
  const tail = readSessionsJsonlTail(1); // last line, normalized
282
295
  return {
283
296
  session_id: tail.session_id,
@@ -314,9 +327,10 @@ four required `runLoop` dependencies (`modeSelector`, `sessionRunner`,
314
327
  `resourceEvaluator`, `peerCounter`) plus the optional `abortSignal` to production
315
328
  sources — distinct from, and more concrete than, the in-process skeleton above:
316
329
 
317
- - `sessionRunner` — spawns `claude -p '/session <mode>'` as a child process; after it
318
- exits cleanly, reads the `sessions.jsonl` tail to construct the return shape
319
- `{session_id, agent_summary?, effectiveness?}` (`scripts/autopilot.mjs` `sessionRunner`).
330
+ - `sessionRunner` — spawns `claude -p '/session <mode>' --session-id <uuid>` as a child
331
+ process; after it exits cleanly, reads the one canonical `sessions.jsonl` record whose
332
+ `raw_session_id` is that uuid to construct the return shape
333
+ `{session_id, agent_summary?, effectiveness?, usage?}` (`scripts/autopilot.mjs` `sessionRunner`).
320
334
  - `resourceEvaluator` — calls `evaluate(cachedProbeSnapshot, thresholds)` from
321
335
  `scripts/lib/resource-probe.mjs`, reading a snapshot refreshed by `peerCounter` on the
322
336
  prior iteration so the function itself stays synchronous, per the `runLoop` contract
@@ -94,7 +94,7 @@ Store `INVOCATION_MODE = transitive | direct`.
94
94
 
95
95
  - `private`: the existing host-local config resolution found a baseline directory and its reduced contract validated. Use `private-contract.md` for selection, templates, commands, CI and rules.
96
96
  - `public`: the resolved baseline is absent, empty, or points to a missing directory. Use plugin-bundled templates.
97
- - Existing but invalid configured baseline: abort before dispatch; report the reader's sanitized error reason.
97
+ - Existing but invalid configured baseline: abort before dispatch; report the reader's sanitized error reason; for `producer-missing` also surface the `message` and the migrate-or-unconfigure choice (see `public-fallback.md` § Step 1).
98
98
 
99
99
  Pass `PATH_TYPE` into Phase 1 and all subsequent phases. All tier templates (`fast-template.md`, `standard-template.md`, `deep-template.md`) must consult `public-fallback.md` for CLAUDE.md generation and archetype file sourcing when `PATH_TYPE = public`.
100
100
 
@@ -17,7 +17,7 @@ Vendor the canonical always-on rules from the plugin's `rules/` library into `$R
17
17
 
18
18
  Idempotency is handled by the writer itself:
19
19
  - Missing → create
20
- - Exists, plugin-owned (first line is the `<!-- source: session-orchestrator plugin ... -->` header) and byte-identical → skip silently
20
+ - Exists, plugin-owned (the `<!-- source: session-orchestrator plugin ... -->` header is on line 1, or is the first line after a line-1 frontmatter's closing `---` — `isPluginOwnedContent()`, #1449) and byte-identical → skip silently
21
21
  - Exists, plugin-owned and stale → overwrite (the plugin copy is canonical)
22
22
  - Exists WITHOUT that header → preserved untouched (a repo-private rule the operator authored)
23
23
 
@@ -182,7 +182,7 @@ inherited Fast passes the confirmed contract ID and records all created paths.
182
182
 
183
183
  Idempotency is handled by the writer itself:
184
184
  - Missing → create
185
- - Exists, plugin-owned (first line is the `<!-- source: session-orchestrator plugin ... -->` header) and byte-identical → skip silently
185
+ - Exists, plugin-owned (the `<!-- source: session-orchestrator plugin ... -->` header is on line 1, or is the first line after a line-1 frontmatter's closing `---` — `isPluginOwnedContent()`, #1449) and byte-identical → skip silently
186
186
  - Exists, plugin-owned and stale → overwrite (the plugin copy is canonical)
187
187
  - Exists WITHOUT that header → preserved untouched (a repo-private rule the operator authored)
188
188
 
@@ -22,7 +22,8 @@ BOOTSTRAP_CONTRACT=$(node "$PLUGIN_ROOT/scripts/baseline-archetypes.mjs" \
22
22
  export BOOTSTRAP_CONTRACT
23
23
  ```
24
24
 
25
- `status: error` aborts before scaffolding. Its reason is safe to report; do not
25
+ `status: error` aborts before scaffolding. Its reason — and, for
26
+ `producer-missing`, its path-free `message` — is safe to report; do not
26
27
  print baseline paths, raw producer diagnostics, or private catalog files.
27
28
  `insufficient-evidence` is a selection state, not permission to use `node-minimal`.
28
29
  Fast tier has no archetype; use the plugin's minimal instruction-file flow and
@@ -23,12 +23,15 @@ export BOOTSTRAP_CONTRACT PATH_TYPE
23
23
  |----------------|--------------------|
24
24
  | No configured baseline, empty value, or missing directory | `public`; use bundled templates |
25
25
  | Existing baseline with valid reduced schema v1 export | `private`; retain `archetypes` and `selected` |
26
- | Existing baseline with missing CLI, invalid export, unsafe source, or producer failure | Abort; report the sanitized error reason |
26
+ | Existing baseline directory without its `scripts/archetype-manifest.mjs` exporter (`reason: producer-missing`) | Abort with the sanitized reason and its `message`; tell the user which interface is missing and offer the two ways forward: migrate the baseline to the exporter contract ([`docs/baseline.md`](../../docs/baseline.md) § How bootstrap finds it) or unset the configured baseline path (`SO_BASELINE_PATH` / matching `owner.yaml` `baselines` entry / `paths.baseline-path` / `plan-baseline-path`) to run the public path. This is the user's decision — never silently downgrade to public. <!-- path-check: example --> |
27
+ | Existing baseline with invalid export, unsafe source (symlink, path escape), or producer failure | Abort; report the sanitized error reason |
27
28
  | Valid private contract but no matching repository markers | `private`, `selected: null`; require selection for Standard/Deep |
28
29
 
29
30
  Lookup is offline and read-only. A broken configured contract never silently
30
- switches to a public archetype. Missing directories retain the established
31
- public fallback. Never guess a baseline path or fetch one automatically.
31
+ switches to a public archetype. `producer-missing` is the one reason with a
32
+ documented migration path (migrate or unconfigure, above); even then the public
33
+ path runs only after the user unsets the configured baseline. Missing
34
+ directories retain the established public fallback. Never guess a baseline path or fetch one automatically.
32
35
 
33
36
  ## Private Path
34
37
 
@@ -28,7 +28,8 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
28
28
  4. **Infer archetype.** Run Phase 0.5's read-only source detection. For a private
29
29
  contract, use its detected `selected.id`; retain `null` with an explicit
30
30
  `insufficient-evidence` report if no markers match. An invalid configured
31
- contract aborts. Do not scaffold or apply rules in this retroactive flow.
31
+ contract aborts (for `producer-missing`, report its path-free `message` with
32
+ the reason). Do not scaffold or apply rules in this retroactive flow.
32
33
  For the public path, use best-effort detection from existing files:
33
34
  - `pyproject.toml` present → `python-uv`
34
35
  - `package.json` with `next` in dependencies → `nextjs-minimal`
@@ -4,7 +4,7 @@ description: >
4
4
  Use when detecting drift between CLAUDE.md (or AGENTS.md, the Codex CLI alias) / _meta narrative and
5
5
  live repository state. Ten checks: absolute-path resolution, 01-projects/ count claims, issue-reference
6
6
  freshness, session-file existence, command-count sync, session-config-parity (mandatory template keys =
7
- error, opt-in gaps = warning), vault-dir-parity (CLAUDE.md vs AGENTS.md), generated-rule-staleness
7
+ error, opt-in gaps = note, unknown local keys = warning), vault-dir-parity (CLAUDE.md vs AGENTS.md), generated-rule-staleness
8
8
  (WARN-only), rule-scoping (paths:/globs: frontmatter defects, dangling rule citations, zero-match
9
9
  globs), and docs-parity (docs/components.md count-claims vs on-disk counts, template-vs-reference
10
10
  config-key parity, stale .claude/metrics/ paths). Full per-check spec in the body table. Invoked as an
@@ -37,18 +37,18 @@ PHASE 1 IMPLEMENTED (2026-04-19). Session-end opt-in quality gate. Upstream of `
37
37
  | 2 | `project-count-sync` | Hardcoded "N registered" / "N projects" claims next to `01-projects/` | compare to `ls -d 01-projects/*/` |
38
38
  | 3 | `issue-reference-freshness` | `#NN` in forward-looking sections (What's Next, Backlog, Open Issues, Offene Themen, Todo, Next Steps) | `glab issue view NN --repo <origin>` |
39
39
  | 4 | `session-file-existence` | `50-sessions/YYYY-MM-DD-*.md` references anywhere in scope | `existsSync(vault/50-sessions/<file>)` |
40
- | 5 | `command-count` | "N commands" / "N /commands" claims in prose | compare to `ls commands/*.md \| wc -l`; skipped if no `commands/` dir |
41
- | 6 | `session-config-parity` | Top-level keys under `## Session Config` in `CLAUDE.md` / `AGENTS.md` | diff against `docs/session-config-template.md`; a missing MANDATORY (minimal-baseline) key is an error, a missing OPT-IN-baseline-only key is a warning |
40
+ | 5 | `command-count` | "N commands" / "N /commands" claims in prose | compare to the name-deduplicated union of `commands/*.md` and skills with explicit `user-invocable: true`; skipped only when both directories are absent |
41
+ | 6 | `session-config-parity` | Top-level keys under `## Session Config` in `CLAUDE.md` / `AGENTS.md` | diff against `docs/session-config-template.md`; a missing MANDATORY (minimal-baseline) key is an error, a missing OPT-IN-baseline-only key is a note, a local key absent from the template's Session Config blocks is a warning |
42
42
  | 7 | `vault-dir-parity` | `vault-integration.vault-dir` in BOTH `CLAUDE.md` AND `AGENTS.md` | reuse `_parseVaultIntegration`; flag when the two files disagree. Short-circuits to PASS when the two are the SAME file by construction (symlink / same inode / byte-identical generated copy) |
43
43
  | 8 | `generated-rule-staleness` *(WARN only)* | `.claude/rules/*.md` with `auto-generated: true` frontmatter | extract `learning-key`; WARN when the key is absent from `.orchestrator/metrics/learnings.jsonl` or its learning's `expires_at` is in the past; skipped silently when no auto-generated rules exist |
44
- | 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | six probes: `paths:` frontmatter (error), cited-but-missing rule citations (error), zero-match `globs:` patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped), declared fleet-intent globs (**note**, never warn); skipped silently when `.claude/rules/` is absent |
44
+ | 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | six probes: `paths:` frontmatter incl. frontmatter not at line 1 (error, #1449), cited-but-missing rule citations (error), zero-match `globs:` patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped), declared fleet-intent globs (**note**, never warn); skipped silently when `.claude/rules/` is absent |
45
45
  | 10 | `docs-parity` | `docs/components.md` count-claims, Session Config key parity (`docs/session-config-template.md` vs `docs/session-config-reference.md`), `.claude/metrics/` stale-path references in `docs/*.md` / `docs/examples/*.md` | three sub-checks (a/b/c) — see below; skipped silently when `docs/components.md` is absent |
46
46
 
47
47
  Check 3 deliberately scopes to forward-looking sections. Mentions inside "Recently Closed", "Decisions", "Archive", etc. describe history and must not be flagged.
48
48
 
49
- Check 5 counts `*.md` files directly inside `commands/` (non-recursive, non-hidden). The `commands/` directory is resolved relative to `VAULT_DIR` by default; use `--commands-dir <path>` to override.
49
+ Check 5 uses `countSlashCommands()` to count the union of non-hidden `*.md` files directly inside `commands/` and skills under `skills/` whose frontmatter explicitly declares `user-invocable: true`, deduplicated by name. The `commands/` directory is resolved relative to `VAULT_DIR` by default; `--commands-dir <path>` overrides that side of the union, while skills remain relative to `VAULT_DIR`. Either directory alone is sufficient; an existing directory with no commands contributes zero rather than causing a skip.
50
50
 
51
- Check 6 (issue #30; severity split per #785) extracts the YAML block under `## Session Config` from both the canonical template (`docs/session-config-template.md` by default, override with `--config-template`) and the resolved local instruction file — reading the template TWICE, once per occurrence: `{ occurrence: 'first' }` reaches the "Full minimal baseline" (the 7 schema-mandatory keys), `{ occurrence: 'last' }` reaches the "Full opt-in baseline" (the full key catalog, a strict superset of the minimal block). A template key missing from the local file is a `session-config-parity` **error** when it belongs to the minimal-baseline set, and a **warning** when it is opt-in-baseline-only — a consumer repo that legitimately does not adopt an opt-in feature (e.g. no `handover-gate`) must not go red; only the true 7-key mandatory contract does. When the template carries a single `## Session Config` heading (no separate opt-in block), `'first'` and `'last'` resolve to the same block and every key is treated as mandatory, preserving prior behaviour for single-block templates. Both fenced YAML (```` ```yaml ... ``` ````) and raw YAML body (up to next `## ` heading) are accepted. The check skips gracefully when the template file is absent, when no instruction file is detected, or when explicitly disabled via `--skip-session-config-parity`.
51
+ Check 6 (issue #30; severity split per #785 and #1356) extracts the YAML block under `## Session Config` from both the canonical template (`docs/session-config-template.md` by default, override with `--config-template`) and the resolved local instruction file — reading the template TWICE, once per occurrence: `{ occurrence: 'first' }` reaches the "Full minimal baseline" (the 7 schema-mandatory keys), `{ occurrence: 'last' }` reaches the "Full opt-in baseline" (the full key catalog, a strict superset of the minimal block). A template key missing from the local file is a `session-config-parity` **error** when it belongs to the minimal-baseline set, and a **note** with `probe: "opt-in-gap"` when it is opt-in-baseline-only. A local key absent from those template blocks produces a **warning**: it may be a typo or a template gap, and this check makes no claim about documentation elsewhere or runtime use. When the template carries a single `## Session Config` heading (no separate opt-in block), `'first'` and `'last'` resolve to the same block, every template key is treated as mandatory, and unknown-local-key warnings are skipped. Both fenced YAML (```` ```yaml ... ``` ````) and raw YAML body (up to next `## ` heading) are accepted. The check skips gracefully when the template file is absent, when no instruction file is detected, or when explicitly disabled via `--skip-session-config-parity`.
52
52
 
53
53
  The parity set is **template-driven**: every column-0 YAML key under `## Session Config` in the template (across both occurrences) is checked. As of the gsd Pattern Adoption Quick-Wins bundle (PRD 2026-05-22, issues #517–#521), the template-side keys include four opt-in top-level blocks:
54
54
 
@@ -57,7 +57,7 @@ The parity set is **template-driven**: every column-0 YAML key under `## Session
57
57
  - `templates-first` (Pattern 3 / #519) — gh/glab template-read enforcement hook
58
58
  - `verification-auto-fix` (Pattern 4 / #521) — opt-in auto-fix retry loop after Quality-Gate fail
59
59
 
60
- A local CLAUDE.md / AGENTS.md that omits any of these now surfaces a `session-config-parity` **warning** (not an error) — they are opt-in-baseline-only keys, so omitting them never trips `mode: hard`, and never blocks `autonomous-gated` skill-evolution's `runConfigValidationGate()` (which runs the checker with `--mode hard` and reads only `errors[]`).
60
+ A local CLAUDE.md / AGENTS.md that omits any of these surfaces a `session-config-parity` **note** — they are opt-in-baseline-only keys, so omitting them never trips `mode: hard`, and never blocks `autonomous-gated` skill-evolution's `runConfigValidationGate()` (which runs the checker with `--mode hard` and reads only `errors[]`).
61
61
 
62
62
  Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruction files rather than the single alias-resolved one. The alias rule (CLAUDE.md wins ties, AGENTS.md is the Codex alias) means `resolveInstructionFile()` picks exactly one — so a repo carrying both files can silently let `AGENTS.md` drift out of sync with `CLAUDE.md`. A sibling project ran for weeks with a correct `vault-integration.vault-dir` in `CLAUDE.md` and a dead path in `AGENTS.md`. Check 7 reads `vault-integration.vault-dir` from each file (reusing the `_parseVaultIntegration` parser from `scripts/lib/config/vault-integration.mjs` — no hand-rolled YAML) and flags a `vault-dir-parity` error when the two values diverge (the error is attributed to `AGENTS.md`, the secondary alias, and names both values). The check skips gracefully when only one instruction file is present (nothing to compare), when neither file declares a `vault-integration:` block, or when explicitly disabled via `--skip-vault-dir-parity`. Two files that both omit `vault-dir` (both unset) agree and pass.
63
63
 
@@ -65,11 +65,11 @@ Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruc
65
65
 
66
66
  The same predicate guards Check 9's probe 2a, which iterates `['CLAUDE.md', 'AGENTS.md']`: with an aliased `AGENTS.md` it scans `CLAUDE.md` only, because otherwise every dangling `.claude/rules/*.md` citation would be reported TWICE — one defect, two errors, and in `--mode strict` a doubled blocking surface against a file the operator is not allowed to edit.
67
67
 
68
- Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Six probes: **(1) paths-presence** — since #795 `paths:` is a full same-shape alias for `globs:` in `rule-loader.mjs` (`globs:` wins silently when both are present), so a well-formed `paths:`-only rule loads correctly SCOPED and is NOT flagged (#840). The probe fires only when the frontmatter textually carries a top-level `paths:` key AND `parseGlobsFrontmatter` — the loader's own parser, imported rather than re-derived — returns `globs === null` (e.g. a malformed frontmatter line makes it throw); that parse mismatch means the rule may silently load ALWAYS-ON, and is flagged as an error. **(2) cited-but-missing** — `(a)` `.claude/rules/<name>.md` citations inside `CLAUDE.md`/`AGENTS.md` that don't resolve to a file on disk, and `(b)` bare `<name>.md` tokens in a rule's own `## See Also` footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. `../../skills/_shared/state-ownership.md`, are cross-directory references and explicitly out of scope); both flagged as errors. **(3) zero-match-globs** — a `globs:` pattern matching zero files in `git ls-files` (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules. **(4) foreign-glob** — a glob pattern containing a PascalCase product-like token (regex `[A-Z][a-z]+[A-Z]`, e.g. `WalkAITalkieTests`) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. **(5) unreadable-file** — a rule file that could not be read (permissions, a race with a concurrent delete); flagged as a WARNING rather than silently skipped, so a completeness audit does not drop files, and so an unreadable file cannot brick the gate under `mode: hard`. **(6) fleet-intent-glob** — a `globs:` pattern DECLARED as fleet intent goes to `notes[]` (see below), never to `warnings[]`, and the declaration also settles probe 4 for that same pattern: a pattern the rule author declared is by construction not a copy-paste leftover. Glob matching reuses the same picomatch-with-inline-fallback resolution `scripts/lib/rule-loader.mjs` uses (`parseGlobsFrontmatter` is imported directly; the picomatch resolution itself is duplicated locally since `rule-loader.mjs` does not export a public matcher function). The check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `.claude/rules/` is absent, or explicitly via `--skip-rule-scoping`.
68
+ Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Six probes: **(1) paths-presence** — a frontmatter block that does not open on line 1 (text or a provenance comment before `---`) is an ERROR since #1449: Claude Code ignores it and loads the rule always-on; the probe reports `parseGlobsFrontmatter(...).defect === 'frontmatter-not-at-top'` with the remedy (move the block to line 1, provenance comment after the closing `---`). Otherwise, since #795 `paths:` is a full same-shape alias for `globs:` in `rule-loader.mjs` (`globs:` wins silently when both are present), so a well-formed `paths:`-only rule loads correctly SCOPED and is NOT flagged (#840). The probe fires only when the frontmatter textually carries a top-level `paths:` key AND `parseGlobsFrontmatter` — the loader's own parser, imported rather than re-derived — returns `globs === null` (e.g. a malformed frontmatter line makes it throw); that parse mismatch means the rule may silently load ALWAYS-ON, and is flagged as an error. **(2) cited-but-missing** — `(a)` `.claude/rules/<name>.md` citations inside `CLAUDE.md`/`AGENTS.md` that don't resolve to a file on disk, and `(b)` bare `<name>.md` tokens in a rule's own `## See Also` footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. `../../skills/_shared/state-ownership.md`, are cross-directory references and explicitly out of scope); both flagged as errors. **(3) zero-match-globs** — a `globs:` pattern matching zero files in `git ls-files` (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules. **(4) foreign-glob** — a glob pattern containing a PascalCase product-like token (regex `[A-Z][a-z]+[A-Z]`, e.g. `WalkAITalkieTests`) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. **(5) unreadable-file** — a rule file that could not be read (permissions, a race with a concurrent delete); flagged as a WARNING rather than silently skipped, so a completeness audit does not drop files, and so an unreadable file cannot brick the gate under `mode: hard`. **(6) fleet-intent-glob** — a `globs:` pattern DECLARED as fleet intent goes to `notes[]` (see below), never to `warnings[]`, and the declaration also settles probe 4 for that same pattern: a pattern the rule author declared is by construction not a copy-paste leftover. Glob matching reuses the same picomatch-with-inline-fallback resolution `scripts/lib/rule-loader.mjs` uses (`parseGlobsFrontmatter` is imported directly; the picomatch resolution itself is duplicated locally since `rule-loader.mjs` does not export a public matcher function). The check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `.claude/rules/` is absent, or explicitly via `--skip-rule-scoping`.
69
69
 
70
70
  **Fleet-intent declaration (Check 9, probe 6).** A rule may declare that a `globs:` pattern is aimed at CONSUMER repos rather than this one, via its own frontmatter key `fleet-intent-globs:`. Accepted forms are block style (`fleet-intent-globs:` plus indented ` - "<pattern>"` lines), flow style (`fleet-intent-globs: ["a", "b"]`) and a single inline value; surrounding quotes are stripped, blank and `#` comment lines are ignored. The key is inert for rule loading — `rule-loader.mjs`'s `parseGlobsFrontmatter` skips unknown top-level keys and their continuation lines. A declared pattern is reported in `notes[]` with its file, the pattern, whether it matches `≥1` or `0` tracked files here, and the declaration `source` (`frontmatter`). The grain is per pattern AND per rule file: declaring one glob amnesties neither a sibling glob in the same rule nor the same pattern in another rule. There is deliberately NO checker-side exemption list: a rule that does not declare the pattern itself is not exempt, so the declaration always sits with the rule it describes.
71
71
 
72
- Check 10 (`docs-parity`, issue #780) validates the public docs surface against live repository state via three sub-checks, all reported under the single `docs-parity` check id: **(a) count-claims** — `docs/components.md`'s own heading counts ("## Skills (N user-facing)", "## Commands (N)", "## Agents (N typed sub-agents)", "## Hook event types (N)") are compared against the SAME actual on-disk derivation the surface-count family (Check 5) uses (`countSkills`/the commands-dir listing/`countAgents`/`readHookCounts`) — NOT Check 5's `claimRe` regexes, which are tuned for CLAUDE.md/README prose phrasing and verifiably do not match `components.md`'s own heading convention; a dedicated set of regexes tailored to the doc's actual authored structure is used instead. **(b) config-block-parity** — top-level Session Config keys documented in `docs/session-config-template.md` (opt-in baseline, via the Check 6 'last'-occurrence extractor) are compared against `docs/session-config-reference.md`; a key is considered documented in the reference when it appears in a `yaml` fence (any indent), a `##`/`###`/`####` heading, or the first cell of a markdown table row (`` | `key-name` | ... ``) — the reference's dominant documentation shape is one table row per field, so a naive fence-only extraction under-counts what it actually documents. Missing keys surface as `docs-parity` errors attributed to `docs/session-config-reference.md`. **(c) metrics-path-liveness** — any `.claude/metrics/` path reference (stale; canonical is `.orchestrator/metrics/`) found in root `docs/*.md` or `docs/examples/*.md` surfaces as an error at its exact file/line. The whole check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `docs/components.md` is absent, or explicitly via `--skip-docs-parity`.
72
+ Check 10 (`docs-parity`, issue #780) validates the public docs surface against live repository state via three sub-checks, all reported under the single `docs-parity` check id: **(a) count-claims** — `docs/components.md`'s own heading counts ("## Skills (N user-facing)", "## Commands (N)", "## Agents (N typed sub-agents)", "## Hook event types (N)") are compared against the SAME actual on-disk derivation the surface-count family (Check 5) uses (`countSkills`/`countSlashCommands`/`countAgents`/`readHookCounts`) — NOT Check 5's `claimRe` regexes, which are tuned for CLAUDE.md/README prose phrasing and verifiably do not match `components.md`'s own heading convention; a dedicated set of regexes tailored to the doc's actual authored structure is used instead. **(b) config-block-parity** — top-level Session Config keys documented in `docs/session-config-template.md` (opt-in baseline, via the Check 6 'last'-occurrence extractor) are compared against `docs/session-config-reference.md`; a key is considered documented in the reference when it appears in a `yaml` fence (any indent), a `##`/`###`/`####` heading, or the first cell of a markdown table row (`` | `key-name` | ... ``) — the reference's dominant documentation shape is one table row per field, so a naive fence-only extraction under-counts what it actually documents. Missing keys surface as `docs-parity` errors attributed to `docs/session-config-reference.md`. **(c) metrics-path-liveness** — any `.claude/metrics/` path reference (stale; canonical is `.orchestrator/metrics/`) found in root `docs/*.md` or `docs/examples/*.md` surfaces as an error at its exact file/line. The whole check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `docs/components.md` is absent, or explicitly via `--skip-docs-parity`.
73
73
 
74
74
  ## Files
75
75
 
@@ -133,7 +133,7 @@ Environment:
133
133
  }
134
134
  ```
135
135
 
136
- `notes[]` is the THIRD output category, alongside `errors[]` and `warnings[]`: a finding that is reported but asks for no action. It never affects `status` or the exit code, and it is always present (empty array when nothing was noted, including on the short-circuit "no scope files matched" path). Its only producer today is Check 9's fleet-intent-glob probe.
136
+ `notes[]` is the THIRD output category, alongside `errors[]` and `warnings[]`: a finding that is reported but asks for no action. It never affects `status` or the exit code, and it is always present (empty array when nothing was noted, including on the short-circuit "no scope files matched" path). Producers are Check 6's `opt-in-gap` probe and Check 9's `fleet-intent-glob` probe. Opt-in-gap notes carry `check: "session-config-parity"`, `probe: "opt-in-gap"`, the instruction file and line, a message, and the omitted key in `extracted`; they have no `source` field.
137
137
 
138
138
  The `resolved_path` / `resolved_kind` pair surfaces the alias resolution outcome (issue #33 AC2) so users on either platform can audit which instruction file the checker scanned. `kind: 'claude'` for `CLAUDE.md`, `kind: 'agents'` for `AGENTS.md`, `null` when neither was found.
139
139
 
@@ -25,7 +25,9 @@
25
25
  * Reuses `_parseVaultIntegration` from scripts/lib/config/ for Check 7;
26
26
  * reuses `parseGlobsFrontmatter` from scripts/lib/rule-loader.mjs for Check 9's
27
27
  * glob extraction (mirrors the same picomatch-with-inline-fallback resolution
28
- * rule-loader.mjs uses); otherwise pure Node stdlib.
28
+ * rule-loader.mjs uses) and its line-1-only frontmatter rule (#1449: a block
29
+ * with anything before its opening `---` is not frontmatter, exactly as Claude
30
+ * Code reads it); otherwise pure Node stdlib.
29
31
  */
30
32
 
31
33
  import { readFileSync, readdirSync, existsSync, statSync, lstatSync, realpathSync } from 'node:fs';
@@ -36,7 +38,7 @@ import { resolveInstructionFile } from '../../scripts/lib/common.mjs';
36
38
  import { _parseVaultIntegration } from '../../scripts/lib/config/vault-integration.mjs';
37
39
  import { _parseDriftCheck } from '../../scripts/lib/config/drift-check.mjs';
38
40
  import { isSessionConfigHeading } from '../../scripts/lib/config/section-extractor.mjs';
39
- import { parseGlobsFrontmatter } from '../../scripts/lib/rule-loader.mjs';
41
+ import { FRONTMATTER_NOT_AT_TOP, parseGlobsFrontmatter } from '../../scripts/lib/rule-loader.mjs';
40
42
  import { resolveRepoSpec } from '../../scripts/lib/vcs-repo-spec.mjs';
41
43
  import { userInvocableSkills } from '../../scripts/lib/user-invocable-skills.mjs';
42
44
 
@@ -573,7 +575,11 @@ function lookupIssueState(iid, repo, cache, vaultDir) {
573
575
  // rule-loader.mjs's parseGlobsFrontmatter (the `paths:` alias, issue
574
576
  // #795) fails to recognise — a genuine parse mismatch. A well-formed
575
577
  // `paths:`-only rule is NOT flagged: since #795, `paths:` is a full
576
- // alias for `globs:` and loads correctly SCOPED (#840).
578
+ // alias for `globs:` and loads correctly SCOPED (#840). Also an error
579
+ // (#1449): a frontmatter block that does not start on line 1 (a
580
+ // provenance comment, a blank line or any text before the opening
581
+ // `---`) — parseGlobsFrontmatter reports it as `frontmatter-not-at-top`,
582
+ // Claude Code ignores such a block, and the rule loads ALWAYS-ON.
577
583
  // 2. cited-but-missing → errors[]: (a) `.claude/rules/<name>.md` citations in
578
584
  // CLAUDE.md/AGENTS.md that don't exist on disk; (b) bare `<name>.md`
579
585
  // tokens in a rule's own `## See Also` footer that don't exist on disk
@@ -674,21 +680,12 @@ function listTrackedFiles(vaultDir) {
674
680
  }
675
681
  }
676
682
 
677
- const RULE_HEADER_LINE_RE = /^[ \t]*(?:<!--.*-->)?[ \t]*$/;
678
-
679
- function stripLeadingRuleHeaderLines(content) {
680
- const lines = content.split(/\r?\n/);
681
- let idx = 0;
682
- while (idx < lines.length && RULE_HEADER_LINE_RE.test(lines[idx])) idx++;
683
- return lines.slice(idx).join('\n');
684
- }
685
-
686
683
  /** Extracts the raw frontmatter block body (text between the `---` delimiters),
687
- * or null when the file has no frontmatter block. Leading blank/comment
688
- * provenance lines are tolerated so Check 9 sees the same frontmatter shape
689
- * that rule-loader.mjs accepts. */
684
+ * or null when the file has no frontmatter block. Only a block whose opening
685
+ * `---` is line 1 counts (#1449) — the rule rule-loader.mjs and Claude Code
686
+ * apply; a displaced block is body text, reported by probe 1 instead. */
690
687
  function extractFrontmatterBlockBody(content) {
691
- const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(stripLeadingRuleHeaderLines(content));
688
+ const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
692
689
  return m ? m[1] : null;
693
690
  }
694
691
 
@@ -1173,7 +1170,7 @@ function main() {
1173
1170
  // Minimal inline frontmatter extractor for auto-generated + learning-key.
1174
1171
  // Reads the opening --- ... --- block from a markdown file.
1175
1172
  function extractFrontmatterFields(mdContent) {
1176
- const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(stripLeadingRuleHeaderLines(mdContent));
1173
+ const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(mdContent);
1177
1174
  if (!m) return { autoGenerated: false, learningKey: null, expiresAt: null, evidenceDigest: null };
1178
1175
  const block = m[1];
1179
1176
  const autoGenM = /^auto-generated:\s*(.+)$/m.exec(block);
@@ -1370,10 +1367,21 @@ function main() {
1370
1367
  // parseGlobsFrontmatter failed to recognise it (globs === null) —
1371
1368
  // a genuine parse mismatch between this probe's textual detection
1372
1369
  // and the loader's actual behaviour, not routine `paths:` usage.
1370
+ // #1449: the same parser also reports a frontmatter block that does
1371
+ // not start on line 1. Claude Code ignores that block, so whatever
1372
+ // key it carries (globs:, paths:, tier:, …) has no effect and the
1373
+ // rule loads ALWAYS-ON — an error of its own, reported instead of
1374
+ // the paths: mismatch (fmBody is null for such a file anyway).
1373
1375
  let parsed;
1374
1376
  try { parsed = parseGlobsFrontmatter(content); } catch { parsed = { globs: null, meta: {} }; }
1375
1377
  const fmBody = extractFrontmatterBlockBody(content);
1376
- if (fmBody && /^paths:/m.test(fmBody) && parsed.globs === null) {
1378
+ if (parsed.defect === FRONTMATTER_NOT_AT_TOP) {
1379
+ errors.push({
1380
+ check: 'rule-scoping', file: relPath, line: 1,
1381
+ message: `${relPath}: frontmatter is not at line 1 (text before the opening ---) — Claude Code ignores it, the rule loads ALWAYS-ON; move the frontmatter to line 1 and put the provenance comment after the closing ---`,
1382
+ extracted: FRONTMATTER_NOT_AT_TOP,
1383
+ });
1384
+ } else if (fmBody && /^paths:/m.test(fmBody) && parsed.globs === null) {
1377
1385
  errors.push({
1378
1386
  check: 'rule-scoping', file: relPath, line: 1,
1379
1387
  message: `Rule frontmatter declares 'paths:' but rule-loader.mjs's parseGlobsFrontmatter did not recognise it — the rule may silently load ALWAYS-ON. Verify the paths:/globs: frontmatter syntax.`,
@@ -173,13 +173,13 @@ Use the same archive-safe pipeline as Phase 3, Step 3.5 — **never** a hand-rol
173
173
 
174
174
  1. Read all lines from `learnings.jsonl`
175
175
  2. Apply the selected operation to selected learnings:
176
- - **Boost:** +0.15 confidence (cap 1.0), reset expires_at to +`learning-expiry-days`
176
+ - **Boost:** +0.15 confidence (cap 1.0), reset expires_at to `deriveExpiresAt(now, type)` (per-type TTL, `scripts/lib/learnings/schema.mjs`)
177
177
  - **Reduce:** -0.2 confidence
178
178
  - **Delete:** omit the selected entries from the next generation — do NOT delete them by hand.
179
179
  `pruneLearnings()` detects every **record** that left the store — reconciled by `id`, or by a
180
180
  content fingerprint when a record carries no usable `id` — and archives it with
181
181
  `_archive_reason: "pruned"`, so a `learning-id` referenced by a rendered rule stays resolvable.
182
- - **Extend:** reset expires_at to current date + `learning-expiry-days`
182
+ - **Extend:** reset expires_at to `deriveExpiresAt(now, type)` — `learning-expiry-days` is parsed but read by no script (`skills/_shared/config-reading.md` § Learning Expiry Semantics)
183
183
  3. Steps 3–5 of the old prose (prune / consolidate / rewrite) are `pruneLearnings()` — run the
184
184
  **exact** Step 3.5(5) invocation, writing the post-operation entry set to the `--entries`
185
185
  sidecar. It prunes
@@ -61,10 +61,9 @@ For each of the 9 built-in analyzer learning types, apply these heuristics.
61
61
 
62
62
  - Compare `total_agents` and `total_waves` across session types
63
63
  - Calculate average agents per wave for each session type. Read a wave's agent count defensively — older records use other keys: `agent_count`, else `agents` when it is a number, else the length of `agents` when it is an array (of descriptions), else `agents_dispatched`. A record with `total_waves: 0` contributes no per-wave ratio — skip it rather than divide by zero. Exclude coordinator-direct waves (`coordinator_direct: true`, which dispatched no agents) from the ratio. In particular, a record whose waves are all coordinator-direct `Housekeeping` waves (predicate `isCoordinatorDirectHousekeeping` in `scripts/lib/session-schema/filters.mjs`, the session-end writer shape since #1321) contributes no per-wave ratio, same as `total_waves: 0`. Counting it would log a false 0.0 agents-per-wave observation.
64
- - **Group by `(session_type, session_profile)` (#1247), never by `session_type` alone.** `ultradeep` is a PROFILE over `session_type: deep` (7 waves vs. deep's 4-5, STATE.md frontmatter key `session-profile` — see `scripts/lib/state-md.mjs`), not a distinct type, so a bare `session_type` grouping folds a 7-wave ultradeep session and a 5-wave deep session into the same row and silently averages their agents-per-wave and over-delivery numbers together.
64
+ - **Group by `(session_type, session_profile)` (#1247), never by `session_type` alone.** `ultradeep` is a PROFILE over `session_type: deep` (7 waves vs. deep's 4-5, STATE.md frontmatter key `session-profile` — see `scripts/lib/state-md.mjs`), not a distinct type, so a bare `session_type` grouping folds a 7-wave ultradeep session and a 5-wave deep session into the same row and silently averages their agents-per-wave numbers together.
65
65
  - Subject = `sizingSubject({ session_type, session_profile })` from `scripts/lib/learnings/sizing-subject.mjs` — e.g. `deep-session-sizing` (no profile, byte-identical to the pre-#1247 literal), `deep-ultradeep-session-sizing` (profile `ultradeep`), or `feature-session-sizing`. Always derive via this helper; do not hand-concatenate the subject string.
66
66
  - Insight = "Deep sessions average X agents across Y waves" or "Ultradeep sessions average X agents across Y waves" (name the profile in the insight whenever `session_profile` is present, so the two rows read as distinct sizing observations, not variants of one sentence)
67
- - **Over-delivery ratio aggregation (#730/H4, #794.7; keyed by profile since #1247):** compute the MEDIAN of `waves[].over_delivery_ratio` across the last ~5 `sessions.jsonl` records of the same `(session_type, session_profile)` pair, filtered to waves whose `role` is not `Discovery`/`Finalization` and which carry the field (skip records lacking the field — pre-#730; also skip Discovery/Finalization waves, whose planned set is empty by design). This exclusion clause is intentionally identical to `skills/session-plan/SKILL.md` Step 0.5 "Over-delivery sizing" — keep the two wordings in sync on edit. Fold the median into this candidate's `insight`/`evidence` fields — e.g. `evidence`: `"median_over_delivery_ratio: 1.4 (n=12 waves, session_type=deep, session_profile=ultradeep)"` — so `session-plan` Step 0.5 can read the ratio from the `effective-sizing` learning first, falling back to its own direct `sessions.jsonl` scan only when no such learning exists.
68
67
 
69
68
  #### 3. recurring-issue (type: `recurring-issue`)
70
69
 
@@ -238,7 +237,12 @@ For confirmed learnings, use atomic rewrite strategy:
238
237
 
239
238
  Write the full next-generation entry set (existing entries **with** the step-2/3 confidence
240
239
  updates, **plus** the step-4 new learnings) as JSONL to a temp sidecar **via the Write tool**
241
- (not a shell `>` redirect — the destructive-command guard blocks it), then invoke the
240
+ (not a shell `>` redirect — the destructive-command guard blocks it) at
241
+ `.orchestrator/tmp/learnings-next.jsonl` (the Write tool creates the missing
242
+ `.orchestrator/tmp/` itself; from a shell, `mkdir -p .orchestrator/tmp` first). The sidecar
243
+ lives under `.orchestrator/tmp/`, never `.orchestrator/metrics/`, because `tmp/` is outside
244
+ the ledger guard's `ledger-delete-protected` denylist — a metrics-dir sidecar made the
245
+ cleanup `rm` below a blocked command (#1453). Then invoke the
242
246
  `--prune` subcommand of the sweep CLI. **This call is also `/evolve`'s ONLY
243
247
  `orchestrator.evolve.completed` success emit (#1206)** — export `N` (Step 3.5(4)'s
244
248
  new-learnings count), `M` (Step 3.5(2)'s reinforced-existing count) and `DURATION_MS`
@@ -247,7 +251,7 @@ For confirmed learnings, use atomic rewrite strategy:
247
251
  an argument error:
248
252
 
249
253
  ```bash
250
- NEXT=".orchestrator/metrics/.learnings-next.jsonl" # written by the step above
254
+ NEXT=".orchestrator/tmp/learnings-next.jsonl" # written by the step above
251
255
  node scripts/sweep-expired-learnings.mjs --prune --apply --json --entries "$NEXT" \
252
256
  --appended "${N:-0}" --boosted "${M:-0}" --duration-ms "${DURATION_MS:-0}" \
253
257
  --repo-root "$(pwd)" && rm -f "$NEXT"
@@ -301,13 +305,13 @@ For confirmed learnings, use atomic rewrite strategy:
301
305
  old "read back the first line to confirm valid JSON" check is redundant here: `rewriteLearnings()`
302
306
  round-trip-validates EVERY line before any byte reaches disk (#662), and the `malformed` guard
303
307
  above rejects an unparseable sidecar before the store is touched at all.
304
- 6. **Vault mirror (conditional):** Check `$CONFIG."vault-integration".enabled` via jq. If the field is missing or `false`, skip this step entirely — skill behavior is unchanged.
308
+ 6. **Vault mirror (conditional):** Check `$CONFIG."vault-integration".enabled` via jq. If `$CONFIG."vault-integration"."host-override"` is set and the integration is off (`enabled` is `false` or `mode` is `off`), report `vault-mirror: bewusst aus auf diesem Host (<host-override>)` and skip this step — the host switched it off on purpose (SO#1448), so this is never a failure, even when the committed mode is `strict`. Otherwise, if the field is missing or `false`, skip this step entirely — skill behavior is unchanged.
305
309
 
306
310
  If `enabled` is `true`:
307
311
 
308
312
  a. Check `$CONFIG."vault-integration".mode`. If `mode` is `off`, skip the mirror invocation (treat as disabled). If `mode` is absent, default to `warn`.
309
313
 
310
- b. Resolve the vault directory: use `$CONFIG."vault-integration"."vault-dir"` if non-null, otherwise fall back to the `$VAULT_DIR` environment variable. If neither is set, emit a warning and skip.
314
+ b. Resolve the vault directory: use `$CONFIG."vault-integration"."vault-dir"` if non-null, otherwise fall back to the `$VAULT_DIR` environment variable. If neither is set, emit a warning and skip. Report `vault-dir=<path> (Quelle: <source>)`, where `<source>` is `$CONFIG."vault-integration"."vault-dir-source"` (`env`, `match`, `owner`, `committed`), or `env VAULT_DIR` when the fallback was used.
311
315
 
312
316
  c. Invoke the mirror script. Derive a synthetic `EVOLVE_SESSION_ID` so the vault-mirror auto-commit phase (#31) produces a traceable commit subject (`chore(vault): mirror evolve-<date> — N learnings + 0 sessions`). Pass `--vault-name` when `vault-integration.vault-name` is set in Session Config:
313
317
  ```bash
@@ -25,7 +25,7 @@ description: >
25
25
 
26
26
  The user invoked `/memory-cleanup` with arguments: **$ARGUMENTS**. Parse them before anything else — two optional, mutually-exclusive flags are recognised (`--dry-run`, `--apply-pending`, see PRD #502); passing both is an error, and the absence of both selects the legacy interactive 4-phase mode. The per-flag behaviour, the exact status lines and the exit codes are in § Argument Handling (Phase 0) below; the interactive default runs Phases 1-4 (Orient → Gather Signal → Consolidate → Prune & Index) against `~/.claude/projects/<encoded-cwd>/memory/` and reports per § Output.
27
27
 
28
- Sidecar producer/consumer contract: session-end Phase 3.6.5 (`scripts/lib/auto-dream.mjs`) is nudge-only (#614) — it never dispatches a subagent to write the sidecar. The only real producer of `.orchestrator/pending-dream.md` is a manual `/memory-cleanup --dry-run` run; `--apply-pending` is the operator-confirmed consumer in a later session. The sidecar file is single-writer — concurrent sessions cannot collide because the writer holds the session-lock. <!-- path-check: example -->
28
+ Sidecar producer/consumer contract: session-start's `maintenance-due` probe uses `scripts/lib/auto-dream.mjs` to report when cleanup is due; the former session-end Phase 3.6.5 nudge was retired in #1288. Neither path generates the sidecar. The only real producer of `.orchestrator/pending-dream.md` is a manual `/memory-cleanup --dry-run` run; `--apply-pending` is the operator-confirmed consumer in a later session. The sidecar file is single-writer — concurrent sessions cannot collide because the writer holds the session-lock. <!-- path-check: example -->
29
29
 
30
30
  Implements the 4-phase memory consolidation process modelled after Claude Code's Auto Dream feature. Run after major refactors, framework migrations, or every 5+ sessions in a repo.
31
31