session-orchestrator 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- 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/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/navigator/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- 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 +70 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +57 -4
- package/docs/components.md +12 -12
- package/docs/events-schema.md +11 -8
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +55 -6
- package/docs/session-config-template.md +11 -2
- package/hooks/_lib/hook-import-set.json +27 -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 +53 -0
- package/hooks/on-stop.mjs +3 -1
- package/hooks/post-tool-batch-wave-signal.mjs +3 -1
- package/hooks/pre-bash-memory-propose-audit.mjs +52 -3
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +559 -72
- package/hooks/subagent-telemetry.mjs +96 -19
- package/package.json +3 -3
- package/pi/prompts/navigator.md +12 -0
- 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 +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +49 -53
- package/scripts/backfill-abandoned-sessions.mjs +1 -1
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/emit-session.mjs +296 -2
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -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 +10 -6
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- 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-schema.mjs +13 -0
- package/scripts/lib/config.mjs +71 -10
- package/scripts/lib/events.mjs +21 -2
- package/scripts/lib/fleet-protocol.mjs +270 -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/instruction-budget-guard.mjs +69 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- 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 +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +43 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +62 -11
- 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 +3 -4
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- 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 +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- 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/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +10 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +323 -302
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +72 -46
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/evolve/SKILL.md +2 -2
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- 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 +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- 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 +22 -60
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- 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 +2 -2
- 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/wave-executor/SKILL.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +3 -1
- package/skills/wave-executor/references/wave-loop-review.md +3 -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
|
@@ -3,7 +3,7 @@ name: engineer-lens
|
|
|
3
3
|
schema_version: 1
|
|
4
4
|
version: "1"
|
|
5
5
|
role: "Engineer lens — evaluates feasibility, complexity, failure modes, and long-term maintainability"
|
|
6
|
-
model:
|
|
6
|
+
model: opus
|
|
7
7
|
tier: domain-expert
|
|
8
8
|
evaluation_criteria:
|
|
9
9
|
- "Failure modes are enumerated — what breaks under load, bad input, or partial failure, and how the system responds."
|
|
@@ -3,7 +3,7 @@ name: pm-lens
|
|
|
3
3
|
schema_version: 1
|
|
4
4
|
version: "1"
|
|
5
5
|
role: "Product Manager lens — evaluates user value, outcome-metric clarity, and scope discipline"
|
|
6
|
-
model:
|
|
6
|
+
model: opus
|
|
7
7
|
tier: domain-expert
|
|
8
8
|
evaluation_criteria:
|
|
9
9
|
- "The change is tied to a specific, named user or business problem — not a solution in search of one."
|
|
@@ -236,10 +236,11 @@ If issues were created and a 5+-action Epic wrapper was used (see `SKILL.md` §
|
|
|
236
236
|
|
|
237
237
|
### 3.3 Update Learnings
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
Collect new patterns as learning objects in the same strict schema session-end uses (canonical field reference: `skills/session-end/learning-patterns.md` § 3.5a):
|
|
240
240
|
|
|
241
241
|
```json
|
|
242
242
|
{
|
|
243
|
+
"schema_version": 1,
|
|
243
244
|
"id": "<uuid-v4>",
|
|
244
245
|
"type": "fragile-file|effective-sizing|recurring-issue|scope-guidance|deviation-pattern",
|
|
245
246
|
"subject": "<what the learning is about>",
|
|
@@ -247,11 +248,25 @@ Append new patterns to `.orchestrator/metrics/learnings.jsonl`. Use the same JSO
|
|
|
247
248
|
"evidence": "<supporting data>",
|
|
248
249
|
"confidence": 0.5,
|
|
249
250
|
"source_session": "retro-YYYY-MM-DD",
|
|
250
|
-
"
|
|
251
|
-
"
|
|
251
|
+
"scope": "local",
|
|
252
|
+
"host_class": null,
|
|
253
|
+
"anonymized": false,
|
|
254
|
+
"file_paths": ["<repo-relative path(s) this learning applies to>"],
|
|
255
|
+
"created_at": "<ISO 8601>"
|
|
252
256
|
}
|
|
253
257
|
```
|
|
254
258
|
|
|
259
|
+
`scope` MUST be `local|private|public` — a file path goes in `file_paths`, never in `scope` (GH#69). `schema_version` is the integer `1`. Omit `expires_at`: the CLI derives it per `type` via `deriveExpiresAt()`.
|
|
260
|
+
|
|
261
|
+
Write them **only** through `scripts/apply-session-learnings.mjs` (#1446) — never by hand, never with a shell redirect. Put `{"confidence_updates": [...], "new_learnings": [...]}` in a sidecar under `.orchestrator/tmp/` via the Write tool, preview, then apply. `--decay-rate 0` keeps a retro from applying the per-session passive decay a second time:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
node scripts/apply-session-learnings.mjs --input .orchestrator/tmp/retro-learnings.json --decay-rate 0 --json
|
|
265
|
+
node scripts/apply-session-learnings.mjs --input .orchestrator/tmp/retro-learnings.json --decay-rate 0 --apply --json
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Exit `1` means a record failed validation (the message names its id) and nothing was written.
|
|
269
|
+
|
|
255
270
|
Typical retro learning types:
|
|
256
271
|
- `fragile-file` — hotspots correlating with failures or carryover
|
|
257
272
|
- `effective-sizing` — wave/agent configs producing best completion rates
|
|
@@ -259,7 +274,7 @@ Typical retro learning types:
|
|
|
259
274
|
- `scope-guidance` — session scope calibration insights
|
|
260
275
|
- `deviation-pattern` — systematic plan deviations indicating planning gaps
|
|
261
276
|
|
|
262
|
-
Before writing, check existing entries for matching `type` + `subject`.
|
|
277
|
+
Before writing, check existing entries for matching `type` + `subject`. A match is not a new learning: list its `id` in `confidence_updates` with `"operation": "confirm"` (+0.15, cap 1.0) or `"contradict"` (-0.2) — the CLI applies both. New learnings start at 0.5.
|
|
263
278
|
|
|
264
279
|
### 3.4 Optional Baseline Update
|
|
265
280
|
|
|
@@ -71,9 +71,11 @@ as the session baseline for later comparison.
|
|
|
71
71
|
|
|
72
72
|
**Script output schema (Baseline):**
|
|
73
73
|
```json
|
|
74
|
-
{"variant": "baseline", "typecheck": {"status": "pass|fail|skip"
|
|
74
|
+
{"variant": "baseline", "typecheck": {"status": "pass|fail|skip"}, "test": {"status": "pass|fail|skip"}, "typecheck_output": "string", "test_output": "string"}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
In every variant, each command object that was actually spawned also carries its per-command kill ladder: `timed_out` (boolean), `kill_signals` (string[]) and `survivors` (pid[]). A skipped or stubbed command has none of the three keys (absent, never `false`/`[]`). `scripts/run-quality-gate.mjs` joins them into `orchestrator.quality_gate.{passed,failed}` (#1457). A run whose joined `timed_out` is `true` is recorded as `.failed` in every variant, although Baseline, Incremental and Per-File still exit 0 (#1459, decided 2026-10-01).
|
|
78
|
+
|
|
77
79
|
## Variant 2: Incremental
|
|
78
80
|
|
|
79
81
|
**Used by:** wave-executor (after implementation waves)
|
|
@@ -91,8 +93,8 @@ Metrics output (for consuming skills to capture):
|
|
|
91
93
|
{
|
|
92
94
|
"variant": "incremental",
|
|
93
95
|
"duration_seconds": null,
|
|
94
|
-
"typecheck": "pass|fail|skip",
|
|
95
|
-
"test": "pass|fail|skip",
|
|
96
|
+
"typecheck": {"status": "pass|fail|skip"},
|
|
97
|
+
"test": {"status": "pass|fail|skip"},
|
|
96
98
|
"errors": []
|
|
97
99
|
}
|
|
98
100
|
```
|
|
@@ -182,7 +184,7 @@ node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/run-qual
|
|
|
182
184
|
node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/run-quality-gate.mjs" --variant per-file --config "$CONFIG" --files specific-file.ts
|
|
183
185
|
```
|
|
184
186
|
|
|
185
|
-
The script handles graceful degradation (missing tools → skip), structured JSON output matching the schemas above, and proper exit codes (0=pass, 1=error, 2=gate-failed).
|
|
187
|
+
The script handles graceful degradation (missing tools → skip), structured JSON output matching the schemas above, and proper exit codes (0=pass, 1=error, 2=gate-failed). Only Full Gate exits 2: the three reporting variants exit 0 after a failing or killed check, so their verdict is in the JSON envelope (a kill also turns the event into `.failed`; a plain failing check does not).
|
|
186
188
|
|
|
187
189
|
## Baseline Cache (#258)
|
|
188
190
|
|
|
@@ -28,12 +28,14 @@ Exit codes (`offload --help`, measured 2026-09-02): `0` ok · `1` usage/config
|
|
|
28
28
|
|
|
29
29
|
## 1. Decision rule — offload vs reduce
|
|
30
30
|
|
|
31
|
-
The gate decides, not the coordinator. `applyOffloadDecision()` in `scripts/lib/wave-resource-gate.mjs`
|
|
31
|
+
The gate decides, not the coordinator. `applyOffloadDecision()` in `scripts/lib/wave-resource-gate.mjs` fires when the resource verdict is `reduce` or `coordinator-direct` (and, with `offload-first: true`, on `proceed` as well — see below), and only AFTER the HR-004 heavy-repo cap — a capped wave that offloads still respects the cap. It never probes the network; the coordinator supplies a readiness WITNESS:
|
|
32
32
|
|
|
33
33
|
- `opts.remoteReady` — `{ [alias]: boolean }`, built from the SessionStart banner line `Offload <alias>: ready=yes …`, or
|
|
34
34
|
- `opts.probeFn` — an async `(alias) => boolean` fallback, consulted only for aliases `remoteReady` doesn't answer for (backed by `remoteDoctor()`, i.e. `offload doctor -H <alias> --brief` parsed by `parseDoctorLine()`).
|
|
35
35
|
|
|
36
|
-
With neither supplied, no host counts as ready and the wave stays local — the gate fails toward local, never toward an unverified host. A role in `NEVER_FOREIGN_ROLES` (`scripts/lib/wave-executor/dispatch-common.mjs`: `impl-core`, `security-review`, `migration`, `release`, `secrets`) is never offloaded regardless of readiness.
|
|
36
|
+
With neither supplied, no host counts as ready and the wave stays local — the gate fails toward local, never toward an unverified host. A role in `NEVER_FOREIGN_ROLES` (`scripts/lib/wave-executor/dispatch-common.mjs`: `impl-core`, `security-review`, `migration`, `release`, `secrets`, `incident`, `refactor-crosscut`) is never offloaded regardless of readiness.
|
|
37
|
+
|
|
38
|
+
With `offload-first: true` in Session Config (#1465, default `false`) the placement arm also fires on `proceed`, so an offloadable role goes to a witnessed-ready host without waiting for local pressure. The witness stays mandatory — without `remoteReady`/`probeFn` nothing moves — and a wave that stays local carries a named reason (`offload-first: no ready host for '<role>' — staying local`, or `… no remote-hosts declared — staying local`).
|
|
37
39
|
|
|
38
40
|
## 2. What is declared where
|
|
39
41
|
|
|
@@ -16,7 +16,7 @@ Analyze the completed session to extract reusable learnings for future sessions.
|
|
|
16
16
|
- **Scope guidance**: was the scope too large/small? How many issues fit comfortably in one session?
|
|
17
17
|
- **Deviation patterns**: read the `## Deviations` section from `<state-dir>/STATE.md` — were there plan adaptations? What triggered them? Extract as `deviation-pattern` type if a pattern emerges across sessions (e.g., "scope expansion during Impl-Core is common for this project")
|
|
18
18
|
|
|
19
|
-
**Learning format** (
|
|
19
|
+
**Learning format** (collect each as a learning object; Phase 3.6 writes them through `scripts/apply-session-learnings.mjs`):
|
|
20
20
|
```json
|
|
21
21
|
{
|
|
22
22
|
"schema_version": 1,
|
|
@@ -27,11 +27,19 @@ Analyze the completed session to extract reusable learnings for future sessions.
|
|
|
27
27
|
"evidence": "<what happened this session>",
|
|
28
28
|
"confidence": 0.5,
|
|
29
29
|
"source_session": "<session_id>",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"scope": "local",
|
|
31
|
+
"host_class": null,
|
|
32
|
+
"anonymized": false,
|
|
33
|
+
"file_paths": ["<repo-relative path(s) this learning applies to>"],
|
|
34
|
+
"created_at": "<ISO 8601>"
|
|
32
35
|
}
|
|
33
36
|
```
|
|
34
37
|
|
|
38
|
+
- `scope` MUST be one of `local|private|public`. A file PATH belongs in `file_paths`, **never** in `scope` — a producer that wrote a path into `scope` is the GH#69 root cause, and the strict `validateLearning` in the write CLI rejects it.
|
|
39
|
+
- `schema_version` MUST be the integer `1` (not `"1"`, not `2`).
|
|
40
|
+
- `expires_at` may be omitted: the CLI derives it per `type` via `deriveExpiresAt()` (`LEARNING_TTL_DAYS`), not a flat +30 days.
|
|
41
|
+
- `file_paths` may be omitted when the learning is not about specific files; for `fragile-file`, `file_paths: [subject]`.
|
|
42
|
+
|
|
35
43
|
**Schema versioning** (`schema_version`, introduced 2026-04):
|
|
36
44
|
- All new records MUST carry `schema_version: 1`. `scripts/lib/learnings.mjs` auto-stamps missing values on append/rewrite so callers can omit the field safely.
|
|
37
45
|
- Records without `schema_version` are treated as `schema_version: 0` (pre-versioning legacy). They are read and validated successfully for backward compat, but the reader emits a one-line WARN to stderr flagging the missing tag.
|
|
@@ -39,7 +47,7 @@ Analyze the completed session to extract reusable learnings for future sessions.
|
|
|
39
47
|
|
|
40
48
|
**Confidence updates for existing learnings:**
|
|
41
49
|
Before writing new learnings, read `.orchestrator/metrics/learnings.jsonl` and check for existing entries with the same `type` + `subject` (exact string match on both fields):
|
|
42
|
-
- If this session **confirms** an existing learning: note the update — increment `confidence` by +0.15 (cap at 1.0) and reset `expires_at`
|
|
50
|
+
- If this session **confirms** an existing learning: note the update — increment `confidence` by +0.15 (cap at 1.0) and reset `expires_at` via `deriveExpiresAt(now, type)` (the per-type TTL in `LEARNING_TTL_DAYS`)
|
|
43
51
|
- If this session **contradicts** an existing learning: note the update — decrement `confidence` by -0.2
|
|
44
52
|
- If no existing match: note as a new learning with confidence 0.5
|
|
45
53
|
|
|
@@ -59,11 +67,13 @@ Before writing new learnings, read `.orchestrator/metrics/learnings.jsonl` and c
|
|
|
59
67
|
2. If count exceeds `memory-cleanup-threshold` (default: 5), suggest:
|
|
60
68
|
"You have [N] session memory files. Consider running `/memory-cleanup` to consolidate."
|
|
61
69
|
3. This is a suggestion only — not blocking
|
|
62
|
-
4. **Write learnings** to `.orchestrator/metrics/learnings.jsonl` (if file exists or new learnings were extracted):
|
|
63
|
-
a.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
4. **Write learnings** to `.orchestrator/metrics/learnings.jsonl` (if the file exists or new learnings were extracted) — **only** through `scripts/apply-session-learnings.mjs` (#1446):
|
|
71
|
+
a. Write the Phase 3.5a result as ONE JSON object to a sidecar under `.orchestrator/tmp/` via the Write tool, e.g. `.orchestrator/tmp/session-learnings.json`:
|
|
72
|
+
`{"confidence_updates": [{"id": "<existing id>", "operation": "confirm"}], "new_learnings": [<learning objects per the format above>]}`
|
|
73
|
+
b. Dry run first and show the summary line (`read`, `confirmed`, `contradicted`, `appended`, `decayed`, `pruned`, `consolidated`, `kept`) — nothing is written:
|
|
74
|
+
`node scripts/apply-session-learnings.mjs --input .orchestrator/tmp/session-learnings.json --json`
|
|
75
|
+
c. Apply the same input: `node scripts/apply-session-learnings.mjs --input .orchestrator/tmp/session-learnings.json --apply --json`
|
|
76
|
+
d. The CLI performs everything the old hand-written steps did, in code: confirm (+0.15, cap 1.0, `expires_at` re-derived per type) / contradict (-0.2, floor 0); strict `validateLearning` on every new record; **passive decay (#89)** of every existing learning NOT confirmed or contradicted this session by `learning-decay-rate` (Session Config, default `0.05`; `0.0` opts out; `expires_at` is not reset); prune (`expires_at` < now OR `confidence` <= 0.0); consolidate duplicates (same `type` + `subject`, highest confidence wins); archive every removed record to `learnings-archive.jsonl`; atomic rewrite with a `.bak-<ISO>` snapshot — all via `pruneLearnings()` in `scripts/lib/learnings/expiry-sweep.mjs`.
|
|
67
77
|
|
|
68
78
|
| Sessions since last touch | Confidence (starting 0.5, decay 0.05) | Status |
|
|
69
79
|
|---|---|---|
|
|
@@ -72,7 +82,7 @@ Before writing new learnings, read `.orchestrator/metrics/learnings.jsonl` and c
|
|
|
72
82
|
| 9 | 0.05 | active |
|
|
73
83
|
| 10 | 0.00 | pruned next write |
|
|
74
84
|
|
|
75
|
-
e.
|
|
76
|
-
f.
|
|
77
|
-
g.
|
|
85
|
+
e. Exit codes: `0` applied (or a clean no-op); `1` input or validation error — the message names the record id and the failing field, and **nothing was written**: fix the sidecar and re-run; `2` store/IO error — report it, do not retry by hand.
|
|
86
|
+
f. **Hand-writing the store is FORBIDDEN** — no shell rewrite, no shell append, no `jq … | tee`. Those bypass validation, the `.bak` snapshot and the archive (see `skills/evolve/SKILL.md` § Critical Rules). The blocking validator `scripts/lib/validate/check-learnings-shell-writes.mjs` fails `validate-plugin` on prose that instructs one.
|
|
87
|
+
g. Proof it ran: `--apply` appends one `orchestrator.learnings.session_write_applied` record to `.orchestrator/metrics/events.jsonl` (`appended`, `confirmed`, `contradicted`, `decayed`, `pruned`). No record = no write.
|
|
78
88
|
h. If no existing file and no new learnings: skip
|
|
@@ -59,7 +59,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
59
59
|
# corruption.
|
|
60
60
|
METRICS_ENTRY=$(node --input-type=module -e "
|
|
61
61
|
const entry = {
|
|
62
|
-
session_id: '<
|
|
62
|
+
session_id: '<STATE.md frontmatter session, verbatim>',
|
|
63
63
|
session_type: '<type>',
|
|
64
64
|
platform: '<claude|codex>',
|
|
65
65
|
started_at: '<ISO 8601 from STATE.md frontmatter started_at — already canonical>',
|
|
@@ -69,7 +69,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
69
69
|
total_agents: <N>,
|
|
70
70
|
total_files_changed: <N>,
|
|
71
71
|
agent_summary: {complete: <N>, partial: <N>, failed: <N>, spiral: <N>},
|
|
72
|
-
waves: [/* {wave, role, agent_count, files_changed, quality, agent_count_planned?, agent_count_started?, agent_count_completed
|
|
72
|
+
waves: [/* {wave, role, agent_count, files_changed, quality, agent_count_planned?, agent_count_started?, agent_count_completed?} */],
|
|
73
73
|
// effectiveness is CONSTRUCTED EXPLICITLY (#773) — NOT left as an optional
|
|
74
74
|
// field for the coordinator to remember. Leaving it optional is exactly how
|
|
75
75
|
// the carryover=0 blind spot recurred (41/41 records read carryover:0).
|
|
@@ -101,7 +101,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
101
101
|
**Canonical JSONL schema** (for field reference — populated by the snippet above):
|
|
102
102
|
```json
|
|
103
103
|
{
|
|
104
|
-
"session_id": "<
|
|
104
|
+
"session_id": "<STATE.md frontmatter session, verbatim>",
|
|
105
105
|
"session_type": "<type>",
|
|
106
106
|
"platform": "<claude|codex>",
|
|
107
107
|
"started_at": "<canonical ISO 8601 from STATE.md>",
|
|
@@ -112,7 +112,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
112
112
|
"total_files_changed": N,
|
|
113
113
|
"agent_summary": {"complete": N, "partial": N, "failed": N, "spiral": N},
|
|
114
114
|
"waves": [
|
|
115
|
-
{"wave": 1, "role": "Discovery", "agent_count": N, "files_changed": N, "quality": "pass|fail|skip", "agent_count_planned": N, "agent_count_started": N, "agent_count_completed": N
|
|
115
|
+
{"wave": 1, "role": "Discovery", "agent_count": N, "files_changed": N, "quality": "pass|fail|skip", "agent_count_planned": N, "agent_count_started": N, "agent_count_completed": N},
|
|
116
116
|
...
|
|
117
117
|
],
|
|
118
118
|
"discovery_stats": {
|
|
@@ -164,7 +164,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
164
164
|
|
|
165
165
|
> **ISO-8601 canonical format (#540):** `started_at`, `completed_at`, and `lease_acquired_at` MUST match the regex `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/`. The validating writer (`scripts/emit-session.mjs`) rejects any non-canonical form. Use `new Date().toISOString()` (Node-native, always canonical) — never hand-edit fractional digits or timezone suffixes.
|
|
166
166
|
|
|
167
|
-
> The `session_id`
|
|
167
|
+
> The `session_id` is the STATE.md frontmatter `session` value, copied verbatim — the semantic label session-start minted, which `.orchestrator/current-session.json` also carries as `semantic_session_id`. Never construct one (the former `<branch>-<YYYY-MM-DD>-<HHmm>` recipe is wrong): `scripts/emit-session.mjs` adopts `raw_session_id`, the token rollup and the events-derived `session_start_ref` only when the record's `session_id` equals the marker's `semantic_session_id`; any other label is WARNed and those fields are omitted.
|
|
168
168
|
|
|
169
169
|
> **Conditional fields:**
|
|
170
170
|
> - `discovery_stats`: populated ONLY when `discovery-on-close: true` in Session Config AND Phase 1.5 executed successfully. Source: the stats object returned by the discovery skill (see discovery skill Phase 4.6 for schema). When discovery runs in **embedded mode** (Phases 0-4 only), `user_dismissed`, `issues_created`, and `actioned` per category will always be `0` — embedded mode does not perform user triage (Phase 5) or issue creation (Phase 6).
|
|
@@ -172,7 +172,6 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
172
172
|
> - `effectiveness`: ALWAYS populated from Phase 1 plan verification results, and CONSTRUCTED EXPLICITLY in the METRICS_ENTRY snippet (#773) — never deferred to a "remember to add" optional step (that omission is how `carryover: 0` slipped past 41 records). `completion_rate` = `completed / planned_issues` (0.0-1.0, where 0.0 means nothing was completed). **`carryover` counting rule (#773):** `carryover` is the **length of the Phase 1.65 gate carry-list** — `autoCarry` ∪ the middle-band `ask` items the operator LEFT SELECTED ∪ the answered-question `impliesWork: true` candidates — NOT the raw Phase 1.2+1.3 candidate count. On the fail-open skip (gate disabled / headless / AUQ unavailable), EVERY candidate carries, so `carryover` = the full candidate-list length. Count the gate's OUTPUT (what reaches Phase 5 Step 3 filing), not its INPUT.
|
|
173
173
|
> - `effectiveness.override_ratio` (#730/H5): OPTIONAL nested field = `overridden_findings / max(total_findings_surfaced, 1)` (float 0.0-1.0). Populate ONLY when Phase 2.6 (Broken-Window Budget) ran this session (`broken-window-budget.enabled: true`). OMIT (do NOT write null/0) otherwise — **absent = "not measured"**, `0.0` = "measured, nothing overridden". `overridden_findings` = the summed `count` of the `orchestrator.finding.overridden` events emitted this session; `total_findings_surfaced` = every MED/LOW+ finding surfaced across Phase 1.8 + wave reviewers.
|
|
174
174
|
> - `waves[].agent_count_planned` / `waves[].agent_count_started` / `waves[].agent_count_completed` (#724/#1115): OPTIONAL per-wave fields, sourced from `wave-loop.md` § Capture wave metrics step 7 — mirror its definitions exactly, do not re-derive them here. `agent_count_planned` = agents named in the session plan for this wave. `agent_count_started` = distinct agents whose `agent-<id>.meta.json` sidecar is present, after any silent-drop re-dispatch — NOT "produced a tool-result" (under background dispatch the launch ack is a result and would count an agent that never ran). `agent_count_completed` = distinct agents whose task-notification (`<status>completed</status>`) arrived. Omit each field when the wave did not measure it — **absent = "not measured"**, never zero-fill; `0` would read as "measured, no agent started", which is the opposite of an unmeasured wave. The two gaps carry the diagnosis: `agent_count_planned > agent_count_started` after re-dispatch is a persistent silent drop, `agent_count_started > agent_count_completed` at wave end is an agent that started and never returned. Both are also logged to STATE.md `## Deviations` by wave-loop.md, so a record and a deviation entry should agree.
|
|
175
|
-
> - `waves[].planned_files_count` / `waves[].over_delivery_ratio` (#730/H4): OPTIONAL per-wave fields, populated from STATE.md Wave History headers of the form `(planned <P> files → actual <A>, over-delivery <R>)` (written by wave-executor §3a since #730/H4); omit when absent (pre-#730 sessions / grounding-check: false).
|
|
176
175
|
> - `waves[].suite_passed` / `waves[].suite_failed` / `waves[].suite_platform` (#944): OPTIONAL per-wave fields. Omit all three when absent — absent = "not measured", `suite_failed: 0` = "measured, zero failures".
|
|
177
176
|
> **`suite_passed` / `suite_failed`: read the event FIRST, the STATE.md header only as fallback (#966 step 3).** Since #954/#967 the between-waves gate wrapper `scripts/run-quality-gate.mjs` emits `orchestrator.quality_gate.{passed,failed}` with a machine-measured `counts: {passed, failed, total}` AND the `wave_number` it resolved from the `wave-scope.json` sidecar, so per-wave attribution needs no wall-clock window join. Payload fields are flat at the record's top level; for each wave `N` of this session:
|
|
178
177
|
>
|
|
@@ -169,7 +169,7 @@ Then:
|
|
|
169
169
|
|
|
170
170
|
Read `skills/session-end/session-metrics-write.md` for JSONL append, vault-mirror invocation, and behavior matrix.
|
|
171
171
|
|
|
172
|
-
> **Token Rollup (#644):**
|
|
172
|
+
> **Token Rollup (#644, #1436):** When the incoming record has no `total_tokens`, `scripts/emit-session.mjs` resolves the session's own raw UUID as described in step 1a of session-metrics-write.md and calls `rollupSessionTokens({ parentSessionId: ownUuid })`, joining `parent_session_id` on that UUID rather than the semantic session ID. It fills only absent fields from the rollup whitelist; diagnostic fields `match_status` and `ledger_records` stay out of the record. Missing or unreadable ledgers, or no records for the own UUID, omit token fields with a WARN stating the reason, never zero cost; null totals are also omitted. The rollup remains non-blocking.
|
|
173
173
|
|
|
174
174
|
### 3.7a Compute and Write Recommendations (Epic #271 Phase A)
|
|
175
175
|
|
|
@@ -208,7 +208,7 @@ const boardResult = await mirrorBoard({
|
|
|
208
208
|
repoRoot: process.cwd(),
|
|
209
209
|
explicitStatus: 'closed', // force THIS repo's row to `closed`
|
|
210
210
|
});
|
|
211
|
-
// boardResult.action ∈ { 'written', 'skipped-noop', 'skipped-handwritten', 'skipped-vault-disabled', 'dry-run' }
|
|
211
|
+
// boardResult.action ∈ { 'written', 'skipped-noop', 'skipped-handwritten', 'skipped-vault-disabled', 'skipped-vault-not-canonical', 'dry-run' }
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
> **Note (single-repo close path):** with `repos` omitted, `mirrorBoard` builds the repo descriptor itself as `[{ repoRoot, status: explicitStatus }]` — this is the supported single-repo shape, so `explicitStatus: 'closed'` lands on THIS repo's row. (When a caller DOES pass `repos`, each element must be a `{ repoRoot, repoName?, status? }` object, NOT a bare path string — bare strings are silently skipped by `collectRows`.) The board at `<vault-dir>/01-projects/_active-sessions.md` is generator-owned: `mirrorBoard` refuses to touch any file lacking the `session-orchestrator-active-sessions@1` marker, hard-refuses `_overview.md`, and is idempotent (a re-run after the row is already `closed` returns `skipped-noop`). Rows for repos NOT in this update are preserved verbatim by the idempotent merge.
|
|
@@ -34,6 +34,8 @@ A worktree is clean iff ALL three conditions hold:
|
|
|
34
34
|
> **Authoritative impl:** `scripts/lib/session-end/worktree-cleanup.mjs` — `isWorktreeClean(wtPath, opts)`. Import and call; do NOT re-implement from this doc.
|
|
35
35
|
>
|
|
36
36
|
> Algorithm: run `git status --porcelain`; filter blank lines, then discount EXACTLY the one untracked line the promotion marker itself produces (`?? .orchestrator/promoted-from.json` — in a repo where `.orchestrator/` is only partly gitignored, or on a worktree whose branch predates the ignore line, the marker `enterWorktree()` writes would otherwise make every promoted worktree read "dirty"; a modified/staged/renamed/conflicted marker still counts as dirty). If any lines remain → dirty (`false`). Else run `git status --short --branch`; if it matches `/\bahead\b/` → unpushed (`false`). Otherwise `true`. On ANY git error → `false` (conservative PSA-003 default: never auto-remove a worktree we could not verify). Git invocation is via the injection-safe `opts.execFileFn` (default `execFileSync` with an args array — #577 HARDEN-001).
|
|
37
|
+
>
|
|
38
|
+
> `isWorktreeClean()` discounts exactly the marker's own untracked line, never operator work.
|
|
37
39
|
|
|
38
40
|
### Clean path: auto-remove + WARN (PRD §3 P3 Gherkin row 2)
|
|
39
41
|
|
|
@@ -57,6 +57,6 @@ Present to the user:
|
|
|
57
57
|
- Notes: [any context for next session]
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
> **Test-delta anchor:** the `Δ this session` figures are aggregated from the `test_delta` field of this session's agent reports (added / removed / consolidated test cases); the `tests:src LOC ratio` is `
|
|
60
|
+
> **Test-delta anchor:** the `Δ this session` figures are aggregated from the `test_delta` field of this session's agent reports (added / removed / consolidated test cases); the `tests:src LOC ratio` is the output of `node scripts/lib/tests-src-ratio.mjs --json` — the single measurement per `.claude/rules/test-value.md` § TV-003; never re-derive it by hand. When the envelope carries `reason: "no-tests-found"` (#1451), report the ratio as not measurable, not as within the corridor. A bare `passing/total` count is not a progress signal — a growing suite reads as progress even when the growth is redundant, which is exactly why the delta and the ratio are reported alongside it. When the ratio exceeds the advisory ceiling, recommend that the NEXT session's Quality wave run as a **consolidation wave**: no new test lands without a redundant one being removed in the same change. This is advisory only — it never blocks the close.
|
|
61
61
|
|
|
62
62
|
> **Documentation Coverage anchor:** If Phase 3.2 ran and produced task verification results (i.e. `docs-orchestrator.enabled: true` and `docs-tasks` were found), the results appear here as a `### Documentation Coverage (docs-orchestrator)` subsection emitted by Phase 3.2 Step 7. The content is written dynamically — it is not pre-populated in this template. When `docs-orchestrator.enabled` is `false` or `docs-tasks` were absent, this subsection is omitted entirely.
|
|
@@ -29,68 +29,15 @@
|
|
|
29
29
|
|
|
30
30
|
> **#701.2 DOC NOTE — `completed_at >= started_at` guard:** This invariant is enforced mechanically by `scripts/emit-session.mjs`. The writer applies `clampTimestampsMonotonic()` (from `scripts/lib/session-schema/timestamps.mjs`) before `validateSession()`, clamping any inversion of `completed_at < started_at` to `started_at` and recording forensics in `_clamped: true` / `_original_completed_at`. Previously-inverted entries (e.g. `main-2026-06-21-session-4`) are already corrected. **No per-session coordinator action is needed** — the writer enforces the invariant at write time. Do not add defensive clamping logic here; the canonical guard lives in `emit-session.mjs`.
|
|
31
31
|
|
|
32
|
-
1a. **Token Rollup (#644,
|
|
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
|
-
**
|
|
35
|
-
|
|
36
|
-
**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`: `null` means "at least one agent ran on a model the price table does not know", never "$0".
|
|
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`) — 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, and a session spanning the 2026-09-09 schema boundary is priced over its v2 records only (its v1 records are neither priced nor counted as candidates — 4 distinct fleet sessions carry such a cost, 2026-10-01). 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.
|
|
37
35
|
|
|
38
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.
|
|
39
37
|
|
|
40
38
|
**Schema boundary (#1244, 2026-09-09):** from `schema_version: 2` a subagent record's `token_input` is BILLABLE PROMPT VOLUME (uncached + cache_read + cache_creation); v1 records held raw uncached input only and are therefore EXCLUDED from every total and reported as `legacy_v1_records`. Sessions spanning the boundary are a second series break — do not trend across it.
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```js
|
|
45
|
-
// Available from scripts/lib/session-token-rollup.mjs
|
|
46
|
-
import { rollupSessionTokens } from '../../scripts/lib/session-token-rollup.mjs';
|
|
47
|
-
|
|
48
|
-
const rollup = rollupSessionTokens({ parentSessionId: SESSION_UUID });
|
|
49
|
-
// rollup: { total_token_input, total_token_output, subagents_with_tokens, matched_records,
|
|
50
|
-
// total_token_input_uncached, total_token_cache_read, total_token_cache_creation,
|
|
51
|
-
// total_cost_usd, cost_records_priced, cost_records_total, legacy_v1_records,
|
|
52
|
-
// _token_schema }
|
|
53
|
-
// Merge into the record — every field below is optional in the session schema (additive).
|
|
54
|
-
metricsEntry.total_token_input = rollup.total_token_input; // number | null
|
|
55
|
-
metricsEntry.total_token_output = rollup.total_token_output; // number | null
|
|
56
|
-
metricsEntry.subagents_with_tokens = rollup.subagents_with_tokens; // number (0 when no coverage)
|
|
57
|
-
metricsEntry.total_token_input_uncached = rollup.total_token_input_uncached; // number | null
|
|
58
|
-
metricsEntry.total_token_cache_read = rollup.total_token_cache_read; // number | null
|
|
59
|
-
metricsEntry.total_token_cache_creation = rollup.total_token_cache_creation; // number | null
|
|
60
|
-
metricsEntry.total_cost_usd = rollup.total_cost_usd; // number | null (null = unknown model)
|
|
61
|
-
metricsEntry._token_schema = rollup._token_schema; // 2
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Report `cost_records_priced / cost_records_total` and `legacy_v1_records` in the session summary when either is non-zero — an unpriced or excluded remainder is what makes a cost figure honest.
|
|
65
|
-
|
|
66
|
-
Or, from a bash context, call the rollup via a helper node invocation and `jq`-merge the fields into `$METRICS_ENTRY` before step 2:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
ROLLUP_JSON=$(node -e "
|
|
70
|
-
import('$(dirname "$0")/../scripts/lib/session-token-rollup.mjs').then(m => {
|
|
71
|
-
const r = m.rollupSessionTokens({ parentSessionId: process.env.SESSION_UUID });
|
|
72
|
-
process.stdout.write(JSON.stringify(r));
|
|
73
|
-
});
|
|
74
|
-
" 2>/dev/null) || ROLLUP_JSON='{}'
|
|
75
|
-
|
|
76
|
-
# Merge token fields into METRICS_ENTRY (null for fields absent from rollup)
|
|
77
|
-
METRICS_ENTRY=$(printf '%s' "$METRICS_ENTRY" | jq \
|
|
78
|
-
--argjson r "${ROLLUP_JSON:-{}}" \
|
|
79
|
-
'. + {
|
|
80
|
-
total_token_input: ($r.total_token_input // null),
|
|
81
|
-
total_token_output: ($r.total_token_output // null),
|
|
82
|
-
subagents_with_tokens: ($r.subagents_with_tokens // 0),
|
|
83
|
-
total_token_input_uncached: ($r.total_token_input_uncached // null),
|
|
84
|
-
total_token_cache_read: ($r.total_token_cache_read // null),
|
|
85
|
-
total_token_cache_creation: ($r.total_token_cache_creation // null),
|
|
86
|
-
total_cost_usd: ($r.total_cost_usd // null),
|
|
87
|
-
_token_schema: ($r._token_schema // 2)
|
|
88
|
-
}')
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**If the rollup call fails** (e.g., `subagents.jsonl` absent, parse error), set the numeric totals to `null` / `0` and continue — the rollup is non-blocking. A session without token data still writes cleanly.
|
|
92
|
-
|
|
93
|
-
2. Append the prepared JSONL entry (from Phase 1.7, now including token fields from step 1a) via the validating writer `scripts/emit-session.mjs` (issue #249):
|
|
40
|
+
2. Append the prepared JSONL entry (from Phase 1.7; the writer itself adds the token fields — step 1a) via the validating writer `scripts/emit-session.mjs` (issue #249):
|
|
94
41
|
```bash
|
|
95
42
|
printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs" --file .orchestrator/metrics/sessions.jsonl
|
|
96
43
|
EMIT_EXIT=$?
|
|
@@ -135,11 +82,23 @@
|
|
|
135
82
|
```bash
|
|
136
83
|
VM_ENABLED=$(echo "$CONFIG" | jq -r '."vault-integration".enabled // false')
|
|
137
84
|
VM_MODE=$(echo "$CONFIG" | jq -r '."vault-integration".mode // "warn"')
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
85
|
+
VM_HOST_OVERRIDE=$(echo "$CONFIG" | jq -r '."vault-integration"."host-override" // empty')
|
|
86
|
+
|
|
87
|
+
if [[ -n "$VM_HOST_OVERRIDE" && ( "$VM_ENABLED" != "true" || "$VM_MODE" == "off" ) ]]; then
|
|
88
|
+
# SO#1448: this host switched the mirror off on purpose (env
|
|
89
|
+
# SO_VAULT_INTEGRATION or owner.yaml `vault-integration:`). Say so, instead of
|
|
90
|
+
# running into vault-mirror's exit 2 (missing-vault-dir) and a strict block.
|
|
91
|
+
echo "vault-mirror: bewusst aus auf diesem Host ($VM_HOST_OVERRIDE)"
|
|
92
|
+
elif [[ "$VM_ENABLED" == "true" && "$VM_MODE" != "off" ]]; then
|
|
93
|
+
# Resolve vault directory: config field (host-resolved, see vault-dir-source)
|
|
94
|
+
# takes precedence; $VAULT_DIR is only the bash fallback when it is empty.
|
|
141
95
|
VM_DIR=$(echo "$CONFIG" | jq -r '."vault-integration"."vault-dir" // empty')
|
|
142
|
-
|
|
96
|
+
VM_DIR_SOURCE=$(echo "$CONFIG" | jq -r '."vault-integration"."vault-dir-source" // "committed"')
|
|
97
|
+
if [[ -z "$VM_DIR" && -n "${VAULT_DIR:-}" ]]; then
|
|
98
|
+
VM_DIR="$VAULT_DIR"
|
|
99
|
+
VM_DIR_SOURCE="env VAULT_DIR"
|
|
100
|
+
fi
|
|
101
|
+
echo "vault-dir=$VM_DIR (Quelle: $VM_DIR_SOURCE)"
|
|
143
102
|
|
|
144
103
|
# Quality-gate thresholds (PRD F1.2). Defaults match
|
|
145
104
|
# scripts/vault-mirror.mjs (400 chars / 0.5 confidence). The nested key
|
|
@@ -242,9 +201,12 @@
|
|
|
242
201
|
|
|
243
202
|
| `enabled` | `mode` | Result |
|
|
244
203
|
|-----------|---------|--------|
|
|
204
|
+
| `false` via host override (`host-override` set) | any | Skip; print `vault-mirror: bewusst aus auf diesem Host (<quelle>)` — never blocks |
|
|
245
205
|
| `false` or missing | any | Skip entirely — no-op, no output |
|
|
246
206
|
| `true` | `off` | Skip entirely — no-op, no output |
|
|
247
207
|
| `true` | `warn` | Run mirror; on failure surface a warning but do NOT block close |
|
|
248
208
|
| `true` | `strict` | Run mirror; on failure block session close with an error message |
|
|
249
209
|
|
|
210
|
+
> **Host-local overrides.** `enabled`/`mode` above are the values AFTER the host switch: env `SO_VAULT_INTEGRATION=off|warn|strict` > owner.yaml `vault-integration: { enabled: false | mode: … }` > committed. The host may only lower the committed level, never raise it; `."vault-integration"."host-override"` names the tier that lowered it (`env:SO_VAULT_INTEGRATION`, `owner.yaml`) and is `null` otherwise. `."vault-integration"."vault-dir-source"` (`env`, `match`, `owner`, `committed`) says where the vault path came from. See `docs/session-config-reference.md` § Vault Integration.
|
|
211
|
+
|
|
250
212
|
> **Hand-written note protection:** `vault-mirror.mjs` checks for a `_generator: session-orchestrator-vault-mirror@1` marker before overwriting any existing file. When it skips an existing hand-written note it emits a JSON line `{"action":"skipped-handwritten","path":"<path>","kind":"<kind>","id":"<id>"}` — the step above surfaces this output so the user can see the result. Action names: `created`, `updated`, `skipped-noop`, `skipped-handwritten`, `skipped-collision-resolved`, `skipped-invalid` (entry failed required-field validation, or the mapper crashed rendering an otherwise-parseable record — the latter case carries `reason: "mapper-crash"`, #718), `skipped-quality-low` (entry failed quality gate — PRD F1.2; line carries a `reason` field).
|
|
@@ -140,7 +140,6 @@ Extract these fields for planning:
|
|
|
140
140
|
- **Effective sizing**: use historical sizing data to inform Step 3 complexity scoring
|
|
141
141
|
- **Recurring issues**: pre-populate risk mitigation with known issue patterns
|
|
142
142
|
- **Scope guidance**: validate planned scope against historical session capacity
|
|
143
|
-
- **Over-delivery sizing (#730/H4)**: read the over_delivery_ratio of recent same-session_type waves — from `effective-sizing` learnings if present, else directly from the last ~5 sessions.jsonl records' `waves[].over_delivery_ratio` (skip records lacking the field — pre-#730; also skip Discovery/Finalization waves, whose planned set is empty by design). If the median ratio R > 1.3, the fleet historically under-briefs file scope: inflate the Step 3 "Files to change" estimate by R before scoring the complexity tier; note it under Project Intelligence Applied.
|
|
144
143
|
|
|
145
144
|
For each agreed task/issue:
|
|
146
145
|
1. Read the VCS issue description and acceptance criteria
|
|
@@ -319,8 +318,6 @@ Score the session scope to determine optimal agent counts per wave. Skip only fo
|
|
|
319
318
|
|
|
320
319
|
> **Cross-module scope** counts top-level source directories (e.g., `src/auth/`, `src/api/`, `lib/utils/`). Nested subdirectories under the same parent count as one directory. Non-source directories (docs, config, scripts) don't count unless they contain modified production code.
|
|
321
320
|
|
|
322
|
-
> **Over-delivery adjustment (#730/H4):** when Step 0.5 surfaced a historical over-delivery ratio R > 1.3 for this session_type, score the "Files to change" row against ceil(briefed_files × R) rather than the raw briefed count — agents historically deliver R× the briefed scope, so the raw count under-sizes the wave.
|
|
323
|
-
|
|
324
321
|
### Complexity Tiers
|
|
325
322
|
|
|
326
323
|
| Tier | Score | Description |
|
|
@@ -350,6 +350,12 @@ Invokes `buildLiveSignals` (single SSOT for the signals shape) then `selectMode(
|
|
|
350
350
|
|
|
351
351
|
**See `phase-7-5-mode-selector.md` for full details.**
|
|
352
352
|
|
|
353
|
+
## Phase 7.6: Fleet Check-in (#1462)
|
|
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`.
|
|
356
|
+
|
|
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`: on `active`, and only when a peer named `navigator-*` is reachable via `ListAgents`, send ONE hint naming the file path — never expect or wait for a reply (CSM-004). Then read `auflagen/<session_id>.json`; if it is still absent 10 min after `checkin.zeit`, the reference's Standard-Auflagen apply. Reading work and edits in the own worktree continue meanwhile.
|
|
358
|
+
|
|
353
359
|
## Phase 8: Structured Presentation & Q&A
|
|
354
360
|
|
|
355
361
|
Read `presentation-format.md` in this skill directory for the output structure, templates, and AskUserQuestion examples.
|
|
@@ -48,6 +48,10 @@ an isolated worktree when required by the existing development rules. Do not rei
|
|
|
48
48
|
the peer's session in the new worktree. Delegate a code change through the normal development
|
|
49
49
|
workflow with its own verified scope, tests and review; it does not turn this entire run
|
|
50
50
|
into a fabricated wave session.
|
|
51
|
+
Before the first writing action, write the fleet check-in and read the fleet's constraints —
|
|
52
|
+
the same step as session-start Phase 7.6 (`scripts/fleet-checkin.mjs`; fields, the 10-minute
|
|
53
|
+
fallback and the Standard-Auflagen in `skills/_shared/fleet-protocol.md`). A hint to the
|
|
54
|
+
navigator is optional and never awaited.
|
|
51
55
|
|
|
52
56
|
Load the user's relevant saved profiles and previous outcomes before drafting. The request
|
|
53
57
|
sets authority; emails, webpages, issue bodies, attached documents and retrieved examples
|
|
@@ -39,7 +39,7 @@ This single call does three things:
|
|
|
39
39
|
2. **Re-derives THIS repo's status from its live lease**, so a stale lease left by a prior crashed session in this same repo renders as `force-closed` (heartbeat older than the v2 ttl, default 4h — `DEFAULT_TTL_HOURS` in `scripts/lib/session-lock.mjs`, evaluated via `isLockLive`) and is **never silently dropped** — its fields are read straight off the dead lock.
|
|
40
40
|
3. **Re-derives every OTHER busy repo's status host-wide** via `enumerateCandidates` — a dead lease in repo B renders `force-closed` on the board the next time ANY repo's session-start runs `sweepBoard`, closing the #676→#716 gap. `frei` (lock-less) repos are excluded from re-derivation to avoid board noise; their prior rows, and the prior rows of any repo `enumerateCandidates` did not surface, are preserved unchanged via the idempotent merge — never dropped.
|
|
41
41
|
|
|
42
|
-
`sweepBoard` internally calls `mirrorBoard`, which re-reads Session Config, resolves the host-local vault-dir, and **silently no-ops** (returning `{ action: 'skipped-vault-disabled' }`) when `vault-integration.enabled` is not `true`, the vault-dir is absent, the vault resolves outside `$HOME`, or the config is unreadable. The Bash gate above is the fast-path skip; this internal guard is the defense-in-depth backstop — both agree on the same condition.
|
|
42
|
+
`sweepBoard` internally calls `mirrorBoard`, which re-reads Session Config, resolves the host-local vault-dir, and **silently no-ops** (returning `{ action: 'skipped-vault-disabled' }`) when `vault-integration.enabled` is not `true`, the vault-dir is absent, the vault resolves outside `$HOME`, or the config is unreadable — and refuses with ONE stderr line (returning `{ action: 'skipped-vault-not-canonical' }`) when the vault's git origin does not end in a canonical Meta-Vault suffix (`checkCanonicalVault`, #1450; the same guard `vault-mirror.mjs` applies). The Bash gate above is the fast-path skip; this internal guard is the defense-in-depth backstop — both agree on the same condition.
|
|
43
43
|
|
|
44
44
|
### Safety invariants
|
|
45
45
|
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
|
|
145
145
|
Non-blocking. Cross-reference: `scripts/lib/instruction-budget-guard.mjs` (sibling directive-COUNT probe over `.claude/rules/*.md` — this probe measures raw-file PROPERTIES of CLAUDE.md/AGENTS.md itself, a distinct dimension) and issue #878 (FA2b).
|
|
146
146
|
|
|
147
|
-
The tests:src-ratio probe (`scripts/lib/tests-src-ratio.mjs`, `checkTestsSrcRatio`) runs in the Phase 4 banner family as `id: 'tests-src-ratio'` in `scripts/lib/session-start-probes.mjs` `PROBES`. It returns `null` (silent no-op) when the ratio is inside the TV-003 corridor, when `repoRoot` is missing, or
|
|
147
|
+
The tests:src-ratio probe (`scripts/lib/tests-src-ratio.mjs`, `checkTestsSrcRatio`) runs in the Phase 4 banner family as `id: 'tests-src-ratio'` in `scripts/lib/session-start-probes.mjs` `PROBES`. It returns `null` (silent no-op) when the ratio is inside the TV-003 corridor, when the ratio is undefined because the repo has no src code, when `repoRoot` is missing or does not exist, or when the measurement throws. It returns a warn in two cases: the ratio exceeds the ceiling, or the ratio is not measurable — 0 test files counted while test-like files are tracked (`reason: 'no-tests-found'`, `ratio: null`, message "tests:src ratio nicht messbar", #1451). The result shape is `{ severity: 'warn', message, ratio: number|null, ceiling, reason? }` — `reason` is set only on the not-measurable warn. Render `result.message` alongside the other banners.
|
|
148
148
|
|
|
149
149
|
**Why this is a banner and not a gate.** `.claude/rules/test-value.md` § TV-003 names the ceiling as the trigger for a CONSOLIDATION wave — the rule's operative instrument. Before this wiring the trigger fired into a void: the only references were two rule files asking a human to run the command, so the condition could be true for months with nothing saying so (it was true, at 1.70, on the commit that introduced the script). The rule's refusal of a bidirectional ratchet stands unchanged — this surfaces the trigger, it does not block on it. `--check` remains deliberately unwired from CI.
|
|
150
150
|
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
- **Findings present**: render the message verbatim. It already leads with the count and the mechanically-fixable subset, then names the top 3 and summarises the remainder — this shape was chosen because a flat list stops being read past roughly 25 findings.
|
|
157
157
|
- **Healthy repo**: silent (no banner).
|
|
158
158
|
|
|
159
|
-
Use `result.mechanical` when proposing session scope: findings with `fixable: true` (aged artifacts, ignored ballast, a missing CI audit step) are safe batch work, while the rest (release cadence, absent CI, undocumented configuration) need an operator decision and belong in the Q&A, not in an auto-fix batch.
|
|
159
|
+
Use `result.mechanical` when proposing session scope: findings with `fixable: true` (aged artifacts, ignored ballast, a missing CI audit step — an audit that cannot be verified because `.gitlab-ci.yml` pulls jobs via `include:` is reported `fixable: false` instead) are safe batch work, while the rest (release cadence, absent CI, undocumented configuration) need an operator decision and belong in the Q&A, not in an auto-fix batch.
|
|
160
160
|
|
|
161
161
|
The checks are: release-tag/CHANGELOG distance from HEAD, ignored working-tree ballast plus files that are neither tracked nor ignored, aged `.orchestrator/` artifacts, CI pipeline presence and dependency-audit coverage, and `.env.example` presence. Two high-yield checks are intentionally NOT here: **docs-drift** is already covered by `claude-md-drift-check` (it only runs at session-END, so the gap is scheduling, not implementation), and **env completeness** is omitted because diffing `process.env` reads against `.env.example` produced a 100% false-positive rate against code that reads configuration through a central schema module.
|
|
162
162
|
|