session-orchestrator 5.3.0 → 5.5.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/.agents/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +28 -13
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +9 -31
- package/CHANGELOG.md +162 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +154 -4
- package/docs/components.md +11 -11
- package/docs/events-schema.md +25 -19
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/persona-panel.md +5 -4
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +40 -16
- package/docs/session-config-reference.md +61 -12
- package/docs/session-config-template.md +11 -2
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +42 -29
- 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-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/on-session-start.mjs +57 -2
- package/hooks/on-stop.mjs +70 -168
- package/hooks/post-bash-write-verify.mjs +98 -17
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +32 -152
- package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +1143 -279
- package/hooks/subagent-telemetry.mjs +96 -19
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +6 -1
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +5 -1
- package/rules/opt-in-stack/backend-data.md +9 -1
- package/rules/opt-in-stack/backend.md +7 -1
- package/rules/opt-in-stack/frontend.md +6 -1
- package/rules/opt-in-stack/security-web.md +7 -1
- package/rules/opt-in-stack/swift.md +5 -1
- package/scripts/apply-session-learnings.mjs +513 -0
- package/scripts/archive-closed-prds.mjs +40 -12
- package/scripts/autopilot.mjs +57 -54
- package/scripts/backfill-abandoned-sessions.mjs +81 -6
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +47 -6
- package/scripts/emit-session.mjs +298 -2
- package/scripts/export-hw-learnings.mjs +40 -10
- package/scripts/fleet-checkin.mjs +170 -0
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +6 -1
- package/scripts/lib/autopilot/loop.mjs +32 -13
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +7 -6
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +75 -10
- 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 +72 -5
- package/scripts/lib/fleet-protocol.mjs +288 -0
- package/scripts/lib/gates/gate-baseline.mjs +13 -3
- package/scripts/lib/gates/gate-full.mjs +7 -2
- package/scripts/lib/gates/gate-helpers.mjs +93 -2
- package/scripts/lib/gates/gate-incremental.mjs +23 -22
- package/scripts/lib/gates/gate-per-file.mjs +16 -7
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +77 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
- package/scripts/lib/learnings/io.mjs +274 -37
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +147 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
- package/scripts/lib/orphan-reaper/scan.mjs +510 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +47 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +134 -22
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +45 -5
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +100 -62
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +119 -29
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +194 -27
- package/scripts/lib/session-close-backfill.mjs +129 -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-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +67 -17
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +252 -54
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +121 -71
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +215 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/ux-grill/reconcile.mjs +1 -1
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +388 -27
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- 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 +25 -11
- package/scripts/lib/vault-status/board-writer.mjs +53 -12
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +11 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
- package/scripts/run-quality-gate.mjs +331 -307
- package/scripts/self-update.mjs +229 -0
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +603 -20
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +238 -75
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +134 -0
- package/skills/_shared/state-ownership.md +41 -12
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +6 -4
- package/skills/bootstrap/_shared-template.md +97 -2
- package/skills/bootstrap/fast-template.md +4 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- 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 +13 -4
- package/skills/evolve/references/evolve-analyze-mode.md +42 -18
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/learning-patterns.md +23 -13
- package/skills/session-end/metrics-collection.md +6 -7
- 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 +13 -3
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +65 -68
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +8 -2
- package/skills/session-start/references/operations-contract.md +6 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +10 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +2 -2
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
- package/skills/wave-executor/references/wave-loop-review.md +4 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Fleet Protocol v1 — Dateien statt Nachrichten (#1462)
|
|
2
|
+
|
|
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
|
+
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. 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).
|
|
11
|
+
|
|
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.
|
|
16
|
+
|
|
17
|
+
## Verzeichnis und Dateiform
|
|
18
|
+
|
|
19
|
+
- Wurzel `~/.config/navigator/`, Override `NAVIGATOR_CONFIG_DIR` (getrimmt; leer oder nur
|
|
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.
|
|
23
|
+
- Verzeichnisse Modus `0700`, Dateien Modus `0600`. Schreiben immer atomar (tmp-Datei im
|
|
24
|
+
Zielverzeichnis, dann `rename`).
|
|
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.
|
|
33
|
+
- Tests berühren das echte Verzeichnis nie: `tests/setup/navigator-dir-guard.mjs` setzt
|
|
34
|
+
`NAVIGATOR_CONFIG_DIR` je vitest-Worker auf ein tmp-Verzeichnis.
|
|
35
|
+
|
|
36
|
+
## `leases/navigator.json` — genau ein aktiver Navigator
|
|
37
|
+
|
|
38
|
+
| Feld | Inhalt |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `session_id` | Registry-ID des Navigators (Pflicht; pfadsicher wie oben) |
|
|
41
|
+
| `plattform` | `claude` \| `codex` \| `kopflos` (Pflicht) |
|
|
42
|
+
| `adresse` | `ListAgents`-Name, wenn Claude (`[A-Za-z0-9._:@-]`, 1–128 Zeichen); sonst `null` |
|
|
43
|
+
| `seit` | Zeit des Erwerbs, UTC mit `Z`, Sekunden oder Millisekunden (Pflicht) |
|
|
44
|
+
| `laeuft_ab` | UTC mit `Z`, gleiche Form; nach diesem Zeitpunkt ist die Lease abgelaufen (Pflicht) |
|
|
45
|
+
| `uebergabe_an` | `null` oder Session-ID des Nachfolgers |
|
|
46
|
+
|
|
47
|
+
**„Aktiv" heißt ausschließlich: gültige Lease.** `readNavigatorLease()` liefert genau eines von
|
|
48
|
+
`active` (Datei lesbar, Pflichtfelder da, `laeuft_ab` in der Zukunft), `none` (Datei fehlt oder
|
|
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.
|
|
56
|
+
|
|
57
|
+
## `checkin/<session_id>.json` — Session an Navigator
|
|
58
|
+
|
|
59
|
+
**Schreibend** heißt: Änderung an Repo-Inhalt oder VCS-Zustand (Datei im Arbeitsbaum außerhalb
|
|
60
|
+
`.orchestrator/` und `.claude/`, Commit, Branch, Push, MR, Issue, Kommentar). SO-eigener
|
|
61
|
+
Sitzungszustand zählt nicht: Phase 1.1 (CLAUDE.md-Migration, einmalig), 1.2 `session.lock`, 1.5
|
|
62
|
+
`STATE.md`, 1.6 Metriken, 1.7 Live-Status-Board. Phase 1.1 ist eine Repo-Datei und wird deshalb
|
|
63
|
+
als Ausnahme im Check-in genannt (`vorab_geschrieben`). (`CLAUDE.md` heißt auf Codex CLI `AGENTS.md`, siehe
|
|
64
|
+
`skills/_shared/instruction-file-resolution.md`.)
|
|
65
|
+
|
|
66
|
+
Der Check-in liegt **vor der ersten schreibenden Aktion** (Phase 7.6, oder der Peer-Preflight der
|
|
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.
|
|
77
|
+
|
|
78
|
+
| Feld | Inhalt |
|
|
79
|
+
|---|---|
|
|
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` |
|
|
87
|
+
| `stand` | Objekt: main-, origin/main-, Prod-SHA (sonst `nicht messbar`), offene MRs |
|
|
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) |
|
|
93
|
+
| `zeit` | von der CLI gesetzt |
|
|
94
|
+
|
|
95
|
+
Eine `SendMessage` an den Navigator ist optional und nur ein Hinweis auf die Datei: höchstens eine,
|
|
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
|
|
98
|
+
erwarten, nie darauf warten (CSM-004).
|
|
99
|
+
|
|
100
|
+
## `auflagen/<session_id>.json` — Navigator an Session
|
|
101
|
+
|
|
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.
|
|
105
|
+
|
|
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:
|
|
110
|
+
|
|
111
|
+
- Caps: 2 prozessstartende + 2 lesende Agents, solange der lokale Host ≥ 50 % Speicher frei hat.
|
|
112
|
+
- Pipelines: ≤ 2 volle je Repo; Nachzug-Pushes vorher im Check-in (`bedarf`) benennen.
|
|
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`.
|
|
116
|
+
- Zeiten nur aus `date`.
|
|
117
|
+
- Kein Merge ohne ausdrücklichen Owner-Auftrag, der Merge für genau dieses Repo nennt
|
|
118
|
+
(`auftrag_ref`).
|
|
119
|
+
|
|
120
|
+
Lesende Arbeit und Schreiben im eigenen Worktree laufen währenddessen weiter; der Rückfall ist ein
|
|
121
|
+
Dateistand mit Frist, kein Warten auf eine Antwort.
|
|
122
|
+
|
|
123
|
+
## Verhalten ohne Navigator
|
|
124
|
+
|
|
125
|
+
Lease `none` oder `unreadable`: die Check-in-Datei wird **trotzdem** geschrieben (sie ist der
|
|
126
|
+
zählbare Beleg), die Standard-Auflagen gelten sofort, es geht keine Nachricht ab. Kopflose Läufe
|
|
127
|
+
sind per Nachricht nicht erreichbar und nutzen dieselben Dateien: vor jeder Welle `auflagen/` lesen.
|
|
128
|
+
|
|
129
|
+
## See Also
|
|
130
|
+
|
|
131
|
+
`skills/session-start/SKILL.md` Phase 7.6 · `skills/session-start/references/operations-contract.md`
|
|
132
|
+
(Peer-Preflight) · `skills/_shared/state-ownership.md` § Session Identity and Lock Ownership (`role`) ·
|
|
133
|
+
`docs/events-schema.md` (`orchestrator.fleet.checkin`) · `.claude/rules/cross-session-messaging.md`
|
|
134
|
+
(CSM-003/004) · `scripts/lib/fleet-protocol.mjs` · `scripts/fleet-checkin.mjs`
|
|
@@ -104,6 +104,12 @@ 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 a navigator
|
|
111
|
+
(operated outside this plugin) has to re-set it on every round via
|
|
112
|
+
`heartbeat(id, { role })` (#1462).
|
|
107
113
|
|
|
108
114
|
The peer-discovery and issue-budget procedures below apply these rules at their
|
|
109
115
|
narrow surfaces; neither creates a second ownership model.
|
|
@@ -245,8 +251,11 @@ The `.orchestrator/session.lock` file is written mechanically by `hooks/_lib/loc
|
|
|
245
251
|
|
|
246
252
|
```
|
|
247
253
|
isAlive = (Date.now() - Date.parse(last_heartbeat)) < ttl_hours * 3600 * 1000
|
|
254
|
+
&& Date.parse(last_heartbeat) <= Date.now() + 5 * 60 * 1000
|
|
248
255
|
```
|
|
249
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
|
+
|
|
250
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).
|
|
251
260
|
|
|
252
261
|
### #744 — heartbeat is the SOLE active gate (incident + fix)
|
|
@@ -272,23 +281,43 @@ The fix removes the question rather than re-answering it. `classifyExisting()` r
|
|
|
272
281
|
|
|
273
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.
|
|
274
283
|
|
|
275
|
-
### 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**.
|
|
276
305
|
|
|
277
|
-
|
|
306
|
+
**Decision (owner-approved scope, 2026-10-02):**
|
|
278
307
|
|
|
279
|
-
|
|
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.
|
|
280
310
|
|
|
281
|
-
|
|
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:
|
|
282
312
|
|
|
283
|
-
|
|
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.
|
|
284
316
|
|
|
285
|
-
-
|
|
286
|
-
- `~/.config/session-orchestrator/sessions/active/*.json` → **3 entries, 3/3 carry the `mode` key** (0 v1).
|
|
287
|
-
- 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".
|
|
288
318
|
|
|
289
|
-
**
|
|
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).
|
|
290
320
|
|
|
291
|
-
|
|
292
|
-
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'`.
|
|
293
322
|
|
|
294
|
-
**
|
|
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.
|
|
@@ -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`
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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. 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 |
|
|
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 `
|
|
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>'
|
|
318
|
-
exits cleanly, reads the `sessions.jsonl`
|
|
319
|
-
`
|
|
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
|
|
@@ -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. |
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -17,7 +18,7 @@ Vendor the canonical always-on rules from the plugin's `rules/` library into `$R
|
|
|
17
18
|
|
|
18
19
|
Idempotency is handled by the writer itself:
|
|
19
20
|
- Missing → create
|
|
20
|
-
- Exists, plugin-owned (
|
|
21
|
+
- 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
22
|
- Exists, plugin-owned and stale → overwrite (the plugin copy is canonical)
|
|
22
23
|
- Exists WITHOUT that header → preserved untouched (a repo-private rule the operator authored)
|
|
23
24
|
|
|
@@ -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.
|
|
@@ -182,7 +185,7 @@ inherited Fast passes the confirmed contract ID and records all created paths.
|
|
|
182
185
|
|
|
183
186
|
Idempotency is handled by the writer itself:
|
|
184
187
|
- Missing → create
|
|
185
|
-
- Exists, plugin-owned (
|
|
188
|
+
- 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
189
|
- Exists, plugin-owned and stale → overwrite (the plugin copy is canonical)
|
|
187
190
|
- Exists WITHOUT that header → preserved untouched (a repo-private rule the operator authored)
|
|
188
191
|
|
|
@@ -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
|
|
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
|
|
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.
|
|
31
|
-
|
|
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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
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`
|
|
@@ -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
|
|