session-orchestrator 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +28 -13
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +9 -31
- package/CHANGELOG.md +162 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +154 -4
- package/docs/components.md +11 -11
- package/docs/events-schema.md +25 -19
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/persona-panel.md +5 -4
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +40 -16
- package/docs/session-config-reference.md +61 -12
- package/docs/session-config-template.md +11 -2
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +42 -29
- package/hooks/_lib/lock-bootstrap.mjs +118 -26
- package/hooks/config-protection.mjs +1 -1
- package/hooks/enforce-commands.mjs +28 -13
- package/hooks/enforce-scope.mjs +201 -23
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/on-session-start.mjs +57 -2
- package/hooks/on-stop.mjs +70 -168
- package/hooks/post-bash-write-verify.mjs +98 -17
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +32 -152
- package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +1143 -279
- package/hooks/subagent-telemetry.mjs +96 -19
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +6 -1
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +5 -1
- package/rules/opt-in-stack/backend-data.md +9 -1
- package/rules/opt-in-stack/backend.md +7 -1
- package/rules/opt-in-stack/frontend.md +6 -1
- package/rules/opt-in-stack/security-web.md +7 -1
- package/rules/opt-in-stack/swift.md +5 -1
- package/scripts/apply-session-learnings.mjs +513 -0
- package/scripts/archive-closed-prds.mjs +40 -12
- package/scripts/autopilot.mjs +57 -54
- package/scripts/backfill-abandoned-sessions.mjs +81 -6
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +47 -6
- package/scripts/emit-session.mjs +298 -2
- package/scripts/export-hw-learnings.mjs +40 -10
- package/scripts/fleet-checkin.mjs +170 -0
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +6 -1
- package/scripts/lib/autopilot/loop.mjs +32 -13
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +7 -6
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +75 -10
- package/scripts/lib/eval/engine.mjs +13 -0
- package/scripts/lib/events-retention-banner.mjs +393 -0
- package/scripts/lib/events-rotation.mjs +21 -6
- package/scripts/lib/events.mjs +72 -5
- package/scripts/lib/fleet-protocol.mjs +288 -0
- package/scripts/lib/gates/gate-baseline.mjs +13 -3
- package/scripts/lib/gates/gate-full.mjs +7 -2
- package/scripts/lib/gates/gate-helpers.mjs +93 -2
- package/scripts/lib/gates/gate-incremental.mjs +23 -22
- package/scripts/lib/gates/gate-per-file.mjs +16 -7
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +77 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
- package/scripts/lib/learnings/io.mjs +274 -37
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +147 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
- package/scripts/lib/orphan-reaper/scan.mjs +510 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +47 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +134 -22
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +45 -5
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +100 -62
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +119 -29
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +194 -27
- package/scripts/lib/session-close-backfill.mjs +129 -4
- package/scripts/lib/session-end/tail-runner.mjs +10 -5
- package/scripts/lib/session-lock-shape.mjs +6 -3
- package/scripts/lib/session-lock.mjs +320 -67
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +67 -17
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +252 -54
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +121 -71
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +215 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/ux-grill/reconcile.mjs +1 -1
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +388 -27
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
- package/scripts/lib/vault-mirror/namespace.mjs +47 -26
- package/scripts/lib/vault-mirror/process.mjs +350 -62
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
- package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
- package/scripts/lib/vault-mirror/telemetry.mjs +25 -11
- package/scripts/lib/vault-status/board-writer.mjs +53 -12
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +11 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
- package/scripts/run-quality-gate.mjs +331 -307
- package/scripts/self-update.mjs +229 -0
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +603 -20
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +238 -75
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +134 -0
- package/skills/_shared/state-ownership.md +41 -12
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +6 -4
- package/skills/bootstrap/_shared-template.md +97 -2
- package/skills/bootstrap/fast-template.md +4 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
- package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
- package/skills/discovery/probes/vault-staleness.mjs +9 -3
- package/skills/docs-orchestrator/SKILL.md +12 -5
- package/skills/docs-orchestrator/audience-mapping.md +5 -3
- package/skills/evolve/SKILL.md +13 -4
- package/skills/evolve/references/evolve-analyze-mode.md +42 -18
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/learning-patterns.md +23 -13
- package/skills/session-end/metrics-collection.md +6 -7
- package/skills/session-end/phase-3-2-docs-verification.md +7 -6
- package/skills/session-end/plan-verification.md +25 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +13 -3
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +65 -68
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +8 -2
- package/skills/session-start/references/operations-contract.md +6 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +10 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +2 -2
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
- package/skills/wave-executor/references/wave-loop-review.md +4 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
|
@@ -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
|
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
Analyze the completed session to extract reusable learnings for future sessions.
|
|
11
11
|
|
|
12
12
|
**What to extract:**
|
|
13
|
-
- **Fragile files**: use `git log --name-only --format="" $SESSION_START_REF..HEAD | sort | uniq -c | sort -rn | head -10` to find files changed most frequently across commits this session. Files appearing in 3+ commits are candidates for fragile-file learnings. Cross-reference with `<state-dir>/STATE.md` Wave History to correlate with specific waves.
|
|
13
|
+
- **Fragile files**: use `git log --name-only --format="" $SESSION_START_REF..HEAD | sort | uniq -c | sort -rn | head -10` to find files changed most frequently across commits this session. Files appearing in 3+ commits are candidates for fragile-file learnings. Cross-reference with `<state-dir>/STATE.md` Wave History to correlate with specific waves. Skip this when `$SESSION_START_REF` is empty (the accessor's skip in `plan-verification.md`): `git log ..HEAD` reads as `HEAD..HEAD` and prints nothing with exit 0, which would pass for "no fragile files".
|
|
14
14
|
- **Effective sizing**: actual agent count vs. planned — what worked for this complexity level
|
|
15
15
|
- **Recurring issues**: same issue type appearing across waves (e.g., type errors, missing imports)
|
|
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. Unparseable lines already in the store are NOT an error and never block the write: the CLI keeps them verbatim at the end of the store (never archived), reports `malformed: N` in its summary and prints a stderr WARN with their line numbers — carry that count into the Phase 6 report.
|
|
86
|
+
f. **Hand-writing the store is FORBIDDEN** — no shell rewrite, no shell append, no `jq … | tee`, and that includes "repairing" a malformed line the WARN reports: it stays until the operator decides. 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. Once the operator decides to remove a line, the ONE sanctioned path is the CLI (#1500): `node scripts/sweep-expired-learnings.mjs --drop-malformed --line N` previews it (dry run: the line's text and the store `generation`), then `node scripts/sweep-expired-learnings.mjs --drop-malformed --line N --generation <that token> --apply --repo-root .` removes it. The apply copies the whole store to `learnings.pre-drop-malformed.jsonl.bak-<ISO>` beside it first and appends one `orchestrator.learnings.malformed_dropped` record. Line numbers move with every rewrite (malformed lines go to the end), so a store changed since the dry run exits `3` — re-run the dry run; a line that is not malformed exits `1`. Both write nothing. A malformed line that still holds a readable record — behind a UTF-8 BOM, or fused after a torn prefix (the dry run reports `bom` / `embedded_record`) — is refused at `--apply` with exit `1` as well, because dropping it loses that record; `--accept-embedded` is the operator's explicit decision to drop it anyway (the pre-drop snapshot keeps the line).
|
|
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
|
|
@@ -19,7 +19,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
19
19
|
- `total_agents`: sum of agents across all waves
|
|
20
20
|
- `total_files_changed`: unique files changed across entire session (from `git diff --stat`)
|
|
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 "$
|
|
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 validator still requires a number there, so name the accessor's reason in the record's `notes` beside it). 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:
|
|
@@ -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
|
>
|
|
@@ -31,11 +31,12 @@ process.stdout.write(JSON.stringify(tasks));
|
|
|
31
31
|
⚠ Phase 3.2: docs-tasks parse error — <reason>. Manual docs verification required.
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
## Step 2 —
|
|
34
|
+
## Step 2 — Resolve SESSION_RANGE
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
- Primary: `session-start-ref`
|
|
38
|
-
-
|
|
36
|
+
Run the accessor in `plan-verification.md § SESSION_START_REF accessor`; it exports `$SESSION_RANGE`, one complete diff range. Summary:
|
|
37
|
+
- Primary: the pinned `session-start-ref` from STATE.md frontmatter → `<sha>..HEAD`.
|
|
38
|
+
- Field absent: the baseline `resolveBaselineRange()` resolves → `<base>...HEAD` (never a hard-coded `origin/main`).
|
|
39
|
+
- Nothing resolves: `$SESSION_RANGE` is empty and the accessor names `no-baseline-ref` or `unresolvable-ref` — log `⚠ Phase 3.2: no session range (<reason>). Docs verification skipped.` and skip Phase 3.2. Never treat the empty range as "no docs changed".
|
|
39
40
|
|
|
40
41
|
If `git diff` itself fails (network issue, corrupt repo), log:
|
|
41
42
|
```
|
|
@@ -46,7 +47,7 @@ and skip Phase 3.2. This is an explicit error, not a silent skip.
|
|
|
46
47
|
## Step 3 — Compute changed files
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
|
-
CHANGED_FILES=$(git diff --name-only "$
|
|
50
|
+
CHANGED_FILES=$(git diff --name-only "$SESSION_RANGE")
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
Cache this list for the per-task loop below. If the command exits non-zero, surface the error per the guard above and skip Phase 3.2.
|
|
@@ -75,7 +76,7 @@ For each `task` in `docs-tasks`:
|
|
|
75
76
|
|
|
76
77
|
3. **Matched → inspect diff:**
|
|
77
78
|
```bash
|
|
78
|
-
git diff "$
|
|
79
|
+
git diff "$SESSION_RANGE" -- <matched-file>
|
|
79
80
|
```
|
|
80
81
|
- **Substantive content change** (non-whitespace, non-comment-only lines added/removed): outcome `ok`.
|
|
81
82
|
- **Whitespace-only or structural-only diff** (no prose or code content changed): outcome `gap`.
|
|
@@ -9,19 +9,34 @@
|
|
|
9
9
|
|
|
10
10
|
> Referenced by Phase 3.2 (Docs Verification) and Phase 1.1a (File-Level Grounding). Canonical definition lives here.
|
|
11
11
|
|
|
12
|
-
Read `session-start-ref` from STATE.md frontmatter. If the field is missing (older session or persistence disabled),
|
|
12
|
+
Read `session-start-ref` from STATE.md frontmatter; it is the pinned session-start SHA and always wins. If the field is missing (older session or persistence disabled), RESOLVE the base with `resolveBaselineRange()` (`scripts/lib/vcs-repo-spec.mjs`, #1039 — preferred remote → `<R>/HEAD` → `<R>/{main,master}` → local `main`/`master`), the same chain `computeDrift()` uses. Never assume `origin/main`: in a repo whose remote is not named `origin` or whose default branch is not `main` that literal fails every diff below.
|
|
13
|
+
|
|
14
|
+
The accessor exports two variables. Consumers that DIFF use `"$SESSION_RANGE"` as one complete range — never `"$SESSION_START_REF..HEAD"`, which turned the old fallback into a TWO-dot `origin/main..HEAD` (a diff that also counts everything `main` gained since the branch point). `$SESSION_START_REF` stays the log base (`git log "$SESSION_START_REF..HEAD"`).
|
|
13
15
|
|
|
14
16
|
```bash
|
|
15
|
-
|
|
17
|
+
SESSION_SPEC=$(node --input-type=module -e "
|
|
16
18
|
import {readFileSync} from 'node:fs';
|
|
17
19
|
import {parseStateMd} from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
import {resolveBaselineRange, isQueryFailure} from '${PLUGIN_ROOT}/scripts/lib/vcs-repo-spec.mjs';
|
|
21
|
+
let ref = '';
|
|
22
|
+
try { ref = String(parseStateMd(readFileSync('<state-dir>/STATE.md', 'utf8')).frontmatter['session-start-ref'] ?? '').trim(); } catch {}
|
|
23
|
+
if (/^[0-9a-f]{7,64}$/.test(ref)) process.stdout.write(ref + ' ' + ref + '..HEAD');
|
|
24
|
+
else {
|
|
25
|
+
const r = resolveBaselineRange({ repoRoot: process.cwd() });
|
|
26
|
+
if (r.ok) process.stdout.write(r.base + ' ' + r.range);
|
|
27
|
+
else process.stderr.write('SESSION_RANGE skipped: ' + (isQueryFailure(r.reason) ? 'unresolvable-ref' : 'no-baseline-ref') + ' (' + r.reason + ')\n');
|
|
28
|
+
}
|
|
29
|
+
")
|
|
30
|
+
SESSION_START_REF=${SESSION_SPEC%% *} # log base: the pinned SHA, else the resolved base ref
|
|
31
|
+
SESSION_RANGE=${SESSION_SPEC#* } # diff range: <sha>..HEAD (pinned), else <base>...HEAD
|
|
23
32
|
```
|
|
24
33
|
|
|
34
|
+
**Empty `$SESSION_RANGE` is an explicit skip, never "no changes".** The accessor names the reason on stderr, split by `isQueryFailure()`:
|
|
35
|
+
- `no-baseline-ref` — the repo genuinely has no base to measure against (no remote, no `main`/`master`). A real, benign state: report "no session range" and skip each diff-based step below.
|
|
36
|
+
- `unresolvable-ref` — git could not be asked (not a repo, git missing or failing). A degraded measurement: report it as such and skip; never read the empty file list as a clean session.
|
|
37
|
+
|
|
38
|
+
On either skip `$SESSION_START_REF` is empty too, so skip every LOG-based step as well: `git log --oneline "$SESSION_START_REF..HEAD"` then becomes `git log ..HEAD`, which git reads as `HEAD..HEAD` — it prints nothing and exits 0, an empty log indistinguishable from "no commits this session". The pinned value is accepted only as a hex object name (`/^[0-9a-f]{7,64}$/`), so a STATE.md value such as `--output=x` is never handed to git as an option; anything else falls through to the resolved base.
|
|
39
|
+
|
|
25
40
|
Read back the session plan that was agreed at the start. For EACH planned item:
|
|
26
41
|
|
|
27
42
|
### 1.1 Done Items
|
|
@@ -36,7 +51,7 @@ Read back the session plan that was agreed at the start. For EACH planned item:
|
|
|
36
51
|
Compare the files the plan said would be touched against the files actually changed in the session. Catches both **scope creep** (files changed that were not in any agent's prompt scope) and **incomplete coverage** (files in the plan that were never edited).
|
|
37
52
|
|
|
38
53
|
1. **Planned files** = union of all file paths from agent prompt scopes across all waves. Source: STATE.md Wave History, falling back to the original session plan's per-agent "Files:" specs. Glob patterns are expanded against the working tree at session-start time.
|
|
39
|
-
2. **Actual files** = `git diff --name-only $
|
|
54
|
+
2. **Actual files** = `git diff --name-only "$SESSION_RANGE"` (accessor above: the pinned `session-start-ref`, else the resolved baseline range). Empty `$SESSION_RANGE` → skip 1.1a with the accessor's reason.
|
|
40
55
|
3. **Compute discrepancies:**
|
|
41
56
|
- **Touched** = files in both Planned and Actual
|
|
42
57
|
- **Unplanned (scope creep)** = files in Actual but not in Planned
|
|
@@ -46,7 +61,7 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
46
61
|
- Generated/lock files (`pnpm-lock.yaml`, `*.lock`, `dist/**`, `node_modules/**`) are excluded from both planned and actual sets
|
|
47
62
|
- The `.claude/`, `.codex/`, and `.cursor/` state directories are excluded — they are session artifacts, not code
|
|
48
63
|
|
|
49
|
-
> **Scope-drift cross-reference:** the S2 warn-only drift tripwire (below) uses its own separately-maintained filter list — `DRIFT_EXCLUDE_PATTERNS` in `scripts/lib/scope-baseline.mjs` — and is NOT derived from the filters above. That list is the shared filter source for both sides of its ratio IN CODE: `writeBaseline()`'s denominator (`countPlannedFiles()`) and `computeDrift()`'s numerator both call the same internal `filterExcluded()` helper (#894 review finding F1 — previously only the numerator was code-filtered; the denominator relied on a coordinator prose instruction to pre-filter before calling `writeBaseline()`, which is why three earlier PRD revisions shipped a tripwire that read a wrong ratio).
|
|
64
|
+
> **Scope-drift cross-reference:** the S2 warn-only drift tripwire (below) uses its own separately-maintained filter list — `DRIFT_EXCLUDE_PATTERNS` in `scripts/lib/scope-baseline.mjs` — and is NOT derived from the filters above. That list is the shared filter source for both sides of its ratio IN CODE: `writeBaseline()`'s denominator (`countPlannedFiles()`) and `computeDrift()`'s numerator both call the same internal `filterExcluded()` helper — the numerator additionally drops UNTRACKED paths under `.orchestrator/` (`isUntrackedRuntimeWrite()`), a rule keyed on tracked status that only the live numerator has (#894 review finding F1 — previously only the numerator was code-filtered; the denominator relied on a coordinator prose instruction to pre-filter before calling `writeBaseline()`, which is why three earlier PRD revisions shipped a tripwire that read a wrong ratio).
|
|
50
65
|
5. **Report** in the verification output. Also call `computeDrift({ repoRoot, threshold: 2.0 })` (`scripts/lib/scope-baseline.mjs`) and append its result — warn-only, informational, never blocks close:
|
|
51
66
|
```
|
|
52
67
|
File-level grounding:
|
|
@@ -56,6 +71,7 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
56
71
|
- Untouched (planned but not edited): N files [list first 5]
|
|
57
72
|
- Scope drift: filesRatio X.X (Y actual / Z planned, threshold 2.0) — [breached | ok | skipped: <reason>]
|
|
58
73
|
```
|
|
74
|
+
Read `skipped: unresolvable-ref` as "a git query the numerator needs failed", not only "the ref is dead": besides a `session-start-ref` lost to a rebase or force-push it also covers a checkout git cannot read, the working-tree or untracked query failing, output over 64 MiB (ENOBUFS) and the 10 s per-query timeout. Run `git rev-parse --verify <session-start-ref>` before concluding the ref is gone. `skipped: no-baseline-ref` means the repo has no base to measure against (no remotes, no `main`/`master`). A `breached` result can also be an over-report: files already dirty at session start, or a same-checkout peer's files, count as drift (see `computeDrift()`'s JSDoc).
|
|
59
75
|
6. **Append to session metrics** (`grounding` field in the Phase 1.7 JSONL entry):
|
|
60
76
|
```json
|
|
61
77
|
"grounding": {
|
|
@@ -26,6 +26,16 @@ rm -f <state-dir>/wave-scope.json
|
|
|
26
26
|
|
|
27
27
|
This should have been cleaned up by wave-executor after the final wave, but crashed sessions or interrupted executions may leave it behind. A stale scope manifest from a previous session could incorrectly restrict the next session's enforcement hooks.
|
|
28
28
|
|
|
29
|
+
Delete THIS session's dispatch ledger the same way (`hooks/pre-task-scope-disjoint.mjs`, one file per session since #1493 — `skills/wave-executor/references/wave-loop-scope-manifest.md` step 7). Never a file named after another session id: that is a peer's live state.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
SID="${CLAUDE_CODE_SESSION_ID:-}"
|
|
33
|
+
case "$SID" in
|
|
34
|
+
''|*[!A-Za-z0-9._-]*) ;; # no id on this harness, or not path-safe: leave the file (inert)
|
|
35
|
+
*) rm -f ".orchestrator/wave-dispatch-scopes.$SID.json" ".orchestrator/wave-dispatch-scopes.$SID.lock" ;;
|
|
36
|
+
esac
|
|
37
|
+
```
|
|
38
|
+
|
|
29
39
|
### 3.1 SSOT Files
|
|
30
40
|
- Update `STATUS.md` / `STATE.md` if they exist (metrics, dates, status)
|
|
31
41
|
- Update `CLAUDE.md` (or `AGENTS.md` on Codex CLI) if patterns or conventions changed during this session
|
|
@@ -35,7 +45,7 @@ This should have been cleaned up by wave-executor after the final wave, but cras
|
|
|
35
45
|
|
|
36
46
|
> Skip this subsection if `docs-orchestrator.enabled` config is not `true` (default: `false`). Also skip entirely if `docs-orchestrator.mode` is `off`.
|
|
37
47
|
|
|
38
|
-
Reads `docs-tasks` from STATE.md frontmatter (written by wave-executor Pre-Wave 1b), computes `CHANGED_FILES` via `git diff --name-only "$
|
|
48
|
+
Reads `docs-tasks` from STATE.md frontmatter (written by wave-executor Pre-Wave 1b), computes `CHANGED_FILES` via `git diff --name-only "$SESSION_RANGE"` (the `plan-verification.md` accessor; an empty range skips with its named reason), and runs a per-task verification loop (outcome: `ok`/`partial`/`gap`). In `warn` mode logs results non-blocking; in `strict` mode blocks on any gap and presents an AskUserQuestion override prompt. Emits a `### Documentation Coverage (docs-orchestrator)` block for inclusion in the Phase 6 Final Report.
|
|
39
49
|
|
|
40
50
|
**See `phase-3-2-docs-verification.md` for full details.**
|
|
41
51
|
|
|
@@ -169,7 +179,7 @@ Then:
|
|
|
169
179
|
|
|
170
180
|
Read `skills/session-end/session-metrics-write.md` for JSONL append, vault-mirror invocation, and behavior matrix.
|
|
171
181
|
|
|
172
|
-
> **Token Rollup (#644):**
|
|
182
|
+
> **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
183
|
|
|
174
184
|
### 3.7a Compute and Write Recommendations (Epic #271 Phase A)
|
|
175
185
|
|
|
@@ -208,7 +218,7 @@ const boardResult = await mirrorBoard({
|
|
|
208
218
|
repoRoot: process.cwd(),
|
|
209
219
|
explicitStatus: 'closed', // force THIS repo's row to `closed`
|
|
210
220
|
});
|
|
211
|
-
// boardResult.action ∈ { 'written', 'skipped-noop', 'skipped-handwritten', 'skipped-vault-disabled', 'dry-run' }
|
|
221
|
+
// boardResult.action ∈ { 'written', 'skipped-noop', 'skipped-handwritten', 'skipped-vault-disabled', 'skipped-vault-not-canonical', 'dry-run' }
|
|
212
222
|
```
|
|
213
223
|
|
|
214
224
|
> **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.
|