session-orchestrator 5.4.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/060-evolve.mdc +5 -5
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +115 -3
- package/README.md +30 -15
- package/docs/ci-setup.md +120 -23
- package/docs/components.md +7 -7
- package/docs/events-schema.md +17 -14
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/scope-collision-guard.md +19 -15
- package/docs/session-config-reference.md +7 -7
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +17 -8
- package/hooks/_lib/lock-bootstrap.mjs +118 -26
- package/hooks/config-protection.mjs +1 -1
- package/hooks/enforce-commands.mjs +28 -13
- package/hooks/enforce-scope.mjs +201 -23
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/on-session-start.mjs +4 -2
- package/hooks/on-stop.mjs +77 -171
- package/hooks/post-bash-write-verify.mjs +219 -24
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +41 -156
- package/hooks/pre-bash-memory-propose-audit.mjs +28 -11
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-task-scope-disjoint.mjs +603 -241
- package/hooks/wave-scope-commit-guard.mjs +235 -73
- package/package.json +9 -8
- package/scripts/apply-session-learnings.mjs +35 -14
- package/scripts/archive-closed-prds.mjs +38 -11
- package/scripts/autopilot.mjs +8 -1
- package/scripts/backfill-abandoned-sessions.mjs +80 -5
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +46 -2
- package/scripts/emit-session.mjs +5 -3
- package/scripts/export-hw-learnings.mjs +18 -7
- package/scripts/fleet-checkin.mjs +26 -3
- package/scripts/lib/autopilot/loop.mjs +25 -10
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config.mjs +5 -1
- package/scripts/lib/eval/engine.mjs +13 -0
- package/scripts/lib/events-retention-banner.mjs +393 -0
- package/scripts/lib/events-rotation.mjs +21 -6
- package/scripts/lib/events.mjs +51 -3
- package/scripts/lib/fleet-protocol.mjs +20 -2
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +8 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +387 -20
- package/scripts/lib/learnings/io.mjs +125 -16
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/named-vault-resolver.mjs +48 -5
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +101 -69
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +79 -27
- package/scripts/lib/orphan-reaper/scan.mjs +4 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +5 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +83 -22
- package/scripts/lib/scope-baseline.mjs +116 -25
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +201 -27
- package/scripts/lib/session-close-backfill.mjs +202 -4
- package/scripts/lib/session-end/tail-runner.mjs +10 -5
- package/scripts/lib/session-lock-shape.mjs +6 -3
- package/scripts/lib/session-lock.mjs +320 -67
- package/scripts/lib/session-registry.mjs +41 -16
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +39 -34
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +5 -2
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +77 -2
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +320 -32
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
- package/scripts/lib/vault-mirror/namespace.mjs +47 -26
- package/scripts/lib/vault-mirror/process.mjs +350 -62
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
- package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
- package/scripts/lib/vault-mirror/telemetry.mjs +18 -11
- package/scripts/lib/vault-status/board-writer.mjs +5 -1
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/release.mjs +1 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +14 -2
- package/scripts/run-quality-gate.mjs +8 -5
- package/scripts/self-update.mjs +229 -0
- package/scripts/sweep-expired-learnings.mjs +560 -28
- package/scripts/validate-wave-scope.mjs +60 -6
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-mirror.mjs +170 -33
- package/skills/_shared/fleet-protocol.md +74 -71
- package/skills/_shared/parallel-aware-auq.md +1 -1
- package/skills/_shared/state-ownership.md +38 -14
- package/skills/autopilot/SKILL.md +1 -1
- package/skills/bootstrap/SKILL.md +5 -3
- package/skills/bootstrap/_shared-template.md +96 -1
- package/skills/bootstrap/fast-template.md +3 -0
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
- package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
- package/skills/discovery/probes/vault-staleness.mjs +9 -3
- package/skills/docs-orchestrator/SKILL.md +12 -5
- package/skills/docs-orchestrator/audience-mapping.md +5 -3
- package/skills/evolve/SKILL.md +11 -2
- package/skills/evolve/references/evolve-analyze-mode.md +37 -17
- package/skills/session-end/learning-patterns.md +3 -3
- package/skills/session-end/metrics-collection.md +1 -1
- package/skills/session-end/phase-3-2-docs-verification.md +7 -6
- package/skills/session-end/plan-verification.md +25 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +11 -1
- package/skills/session-end/session-metrics-write.md +45 -10
- package/skills/session-start/SKILL.md +3 -3
- package/skills/session-start/references/operations-contract.md +5 -3
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- package/skills/session-start/references/phase-4-ssot-environment-check.md +8 -1
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +1 -1
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +1 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +8 -0
- package/skills/wave-executor/references/wave-loop-review.md +2 -2
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +14 -6
- package/.agents/skills/navigator/SKILL.md +0 -22
- package/.agents/skills/navigator/agents/openai.yaml +0 -5
- package/.codex-plugin/skills/navigator/SKILL.md +0 -22
- package/.codex-plugin/skills/navigator/agents/openai.yaml +0 -5
- package/.cursor/commands/navigator.md +0 -15
- package/.cursor/skills/navigator/SKILL.md +0 -13
- package/pi/prompts/navigator.md +0 -12
- package/skills/navigator/SKILL.md +0 -193
- package/skills/navigator/references/account-check.sh +0 -87
- package/skills/navigator/references/fleet-decisions.mjs +0 -231
- package/skills/navigator/references/slot-check.sh +0 -114
|
@@ -1,54 +1,60 @@
|
|
|
1
|
-
# Fleet Protocol v1 — Dateien statt Nachrichten (#1462
|
|
1
|
+
# Fleet Protocol v1 — Dateien statt Nachrichten (#1462)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`.claude/rules/**` loads it
|
|
3
|
+
Generic contract note for every session-orchestrator session (Claude Code, Codex, headless) that
|
|
4
|
+
may run 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
6
|
point here. The mechanics live in code — `scripts/lib/fleet-protocol.mjs` is the only place that
|
|
7
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.
|
|
9
|
-
|
|
8
|
+
This file describes them and never redefines them. The coordinator itself is not part of this
|
|
9
|
+
package: it is operated outside the plugin, which owns its operational detail (how it measures,
|
|
10
|
+
which conditions it issues, how it acquires and hands over the lease).
|
|
10
11
|
|
|
11
|
-
**Grundsatz (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
**Grundsatz (CSM-003).** Zustand liegt in Dateien; Nachrichten sind nur Hinweise auf eine Datei.
|
|
13
|
+
Der Navigator erteilt keine Freigaben — maßgeblich ist der Owner-Auftrag der Session. Eine Datei,
|
|
14
|
+
die fehlt, heißt „nicht nachweisbar", nie „nicht vorhanden": eine fehlende Lease ist kein freies
|
|
15
|
+
Feld, sondern ein Zustand ohne Beleg.
|
|
15
16
|
|
|
16
17
|
## Verzeichnis und Dateiform
|
|
17
18
|
|
|
18
19
|
- Wurzel `~/.config/navigator/`, Override `NAVIGATOR_CONFIG_DIR` (getrimmt; leer oder nur
|
|
19
|
-
Whitespace zählt als nicht gesetzt)
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
Whitespace zählt als nicht gesetzt). Code: `navigatorDir()`. Ein Override zeigt nur auf ein
|
|
21
|
+
eigenes Verzeichnis mit Modus 0700, nie auf einen geteilten Ort: wer dort schreiben kann, kann
|
|
22
|
+
eine „aktive" Lease vortäuschen.
|
|
22
23
|
- Verzeichnisse Modus `0700`, Dateien Modus `0600`. Schreiben immer atomar (tmp-Datei im
|
|
23
24
|
Zielverzeichnis, dann `rename`).
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
25
|
+
- Zeiten in Check-in und Lease stehen in UTC mit `Z`; eine Zeit ohne `Z` oder mit Offset ist dort
|
|
26
|
+
ungültig. Die Form hängt an der Datei: Nur der Check-in trägt `zeit`, in Sekunden wie `date -u +%FT%TZ`
|
|
27
|
+
(`YYYY-MM-DDTHH:MM:SSZ`), gesetzt von der CLI, nie geschätzt, nie aus einer anderen Uhr (Code:
|
|
28
|
+
`utcSecondsTimestamp()`). Die Lease trägt `seit` und `laeuft_ab` in Sekunden oder Millisekunden
|
|
29
|
+
(Tabelle unten, Code: `LEASE_TS_RE`). Inhalt und Form der Auflagen gehören dem Navigator.
|
|
30
|
+
- Dateinamen aus einer `session_id` sind pfadsicher: nur `[A-Za-z0-9._-]`, 1–128 Zeichen, nicht
|
|
31
|
+
`.` oder `..`, kein `/`. Code: `isSafeSessionId()`; `checkinPath()` und `auflagenPath()` werfen
|
|
32
|
+
sonst.
|
|
28
33
|
- Tests berühren das echte Verzeichnis nie: `tests/setup/navigator-dir-guard.mjs` setzt
|
|
29
34
|
`NAVIGATOR_CONFIG_DIR` je vitest-Worker auf ein tmp-Verzeichnis.
|
|
30
35
|
|
|
31
|
-
## `leases/navigator.json` — genau ein aktiver Navigator
|
|
36
|
+
## `leases/navigator.json` — genau ein aktiver Navigator
|
|
32
37
|
|
|
33
38
|
| Feld | Inhalt |
|
|
34
39
|
|---|---|
|
|
35
|
-
| `session_id` | Registry-ID des Navigators (Pflicht; pfadsicher wie oben
|
|
40
|
+
| `session_id` | Registry-ID des Navigators (Pflicht; pfadsicher wie oben) |
|
|
36
41
|
| `plattform` | `claude` \| `codex` \| `kopflos` (Pflicht) |
|
|
37
42
|
| `adresse` | `ListAgents`-Name, wenn Claude (`[A-Za-z0-9._:@-]`, 1–128 Zeichen); sonst `null` |
|
|
38
43
|
| `seit` | Zeit des Erwerbs, UTC mit `Z`, Sekunden oder Millisekunden (Pflicht) |
|
|
39
|
-
| `laeuft_ab` | UTC mit `Z
|
|
44
|
+
| `laeuft_ab` | UTC mit `Z`, gleiche Form; nach diesem Zeitpunkt ist die Lease abgelaufen (Pflicht) |
|
|
40
45
|
| `uebergabe_an` | `null` oder Session-ID des Nachfolgers |
|
|
41
46
|
|
|
42
47
|
**„Aktiv" heißt ausschließlich: gültige Lease.** `readNavigatorLease()` liefert genau eines von
|
|
43
48
|
`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,
|
|
45
|
-
Tabellenform: unbekannte `plattform`, Steuerzeichen in
|
|
46
|
-
oder mit Offset — sie würde in lokaler Zeit gelesen und
|
|
47
|
-
Unlesbar oder abgelaufen ist **nie** aktiv (fail-closed).
|
|
48
|
-
`scripts/lib/session-registry.mjs`) und `ListAgents` sind
|
|
49
|
-
|
|
49
|
+
`laeuft_ab` überschritten) oder `unreadable` (kaputtes JSON, fehlende Pflichtfelder, Lesefehler,
|
|
50
|
+
oder ein Feld außerhalb der Tabellenform: unbekannte `plattform`, Steuerzeichen in
|
|
51
|
+
`session_id`/`adresse`, eine Zeit ohne `Z` oder mit Offset — sie würde in lokaler Zeit gelesen und
|
|
52
|
+
könnte die Lease verlängern). Unlesbar oder abgelaufen ist **nie** aktiv (fail-closed).
|
|
53
|
+
Registry-Heartbeat (`role: navigator`, `scripts/lib/session-registry.mjs`) und `ListAgents` sind
|
|
54
|
+
Zusatzsignale, keine Lebendbeweise. Dieses Paket liest die Lease nur (SessionStart-Banner,
|
|
55
|
+
`fleet-checkin.mjs`); Erwerb, Erneuerung und Übergabe liegen beim Navigator.
|
|
50
56
|
|
|
51
|
-
## `checkin/<session_id>.json` — Session an Navigator
|
|
57
|
+
## `checkin/<session_id>.json` — Session an Navigator
|
|
52
58
|
|
|
53
59
|
**Schreibend** heißt: Änderung an Repo-Inhalt oder VCS-Zustand (Datei im Arbeitsbaum außerhalb
|
|
54
60
|
`.orchestrator/` und `.claude/`, Commit, Branch, Push, MR, Issue, Kommentar). SO-eigener
|
|
@@ -58,74 +64,71 @@ als Ausnahme im Check-in genannt (`vorab_geschrieben`). (`CLAUDE.md` heißt auf
|
|
|
58
64
|
`skills/_shared/instruction-file-resolution.md`.)
|
|
59
65
|
|
|
60
66
|
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,
|
|
62
|
-
`zeit
|
|
63
|
-
`orchestrator.fleet.checkin` (Payload `session`, `repo`, `modus`, `kandidaten`,
|
|
64
|
-
|
|
67
|
+
Operations-Route). Schreiber: `node scripts/fleet-checkin.mjs` liest das JSON von stdin, setzt
|
|
68
|
+
`zeit` (überschreibt immer), validiert (`validateCheckin()`), schreibt atomar mit Modus 0600 und
|
|
69
|
+
emittiert `orchestrator.fleet.checkin` (Payload `session`, `repo`, `modus`, `kandidaten`,
|
|
70
|
+
`navigator_state` — keine Pfade, keine Quota, kein `auftrag_ref`). stdout: eine JSON-Zeile
|
|
71
|
+
`{ ok, path, navigator_state, navigator_adresse, fallback_due_at, event }`; `navigator_adresse` ist
|
|
72
|
+
die vom Lease-Validator akzeptierte `adresse` einer aktiven Lease, sonst `null` (keine aktive
|
|
73
|
+
Lease, kein Eintrag oder ein abgelehnter Wert — nie ein ungeprüfter String). `fallback_due_at` ist
|
|
74
|
+
der Zeitpunkt, ab dem die Standard-Auflagen (unten) gelten, in der Form von `zeit` (Code:
|
|
75
|
+
`fallbackDueAt()`); er steht nur auf stdout, die Check-in-Datei behält ihr Schema. Exit 0
|
|
76
|
+
geschrieben, 2 ungültige Eingabe (Fehlerliste auf stderr, keine Datei), 1 Schreibfehler.
|
|
65
77
|
|
|
66
78
|
| Feld | Inhalt |
|
|
67
79
|
|---|---|
|
|
68
|
-
| `session
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
80
|
+
| `session` | Registry-ID aus `.orchestrator/current-session.json`, pfadsicher (Pflicht) |
|
|
81
|
+
| `plattform` | `claude` / `codex` / `kopflos` (Pflicht) |
|
|
82
|
+
| `repo` | Repo-Name `name` oder `gruppe/name`, nie ein Pfad (Pflicht) |
|
|
83
|
+
| `repo_id`, `worktree` | String oder `null`; Worktree `true`/`false` |
|
|
84
|
+
| `modus` | `housekeeping` / `feature` / `deep` / `operations` (Pflicht) |
|
|
85
|
+
| `auftrag_ref` | Pfad der Auftragsdatei oder „Owner-Chat <zeit>" mit Wortlaut in einem Satz (Pflicht) |
|
|
86
|
+
| `konto_slot`, `quota` | Zahl, String oder `null`; Quota als String, sonst `nicht messbar` |
|
|
73
87
|
| `stand` | Objekt: main-, origin/main-, Prod-SHA (sonst `nicht messbar`), offene MRs |
|
|
74
|
-
| `kandidaten
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
88
|
+
| `kandidaten` | höchstens 64 Issue-Nummern (`42` oder `"#42"`) (Pflicht) |
|
|
89
|
+
| `schreibbereich` | Liste von Verzeichnissen/Dateien als Strings (Pflicht) |
|
|
90
|
+
| `bedarf` | Objekt: geplante volle Pipelines, Remote-Offload-Jobs, prozessstartende Agents |
|
|
91
|
+
| `vorab_geschrieben` | Liste der Ausnahmen nach der Definition oben |
|
|
92
|
+
| `rueckfall` | Verhalten ohne Auflagen (unten), nicht leer (Pflicht) |
|
|
77
93
|
| `zeit` | von der CLI gesetzt |
|
|
78
94
|
|
|
79
95
|
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
|
|
96
|
+
nur wenn die Lease aktiv ist und `navigator_adresse` (stdout der Check-in-CLI) nicht `null` und per
|
|
97
|
+
`ListAgents` erreichbar ist, und nur an genau diesen Namen; nie eine Antwort
|
|
81
98
|
erwarten, nie darauf warten (CSM-004).
|
|
82
99
|
|
|
83
|
-
## `auflagen/<session_id>.json` — Navigator an Session
|
|
100
|
+
## `auflagen/<session_id>.json` — Navigator an Session
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Owner-Auftrag". Die Session **liest** die Datei; sie wartet auf keine Nachricht.
|
|
102
|
+
Bedingungen, die der Navigator einer Session schreibt (z. B. Agent-Caps, volle Pipelines je Repo,
|
|
103
|
+
Offload-Regel, Merge- und Zeitregeln, Besitzkonflikte); Inhalt und Form gehören dem Navigator. Die
|
|
104
|
+
Session **liest** die Datei; sie wartet auf keine Nachricht. Keine Auflage ist eine Freigabe.
|
|
89
105
|
|
|
90
|
-
**Standard-Auflagen (konservative Stufe).** Sie gelten
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
**Standard-Auflagen (konservative Stufe).** Sie gelten ab `fallback_due_at` (stdout der
|
|
107
|
+
Check-in-CLI), wenn bis dahin keine Auflagen-Datei liegt. Die Frist rechnet `fallbackDueAt()`:
|
|
108
|
+
`CHECKIN_FALLBACK_MIN` = 10 Minuten nach `checkin.zeit`, solange ein Navigator aktiv ist; ohne
|
|
109
|
+
aktiven Navigator ist `fallback_due_at` gleich `zeit`, die Auflagen gelten also sofort:
|
|
93
110
|
|
|
94
|
-
- Caps: 2 prozessstartende + 2 lesende Agents, solange der lokale Host ≥ 50 % Speicher frei hat
|
|
95
|
-
(`memory_pressure | tail -1`).
|
|
111
|
+
- Caps: 2 prozessstartende + 2 lesende Agents, solange der lokale Host ≥ 50 % Speicher frei hat.
|
|
96
112
|
- Pipelines: ≤ 2 volle je Repo; Nachzug-Pushes vorher im Check-in (`bedarf`) benennen.
|
|
97
|
-
- Offload: Heavy-Rollen (test, build, lint, audit) nur auf
|
|
98
|
-
|
|
99
|
-
|
|
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`.
|
|
113
|
+
- Offload: Heavy-Rollen (test, build, lint, audit) nur auf einen deklarierten Remote-Host, der
|
|
114
|
+
unmittelbar davor als bereit belegt ist (`skills/remote-offload/SKILL.md`); ohne Beleg kein neues
|
|
115
|
+
Offload. Lokal nur `vitest` bzw. `eslint` je Datei mit `timeout`.
|
|
102
116
|
- Zeiten nur aus `date`.
|
|
103
|
-
- Kein Merge ohne
|
|
104
|
-
|
|
105
|
-
(`auftrag_ref`). Ohne navigator-CLI ist jeder Merge `einzeln`.
|
|
117
|
+
- Kein Merge ohne ausdrücklichen Owner-Auftrag, der Merge für genau dieses Repo nennt
|
|
118
|
+
(`auftrag_ref`).
|
|
106
119
|
|
|
107
120
|
Lesende Arbeit und Schreiben im eigenen Worktree laufen währenddessen weiter; der Rückfall ist ein
|
|
108
121
|
Dateistand mit Frist, kein Warten auf eine Antwort.
|
|
109
122
|
|
|
110
|
-
## Verhalten ohne Navigator
|
|
123
|
+
## Verhalten ohne Navigator
|
|
111
124
|
|
|
112
125
|
Lease `none` oder `unreadable`: die Check-in-Datei wird **trotzdem** geschrieben (sie ist der
|
|
113
126
|
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
|
|
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`.
|
|
127
|
+
sind per Nachricht nicht erreichbar und nutzen dieselben Dateien: vor jeder Welle `auflagen/` lesen.
|
|
124
128
|
|
|
125
129
|
## See Also
|
|
126
130
|
|
|
127
131
|
`skills/session-start/SKILL.md` Phase 7.6 · `skills/session-start/references/operations-contract.md`
|
|
128
132
|
(Peer-Preflight) · `skills/_shared/state-ownership.md` § Session Identity and Lock Ownership (`role`) ·
|
|
129
133
|
`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`)
|
|
134
|
+
(CSM-003/004) · `scripts/lib/fleet-protocol.mjs` · `scripts/fleet-checkin.mjs`
|
|
@@ -113,7 +113,7 @@ In-place beside a peer is survivable when the two scopes are DECLARED to each ot
|
|
|
113
113
|
Four steps, in order. Steps 1 and 2 are the protocol; 3 and 4 are what keeps it honest.
|
|
114
114
|
|
|
115
115
|
1. **Declare the COMPLETE path list.** The arriving session sends the peer every path it will write — including the ones a script produces (fixtures, snapshots, result files, temp helpers), not only the ones it plans to edit by hand. A path omitted here is a path the peer's guard reports as a violation. In the same message it adopts the peer's resource rules.
|
|
116
|
-
2. **The peer unions.** The receiving coordinator adds those paths to its `allowedPaths` as ONE record `peer-session-<id>` in the wave's scope manifest, re-asserts subset/disjointness, and re-materializes them on every wave rollover (`skills/wave-executor/wave-loop.md` § Scope Manifest).
|
|
116
|
+
2. **The peer unions.** The receiving coordinator adds those paths to its `allowedPaths` as ONE record `peer-session-<id>` in the wave's scope manifest, re-asserts subset/disjointness, and re-materializes them on every wave rollover (`skills/wave-executor/wave-loop.md` § Scope Manifest). Mid-wave, re-run the FULL sequence of `skills/wave-executor/references/wave-loop-scope-manifest.md` § 3, including § 3.3's skeleton write (`"allowedPaths": []`) before the final manifest: `--union` excludes `peer-session-*` records, so re-materializing the sidecar alone leaves the manifest bytes unchanged, `bash-write-verify` keeps the sidecar bound to the OLD hash, and every agreed peer write then reports as a violation for the rest of the wave (#1504 point 5). The skeleton rewrite changes the manifest hash, which re-binds the new sidecar.
|
|
117
117
|
3. **Probe with ONE real write.** Before dispatching any agent, make one small PLANNED Edit from the declared list. A denial here costs one edit; the same denial found after a fan-out costs the wave.
|
|
118
118
|
4. **Announce before committing.** The arriving session sends its final file list, the peer sequences its own push behind it, and the SHAs come back. The git index is shared (PSA-007) — sequencing it is the only thing that makes two sessions in one checkout committable.
|
|
119
119
|
|
|
@@ -107,8 +107,9 @@ layer.
|
|
|
107
107
|
- **Registry `role` (`'navigator'`) is display, never ownership.** The
|
|
108
108
|
authoritative claim is the navigator lease (`leases/navigator.json` under
|
|
109
109
|
`~/.config/navigator/`, see `skills/_shared/fleet-protocol.md`); `registerSelf()`
|
|
110
|
-
does not carry the field across a repeated SessionStart, so
|
|
111
|
-
|
|
110
|
+
does not carry the field across a repeated SessionStart, so a navigator
|
|
111
|
+
(operated outside this plugin) has to re-set it on every round via
|
|
112
|
+
`heartbeat(id, { role })` (#1462).
|
|
112
113
|
|
|
113
114
|
The peer-discovery and issue-budget procedures below apply these rules at their
|
|
114
115
|
narrow surfaces; neither creates a second ownership model.
|
|
@@ -250,8 +251,11 @@ The `.orchestrator/session.lock` file is written mechanically by `hooks/_lib/loc
|
|
|
250
251
|
|
|
251
252
|
```
|
|
252
253
|
isAlive = (Date.now() - Date.parse(last_heartbeat)) < ttl_hours * 3600 * 1000
|
|
254
|
+
&& Date.parse(last_heartbeat) <= Date.now() + 5 * 60 * 1000
|
|
253
255
|
```
|
|
254
256
|
|
|
257
|
+
The second conjunct (#1494): a heartbeat stamped more than 5 minutes in the future is not live. Without it the age went negative and stayed below every TTL, so a hand-edited or clock-skewed lock held the repo until the wall clock caught up. The 5 minutes absorb a clock stepped back a little; the `category4.mjs` mirror carries the same guard.
|
|
258
|
+
|
|
255
259
|
This replaces the v1 PID-liveness check (`process.kill(pid, 0)`) which was fundamentally broken because the recorded `pid` belongs to the ephemeral hook subprocess (dies in <1s), not the long-lived Claude coordinator process. The PostgreSQL pattern — use a heartbeat timestamp rather than PID to establish liveness — is the authoritative reference (see W1-D4 best-practices §1.5).
|
|
256
260
|
|
|
257
261
|
### #744 — heartbeat is the SOLE active gate (incident + fix)
|
|
@@ -277,23 +281,43 @@ The fix removes the question rather than re-answering it. `classifyExisting()` r
|
|
|
277
281
|
|
|
278
282
|
`isPidAliveOnHost` remains exported from `session-lock.mjs` and is unaffected — `file-lock.mjs` and `lock-reaper.mjs` are legitimate callers, because there the pid IS the process being asked about.
|
|
279
283
|
|
|
280
|
-
### Schema v1 → v2 backward-compat
|
|
284
|
+
### Schema v1 → v2 backward-compat — ENDED 2026-10-02 (#595)
|
|
285
|
+
|
|
286
|
+
Lock readers no longer tolerate a v1 lock. A `session.lock` without `last_heartbeat` is foreign or damaged — no writer has produced one since Epic #583 (`buildLock()`, `hooks/_lib/lock-bootstrap.mjs` and `updateHeartbeat()` all set it) — and it is handled as **visible but not live**:
|
|
287
|
+
|
|
288
|
+
- `parseLock()` returns it unchanged: still shape-valid (`isLockShape()` does not require `last_heartbeat`), so `readLockDetailed()` reports `ok` and the lock is never silently dropped.
|
|
289
|
+
- `isLockLive()` and its stdlib-only mirror `lockIsLive()` in `scripts/lib/harness-audit/categories/category4.mjs` call it NOT live; there is no `started_at` fallback any more.
|
|
290
|
+
- `heartbeatAgeMinutes` is `null` (no heartbeat, so no heartbeat age) in both `acquire()`'s result and `checkStale()`.
|
|
291
|
+
- `acquire()` classifies it `stale-heartbeat`, so the SessionStart bootstrap reclaims it instead of the repo staying blocked for a TTL window that nothing heartbeats.
|
|
292
|
+
|
|
293
|
+
Rejecting such a lock outright was considered and refused. At the time, a rejected lock made `readLock()` return `null`, the create-or-fail in `acquire()` hit `EEXIST` on the file still there, and the vanished-race branch answered `active` with `existingLock: null` on every attempt, so a lock nobody could see blocked every new session. That wedge is closed now (see **Corrupt lock** below), but rejecting would still hide the lock's `session_id`, host and `started_at` from every `readLock()` caller.
|
|
294
|
+
|
|
295
|
+
`semantic_session_id` stays optional by schema, not as a v1 artefact: when absent, treat it as unknown.
|
|
296
|
+
|
|
297
|
+
#### Schema v1 Sunset — decided 2026-10-02 (#595)
|
|
298
|
+
|
|
299
|
+
**History.** The 90-day window from Epic #583 (target 2026-08-25) was evaluated on 2026-08-15 and the tolerance RETAINED. The blocker then was not v1 data but a second production copy of the rule (the `category4.mjs` mirror, pinned by `tests/lib/lock-ttl-parity.test.mjs` *for a v1 lock*) plus v1-shaped test fixtures: a removal attempt turned 18 tests red across 4 files.
|
|
300
|
+
|
|
301
|
+
**Precondition, re-measured 2026-10-02 on this host (read-only):**
|
|
302
|
+
|
|
303
|
+
- `find ~/Projects -maxdepth 4 -path '*/.orchestrator/session.lock'` → **8 files, 8/8 carry a non-empty `last_heartbeat`**.
|
|
304
|
+
- `~/.config/session-orchestrator/sessions/active/*.json` → **10 entries, 10/10 carry `last_heartbeat` and the `mode` key**.
|
|
281
305
|
|
|
282
|
-
|
|
306
|
+
**Decision (owner-approved scope, 2026-10-02):**
|
|
283
307
|
|
|
284
|
-
|
|
308
|
+
1. **Lock side: the fallback is REMOVED** from all three readers in `scripts/lib/session-lock.mjs` (`parseLock()` normalisation, `isLockLive()`, `heartbeatAgeMinutes()`) and from the `category4.mjs` mirror in the same change. The mirror stays, because the audit path must not import the lock manager. `lock-ttl-parity.test.mjs` now asserts that both copies call a heartbeat-less lock not live, so a copy that grows the fallback back breaks parity. The default-lock fixture factories in `tests/lib/session-discovery.test.mjs` and `tests/lib/session-discovery-fallback.test.mjs` now set `last_heartbeat`; at the measurement those two files carried 17 of the 19 tests the removal turned red.
|
|
309
|
+
2. **Registry side: `_validEntry()` keeps accepting an entry without `mode`.** This is no longer framed as v1 compat. It is a deliberate fail-open for peer visibility: rejecting a mode-less entry would drop a possibly LIVE peer from `readRegistry()` / `detectPeers()` and hide it from parallel-session detection, while an unclassifiable peer mode already degrades to `parallel-ok` in `acquire()`. This follows `.claude/rules/development.md` § Guard & Threshold Design.
|
|
285
310
|
|
|
286
|
-
|
|
311
|
+
**Display readers aligned (2026-10-02, follow-up to the sunset).** Three readers that parse the lock themselves kept the `started_at` stand-in after the change above, and now read `last_heartbeat` only:
|
|
287
312
|
|
|
288
|
-
|
|
313
|
+
- `scripts/lib/lock-reaper.mjs` `ageHoursOf()` reports `null` for a heartbeat-less lock (the CLI prints "unknown age") instead of the age since `started_at` labelled as staleness. It is display only; the reap gate is `isLockLive()`.
|
|
314
|
+
- `scripts/lib/autopilot/stall-sampler.mjs` `readHeartbeatMs()` treats a heartbeat-less lock like a timestamp-less one and falls back to the `autopilot.jsonl` mtime marker. `started_at` never advances during a run, so as a stand-in it read as a stall after 600 s, or as fresh in the first 30 s.
|
|
315
|
+
- `scripts/lib/vault-status/board-writer.mjs` renders `—` in the "Last heartbeat" column, never `started_at` under that label.
|
|
289
316
|
|
|
290
|
-
-
|
|
291
|
-
- `~/.config/session-orchestrator/sessions/active/*.json` → **3 entries, 3/3 carry the `mode` key** (0 v1).
|
|
292
|
-
- The only co-installed older plugin build (`~/.claude/plugins/cache/session-orchestrator/session-orchestrator/3.13.0`) already writes `last_heartbeat` (`session-lock.mjs:195`) and `mode` (`session-registry.mjs:209`) — **no v1 writer remains on this host.**
|
|
317
|
+
The Phase-1.2 stale-lock prompt (`skills/session-start/references/phase-1-2-session-lock.md`) renders a `null` `heartbeatAgeMinutes` as "last heartbeat: never (lock has no last_heartbeat)" and drops the "ttl has expired" clause in that case, instead of `Math.round(null)` = "0 minutes ago".
|
|
293
318
|
|
|
294
|
-
**
|
|
319
|
+
**Corrupt lock (2026-10-02).** A `session.lock` that fails `parseLock()` (invalid JSON, or JSON without the six-field shape) makes `acquire()` return `reason: 'corrupt'` (`existingLock: null`), and the SessionStart bootstrap reclaims it with one stderr WARN. Before this, the file wedged the repo: `readLock()` returned `null`, the create-or-fail hit `EEXIST`, and the vanished-race branch answered `active` with `existingLock: null` on every attempt, which the bootstrap never forces. Reclaiming is safe because such a file can never be a live lock: all four production writers write a full `buildLock()` body atomically (tmp+link / tmp+rename).
|
|
295
320
|
|
|
296
|
-
|
|
297
|
-
2. **`_validEntry()` optional `mode` — removal is a net safety LOSS.** Rejecting a mode-less registry entry drops a **live peer** from `readRegistry()`, making it invisible to the exclusivity matrix. An absent `mode` already degrades to the `parallel-ok` bucket, so strictening buys no detection and costs peer visibility — the wrong direction under `.claude/rules/development.md` § Guard & Threshold Design.
|
|
321
|
+
**Unreadable lock (#1494).** A lock path that exists but cannot be read (`EACCES`, or a directory: `EISDIR`) makes `acquire()` return `reason: 'unreadable'` (`existingLock: null`, `error`). It used to answer `active` / `existingLock: null`, which the bootstrap neither forced nor reported, so every session ran without a lock and left no trace. It is still never reclaimed (no lock writer produces it, and a directory may hold content nobody here created), but the SessionStart bootstrap now prints one stderr WARN and emits `orchestrator.session.lock.read_anomaly` with `status: 'unreadable'`.
|
|
298
322
|
|
|
299
|
-
**
|
|
323
|
+
**Single-winner reclaim (#1494).** The bootstrap reclaims a `stale-heartbeat` or `corrupt` lock with `forceAcquire({ onlyIfNotLive: true })`. The lock is renamed to a unique tombstone first, so only one caller can hold the moved file; a moved lock that is not live is replaced by the create-or-fail link, a live one (a racer's fresh lock) is linked back. Every lost path returns a fresh `acquire()` result, normally `active` with the winner's lock, so the loser records `conflict_with_session_id` instead of running on as the owner. Before, two starts that saw the same stale lock both forced it, both got `ok: true`, and the later rename won. A plain `forceAcquire()` (the operator-approved force-take of a live lock in Phase 1.2) still overwrites unconditionally.
|
|
@@ -127,7 +127,7 @@ intent for THIS run, not a standing policy.
|
|
|
127
127
|
| `--max-sessions` | `5` | 1..50 | Iteration cap (graceful exit when reached) |
|
|
128
128
|
| `--max-hours` | `4.0` | 0.5..24.0 | Wall-clock budget for entire loop |
|
|
129
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 |
|
|
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. An iteration whose record carries no token figure adds nothing and is counted as `tokens_unknown_sessions` (state, return value, `autopilot.jsonl`, and the run summary line on stdout, which prints `tokens=<sum>` and — when non-zero — `tokens_unknown_sessions=<n>`), so with any such iteration the sum is a LOWER bound and the budget can be overshot |
|
|
131
131
|
| `--dry-run` | `false` | — | Print planned iterations without executing |
|
|
132
132
|
|
|
133
133
|
Out-of-range values silently clamp to bounds. `--dry-run` exits after printing — never
|
|
@@ -58,7 +58,7 @@ skill without dispatching to a tier template.
|
|
|
58
58
|
|------|----------------|
|
|
59
59
|
| `--upgrade <tier>` | You bootstrapped `fast` earlier and now need `standard` or `deep`. Idempotent — writes only the delta. Refuses downgrade. Valid: `fast → standard`, `fast → deep`, `standard → deep`. |
|
|
60
60
|
| `--retroactive` | The repo already has `CLAUDE.md` (or `AGENTS.md` on Codex CLI) + `## Session Config` but no `bootstrap.lock` (manually bootstrapped before the gate existed). Writes the lock based on file inventory; **makes no scaffolding changes**. Commit: `chore: bootstrap lock (retroactive)`. |
|
|
61
|
-
| `--refresh-lock` | Your `bootstrap.lock` already has valid `version`/`tier` fields but the freshness probe flags it as stale or plugin-version-drifted — `--retroactive` is a no-op here. Acknowledges the current plugin version and resets the freshness clock (`refreshed-at` + `refreshed-plugin-version`) without touching the lock's original bootstrap provenance. No scaffolding, no auto-commit. |
|
|
61
|
+
| `--refresh-lock` | Your `bootstrap.lock` already has valid `version`/`tier` fields but the freshness probe flags it as stale or plugin-version-drifted — `--retroactive` is a no-op here. Acknowledges the current plugin version and resets the freshness clock (`refreshed-at` + `refreshed-plugin-version`) without touching the lock's original bootstrap provenance. Also appends missing store-lock `.gitignore` lines. No scaffolding, no auto-commit. |
|
|
62
62
|
| `--sync-rules` | Pull canonical rules from the plugin's `rules/` library into `.claude/rules/`. Preserves local rules (files without the plugin source header). Standalone — does not touch `bootstrap.lock`. |
|
|
63
63
|
| `--ecosystem-health` | Run the ecosystem-health wizard: detects CI provider + package manager, prompts for health endpoints, pipelines, and critical issue labels. Writes the config block + `.orchestrator/policy/ecosystem.json`. No scaffolding, no auto-commit. |
|
|
64
64
|
| `--fast` / `--standard` / `--deep` | Skip the tier confirmation question (e.g., for scripted runs). Equivalent to running `/bootstrap` and selecting that option. |
|
|
@@ -213,6 +213,8 @@ Entered when `$ARGUMENTS` contains `--upgrade <tier>`. No scaffolding questions
|
|
|
213
213
|
|
|
214
214
|
6. **Apply delta files.** Execute only the relevant template steps for the missing files. Read the appropriate template (`standard-template.md` and/or `deep-template.md`) and execute ONLY the steps that produce the delta files. Do NOT re-run already-completed steps.
|
|
215
215
|
|
|
216
|
+
6a. **Ignore the store-lock and runtime-lock artifacts (#1489 Pkt 17, #1495).** Execute the bash block in [`_shared-template.md#store-lock-ignore`](_shared-template.md) regardless of the delta. It amends the existing `.gitignore` instead of producing a delta file, so steps 5–6 never reach it — and a repo bootstrapped before it existed lacks some or all of its patterns (the store locks, the session lock, `runtime/`, `current-session.json`, `tmp/` and the other runtime files listed there). Idempotent: only missing lines are appended. The append is NOT a delta file: step 8 leaves it unstaged, and step 9 names it.
|
|
217
|
+
|
|
216
218
|
7. **Update bootstrap.lock atomically.** Overwrite `.orchestrator/bootstrap.lock` with `tier: <TARGET_TIER>`. Preserve a validated existing `archetype`; when upgrading a null Fast archetype, record the newly confirmed ID and scaffold source. Update `timestamp` to now. Preserve the prior `source` otherwise. Write `plugin-version` from `$PLUGIN_ROOT/package.json` (current plugin version at upgrade time).
|
|
217
219
|
|
|
218
220
|
8. **Commit.** Stage only the delta files that were just written and commit:
|
|
@@ -224,7 +226,7 @@ Entered when `$ARGUMENTS` contains `--upgrade <tier>`. No scaffolding questions
|
|
|
224
226
|
git commit -m "chore: bootstrap upgrade to <TARGET_TIER>"
|
|
225
227
|
```
|
|
226
228
|
|
|
227
|
-
9. **Report.** Print a one-line summary: `Bootstrap upgraded from <CURRENT_TIER> to <TARGET_TIER>. <N> files added.`
|
|
229
|
+
9. **Report.** Print a one-line summary: `Bootstrap upgraded from <CURRENT_TIER> to <TARGET_TIER>. <N> files added.` When step 6a appended to `.gitignore`, add: `.gitignore: store-lock patterns appended (unstaged).`
|
|
228
230
|
|
|
229
231
|
---
|
|
230
232
|
|
|
@@ -240,7 +242,7 @@ See [references/bootstrap-retroactive-flow.md](references/bootstrap-retroactive-
|
|
|
240
242
|
|
|
241
243
|
## Refresh-Lock Flow (`--refresh-lock`)
|
|
242
244
|
|
|
243
|
-
Acknowledges the current plugin version and resets the freshness clock on an already-valid `bootstrap.lock` (`refreshed-at` + `refreshed-plugin-version`) without disturbing its original bootstrap provenance or re-running scaffolding.
|
|
245
|
+
Acknowledges the current plugin version and resets the freshness clock on an already-valid `bootstrap.lock` (`refreshed-at` + `refreshed-plugin-version`) without disturbing its original bootstrap provenance or re-running scaffolding; appends missing store-lock `.gitignore` lines via `_shared-template.md#store-lock-ignore` (#1489 Pkt 17).
|
|
244
246
|
|
|
245
247
|
See [references/bootstrap-refresh-lock-flow.md](references/bootstrap-refresh-lock-flow.md).
|
|
246
248
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Bootstrap Shared Template Partials
|
|
2
2
|
|
|
3
3
|
> This file is NOT a standalone template. It contains shared step definitions
|
|
4
|
-
> referenced from
|
|
4
|
+
> referenced from the tier templates (and, for `#store-lock-ignore`, from the
|
|
5
|
+
> `SKILL.md` Upgrade and Refresh-Lock flows) via
|
|
5
6
|
> `<!-- @include _shared-template.md#section-name -->` markers.
|
|
6
7
|
>
|
|
7
8
|
> When the bootstrap skill reads a tier template, sections marked with an
|
|
@@ -334,3 +335,97 @@ fi
|
|
|
334
335
|
```
|
|
335
336
|
|
|
336
337
|
On Codex CLI / Cursor IDE, substitute `.codex/` or `.cursor/` for `.claude/` per the platform state-directory convention.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## #store-lock-ignore — Store-Lock and Runtime-Lock Artifacts in .gitignore (#1487, #1489 Pkt 17, #1495, #1500)
|
|
342
|
+
|
|
343
|
+
Applied by Fast Step 3 (so by every tier), by the Upgrade Flow and by the Refresh-Lock Flow in `SKILL.md` — the last two because a repo bootstrapped before this step existed never gets it otherwise: both flows write no `.gitignore` of their own.
|
|
344
|
+
|
|
345
|
+
`.orchestrator/` itself stays versioned: `bootstrap.lock`, `policy/`, `steering/`, `peers/` and the `.orchestrator/metrics/*.jsonl` ledgers are durable project data. What the plugin writes there at runtime is per-process state or a per-run artifact that a `git add -A` would otherwise commit — the session lock is rewritten on every SessionStart, every store rewrite leaves a full backup copy of the ledger beside it, the auto-dream sidecar is a full replacement body of the operator's private MEMORY.md, and the quality-gate and ux-grill artifacts can carry secrets or a rendered password. Each pattern below names one writer's file, its `.acquire` guard where it has one, and its own temp files (left behind only by a crash mid-write). Rotated and archived ledger copies (`*.jsonl.[0-9]*`, `*.jsonl.archive-*`, `metrics/_archive/`) are not on this list: they carry ledger history, and whether that is versioned is the consumer's call.
|
|
346
|
+
|
|
347
|
+
| Pattern(s) | Written by |
|
|
348
|
+
|---|---|
|
|
349
|
+
| `.orchestrator/metrics/*.jsonl.lock*`, `.file.lock.*` | the store locks of the ledgers (`scripts/lib/file-lock.mjs` temps sit beside every lock) |
|
|
350
|
+
| `.orchestrator/metrics/*.jsonl.bak-*` | the store backups: the keep-3 `<store>.bak-<ISO>` copy before every learnings rewrite (`scripts/lib/learnings/io.mjs`) and the `<stem>.pre-drop-malformed.jsonl.bak-<ISO>` snapshot of `sweep-expired-learnings --drop-malformed` (`scripts/lib/learnings/expiry-sweep.mjs`); the `*.jsonl` ledgers themselves stay versioned |
|
|
351
|
+
| `.orchestrator/session.lock`, `.orchestrator/.session.lock.*` | the session lock, every SessionStart (`scripts/lib/session-lock.mjs`, `hooks/_lib/lock-bootstrap.mjs`) |
|
|
352
|
+
| `.orchestrator/runtime/` | the lock-owner proof written beside it on every SessionStart, plus agent-status and cache files |
|
|
353
|
+
| `.orchestrator/current-session.json`, `.orchestrator/.current-session.*` | the SessionStart hook |
|
|
354
|
+
| `.orchestrator/host.json*` | the host-identity cache |
|
|
355
|
+
| `.orchestrator/state.lock*`, `.orchestrator/.state.lock.*` | the STATE.md write lock |
|
|
356
|
+
| `.orchestrator/rules.lock*` | the `/reconcile` rules lock |
|
|
357
|
+
| `.orchestrator/wave-dispatch-scopes.*`, `.orchestrator/.wave-dispatch-scopes.*` | the dispatch-scope ledger and its lock (`hooks/pre-task-scope-disjoint.mjs`) |
|
|
358
|
+
| `.orchestrator/wave-transcript-tail.lock*`, `.orchestrator/.wave-transcript-tail.lock.*` | the wave-transcript-tail singleton |
|
|
359
|
+
| `.orchestrator/metrics/proposals-write.lock*`, `.orchestrator/metrics/.proposals-write.lock.*` | the memory-proposals store lock |
|
|
360
|
+
| `.orchestrator/staging-fence/` | the staging-fence commit mutex and intent log |
|
|
361
|
+
| `.orchestrator/tmp/` | machine-local scratch: lock-reaper archives of reaped session locks (`tmp/reaped-locks/`, carrying host name, pid and session ids), worktree metadata (`tmp/worktree-meta/`), the orphan-reaper throttle marker (`tmp/reaper-last-scan`) |
|
|
362
|
+
| `.orchestrator/metrics/*.jsonl.bak.*` | the legacy evolve backup `learnings.jsonl.bak.evolve-<ts>`, a full store copy still on disk in older repos (`scripts/lib/learnings/io.mjs`) |
|
|
363
|
+
| `.orchestrator/metrics/*.jsonl.tmp-*` | atomic-write temp files of the ledgers (`scripts/lib/autopilot/telemetry.mjs`), left behind only by a crash |
|
|
364
|
+
| `.orchestrator/pending-dream.md` | the auto-dream sidecar — a full replacement body of the private MEMORY.md (`scripts/lib/auto-dream.mjs`) |
|
|
365
|
+
| `.orchestrator/dialectic-pending.md`, `.orchestrator/dialectic-last-run`, `.orchestrator/consumed/` | the auto-dialectic sidecar, its last-run timestamp and the archive of consumed sidecars (`scripts/lib/auto-dialectic.mjs`) |
|
|
366
|
+
| `.orchestrator/promoted-from.json` | the worktree-promotion marker, a per-worktree fact (`scripts/lib/autopilot/worktree-pipeline.mjs`, read by `scripts/lib/session-end/worktree-cleanup.mjs`) |
|
|
367
|
+
| `.orchestrator/welcome-banner-pending` | the zero-byte cold-start marker (`scripts/migrate-cold-start-seed.mjs`, read by `scripts/lib/cold-start-detector.mjs`) |
|
|
368
|
+
| `.orchestrator/debug/` | the `/debug` Phase-1 investigation artifacts |
|
|
369
|
+
| `.orchestrator/eval/` | the eval HTML run reports, rebuildable from `eval.jsonl` (`scripts/lib/eval/report.mjs`) |
|
|
370
|
+
| `.orchestrator/metrics/verification-failures/` | the quality-gate auto-fix diagnostics bundles, which can carry secrets from gate output (`scripts/lib/quality-gate.mjs`) |
|
|
371
|
+
| `.orchestrator/metrics/ux-grill/`, `.orchestrator/metrics/test-runs/` | the per-run artifact directories of `/ux-grill` and `/test`, screenshots included — ux-grill takes one after every journey step, including the step after a password fill (`scripts/lib/ux-grill/paths.mjs`, `scripts/lib/test-runner/artifact-paths.mjs`); their `*.jsonl` rollups stay versioned |
|
|
372
|
+
| `.orchestrator/metrics/repo-audit-*.json` | the `/repo-audit` JSON sidecar of each run |
|
|
373
|
+
| `.orchestrator/metrics/proposals-summary-*.json` | the per-wave memory-proposals summary sidecars (`scripts/lib/memory-proposals/sink.mjs`) |
|
|
374
|
+
| `.orchestrator/metrics/.backfilled-*.marker` | the session-close-backfill idempotency markers (`scripts/lib/session-close-backfill.mjs`) |
|
|
375
|
+
| `.orchestrator/*.log`, `.orchestrator/metrics/*.log` | the append-only diagnostic logs: the declined-proposal archives `reconcile.rejected.log` (`scripts/lib/reconcile/writer.mjs`) and `proposals.rejected.log` (`scripts/lib/memory-proposals/sink.mjs`), and `metrics/session-close-backfill.log`, whose `error` text carries fs error messages with absolute paths (`scripts/lib/session-close-backfill.mjs`) |
|
|
376
|
+
|
|
377
|
+
Append the missing patterns:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
# The Upgrade and Refresh-Lock flows run in a fresh shell where REPO_ROOT may be unset.
|
|
381
|
+
_GI_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null)}"
|
|
382
|
+
_GI="$_GI_ROOT/.gitignore"
|
|
383
|
+
_GI_MISSING=()
|
|
384
|
+
# Never add a file consumers COMMIT here: .orchestrator/bootstrap.lock, policy/, steering/, the *.jsonl ledgers.
|
|
385
|
+
_GI_PATTERNS=(
|
|
386
|
+
'.orchestrator/metrics/*.jsonl.lock*' '.file.lock.*'
|
|
387
|
+
'.orchestrator/metrics/*.jsonl.bak-*'
|
|
388
|
+
'.orchestrator/session.lock' '.orchestrator/.session.lock.*'
|
|
389
|
+
'.orchestrator/runtime/'
|
|
390
|
+
'.orchestrator/current-session.json' '.orchestrator/.current-session.*'
|
|
391
|
+
'.orchestrator/host.json*'
|
|
392
|
+
'.orchestrator/state.lock*' '.orchestrator/.state.lock.*'
|
|
393
|
+
'.orchestrator/rules.lock*'
|
|
394
|
+
'.orchestrator/wave-dispatch-scopes.*' '.orchestrator/.wave-dispatch-scopes.*'
|
|
395
|
+
'.orchestrator/wave-transcript-tail.lock*' '.orchestrator/.wave-transcript-tail.lock.*'
|
|
396
|
+
'.orchestrator/metrics/proposals-write.lock*' '.orchestrator/metrics/.proposals-write.lock.*'
|
|
397
|
+
'.orchestrator/staging-fence/'
|
|
398
|
+
'.orchestrator/tmp/'
|
|
399
|
+
'.orchestrator/metrics/*.jsonl.bak.*' '.orchestrator/metrics/*.jsonl.tmp-*'
|
|
400
|
+
'.orchestrator/pending-dream.md'
|
|
401
|
+
'.orchestrator/dialectic-pending.md' '.orchestrator/dialectic-last-run' '.orchestrator/consumed/'
|
|
402
|
+
'.orchestrator/promoted-from.json'
|
|
403
|
+
'.orchestrator/welcome-banner-pending'
|
|
404
|
+
'.orchestrator/debug/'
|
|
405
|
+
'.orchestrator/eval/'
|
|
406
|
+
'.orchestrator/metrics/verification-failures/'
|
|
407
|
+
'.orchestrator/metrics/ux-grill/' '.orchestrator/metrics/test-runs/'
|
|
408
|
+
'.orchestrator/metrics/repo-audit-*.json'
|
|
409
|
+
'.orchestrator/metrics/proposals-summary-*.json'
|
|
410
|
+
'.orchestrator/metrics/.backfilled-*.marker'
|
|
411
|
+
'.orchestrator/*.log' '.orchestrator/metrics/*.log'
|
|
412
|
+
)
|
|
413
|
+
if [[ -z "$_GI_ROOT" ]]; then
|
|
414
|
+
# No repo root: never fall back to "/.gitignore" (a stray file at / when run as root).
|
|
415
|
+
echo "store-lock-ignore: no repository root (REPO_ROOT unset, not inside a repo) — skipped" >&2
|
|
416
|
+
else
|
|
417
|
+
for _pat in "${_GI_PATTERNS[@]}"; do
|
|
418
|
+
# A CRLF .gitignore stores "pattern\r" — count that as present, or every run appends a duplicate block.
|
|
419
|
+
grep -qxF -- "$_pat" "$_GI" 2>/dev/null || grep -qxF -- "$_pat"$'\r' "$_GI" 2>/dev/null || _GI_MISSING+=("$_pat")
|
|
420
|
+
done
|
|
421
|
+
fi
|
|
422
|
+
# An unreadable .gitignore (e.g. mode 0200) defeats both the presence check and the newline check — leave it alone.
|
|
423
|
+
if [[ -n "$_GI_ROOT" && ! -L "$_GI" && ( ! -e "$_GI" || -r "$_GI" ) && ${#_GI_MISSING[@]} -gt 0 ]]; then
|
|
424
|
+
# A last line without a trailing newline would fuse with the comment below: `.env` + `# …`
|
|
425
|
+
# becomes `.env# …`, which git no longer reads as `.env` — the append would UN-ignore it.
|
|
426
|
+
if [[ -s "$_GI" && -n "$(tail -c1 "$_GI")" ]]; then printf '\n' >> "$_GI"; fi
|
|
427
|
+
printf '%s\n' '# session-orchestrator runtime locks and state (never versioned)' "${_GI_MISSING[@]}" >> "$_GI"
|
|
428
|
+
fi
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Idempotent (only missing patterns are appended — a repo that already carries the two store-lock lines from before #1495 gets just the runtime lines), and a symlinked `.gitignore` is left alone. A `.gitignore` created earlier in the same run is already in `BOOTSTRAP_FILES` and is committed with that run; an append to a pre-existing one stays an unstaged change, because no bootstrap commit stages a file bootstrap did not create (the Upgrade Flow stages only its delta files, the Refresh-Lock Flow commits nothing). Name the append in the flow's report so the operator commits it.
|
|
@@ -172,6 +172,9 @@ build/
|
|
|
172
172
|
|
|
173
173
|
Note: `.orchestrator/` is NOT gitignored — `bootstrap.lock` must be committed. Only the platform state dirs (`.claude/`, `.codex/`, `.cursor/`) are excluded.
|
|
174
174
|
|
|
175
|
+
<!-- @include _shared-template.md#store-lock-ignore -->
|
|
176
|
+
**Then ignore the store-lock artifacts** — whether `.gitignore` was just written or already existed. Execute the bash block in [`_shared-template.md#store-lock-ignore`](_shared-template.md): it appends the missing store-lock patterns (`.orchestrator/metrics/*.jsonl.lock*`, `.file.lock.*`) and the runtime-lock and state patterns (`.orchestrator/session.lock`, `runtime/`, `current-session.json`, … — the full list and their writers are in that section; idempotent, a symlinked `.gitignore` is left alone). `bootstrap.lock`, `policy/`, `steering/` and the ledgers stay versioned. Standard and Deep inherit this through their Fast steps.
|
|
177
|
+
|
|
175
178
|
## Step 3a: Install Canonical Rules
|
|
176
179
|
|
|
177
180
|
Vendor the canonical always-on rules from the plugin's `rules/` library into `$REPO_ROOT/.claude/rules/`. `rules/` is the single source of truth for every distributable rule — never `cp` a rule file from anywhere else.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Bootstrap — Refresh-Lock Flow (`--refresh-lock`)
|
|
2
2
|
|
|
3
|
-
> Reference of the `bootstrap` skill, split out of `SKILL.md` (#1246). Body moved **byte-identical**; only this header is new.
|
|
3
|
+
> Reference of the `bootstrap` skill, split out of `SKILL.md` (#1246). Body moved **byte-identical**; only this header is new. Later addition: step 3a (#1489 Pkt 17, 2026-10-02).
|
|
4
4
|
> **Sibling-file paths inside this body are relative to the parent directory, not to `references/`**: `SKILL.md` → `../SKILL.md`. They were deliberately NOT rewritten, so the moved bytes stay verifiable against the pre-split file.
|
|
5
5
|
|
|
6
6
|
## Refresh-Lock Flow (`--refresh-lock`)
|
|
@@ -27,9 +27,11 @@ Entered when `$ARGUMENTS` contains `--refresh-lock`. No scaffolding questions ar
|
|
|
27
27
|
|
|
28
28
|
`refreshBootstrapLock` writes (or replaces, if already present) exactly two lines — `refreshed-at: <ISO 8601 UTC>` and `refreshed-plugin-version: <current plugin version>` — via the same atomic tmp-file + rename pattern used by the Retroactive Flow's lock write: write to a sibling tmp file, then rename over the target so the lock is never observed half-written. **Every other line of the lock — `bootstrapped-at`, `timestamp`, `plugin-version`, `tier`, `archetype`, `source`, … — is left byte-identical.** This is the provenance-honesty guarantee: a refresh is an acknowledgement, not a re-bootstrap. On failure (`result.ok === false`), surface `result.message` and stop — do not retry with a fabricated lock.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
3a. **Ignore the store-lock and runtime-lock artifacts (#1489 Pkt 17, #1495, added 2026-10-02).** Execute the bash block in [`_shared-template.md#store-lock-ignore`](../_shared-template.md) (link relative to `references/` — new in this step, not moved). The freshness probe sends every drifted repo here, so this is the one path on which a repo bootstrapped before that step existed — any tier, Deep included — picks up the `.gitignore` patterns that section lists (store locks, session lock, `runtime/`, `current-session.json`, `tmp/` and the other runtime files). Only a repo the probe flags as drifted gets here. Idempotent; it touches nothing but missing ignore lines.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
4. **No auto-commit.** Unlike the Retroactive Flow, `--refresh-lock` does not stage or commit. The refreshed lock (two changed/added lines) and any `.gitignore` append from step 3a are small, reviewable diffs; the user commits them alongside their own work at their own cadence.
|
|
33
|
+
|
|
34
|
+
5. **Report.** Print: `Lock refreshed (refreshed-at: <now>, plugin-version: <current>). Original bootstrap provenance unchanged.` When step 3a appended to `.gitignore`, add: `.gitignore: store-lock patterns appended (unstaged).`
|
|
33
35
|
|
|
34
36
|
**Idempotency.** Running `/bootstrap --refresh-lock` twice in a row replaces the same two lines in place — it never duplicates them.
|
|
35
37
|
|
|
@@ -634,6 +634,8 @@ fi
|
|
|
634
634
|
|
|
635
635
|
**Gitignore guidance:** Do NOT add `.orchestrator/metrics/*.jsonl` to `.gitignore`. The files are intentionally visible in version control — they carry project learnings and session history that future contributors benefit from.
|
|
636
636
|
|
|
637
|
+
The store's LOCK artifacts are the opposite case and DO get ignored (#1487), together with the plugin's other runtime locks and state under `.orchestrator/` (#1495): Fast Step 3, inherited above, already appended `.orchestrator/metrics/*.jsonl.lock*`, `.file.lock.*`, `.orchestrator/session.lock` and the rest of the list via [`_shared-template.md#store-lock-ignore`](_shared-template.md) — the one canonical snippet, also run by the `--upgrade` and `--refresh-lock` flows. Nothing to repeat here.
|
|
638
|
+
|
|
637
639
|
<!-- @include _shared-template.md#baseline-fetch -->
|
|
638
640
|
## Step S99: (Optional) Fetch Canonical Rules + Agents from Baseline
|
|
639
641
|
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* 2. `.claude/rules/*.md` file count (non-recursive) vs. "N rule files" claims.
|
|
25
25
|
* 3. `skills/` user-facing directory count (excludes `_shared/`) vs.
|
|
26
26
|
* "N user-facing skills" / "N skills" claims.
|
|
27
|
-
* 4. `commands/*.md`
|
|
27
|
+
* 4. Slash-command count (`commands/*.md` ∪ skills with `user-invocable: true`)
|
|
28
|
+
* vs. "N slash-commands" / "N commands" claims.
|
|
28
29
|
*
|
|
29
30
|
* Scope exclusions: CHANGELOG.md, docs/adr/, docs/prd/, docs/retro/ are never
|
|
30
31
|
* scanned — same principle as the docs-staleness probe (historical/immutable
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
|
|
46
47
|
import { existsSync, readdirSync, readFileSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
47
48
|
import { join } from 'node:path';
|
|
49
|
+
import { slashCommandNames } from '../../../scripts/lib/user-invocable-skills.mjs';
|
|
48
50
|
|
|
49
51
|
// ---------------------------------------------------------------------------
|
|
50
52
|
// Doc source sets (deliberately small and hand-curated)
|
|
@@ -101,12 +103,16 @@ function countUserFacingSkills(root) {
|
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
// Slash commands = `commands/*.md` ∪ skills with an explicit
|
|
107
|
+
// `user-invocable: true` (the #1370 commands→skills fold) — the shared census
|
|
108
|
+
// in user-invocable-skills.mjs, not a second copy. Counting `commands/` alone
|
|
109
|
+
// reported 2 against the real 27 on every run (#1506 pt 7). `null` (entry
|
|
110
|
+
// skipped) only when the repo has neither source.
|
|
104
111
|
function countCommands(root) {
|
|
105
|
-
const dir = join(root, 'commands');
|
|
106
|
-
if (!existsSync(dir)) return null;
|
|
107
112
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
113
|
+
const names = slashCommandNames(root);
|
|
114
|
+
if (names.length === 0 && !existsSync(join(root, 'commands'))) return null;
|
|
115
|
+
return names.length;
|
|
110
116
|
} catch {
|
|
111
117
|
return null;
|
|
112
118
|
}
|
|
@@ -180,7 +186,7 @@ const REGISTRY = [
|
|
|
180
186
|
},
|
|
181
187
|
{
|
|
182
188
|
id: 'commands-count',
|
|
183
|
-
label: 'commands/*.md
|
|
189
|
+
label: 'slash-command count (commands/*.md + user-invocable skills)',
|
|
184
190
|
sources: SOURCE_SET_INVENTORY,
|
|
185
191
|
severity: 'medium',
|
|
186
192
|
getActual: countCommands,
|