session-orchestrator 5.10.0 → 5.12.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 +12 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +51 -0
- package/README.md +22 -11
- package/SECURITY.md +1 -1
- package/agents/code-implementer.md +7 -1
- package/commands/session.md +3 -1
- package/docs/agent-authoring.md +2 -0
- package/docs/events-schema.md +2 -1
- package/docs/fleet-checkin-audit.md +172 -0
- package/docs/install.md +12 -8
- package/docs/role-profile-contract.md +139 -0
- package/docs/scope-collision-guard.md +3 -0
- package/docs/session-config-reference.md +7 -7
- package/docs/session-config-template.md +2 -2
- package/evals/instruction-ablation/run.mjs +11 -6
- package/hooks/_lib/hook-import-set.json +27 -2
- package/hooks/_lib/orchestrator-ignore.mjs +112 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/on-session-end.mjs +8 -1
- package/hooks/on-session-start.mjs +26 -10
- package/hooks/post-bash-write-verify.mjs +16 -3
- package/hooks/pre-task-scope-disjoint.mjs +12 -0
- package/hooks/subagent-telemetry.mjs +8 -3
- package/hooks/wave-scope-commit-guard.mjs +4 -1
- package/package.json +1 -2
- package/rules/opt-in-stack/security-web.md +1 -1
- package/scripts/emit-session.mjs +13 -0
- package/scripts/fleet-checkin-audit.mjs +138 -0
- package/scripts/fleet-checkin.mjs +61 -3
- package/scripts/lib/common.mjs +2 -4
- package/scripts/lib/config/remote-hosts.mjs +2 -2
- package/scripts/lib/config.mjs +1 -1
- package/scripts/lib/context-overhead-format.mjs +57 -0
- package/scripts/lib/fleet-checkin-audit.mjs +244 -0
- package/scripts/lib/fleet-protocol.mjs +17 -0
- package/scripts/lib/memory-proposals/sink.mjs +79 -17
- package/scripts/lib/memory-proposals/store.mjs +245 -9
- package/scripts/lib/project-hygiene.mjs +37 -0
- package/scripts/lib/role-profile/schema.mjs +219 -0
- package/scripts/lib/session-end/vault-orphans.mjs +205 -0
- package/scripts/lib/session-invocation.mjs +14 -3
- package/scripts/lib/session-lock.mjs +57 -10
- package/scripts/lib/session-record-repair.mjs +63 -30
- package/scripts/lib/session-registry.mjs +14 -4
- package/scripts/lib/session-schema/constants.mjs +8 -0
- package/scripts/lib/session-schema/validator.mjs +33 -0
- package/scripts/lib/so-hud/collect.mjs +130 -0
- package/scripts/lib/so-hud/runtime.mjs +76 -0
- package/scripts/lib/state-md/idle-reset.mjs +239 -0
- package/scripts/lib/state-md/recommendations.mjs +26 -4
- package/scripts/lib/subagents-schema.mjs +9 -0
- package/scripts/lib/telemetry/pricing.mjs +11 -0
- package/scripts/lib/vault-mirror/namespace.mjs +3 -2
- package/scripts/lib/vault-mirror/process.mjs +143 -40
- package/scripts/lib/vault-mirror/render-learnings.mjs +6 -5
- package/scripts/lib/vault-mirror/utils.mjs +27 -13
- package/scripts/lib/vault-status/board-writer.mjs +32 -4
- package/scripts/measure-context-overhead.sh +4 -21
- package/scripts/release.mjs +359 -11
- package/scripts/resolve-session-invocation.mjs +64 -8
- package/scripts/run-quality-gate.mjs +47 -0
- package/scripts/so-hud.mjs +45 -0
- package/scripts/state-md-idle-reset.mjs +81 -0
- package/scripts/validate-wave-scope.mjs +59 -6
- package/scripts/vault-orphans.mjs +171 -0
- package/skills/_shared/fleet-protocol.md +24 -2
- package/skills/_shared/state-ownership.md +3 -3
- package/skills/autopilot/SKILL.md +3 -2
- package/skills/claude-md-drift-check/checker.mjs +8 -6
- package/skills/npm-publish/SKILL.md +3 -3
- package/skills/release/SKILL.md +2 -2
- package/skills/repo-audit/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +3 -2
- package/skills/session-end/metrics-collection.md +2 -2
- package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +11 -1
- package/skills/session-end/references/session-summary-template.md +4 -0
- package/skills/session-end/session-metrics-write.md +1 -1
- package/skills/session-start/SKILL.md +1 -1
- package/skills/session-start/phase-7-5-mode-selector.md +3 -1
- package/skills/session-start/references/phase-1-2-session-lock.md +1 -1
- package/skills/session-start/references/phase-1-5-session-continuity.md +12 -4
- package/skills/wave-executor/circuit-breaker.md +1 -0
- package/AGENTS.md +0 -205
package/skills/release/SKILL.md
CHANGED
|
@@ -48,7 +48,7 @@ The same reading shows the other half: the GitHub releases for 3.15/3.18/3.19/3.
|
|
|
48
48
|
6. **Preflight.** `node scripts/release.mjs --check --json` — every row green. Run this after the release commit is pushed and its CI succeeds: the preflight requires a clean working tree, exact HEAD parity on both remotes and green CI on that commit. It derives its target from `package.json`, so checking the pre-bump version instead produces registry- and tag-collision failures.
|
|
49
49
|
7. **Publish.** `node scripts/release.mjs --publish` — **give it ≥600 s of wall clock, or run it in the background.** The script sets no spawn timeout on purpose (a kill mid-`npm publish` or mid-push is the very failure the receipt boundary exists to avoid), and the tail is slow by construction: each of the two `git push` remotes re-runs the husky pre-push full gate, plus up to 120 s of live-site polling. A 3-minute default command timeout kills it mid-tail.
|
|
50
50
|
|
|
51
|
-
The target-confirmed npm receipt is the irreversible boundary. Before that receipt, any failure aborts normally. After it, never rerun `--publish`: registry
|
|
51
|
+
The target-confirmed npm receipt is the irreversible boundary. Before that receipt, any failure aborts normally. After it, never rerun `--publish`: the script writes the publish proof `.orchestrator/runtime/release-<target>.json` (checked archive sha256 + sha512 integrity, receipt time, commit, tag), and a registry availability timeout/query/wait failure is reconciliation while the script still tags, pushes `main` + tag to both remotes, handles the GitHub release, and polls the live site. If tag/push fails after the receipt, the dependent GitHub-release and site steps are skipped and the script returns structured reconciliation guidance instead. Add the GitHub release for the new tag (`gh release create`) as part of this step, not "later" — "later" is what produced the three-second backfill above.
|
|
52
52
|
|
|
53
53
|
Steps 2–7 are one continuous act. A release left parked between step 4 and step 7 is exactly the `3.18.0` state: every surface says released, the registry disagrees.
|
|
54
54
|
|
|
@@ -68,7 +68,7 @@ Steps 2–7 are one continuous act. A release left parked between step 4 and ste
|
|
|
68
68
|
|
|
69
69
|
## After
|
|
70
70
|
|
|
71
|
-
`--publish` reports either **Release complete** or **Post-publish reconciliation required**. The latter means npm accepted the target release but registry propagation, tag/push, GitHub-release handling, or the live-site check still needs repair; a tag/push failure explicitly skips its dependent GitHub-release and site steps. **Do not rerun `--publish`**: reconcile the listed state directly, because a second publish cannot replace the immutable version. Exit `1` means a preflight/check failure or post-publish reconciliation is required; exit `2` remains a system/usage failure before the receipt.
|
|
71
|
+
`--publish` reports either **Release complete** or **Post-publish reconciliation required**. The latter means npm accepted the target release but registry propagation, tag/push, GitHub-release handling, or the live-site check still needs repair; a tag/push failure explicitly skips its dependent GitHub-release and site steps. **Do not rerun `--publish`**: reconcile the listed state directly, because a second publish cannot replace the immutable version. For the registry state run `node scripts/release.mjs --reconcile --target X.Y.Z [--json]` — read-only (no publish, tag, push or repo pack), bounded and resumable: it reports `installable` (exit 0) only when the exact version is served as `latest` with the proof's integrity AND a consumer download hashes to it, or `superseded` (exit 0: same checks pass, but `latest` already points at a later release); otherwise `pending` (rerun later), `failed` (integrity mismatch) or `proof-missing` (exit 1 — success is never inferred from `latest`). See `skills/npm-publish/SKILL.md` § Failure-mode table for why an accepted upload can stay unavailable. Exit `1` means a preflight/check failure or post-publish reconciliation is required; exit `2` remains a system/usage failure before the receipt.
|
|
72
72
|
|
|
73
73
|
After a complete release, `--publish` prints the remaining manual items (token rotation, async gallery indexing). Rotate the npm token — write tokens are short-lived by policy, and a token that transited a log or a chat is burned.
|
|
74
74
|
|
|
@@ -118,7 +118,7 @@ Commands resolved from Session Config per Phase 1.
|
|
|
118
118
|
| Auth-at-boundary pattern (`requireAuth`) | `grep -r 'requireAuth' --include='*.ts' src/ 2>/dev/null \|\| echo "N/A (no src/)"` |
|
|
119
119
|
| Zod validation on inputs | `grep -r 'z\.object\|z\.string\|z\.parse\|safeParse' --include='*.ts' src/ 2>/dev/null \|\| echo "N/A"` |
|
|
120
120
|
| No hardcoded secrets (scan for API key patterns) | `grep -r 'sk-\|api_key\s*=\s*"' --include='*.ts' --include='*.mts' --exclude-dir=node_modules . 2>/dev/null` — warn if found |
|
|
121
|
-
| No PAT/token in settings-allowlist entries (`.claude/settings.json`, `.claude/settings.local.json` — on-disk, incl. untracked) | `grep -nEo 'glpat-[A-Za-z0-9_-]{20,}\|ghp_[A-Za-z0-9]{36,}\|github_pat_[A-Za-z0-9_]{22,}\|sk-ant-[A-Za-z0-9_-]{20,}\|AKIA[0-9A-Z]{16}' .claude/settings.json .claude/settings.local.json 2>/dev/null \| grep -vE 'AKIAIOSFODNN7EXAMPLE\|-PLACEHOLDER'` — **fail** if any match (hard — unlike the `sk-` heuristic above, these 5 prefixes are high-signal; see SEC-
|
|
121
|
+
| No PAT/token in settings-allowlist entries (`.claude/settings.json`, `.claude/settings.local.json` — on-disk, incl. untracked) | `grep -nEo 'glpat-[A-Za-z0-9_-]{20,}\|ghp_[A-Za-z0-9]{36,}\|github_pat_[A-Za-z0-9_]{22,}\|sk-ant-[A-Za-z0-9_-]{20,}\|AKIA[0-9A-Z]{16}' .claude/settings.json .claude/settings.local.json 2>/dev/null \| grep -vE 'AKIAIOSFODNN7EXAMPLE\|-PLACEHOLDER'` — **fail** if any match (hard — unlike the `sk-` heuristic above, these 5 prefixes are high-signal; see SEC-025 in `.claude/rules/security.md`). This check is required because `check-owner-leakage.mjs` scans `git ls-files` only — `settings.local.json` is gitignored/untracked by convention and structurally invisible to it. |
|
|
122
122
|
| `.env*` files not tracked | `git ls-files | grep '\.env'` — fail if any `.env` (not `.env.example`) tracked |
|
|
123
123
|
| `.env.example` documents all secrets | `ls .env.example 2>/dev/null` |
|
|
124
124
|
|
|
@@ -145,7 +145,7 @@ discipline as `createSpiralCarryoverIssue`).
|
|
|
145
145
|
|
|
146
146
|
## Phase 3: Documentation Updates
|
|
147
147
|
|
|
148
|
-
> Always runs. Refreshes the session-lock heartbeat at entry (`updateHeartbeat`, #590-3 — skip when `persistence: false`) so a long close-out cannot lapse the 4h TTL, then walks 3.0 Defensive Cleanup → 3.1 SSOT files → 3.2 Docs Verification → 3.2a Session Handover → 3.3 Claude-rules freshness → 3.4/3.4a STATE.md write + coordinator-snapshot cleanup → 3.45 Telemetry Flush → 3.5/3.5a/3.6.x session memory, learning extraction and the mechanical skip-plan tail → 3.7/3.7a/3.7b/3.7c/3.7d metrics write, recommendations, durable commit (#490), vault board → closed, session-eval. Full procedure: [`references/phase-3-documentation-updates.md`](references/phase-3-documentation-updates.md).
|
|
148
|
+
> Always runs. Refreshes the session-lock heartbeat at entry (`updateHeartbeat`, #590-3 — skip when `persistence: false`) so a long close-out cannot lapse the 4h TTL, then walks 3.0 Defensive Cleanup → 3.1 SSOT files → 3.2 Docs Verification → 3.2a Session Handover → 3.3 Claude-rules freshness → 3.4/3.4a STATE.md write + coordinator-snapshot cleanup → 3.45 Telemetry Flush → 3.5/3.5a/3.6.x session memory, learning extraction and the mechanical skip-plan tail → 3.7/3.7a/3.7b/3.7c/3.7d/3.7e metrics write, recommendations, durable commit (#490), vault board → closed, session-eval, vault orphans named (#1563). Full procedure: [`references/phase-3-documentation-updates.md`](references/phase-3-documentation-updates.md).
|
|
149
149
|
|
|
150
150
|
## Phase 3.8: Session Lock Release (#330)
|
|
151
151
|
|
|
@@ -305,7 +305,7 @@ Present to the user the **Session Summary**: Completed / Carried Over / Dropped
|
|
|
305
305
|
| `metrics-collection.md` | Phase 1.7 JSONL schema and conditional field rules |
|
|
306
306
|
| `vault-operations.md` | Phase 2.1 validator bash contract and reporting matrix |
|
|
307
307
|
| `drift-operations.md` | Phase 2.2 drift-checker bash contract and reporting matrix |
|
|
308
|
-
| `references/phase-3-documentation-updates.md` | Phase 3 full procedural body — final heartbeat (#590-3), 3.0 Defensive Cleanup, 3.1 SSOT files, 3.2/3.2a docs + handover, 3.3 rules freshness, 3.4/3.4a STATE.md write + snapshot cleanup, 3.45 Telemetry Flush, 3.5/3.5a/3.6.x memory + learnings + tail dispatcher, 3.7/3.7a/3.7b/3.7c/3.7d metrics, recommendations, durable commit, vault board, session-eval |
|
|
308
|
+
| `references/phase-3-documentation-updates.md` | Phase 3 full procedural body — final heartbeat (#590-3), 3.0 Defensive Cleanup, 3.1 SSOT files, 3.2/3.2a docs + handover, 3.3 rules freshness, 3.4/3.4a STATE.md write + snapshot cleanup, 3.45 Telemetry Flush, 3.5/3.5a/3.6.x memory + learnings + tail dispatcher, 3.7/3.7a/3.7b/3.7c/3.7d/3.7e metrics, recommendations, durable commit, vault board, session-eval, vault orphans |
|
|
309
309
|
| `phase-3-2-docs-verification.md` | Phase 3.2 full procedural body — docs-tasks load, SESSION_START_REF, per-task loop, mode-gated report, Documentation Coverage block |
|
|
310
310
|
| `learning-patterns.md` | Phases 3.5a + 3.6 extraction heuristics, confidence updates, passive decay, and JSONL write procedure |
|
|
311
311
|
| `phase-3-6-tail.md` | Phase 3.6.x tail — detail procedures for the tail phases: 3.6.3 Memory-Proposals Collection (`collectProposals` + AUQ multiSelect + `promoteAndClear`, composing `writeApproved` + `clearProposalsJsonl` behind a mechanical write-before-clear guard, #828), **3.6.4 Expired-Learnings Sweep — MECHANICAL since 2026-09-09**: after `planTailPhases()`, call `runTailPhases({ repoRoot, plan })` from `scripts/lib/session-end/tail-runner.mjs` (delegating to `runExpiredSweep`) and report `result['3.6.4']` (`ran`, `scanned`, `archived`); the event `orchestrator.learnings.sweep_applied` is the proof it ran (Epic #723 B4), **3.6.5 Auto-Dream — RETIRED** and **3.6.7 Auto-Dialectic — RETIRED** (both replaced by the session-start `maintenance-due` probe, `checkMaintenanceDue` in `scripts/lib/maintenance-due-banner.mjs`; headings kept as two-line stubs because other docs cite them), 3.6.6 Skill-Applied Judge (#645 L3 — two calls, not one: `buildSkillEvidence` from `scripts/lib/skill-evidence-window.mjs` renders this session's transcript into the evidence window, then `runSkillJudge` judges it and the coordinator writes each judgment via `appendSkillJudgment`; on empty evidence text `runSkillJudge` returns `status: 'no-evidence'` and does NOT dispatch, #1399 — the exact call shape is in the sub-file, do not reconstruct it here), 3.6.8 Reconciliation Rule Proposals (#696 FA3 — `runReconcile` + AUQ + `writeApprovedRules`). Loaded on demand by the SKILL.md skip-plan dispatcher (#724) — only phases with `run: true` in the `planTailPhases()` plan execute |
|
|
@@ -316,6 +316,7 @@ Present to the user the **Session Summary**: Completed / Carried Over / Dropped
|
|
|
316
316
|
| `phase-3-7a-recommendations.md` § 3.7b | Phase 3.7b full procedural body — `withDurableCommit` invocation for `sessions.jsonl` + `STATE.md` (#490 AC2), `enabled:false` local no-op, autopilot.jsonl exclusion note |
|
|
317
317
|
| `references/phase-3-documentation-updates.md` § 3.7c | Vault Board → Closed (#674) — `mirrorBoard({ explicitStatus: 'closed' })` transitions this repo's board row to `closed`; gated on `vault-integration.enabled`, generator-marked + idempotent, non-blocking, ordered after 3.7b and before 3.7d/3.4/3.8 |
|
|
318
318
|
| `references/phase-3-documentation-updates.md` § 3.7d | Session-Eval (opt-in — #803) — `node scripts/eval-session.mjs --json` scores the just-closed session; gated on `eval.enabled` + `eval.mode != off` (parsed by `scripts/lib/config/eval.mjs`), optional `eval-judge` dispatch + `writeEvalReport`, advisory/never-blocks-close, ordered after 3.7 (record must exist) and before 3.4/Phase 4 (record committed with the session). Full flow in `skills/eval/SKILL.md` |
|
|
319
|
+
| `references/phase-3-7e-vault-orphans.md` | Phase 3.7e Vault Orphans (#1563) — `node scripts/vault-orphans.mjs --since <started_at> --json` (`findVaultOrphans` in `scripts/lib/session-end/vault-orphans.mjs`, read-only `git ls-files --others`) names untracked vault files modified since the session start, minus `_generator: session-orchestrator-` files; candidates only (mtime is no proof), one Open Question via `appendOpenQuestionOnDisk`, never commits/moves/deletes in the vault; `unavailable` = "could not measure", never "no orphans"; gated on `vault-integration.enabled`, ordered after 3.7d and before 3.4 |
|
|
319
320
|
| (inline) Phase 3.8 | Session Lock Release — `release()` uses the physical raw `session_id`; raw mismatch/absent is non-fatal but never repaired with semantic labels or proof (live ambiguity remains for TTL/Reaper); fs-errors are non-fatal; runs after STATE.md writes and before Phase 4 commit staging |
|
|
320
321
|
| (inline) Phase 4 | Commit & Push — stage individually (PSA-004), commit, push to origin, then the `github-mirror-push` block (4 states: not-a-repo / no `github` remote / pushed / push failed) |
|
|
321
322
|
| `references/phase-4a-worktree-cleanup.md` | Phase 4a full procedural body — auto-promoted-worktree detection (`detectAutoPromotedWorktree`, marker-keyed since #1069), clean-check, clean auto-remove path, dirty 3-option AUQ (`Behalten`/`Löschen`/`Manuell`), PSA-003 + #490 ordering rationale |
|
|
@@ -17,9 +17,9 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
17
17
|
- `total_duration_seconds`: from `started_at` to now (ISO 8601 diff)
|
|
18
18
|
- `total_waves`: count of completed waves
|
|
19
19
|
- `total_agents`: sum of agents across all waves
|
|
20
|
-
- `total_files_changed`: unique files changed across entire session (from `git diff --stat`)
|
|
20
|
+
- `total_files_changed`: unique files changed across entire session (from `git diff --stat`). **Not measured** (empty `$SESSION_RANGE`, #1505): the value is unknown, not zero — write `total_files_changed: 0` (`validateSession` requires a non-negative number; `null` or omitting the key is rejected) AND `_backfill_incomplete_fields: ["total_files_changed"]` (append if the array exists), which marks that 0 as a placeholder, the same pair `scripts/lib/session-record-repair.mjs` writes. A bare 0 without the marker claims a measured "no changes".
|
|
21
21
|
- `agent_summary`: `{complete: N, partial: N, failed: N, spiral: N}`
|
|
22
|
-
- **Coordinator-direct fallback (#1321):** when Wave History is empty AND the session ran coordinator-direct (housekeeping — `scripts/lib/session-shape.mjs:364-376` resolves it to ONE coordinator-direct wave that `/go` never hands to wave-executor, so no wave metrics are captured — or the express path), do NOT write `waves: []` / `total_waves: 0`. Write exactly one wave entry `{wave: 1, role: "Housekeeping", agent_count: 0, coordinator_direct: true, files_changed: <N>, quality: "<pass|fail|skip — the close quality-gate result>"}` with `total_waves: 1`, `total_agents: 0`. On this path both that wave's `files_changed` and `total_files_changed` are `git diff --name-only "$SESSION_RANGE" | wc -l` (accessor: `plan-verification.md` § SESSION_START_REF accessor; an empty `$SESSION_RANGE` is a skip, not zero changes — the
|
|
22
|
+
- **Coordinator-direct fallback (#1321):** when Wave History is empty AND the session ran coordinator-direct (housekeeping — `scripts/lib/session-shape.mjs:364-376` resolves it to ONE coordinator-direct wave that `/go` never hands to wave-executor, so no wave metrics are captured — or the express path), do NOT write `waves: []` / `total_waves: 0`. Write exactly one wave entry `{wave: 1, role: "Housekeeping", agent_count: 0, coordinator_direct: true, files_changed: <N>, quality: "<pass|fail|skip — the close quality-gate result>"}` with `total_waves: 1`, `total_agents: 0`. On this path both that wave's `files_changed` and `total_files_changed` are `git diff --name-only "$SESSION_RANGE" | wc -l` (accessor: `plan-verification.md` § SESSION_START_REF accessor; an empty `$SESSION_RANGE` is a skip, not zero changes — write the unmeasured pair from the `total_files_changed` bullet above and name the accessor's reason in the record's `notes`). The single definition of this shape is `isCoordinatorDirectHousekeeping(record)` in `scripts/lib/session-schema/filters.mjs` (every wave `role: "Housekeeping"` AND `coordinator_direct: true`). Consumers key on it (eval gate-health, evolve effective-sizing), so write `role` and `coordinator_direct` exactly as shown. `session_type` alone never marks it.
|
|
23
23
|
3. Read `.orchestrator/metrics/events.jsonl` **once** and build both event aggregates in a single pass. If the file does not exist, treat both aggregates as zero events (omit both fields per the rules below) — do NOT fail the session close.
|
|
24
24
|
|
|
25
25
|
Filter all lines where `session == <session_id>`, then partition by `event` value:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Phase 3.7e: Vault Orphans (#1563)
|
|
2
|
+
|
|
3
|
+
> Sub-file of the session-end skill (#1157 - agentskills.io: SKILL.md core < 500 lines, procedure in `references/`). `SKILL.md` and `references/phase-3-documentation-updates.md` keep a short stub naming this phase and its gate condition.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
A session that writes into the Meta-Vault (an audit, a report, a review, a handover) leaves an untracked file in ANOTHER repo. Its own close commits only in its own repo, and vault-mirror auto-commits only its own generator-marked files in `40-learnings/` and `50-sessions/` (`scripts/lib/vault-mirror/auto-commit.mjs`). Nothing else ever looks. Measured 2026-10-11: 268 untracked files had piled up in one vault checkout over five days. The writing session is the only one that knows it wrote them, so this phase makes it look before it closes.
|
|
8
|
+
|
|
9
|
+
## Gate
|
|
10
|
+
|
|
11
|
+
- Skip silently unless `vault-integration.enabled: true` in Session Config.
|
|
12
|
+
- Skip silently when `<state-dir>/STATE.md` does not exist or has no `started_at` in its frontmatter (`persistence: false`): there is no session window to measure against and no place to record the result.
|
|
13
|
+
|
|
14
|
+
## Ordering
|
|
15
|
+
|
|
16
|
+
Runs AFTER Phase 3.7d (Session-Eval) and BEFORE Phase 3.4 (`status: completed`), so the Open Question below is written while STATE.md is still `status: active`. See the Phase 3.4 Runtime Ordering Note.
|
|
17
|
+
|
|
18
|
+
## Procedure
|
|
19
|
+
|
|
20
|
+
1. Read `started_at` from the STATE.md frontmatter (the same value Phase 3.7 writes into `sessions.jsonl`).
|
|
21
|
+
2. Run the read-only probe from the repo root, so the host-local vault-dir resolution (`SO_VAULT_DIR` > owner.yaml > committed `vault-integration.vault-dir`) sees this repo's Session Config:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node "$PLUGIN_ROOT/scripts/vault-orphans.mjs" --since "<STATE.md started_at>" --json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Exit `0` = measured (the JSON may hold zero candidates), `1` = bad arguments (fix the call), `2` = could not measure. The JSON is the result of `findVaultOrphans()` in `scripts/lib/session-end/vault-orphans.mjs`:
|
|
28
|
+
|
|
29
|
+
- `{ status: 'ok', candidates: [{ path, mtimeMs }], total, truncated, untracked }` - `path` is vault-relative; `candidates` holds at most 200 entries sorted by path, `total` counts all of them, `untracked` is everything git listed before the filters.
|
|
30
|
+
- `{ status: 'unavailable', reason, detail?, candidates: [] }` - no vault dir, not a git repo, or git failed.
|
|
31
|
+
|
|
32
|
+
A candidate is an untracked, not gitignored vault file whose mtime is at or after `started_at` and whose first 4 KB carry no `_generator: session-orchestrator-` frontmatter line (those files belong to vault-mirror, the narrative mirror, the active-sessions board or the portfolio writer).
|
|
33
|
+
|
|
34
|
+
3. Disposition:
|
|
35
|
+
|
|
36
|
+
- **`total > 0`** - NAME the candidates in the Final Report (see `session-summary-template.md`, line `Vault orphans`), then record ONE Open Question in STATE.md, listing up to 10 paths and the count:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import { appendOpenQuestionOnDisk } from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
|
|
40
|
+
|
|
41
|
+
const shown = result.candidates.slice(0, 10).map((c) => c.path);
|
|
42
|
+
const more = result.total - shown.length;
|
|
43
|
+
await appendOpenQuestionOnDisk(process.cwd(), {
|
|
44
|
+
question:
|
|
45
|
+
`Vault: ${result.total} untracked file(s) modified during this session: ` +
|
|
46
|
+
`${shown.join(', ')}${more > 0 ? ` (+${more} more)` : ''}. ` +
|
|
47
|
+
'Commit, move or delete them in the vault - mtime is a hint, not proof this session wrote them.',
|
|
48
|
+
source: 'session-end/3.7e',
|
|
49
|
+
priority: 'medium',
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The question survives the next session-start Idle Reset and is surfaced there as a forced read (`skills/_shared/state-ownership.md` Open Questions), so the next session or the vault owner sorts the files in.
|
|
54
|
+
- **`total === 0`** - report `none since session start` in the Final Report. No Open Question.
|
|
55
|
+
- **`status: unavailable`** (exit 2) - report `could not measure (<reason>)`. NEVER report it as "no orphans": a missing measurement is not a zero. No Open Question.
|
|
56
|
+
|
|
57
|
+
## What this phase never does
|
|
58
|
+
|
|
59
|
+
It does NOT commit into the vault, does NOT delete, does NOT move anything (PSA-003). The mtime window does not prove that this session wrote a file - a concurrent session of another repo may have written the same file in the same window - and the vault is another repo with its own rules. The phase reports; the vault owner disposes.
|
|
60
|
+
|
|
61
|
+
**Non-blocking:** a probe failure, a non-zero exit or a failed STATE.md write MUST NOT block the close. Log one `WARNING: vault orphans - <reason>; continuing close` line and proceed.
|
|
62
|
+
|
|
63
|
+
No Session Config key and no event type belong to this phase; it is a report.
|
|
@@ -65,7 +65,7 @@ Review `<state-dir>/rules/` files that are relevant to this session's work:
|
|
|
65
65
|
|
|
66
66
|
> **Ownership Reference:** See `skills/_shared/state-ownership.md`. session-end is authorized to set `status: completed` plus the optional `updated` timestamp (#184), and — as of Phase A of Epic #271 — the 5 Recommendation fields written by Phase 3.7a. No other fields.
|
|
67
67
|
|
|
68
|
-
> **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.6 → 3.6.8 → 3.7 → 3.45 → 3.7a → 3.7b → 3.7c → 3.7d → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; 3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09 — see `phase-3-6-tail.md` — leaving four live tail phases, not six; the Tail-Diät skip-plan dispatcher dispatches exactly those four mechanically). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.45 (Telemetry Flush, #844) sits AFTER Phase 3.7 because it drains the send-queue with the just-written `sessions.jsonl` record already included, and BEFORE Phase 3.7a because it is a fire-and-forget side-effect with no dependency on the Recommendation-write ordering below it. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
|
|
68
|
+
> **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.6 → 3.6.8 → 3.7 → 3.45 → 3.7a → 3.7b → 3.7c → 3.7d → 3.7e → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; 3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09 — see `phase-3-6-tail.md` — leaving four live tail phases, not six; the Tail-Diät skip-plan dispatcher dispatches exactly those four mechanically). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.45 (Telemetry Flush, #844) sits AFTER Phase 3.7 because it drains the send-queue with the just-written `sessions.jsonl` record already included, and BEFORE Phase 3.7a because it is a fire-and-forget side-effect with no dependency on the Recommendation-write ordering below it. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
|
|
69
69
|
|
|
70
70
|
> Gate: Only run if `persistence` is enabled in Session Config and `<state-dir>/STATE.md` exists.
|
|
71
71
|
1. Set frontmatter `status: completed`
|
|
@@ -239,3 +239,13 @@ Evaluate the just-closed session deterministically and, when configured, with an
|
|
|
239
239
|
|
|
240
240
|
**Advisory — never blocks the close (FA6):** an eval failure — a non-zero `eval-session.mjs` exit, a judge-dispatch error, or a report-write error — MUST NOT abort `/close`. Under `mode: warn`, log a single `WARNING: session-eval failed — <reason>; continuing close` line to stderr and proceed to Phase 3.8 / Phase 4. There is NO exit-code gate on this phase. See `skills/eval/SKILL.md` for the full deterministic-engine + judge + report detail flow.
|
|
241
241
|
|
|
242
|
+
### Phase 3.7e: Vault Orphans (#1563)
|
|
243
|
+
|
|
244
|
+
> Gate: Skip silently unless `vault-integration.enabled: true` in Session Config, and when STATE.md has no `started_at` (no session window to measure).
|
|
245
|
+
|
|
246
|
+
> **Ordering:** Runs AFTER Phase 3.7d and BEFORE Phase 3.4 (`status: completed`), so its one Open Question lands while STATE.md is still `status: active`. See the Phase 3.4 Runtime Ordering Note canonical order.
|
|
247
|
+
|
|
248
|
+
Names the untracked vault files this session may have left behind: `node "$PLUGIN_ROOT/scripts/vault-orphans.mjs" --since <STATE.md started_at> --json` lists untracked vault files modified since the session start, minus files carrying a `_generator: session-orchestrator-` marker. The results are candidates — mtime is a hint, not proof of authorship. The coordinator names them in the Final Report and records ONE Open Question via `appendOpenQuestionOnDisk` (up to 10 paths plus the count). It never commits, moves or deletes anything in the vault (PSA-003). Exit 2 / `status: unavailable` is reported as "could not measure", never as "no orphans". Non-blocking.
|
|
249
|
+
|
|
250
|
+
**See `phase-3-7e-vault-orphans.md` for full details.**
|
|
251
|
+
|
|
@@ -46,6 +46,10 @@ Present to the user:
|
|
|
46
46
|
- Custom Phases: [render based on Phase 2.5 result — omit the line entirely if `custom-phases` was absent/empty]
|
|
47
47
|
- Per phase: `<name>: <pass|FAIL> (exit <code>, mode=<mode>)[ — review: <path>]`
|
|
48
48
|
- None ran (all filtered out by `when`): `none applicable for session-type=<type>.`
|
|
49
|
+
- Vault orphans: [render based on Phase 3.7e — omit the line entirely when `vault-integration.enabled` is not `true`]
|
|
50
|
+
- Candidates present: `<N> untracked vault file(s) modified since session start (candidates, mtime is no proof): <path>, <path>, ... — recorded as Open Question, nothing committed or deleted.`
|
|
51
|
+
- None: `none since session start (<U> untracked in total).`
|
|
52
|
+
- Could not measure (exit 2 / `status: unavailable`): `could not measure (<reason>)` — never "none".
|
|
49
53
|
- Enforcement: [N violations blocked / M warnings] (or "N/A" if enforcement off)
|
|
50
54
|
- Circuit breaker: [N agents hit limits, M spirals detected] (or "none")
|
|
51
55
|
- Metrics written to: `.orchestrator/metrics/sessions.jsonl`
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
1a. **Token Rollup (#644, #1244 — mechanical since #1436)** — no coordinator action. `scripts/emit-session.mjs` (step 2) runs `rollupSessionTokens()` itself whenever the incoming record carries no `total_tokens`: it resolves the session's raw UUID from `.orchestrator/current-session.json` (adopted only when its `semantic_session_id` equals the record's `session_id` and no process-local session id contradicts it), joins the sibling `subagents.jsonl` on `parent_session_id`, and fills `total_tokens`, `total_token_input`, `total_token_output`, the three cache buckets, `total_cost_usd`, `subagents_with_tokens`, `matched_records`, `cost_records_priced`, `cost_records_total` and `_token_schema` for every key the record does not already carry — plus `raw_session_id`. No matching records, an unreadable ledger or a foreign marker omits the fields with a WARN on stderr, never a fabricated `0`. An explicit key on `$METRICS_ENTRY` still wins. The same UUID also backs `session_start_ref` when STATE.md has none (the `head_sha` of the session's own `orchestrator.session.started` event).
|
|
33
33
|
|
|
34
|
-
**Semantics:** `null` totals mean "no token data was captured for this session" — this is NOT the same as zero cost. Do NOT coerce null to 0 when displaying or summing across sessions. The same holds for `total_cost_usd`: it is omitted whenever `cost_records_priced < cost_records_total` (#1475) — at least one cost-relevant subagent record could not be priced (a model the price table does not know, a token-bearing turn naming no model, a transcript that yielded no tokens, e.g. one over the hook's size cap, or an agent with a start record whose stop found no transcript of its own, mostly `workflow-subagent`, or an agent known only from `schema_version` 1 records — #1487 Pkt 1) — and also when `cost_records_total` is 0, because there was nothing to price. It is never "$0" for an unknown cost and never a partial sum over the v2 agents the ledger can see. Phantom stops (#939, no start record) are no agents and never count. The two counters are persisted beside it, so a missing cost says whether part of the session was unpriceable or nothing was priceable. Blind spots remain: an agent whose start record fell outside the hook's tail window looks like a phantom, an agent with neither start nor stop record is invisible. A session spanning the 2026-09-09 schema boundary therefore reports no cost (its v1 agents are unpriced candidates since 2026-10-02; before, it was priced over its v2 records only — 4 distinct fleet sessions carried such a partial sum). A subagent whose turns span several models carries a per-model `models_usage` breakdown in `subagents.jsonl` (#1470) and is priced part by part. The emit-session warning names the reason via `match_status` (ledger absent, ledger with no readable records, or records present but none for the own raw UUID); none of these means zero cost.
|
|
34
|
+
**Semantics:** `null` totals mean "no token data was captured for this session" — this is NOT the same as zero cost. Do NOT coerce null to 0 when displaying or summing across sessions. The same holds for `total_cost_usd`: it is omitted whenever `cost_records_priced < cost_records_total` (#1475) — at least one cost-relevant subagent record could not be priced (a model the price table does not know, a token-bearing turn naming no model, a transcript that yielded no tokens, e.g. one over the hook's size cap, or an agent with a start record whose stop found no transcript of its own, mostly `workflow-subagent`, or an agent known only from `schema_version` 1 records — #1487 Pkt 1) — and also when `cost_records_total` is 0, because there was nothing to price. It is never "$0" for an unknown cost and never a partial sum over the v2 agents the ledger can see. Phantom stops (#939, no start record) are no agents and never count. The two counters are persisted beside it, so a missing cost says whether part of the session was unpriceable or nothing was priceable. Blind spots remain: an agent whose start record fell outside the hook's tail window looks like a phantom, an agent with neither start nor stop record is invisible. A session spanning the 2026-09-09 schema boundary therefore reports no cost (its v1 agents are unpriced candidates since 2026-10-02; before, it was priced over its v2 records only — 4 distinct fleet sessions carried such a partial sum). A subagent whose turns span several models carries a per-model `models_usage` breakdown in `subagents.jsonl` (#1470) and is priced part by part. A written `total_cost_usd` is the **API list-price equivalent** of the metered tokens, never a payment: subscription quota, purchased credits and the amount actually billed are invisible to the rollup, and no record states any of them. Since #1470 emit-session marks this with `cost_basis: "api-list-equivalent"` beside every `total_cost_usd` it writes, and writes `pricing_table_date` (the `PRICING_TABLE_DATE` of `scripts/lib/telemetry/pricing.mjs` that priced the session) beside the cost counters. A record without these fields predates #1470; its cost is a list-price estimate too, priced by an older table, not a verified bill. The emit-session warning names the reason via `match_status` (ledger absent, ledger with no readable records, or records present but none for the own raw UUID); none of these means zero cost.
|
|
35
35
|
|
|
36
36
|
**Provenance (#949):** the rollup sums ONLY records carrying `subagent_transcript_found: true` — the flag the producer sets when it read the subagent's own transcript. Pre-#949 records carry the PARENT transcript's running totals and are excluded, so a session made up entirely of them now reports `null` rather than a fabricated sum (73 historical sessions, 96,148,781 phantom tokens, measured 2026-08-11). Two consequences for readers: totals already written into `sessions.jsonl` before 2026-08-11 were produced by the unfiltered recipe and are a series break, not a trend; and `matched_records` counts start records and phantom stops alike, so it is NOT the denominator for a coverage ratio — use `subagents_with_tokens` against the session's real agent count.
|
|
37
37
|
|
|
@@ -352,7 +352,7 @@ Invokes `buildLiveSignals` (single SSOT for the signals shape) then `selectMode(
|
|
|
352
352
|
|
|
353
353
|
## Phase 7.6: Fleet Check-in (#1462)
|
|
354
354
|
|
|
355
|
-
> Skip silently when `persistence: false` (no ledger for the event). Runs BEFORE the first writing action; everything not stated here is in `skills/_shared/fleet-protocol.md`.
|
|
355
|
+
> Skip silently when `persistence: false` (no ledger for the event). Runs BEFORE the first writing action; everything not stated here is in `skills/_shared/fleet-protocol.md`. Without fleet integration the CLI itself skips (stdout `skipped: "no-fleet"`, no file, no event, #1559): then this phase ends there — no hint, no `auflagen/` read, and no Standard-Auflagen, except for a headless run (`plattform: kopflos`), which applies them at once.
|
|
356
356
|
|
|
357
357
|
Build the check-in JSON (fields per the reference; `session` from `.orchestrator/current-session.json`, `modus` = session type, `kandidaten` = the issue numbers you intend to claim) and pipe it into `node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/fleet-checkin.mjs"` — it validates, stores `checkin/<session_id>.json` (0600) under `~/.config/navigator/` and emits `orchestrator.fleet.checkin`. Its stdout carries `navigator_state` and `navigator_adresse` (the lease's validated `adresse`, else `null`): on `active`, and only when `navigator_adresse` is non-null and reachable via `ListAgents`, send ONE hint to exactly that name, naming the file path — never expect or wait for a reply (CSM-004). Then read `auflagen/<session_id>.json` via `node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/fleet-auflagen.mjs" <session_id>` (only a file whose `repo` resolves to this repo's git common dir is `accepted`; a `rejected` one counts as absent); if it is still absent at the stdout's `fallback_due_at`, the reference's Standard-Auflagen apply (the CLI computes that deadline: `zeit` plus `CHECKIN_FALLBACK_MIN` while a navigator is active, `zeit` itself — immediately — otherwise). Reading work and edits in the own worktree continue meanwhile.
|
|
358
358
|
|
|
@@ -7,7 +7,7 @@ Run immediately before Phase 8 so the Mode-Selector recommendation can influence
|
|
|
7
7
|
|
|
8
8
|
## Distinction from Phase 1.5 Recommendations Banner
|
|
9
9
|
|
|
10
|
-
Phase 1.5 renders a `📋` banner that reads the **previous** session's
|
|
10
|
+
Phase 1.5 renders a `📋` banner that reads the **previous** session's Recommendation fields from the STATE.md frontmatter (`recommended-mode`, `rationale`, `completion-rate`, etc., written by session-end Phase 3.7a). It is a backward-looking handoff signal.
|
|
11
11
|
|
|
12
12
|
Phase 7.5 runs `selectMode(signals)` — a **forward-looking** computation over live signals gathered this session (current carryover ratios, recent sessions trend, learnings, bootstrap tier). It emits a `📊` banner.
|
|
13
13
|
|
|
@@ -45,6 +45,8 @@ truncated window (fixed in `c20d4d2`).
|
|
|
45
45
|
|
|
46
46
|
**Key source bindings (Phase → field):**
|
|
47
47
|
|
|
48
|
+
The five Recommendation fields are read from the STATE.md frontmatter that session-end Phase 3.7a wrote at the previous `/close`. The Phase 1.5 Idle Reset keeps them there (#1560; before, it deleted them and this step saw `recommendedMode: null`, confidence 0); the "Phase" column names the phase that surfaces them first, not a writer.
|
|
49
|
+
|
|
48
50
|
| `signals` field | Populated from | Phase |
|
|
49
51
|
|---|---|---|
|
|
50
52
|
| `recommendedMode` | `parseRecommendations(frontmatter).mode` | Phase 1.5 |
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
Acquire a distributed session-lock to detect parallel sessions in the same repo before initializing STATE.md. This prevents two concurrent Claude/Codex sessions from stomping each other's wave state and metrics writes.
|
|
12
12
|
|
|
13
|
-
**Mechanical wiring (Epic #583, 2026-05-27):** The SessionStart hook (`hooks/on-session-start.mjs` → `hooks/_lib/lock-bootstrap.mjs`) now writes `.orchestrator/session.lock` mechanically BEFORE this skill's prose runs. The prose Phase 1.2 becomes confirmatory — it verifies the lock exists with the expected shape via `readLock({ repoRoot: process.cwd() })`. Re-call `acquire()` only if `readLock()` returns `null` (mechanical hook failed) OR the existing lock's raw `session_id` does not exactly match the current session's raw id (a rare divergence — surface via AUQ before overwriting). A matching `semantic_session_id`, STATE.md `session`, or owner proof cannot repair that mismatch. The decision flow below still applies to all five outcomes (active / stale / corrupt / unreadable / fs-error) when the prose path needs to acquire.
|
|
13
|
+
**Mechanical wiring (Epic #583, 2026-05-27):** The SessionStart hook (`hooks/on-session-start.mjs` → `hooks/_lib/lock-bootstrap.mjs`) now writes `.orchestrator/session.lock` mechanically BEFORE this skill's prose runs. The prose Phase 1.2 becomes confirmatory — it verifies the lock exists with the expected shape via `readLock({ repoRoot: process.cwd() })`. Re-call `acquire()` only if `readLock()` returns `null` (mechanical hook failed) OR the existing lock's raw `session_id` does not exactly match the current session's raw id (a rare divergence — surface via AUQ before overwriting). A matching `semantic_session_id`, STATE.md `session`, or owner proof cannot repair that mismatch. The decision flow below still applies to all five outcomes (active / stale / corrupt / unreadable / fs-error) when the prose path needs to acquire. Every `ok: true` from `acquire()` / `forceAcquire()` also rewrites `.orchestrator/runtime/lock-owner-proof.json` for the new lock (#1541), so the `/close` release stays proof-gated; a failed proof write is reported as `result.ownerProof: { ok: false, reason }`, never thrown.
|
|
14
14
|
|
|
15
15
|
```javascript
|
|
16
16
|
import { acquire, forceAcquire } from 'scripts/lib/session-lock.mjs';
|
|
@@ -98,14 +98,22 @@ When (and only when) the prior `status` is `completed`, rewrite STATE.md to a cl
|
|
|
98
98
|
|
|
99
99
|
Reset rules — applies ONLY on the `completed` branch. Do NOT perform this reset on `active` or `paused`; those paths stay user-interactive via AskUserQuestion.
|
|
100
100
|
|
|
101
|
+
Run it as code, after the Recommendations Banner — never rewrite STATE.md by hand (#1560):
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
node "${PLUGIN_ROOT}/scripts/state-md-idle-reset.mjs" --json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
It prints `{"changed": <bool>, "reason": <string|null>, "path": "<STATE.md>"}` and writes under the STATE.md lock (`resetStateMdToIdle` in `scripts/lib/state-md/idle-reset.mjs`). A no-op exits 0 with `changed: false` and one of `reason: "status-not-completed"` (any status other than `completed`), `"unparseable"` (no frontmatter) or `"missing"` (no or empty STATE.md). Exit 2 means `--repo-root` is not a directory, the write failed, or a STATE.md guard refused it (`reason` names the guard) — report it as a Deviation and continue. Without a new `## Wave History` (a coordinator-direct session) the earlier `## Previous Session` body is kept below the new recommendations copy. The rules below are the contract that code implements:
|
|
108
|
+
|
|
101
109
|
1. Set frontmatter `status: idle`.
|
|
102
110
|
2. Clear `current-wave` (set to `0`).
|
|
103
|
-
3. Move the existing `## Wave History` body into a new `## Previous Session` archive section (retain the record, but demote it below the new session's live state). Remove the original `## Wave History` section — wave-executor will recreate it on the next wave.
|
|
111
|
+
3. Move the existing `## Wave History` body into a new `## Previous Session` archive section under `### Wave History (<session>)`, replacing any earlier `## Previous Session` section (retain the record, but demote it below the new session's live state). Remove the original `## Wave History` section — wave-executor will recreate it on the next wave.
|
|
104
112
|
4. Clear `## Deviations` (leave the heading with an empty body so the schema is preserved).
|
|
105
113
|
- **PRESERVE `## What Not To Retry` (#623):** do NOT clear, demote, or drop this section during the Idle Reset. Unlike `## Deviations` (per-session, emptied above) and `## Wave History` (demoted into `## Previous Session`), `## What Not To Retry` is a **cross-session continuity slot** — its entries must survive into the next session so session-start Phase 6.5.1 can surface them. Leave the section, its heading, and all entries byte-for-byte intact.
|
|
106
114
|
- **PRESERVE `## Open Questions` (#772):** do NOT clear, demote, or drop this section during the Idle Reset. Unlike `## Deviations` (per-session, emptied above) and `## Wave History` (demoted into `## Previous Session`), `## Open Questions` is a **cross-session continuity slot** — unanswered entries must survive into the next session so session-start Phase 6.5.2 can surface them as a forced-read. Leave the section, its heading, and all entries (answered and unanswered) byte-for-byte intact.
|
|
107
115
|
5. Leave other frontmatter fields (`schema-version`, `session-type`, `branch`, `issues`, `started_at`, `total-waves`) intact until Phase 1b overwrites them with the new session's values.
|
|
108
|
-
6. **v1.1 Recommendation
|
|
116
|
+
6. **v1.1 Recommendation fields STAY in the frontmatter (Epic #271 Phase A, AC2; changed by #1560):** do NOT remove `recommended-mode`, `top-priorities`, `carryover-ratio`, `completion-rate`, `rationale`. They are the handoff Phase 7.5 reads; session-end Phase 3.7a, their only writer, overwrites them at the next `/close`. If ANY of them is present, also prepend a readable copy (NOT YAML) to the `## Previous Session` body:
|
|
109
117
|
|
|
110
118
|
```markdown
|
|
111
119
|
### Recommendations (archived from v1.1 frontmatter)
|
|
@@ -117,9 +125,9 @@ Reset rules — applies ONLY on the `completed` branch. Do NOT perform this rese
|
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
Omit individual bullets for null-valued fields. If all 5 are null (i.e., `parseRecommendations` returned non-null but every field is null after type-coercion), skip the archival block entirely.
|
|
120
|
-
7. **Scope-baseline key deletion (Epic #894 S5, #898):** If ANY of the 5 `scope-baseline-*` frontmatter keys (`scope-baseline-intent`, `scope-baseline-owner-boundary`, `scope-baseline-planned-files`, `scope-baseline-session`, `scope-baseline-frozen-at`) is present, remove them via
|
|
128
|
+
7. **Scope-baseline key deletion (Epic #894 S5, #898):** If ANY of the 5 `scope-baseline-*` frontmatter keys (`scope-baseline-intent`, `scope-baseline-owner-boundary`, `scope-baseline-planned-files`, `scope-baseline-session`, `scope-baseline-frozen-at`) is present, remove them via `updateFrontmatterFields(contents, {field: null, ...})` (null value deletes the key). Rule 5 leaves unknown frontmatter fields intact and no other rule removes these five — without this step they survive into session N+1 and silently corrupt the next session's drift-baseline denominator. This is a hygiene layer only: the primary defense is mechanical — `scripts/lib/scope-baseline.mjs` compares `scope-baseline-session` against the canonical `session` field, so a stale baseline self-invalidates (`readBaseline()` returns `{stale: true, …}`) even if this rule were skipped. Delete exactly these five keys; do not remove any other unknown key.
|
|
121
129
|
|
|
122
|
-
Rationale: `/close` intentionally keeps STATE.md as a record so the next session-start can read it. This reset completes that contract by demoting the record before new session state is written, so a fresh session never appears "already completed".
|
|
130
|
+
Rationale: `/close` intentionally keeps STATE.md as a record so the next session-start can read it. This reset completes that contract by demoting the record before new session state is written, so a fresh session never appears "already completed". Rule 6 keeps the Recommendation fields because the Phase 7.5 Mode-Selector (`buildLiveSignals()`) reads them from this same frontmatter later in session-start, and the current session writes its own only at session-end Phase 3.7a. Deleting them (the rule until #1560) left the selector with no recommendation after every clean `/close`: `{"mode":"feature","rationale":"missing/invalid recommendedMode → default","confidence":0}`. The archived block is a human-readable copy for browsing STATE.md history.
|
|
123
131
|
|
|
124
132
|
### Snapshot Recovery (#196)
|
|
125
133
|
|
|
@@ -50,6 +50,7 @@ The coordinator determines each agent's status after the wave completes:
|
|
|
50
50
|
| **partial** | Agent made progress but couldn't finish | Turn limit hit, or agent reports `STATUS: partial` | Carry forward remaining work to next wave with context |
|
|
51
51
|
| **failed** | Agent couldn't make meaningful progress | Tool errors, invalid assumptions, agent reports `STATUS: failed` | Re-dispatch with corrected instructions and narrower scope |
|
|
52
52
|
| **spiral** | Agent got stuck in an edit loop | Same file edited 3+ times (detected post-wave) | Revert agent's changes, narrow scope, split task if needed |
|
|
53
|
+
| **limit** | Agent stopped by a session/usage limit or HTTP 429 mid-wave — not by its task | Output ends in a limit/429 error instead of a `STATUS:` line | No re-dispatch, no carryover issue: read `git diff` in the agent's worktree, finish the small remaining fix passes coordinator-direct, log a Deviation via `appendDeviationOnDisk()` (`scripts/lib/state-md.mjs`) |
|
|
53
54
|
|
|
54
55
|
3. **Recovery protocol**:
|
|
55
56
|
- FAILED agent → log in STATE.md, add fix task to next wave with corrected instructions, AND auto-create a carryover issue (see "Carryover Auto-Create" below) the first time this task is marked FAILED (check STATE.md Wave History for a prior `→ issue #NNN` on this task before filing).
|
package/AGENTS.md
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
# Session Orchestrator Plugin
|
|
2
|
-
|
|
3
|
-
> Project-instruction file resolution: this is `CLAUDE.md` on Claude Code / Cursor IDE; the equivalent file on Codex CLI is `AGENTS.md`. See [skills/_shared/instruction-file-resolution.md](skills/_shared/instruction-file-resolution.md).
|
|
4
|
-
|
|
5
|
-
> Lean root by design (Anthropic large-codebase best-practice: root = pointers + critical gotchas). Delegated docs:
|
|
6
|
-
> - **Install, CLI usage, architecture, component inventory** → [`README.md`](./README.md) (full inventory: [`docs/components.md`](./docs/components.md))
|
|
7
|
-
> - **Plugin ↔ private baseline relationship** (what degrades without it, the public fallback) → [`docs/baseline.md`](./docs/baseline.md)
|
|
8
|
-
> - **Sub-agent authoring spec** (frontmatter, body, `sandbox-tier`, `output-schema`) → [`docs/agent-authoring.md`](./docs/agent-authoring.md) (moved out of `agents/` in 4.0.0 — the plugin loader registers every `agents/*.md` by directory convention, and the spec was dispatched as an agent 5× fleet-wide)
|
|
9
|
-
> - **Stable product/tech/structure context** → [`.orchestrator/steering/`](./.orchestrator/steering/) (injected at session-start Phase 2.6)
|
|
10
|
-
> - **Always-on + path-scoped rules** → [`.claude/rules/`](./.claude/rules/) (per-wave via `rule-loader.mjs`, #336/#694); authoring spec → [`docs/rule-authoring.md`](./docs/rule-authoring.md)
|
|
11
|
-
> - **Decisions log** (hand-authored) → [[01-projects/session-orchestrator/decisions]] in the Meta-Vault
|
|
12
|
-
> - **Session narrative** (machine-written by session-end Phase 3.7 via `scripts/lib/vault-status/narrative-mirror.mjs`) → `01-projects/session-orchestrator/_session-narrative.md`
|
|
13
|
-
> - **Operator tmux side-channel** → `/tmux-layout` (4-pane: STATE.md/CI-watch/events; [ADR-0007](docs/adr/0007-tmux-visualization-substrate.md))
|
|
14
|
-
>
|
|
15
|
-
> **Root `AGENTS.md` is GENERATED byte-identical from this file** (`node scripts/generate-agents-skills.mjs`) for harnesses that read only `AGENTS.md` (Codex CLI, OpenCode, Kiro, Cursor's AGENTS path). Never edit it; edit here and regenerate. `validate-plugin` fails on drift.
|
|
16
|
-
|
|
17
|
-
## Current State <!-- consistency:exempt:lean-root-pointer-section -->
|
|
18
|
-
|
|
19
|
-
State-free by design (see the **Live state is not in this file** gotcha below) — SSOTs:
|
|
20
|
-
- **Version + test/coverage** → README badges
|
|
21
|
-
- **Per-session metrics & history** → [`.orchestrator/metrics/sessions.jsonl`](./.orchestrator/metrics/sessions.jsonl)
|
|
22
|
-
- **Decisions** (hand-authored) → [[01-projects/session-orchestrator/decisions]] (Meta-Vault) · **Session narrative** (generated) → `_session-narrative.md` beside it
|
|
23
|
-
|
|
24
|
-
## Critical Gotchas <!-- consistency:exempt:lean-root-pointer-section -->
|
|
25
|
-
|
|
26
|
-
These are the non-obvious, mistake-causing facts that must load every session. Everything else is delegated above.
|
|
27
|
-
|
|
28
|
-
- **CI status is the source of truth at session-start** — never claim CI green from `npm test` alone (8-pipeline silent regression 2026-05-09 → 2026-05-10, fixed in deep-2); Phase 4 🚨 banner `checkCiStatus({ repoRoot })` in `scripts/lib/ci-status-banner.mjs`; see `skills/session-start/references/phase-4-ssot-environment-check.md` (incident: ADR-0006). <!-- consistency:exempt:runtime-only -->
|
|
29
|
-
- **Destructive-Command Guard** (`hooks/pre-bash-destructive-guard.mjs`, `.orchestrator/policy/blocked-commands.json`: 12 block / 4 warn of 16 rules) is live on Claude Code + Cursor/Pi bridges, instruction-only on Codex. PSA-003 (`parallel-sessions.md`); bypass `allow-destructive-ops: true`; long form incl. #1401: `docs/session-config-reference.md`. <!-- consistency:exempt:runtime-only -->
|
|
30
|
-
- **Session Config below is runtime-critical.** `scripts/parse-config.mjs` parses the `## Session Config` block; `claude-md-drift-check` Check 6 enforces top-level-key parity against `docs/session-config-template.md`. Edit it like code, not prose — a dropped key changes runtime behaviour.
|
|
31
|
-
- **Live state is not in this file.** Stack: Node 24+, vitest, ESLint 10 (`npm ci` after clone). Test counts, backlog, version, component inventory drift fast — the SSOT is README badges + `.orchestrator/metrics/sessions.jsonl`. Per-session detail lives in the Meta-Vault decisions log (linked above), not here.
|
|
32
|
-
- **`memory.propose` requires `SO_WAVE_AGENT=1`** — `scripts/memory-propose.mjs` exits 3 from the coordinator thread (use `/evolve`); wave-executor sets it per dispatched agent. Statuses: queued 0 · dry-run-ok 0 · quota-exceeded 1 · rejected-low-confidence 2 · rejected-wrong-context 3 · error 4 — `docs/session-config-reference.md` § Memory Proposals. <!-- consistency:exempt:runtime-only -->
|
|
33
|
-
- **Auto-promoted worktree cleanup keys on the `.orchestrator/promoted-from.json` marker, not on session-id coincidence (#1069)** — promotion is a process boundary (ADR-0013), so `detectAutoPromotedWorktree()` tries the marker first (`source: 'marker'`) and falls back to the `<repo>-<sessionId>` basename only for pre-marker worktrees.
|
|
34
|
-
`/close` Phase 4a runs AFTER Phase 4 commit+push (#490): clean → auto-remove with WARN, dirty → AUQ `[Behalten/Löschen/Manuell]`; Hybrid Pattern (Anthropic-style, ADR-0008), PSA-003. Long form: `skills/session-end/references/phase-4a-worktree-cleanup.md`. <!-- consistency:exempt:runtime-only -->
|
|
35
|
-
- **`allowedPaths` is COMPUTED, and the scope declaration has TWO shapes — writing only one is the silent failure (#1020).** (a) per-agent `<state-dir>/filescopes/wave-<N>/<agent-id>.json` (path strings = `$AGENT_FILESCOPE_JSON`: FILE-SCOPE injection, Learnings-Index, `--assert-subset`); (b) per-wave `{id, files}` records, the only input of `--assert-disjoint`/`--union`.
|
|
36
|
-
`node scripts/materialize-wave-scope.mjs --state-dir <state-dir> --wave <N>` writes both; fold `coordinator.json` into (b). Skipping (a) = signal-free ALLOW (2026-08-19: six waves, ~27 dispatches, zero injection). Long form: `docs/scope-collision-guard.md` § The two-shape trap; `skills/wave-executor/references/wave-loop-scope-manifest.md` § 3.1/3.2. <!-- consistency:exempt:runtime-only -->
|
|
37
|
-
- **`vault-dir` resolves HOST-LOCALLY** (`SO_VAULT_DIR` > `owner.yaml` > committed `vault-integration.vault-dir`, `scripts/lib/config/host-paths.mjs`) — a hand-run vault writer with a synthetic `repoRoot` still writes into the REAL vault; export `SO_VAULT_DIR=<tmpdir>` before any manual invocation. Detail: `docs/session-config-reference.md` (#1448). <!-- consistency:exempt:runtime-only -->
|
|
38
|
-
|
|
39
|
-
## Session Config
|
|
40
|
-
|
|
41
|
-
persistence: true
|
|
42
|
-
enforcement: warn
|
|
43
|
-
agents-per-wave: 6 (deep: 18)
|
|
44
|
-
waves: 5
|
|
45
|
-
recent-commits: 20
|
|
46
|
-
test-command: npm test
|
|
47
|
-
typecheck-command: npm run typecheck
|
|
48
|
-
lint-command: npm run lint
|
|
49
|
-
stale-branch-days: 7
|
|
50
|
-
plugin-freshness-days: 30
|
|
51
|
-
plan-baseline-path: OVERRIDE-IN-owner.yaml # placeholder, NOT a real path — resolve host-locally
|
|
52
|
-
plan-prd-location: docs/prd
|
|
53
|
-
plan-retro-location: docs/retro
|
|
54
|
-
plan-default-visibility: internal
|
|
55
|
-
vcs: gitlab
|
|
56
|
-
auto-skill-dispatch: false
|
|
57
|
-
vault-integration:
|
|
58
|
-
enabled: true
|
|
59
|
-
vault-dir: ~/Projects/vault
|
|
60
|
-
mode: warn
|
|
61
|
-
vault-name:
|
|
62
|
-
docs-orchestrator:
|
|
63
|
-
enabled: false
|
|
64
|
-
audiences: [user, dev, vault]
|
|
65
|
-
mode: warn
|
|
66
|
-
vault-staleness:
|
|
67
|
-
enabled: false
|
|
68
|
-
thresholds:
|
|
69
|
-
top: 30
|
|
70
|
-
active: 60
|
|
71
|
-
archived: 180
|
|
72
|
-
mode: warn
|
|
73
|
-
docs-staleness:
|
|
74
|
-
enabled: false
|
|
75
|
-
thresholds:
|
|
76
|
-
living: 90
|
|
77
|
-
mode: warn
|
|
78
|
-
moc-staleness:
|
|
79
|
-
enabled: false
|
|
80
|
-
thresholds:
|
|
81
|
-
moc: 90
|
|
82
|
-
mode: warn
|
|
83
|
-
context-coverage:
|
|
84
|
-
enabled: false
|
|
85
|
-
mode: warn
|
|
86
|
-
worktree-orphans:
|
|
87
|
-
enabled: false
|
|
88
|
-
base-branch: main
|
|
89
|
-
mode: warn
|
|
90
|
-
drift-check:
|
|
91
|
-
enabled: true
|
|
92
|
-
mode: warn
|
|
93
|
-
check-docs-parity: true
|
|
94
|
-
wave-reviewers:
|
|
95
|
-
enabled: false
|
|
96
|
-
reviewers: []
|
|
97
|
-
mode: warn
|
|
98
|
-
memory-cleanup-soft-limit: 180
|
|
99
|
-
vault-mirror:
|
|
100
|
-
quality:
|
|
101
|
-
min-narrative-chars: 400
|
|
102
|
-
min-confidence: 0.5
|
|
103
|
-
memory:
|
|
104
|
-
banner:
|
|
105
|
-
enabled: true
|
|
106
|
-
proposals:
|
|
107
|
-
enabled: true
|
|
108
|
-
quota-per-wave: 5
|
|
109
|
-
confidence-floor: 0.5
|
|
110
|
-
auto-dream:
|
|
111
|
-
min-confidence: 0.5
|
|
112
|
-
cold-start:
|
|
113
|
-
enabled: true
|
|
114
|
-
nudge-after-hours: 1
|
|
115
|
-
silence-after-sessions: 1
|
|
116
|
-
state-md-lock:
|
|
117
|
-
enabled: true
|
|
118
|
-
timeout-ms: 10000
|
|
119
|
-
handover-gate:
|
|
120
|
-
enabled: true
|
|
121
|
-
max-open-questions: 3
|
|
122
|
-
issue-budget:
|
|
123
|
-
max-per-session: 12
|
|
124
|
-
mode: strict
|
|
125
|
-
overflow: collect-issue
|
|
126
|
-
broken-window-budget:
|
|
127
|
-
enabled: false
|
|
128
|
-
due-days: 7
|
|
129
|
-
slopcheck:
|
|
130
|
-
enabled: false
|
|
131
|
-
sources: [plan, discovery]
|
|
132
|
-
templates-first:
|
|
133
|
-
enabled: true
|
|
134
|
-
hosts: [github, gitlab]
|
|
135
|
-
verification-auto-fix:
|
|
136
|
-
enabled: false
|
|
137
|
-
max-retries: 2
|
|
138
|
-
discovery-validator:
|
|
139
|
-
enabled: false
|
|
140
|
-
frontend-slop-hook:
|
|
141
|
-
enabled: false
|
|
142
|
-
loop-guard:
|
|
143
|
-
enabled: true
|
|
144
|
-
threshold: 3
|
|
145
|
-
window: 5
|
|
146
|
-
instruction-budget:
|
|
147
|
-
enabled: true
|
|
148
|
-
ceiling: 480
|
|
149
|
-
mode: warn
|
|
150
|
-
config-protection:
|
|
151
|
-
enabled: true
|
|
152
|
-
mode: warn
|
|
153
|
-
allow-config-weakening: false
|
|
154
|
-
custom-phases:
|
|
155
|
-
- name: archive-closed-prds
|
|
156
|
-
when: both
|
|
157
|
-
command: node scripts/archive-closed-prds.mjs --apply
|
|
158
|
-
mode: warn
|
|
159
|
-
- name: archive-closed-plans
|
|
160
|
-
when: both
|
|
161
|
-
command: node scripts/archive-closed-prds.mjs --apply --prd-dir docs/plans --vault-subdir 01-projects/session-orchestrator/plans
|
|
162
|
-
mode: warn
|
|
163
|
-
remote-hosts:
|
|
164
|
-
- alias: m5
|
|
165
|
-
roles-allowed: [test, ui, perf]
|
|
166
|
-
evolve:
|
|
167
|
-
extra-sources: []
|
|
168
|
-
dialectic:
|
|
169
|
-
cadence: 5
|
|
170
|
-
model: haiku
|
|
171
|
-
budget-tokens: 32000
|
|
172
|
-
eval:
|
|
173
|
-
enabled: true
|
|
174
|
-
mode: warn
|
|
175
|
-
judge: off
|
|
176
|
-
report: html
|
|
177
|
-
handle:
|
|
178
|
-
reconcile:
|
|
179
|
-
enabled: false
|
|
180
|
-
mode: warn
|
|
181
|
-
targets: [repo-local]
|
|
182
|
-
rule-expiry-days: null
|
|
183
|
-
confidence-floor: 0.5
|
|
184
|
-
min-rule-days: 7
|
|
185
|
-
min-insight-chars: 24
|
|
186
|
-
|
|
187
|
-
## Skill Evolution <!-- consistency:exempt:parity-exempt-skill-evolution-block -->
|
|
188
|
-
|
|
189
|
-
> Opt-in self-evolution autonomy gate (Epic #643). A DISTINCT top-level block from the `evolve:` Session Config key above — `scripts/lib/config/skill-evolution.mjs` parses it independently of the `## Session Config` boundary, so `claude-md-drift-check` Check 6 (session-config-parity) never flags it.
|
|
190
|
-
> See `docs/session-config-reference.md` § Skill Evolution for the #652 activation history, the C2/H1-guard detail, and the quadruple-gate + MR-only rules.
|
|
191
|
-
|
|
192
|
-
skill-evolution:
|
|
193
|
-
autonomy: autonomous-gated # off | advisory | autonomous-gated — armed (#652)
|
|
194
|
-
evidence-floor: 0.5 # float 0.0..1.0 — min evidence before an autonomous-gated repair acts
|
|
195
|
-
judge: off # opt-in session-end LLM-judge (advisory only); default off
|
|
196
|
-
|
|
197
|
-
## Dispatcher Autonomy <!-- consistency:exempt:parity-exempt-dispatcher-autonomy-block -->
|
|
198
|
-
|
|
199
|
-
> **Parity-exempt section** (Epic #673, #679). Intentionally OUTSIDE `## Session Config` so `claude-md-drift-check` Check-6 (session-config-parity) does not flag repos that have not adopted this feature — `scripts/lib/config/dispatcher-autonomy.mjs` parses it independently.
|
|
200
|
-
> This block's committed presence is itself the never-re-ask marker (session-start Phase 1.1 migration trigger). Effective autonomy resolves host-locally: env > `owner.yaml` > committed > `off`.
|
|
201
|
-
> See `docs/session-config-reference.md` § Dispatcher Autonomy for the #681 capture history and the full precedence chain.
|
|
202
|
-
|
|
203
|
-
dispatcher-autonomy:
|
|
204
|
-
autonomy: off # off | advisory | autonomous-gated — default off (fail-closed)
|
|
205
|
-
confidence-floor: 0.5 # float 0.0..1.0
|