session-orchestrator 3.16.0 → 3.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/030-wave-execution.mdc +17 -1
- package/CHANGELOG.md +130 -412
- package/README.md +17 -12
- package/SECURITY.md +190 -27
- package/agents/AGENTS.md +20 -3
- package/agents/code-implementer.md +6 -6
- package/agents/db-specialist.md +1 -1
- package/agents/qa-strategist.md +31 -6
- package/agents/schemas/qa-strategist.schema.json +27 -0
- package/agents/schemas/test-writer.schema.json +60 -2
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +1 -1
- package/agents/test-writer.md +29 -10
- package/agents/ui-developer.md +1 -1
- package/commands/contract-version-bump.md +28 -0
- package/commands/portfolio.md +1 -1
- package/docs/README.md +2 -1
- package/docs/USER-GUIDE.md +8 -3
- package/docs/ci-setup.md +121 -7
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +7 -7
- package/docs/cursor-setup.md +22 -9
- package/docs/events-schema.md +5 -1
- package/docs/instruction-delivery.md +444 -0
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +58 -9
- package/docs/session-config-reference.md +306 -6
- package/docs/session-config-template.md +66 -3
- package/docs/telemetry/telemetry-claims.md +204 -0
- package/docs/telemetry.md +158 -0
- package/hooks/_lib/guard-source-loader.mjs +467 -0
- package/hooks/_lib/lock-bootstrap.mjs +21 -0
- package/hooks/_lib/vcs-create-matcher.mjs +119 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/enforce-commands.mjs +10 -2
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +11 -2
- package/hooks/hooks-pi.json +10 -0
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +178 -18
- package/hooks/on-session-start.mjs +23 -0
- package/hooks/post-bash-write-verify.mjs +977 -0
- package/hooks/post-subagent-discovery-validator.mjs +256 -41
- package/hooks/pre-bash-destructive-guard.mjs +525 -160
- package/hooks/pre-bash-issue-budget.mjs +167 -0
- package/hooks/pre-bash-sessions-ledger-guard.mjs +627 -0
- package/hooks/pre-bash-templates-first.mjs +96 -63
- package/hooks/skill-invocation-telemetry.mjs +109 -10
- package/hooks/subagent-telemetry.mjs +527 -37
- package/package.json +16 -3
- package/pi/prompts/contract-version-bump.md +12 -0
- package/rules/README.md +32 -0
- package/scripts/archive-closed-prds.mjs +12 -22
- package/scripts/autopilot-multi.mjs +103 -20
- package/scripts/backfill-abandoned-sessions.mjs +160 -4
- package/scripts/check-doc-consistency.sh +17 -1
- package/scripts/compute-grounding-injection.sh +18 -3
- package/scripts/dialectic-deriver.mjs +7 -2
- package/scripts/eval-session.mjs +50 -9
- package/scripts/fleet-instruction-scan.mjs +141 -0
- package/scripts/lib/auto-dialectic.mjs +11 -2
- package/scripts/lib/auto-dream.mjs +16 -5
- package/scripts/lib/autopilot/mr-draft.mjs +31 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +113 -5
- package/scripts/lib/backlog-scan.mjs +39 -6
- package/scripts/lib/blocked-commands-policy.mjs +340 -0
- package/scripts/lib/build-live-signals.mjs +7 -4
- package/scripts/lib/ci-status-banner.mjs +75 -12
- package/scripts/lib/claude-md-budget-lint.mjs +283 -34
- package/scripts/lib/command-blocker.mjs +1013 -58
- package/scripts/lib/config/config-protection.mjs +2 -1
- package/scripts/lib/config/context-coverage.mjs +82 -0
- package/scripts/lib/config/drift-check.mjs +9 -1
- package/scripts/lib/config/gitlab-portfolio.mjs +1 -1
- package/scripts/lib/config/issue-budget.mjs +123 -0
- package/scripts/lib/config/moc-staleness.mjs +98 -0
- package/scripts/lib/config/reconcile.mjs +21 -0
- package/scripts/lib/config/section-extractor.mjs +121 -1
- package/scripts/lib/config/worktree-orphans.mjs +138 -0
- package/scripts/lib/config-schema.mjs +23 -3
- package/scripts/lib/config.mjs +32 -0
- package/scripts/lib/context-coverage-banner.mjs +223 -0
- package/scripts/lib/convergence-monitor.mjs +49 -3
- package/scripts/lib/description-surface.mjs +535 -0
- package/scripts/lib/dispatcher/enumerate.mjs +136 -30
- package/scripts/lib/dispatcher/rank.mjs +22 -8
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +26 -24
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +1 -1
- package/scripts/lib/eval/engine.mjs +47 -5
- package/scripts/lib/events.mjs +59 -7
- package/scripts/lib/evolve/autonomy-verdict.mjs +5 -0
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +54 -7
- package/scripts/lib/gates/gate-full.mjs +15 -3
- package/scripts/lib/gates/gate-helpers.mjs +132 -6
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +28 -8
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +8 -2
- package/scripts/lib/gitlab-portfolio/cli.mjs +1 -1
- package/scripts/lib/handover-gate.mjs +7 -3
- package/scripts/lib/harness-audit/categories/category4.mjs +22 -5
- package/scripts/lib/instruction-budget-guard.mjs +402 -51
- package/scripts/lib/io.mjs +345 -10
- package/scripts/lib/issue-budget.mjs +269 -0
- package/scripts/lib/issue-close-strip-labels.mjs +39 -9
- package/scripts/lib/label-scope.mjs +47 -0
- package/scripts/lib/learnings/schema.mjs +43 -3
- package/scripts/lib/lock-reaper.mjs +1 -2
- package/scripts/lib/memory-proposals/schema.mjs +36 -1
- package/scripts/lib/moc-staleness-banner.mjs +267 -0
- package/scripts/lib/peer-discovery.mjs +645 -0
- package/scripts/lib/pi-hook-bridge.mjs +146 -17
- package/scripts/lib/product-repo-detect.mjs +9 -8
- package/scripts/lib/project-hygiene.mjs +432 -0
- package/scripts/lib/quality-gate.mjs +167 -0
- package/scripts/lib/recommendations-v0.mjs +1 -1
- package/scripts/lib/reconcile/eligibility.mjs +1 -1
- package/scripts/lib/reconcile/emitter.mjs +23 -4
- package/scripts/lib/reconcile/engine.mjs +147 -39
- package/scripts/lib/reconcile/idempotency.mjs +114 -14
- package/scripts/lib/reconcile-nudge-banner.mjs +65 -9
- package/scripts/lib/resource-probe/evaluate.mjs +70 -4
- package/scripts/lib/resource-probe.mjs +19 -0
- package/scripts/lib/rule-loader.mjs +6 -0
- package/scripts/lib/scope-baseline.mjs +564 -0
- package/scripts/lib/scope-gate.mjs +399 -98
- package/scripts/lib/session-close-backfill.mjs +61 -6
- package/scripts/lib/session-end/phase-skip.mjs +1 -0
- package/scripts/lib/session-end/worktree-orphan-sweep.mjs +252 -0
- package/scripts/lib/session-id.mjs +221 -41
- package/scripts/lib/session-lock.mjs +304 -6
- package/scripts/lib/session-schema/constants.mjs +22 -3
- package/scripts/lib/session-schema/filters.mjs +88 -0
- package/scripts/lib/session-schema/validator.mjs +16 -0
- package/scripts/lib/session-schema.mjs +1 -0
- package/scripts/lib/sessions-integrity-banner.mjs +294 -0
- package/scripts/lib/sessions-staleness-banner.mjs +121 -12
- package/scripts/lib/skill-evolution/idempotency.mjs +135 -16
- package/scripts/lib/skill-evolution/mr-opener.mjs +9 -1
- package/scripts/lib/skill-health/join.mjs +35 -9
- package/scripts/lib/spiral-carryover.mjs +142 -30
- package/scripts/lib/state-md/mission-status.mjs +53 -3
- package/scripts/lib/subagents-schema.mjs +43 -9
- package/scripts/lib/telemetry/anon-id.mjs +141 -0
- package/scripts/lib/telemetry/consent.mjs +299 -0
- package/scripts/lib/telemetry/paths.mjs +27 -0
- package/scripts/lib/telemetry/queue.mjs +287 -0
- package/scripts/lib/telemetry/schema.mjs +384 -0
- package/scripts/lib/telemetry/sync.mjs +312 -0
- package/scripts/lib/test-runner/issue-reconcile.mjs +53 -13
- package/scripts/lib/tests-src-ratio.mjs +484 -0
- package/scripts/lib/validate/check-agents.mjs +56 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
- package/scripts/lib/validate/check-rules.mjs +217 -35
- package/scripts/lib/validate/check-test-value-bans.mjs +782 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate-vendored-rules.mjs +10 -2
- package/scripts/lib/vault-archive.mjs +17 -2
- package/scripts/lib/vault-backfill/glab.mjs +8 -0
- package/scripts/lib/vault-mirror/process.mjs +30 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +293 -36
- package/scripts/lib/vault-status/board-writer.mjs +63 -5
- package/scripts/lib/vault-status/narrative-mirror.mjs +13 -7
- package/scripts/lib/vcs-repo-spec.mjs +362 -0
- package/scripts/lib/wave-resource-gate.mjs +115 -11
- package/scripts/lib/worktree/listing.mjs +44 -7
- package/scripts/mcp-server.sh +32 -6
- package/scripts/measure-context-overhead.sh +151 -0
- package/scripts/memory-propose.mjs +72 -9
- package/scripts/print-applicable-rules.mjs +51 -12
- package/scripts/release.mjs +534 -0
- package/scripts/run-quality-gate.mjs +123 -5
- package/scripts/telemetry.mjs +250 -0
- package/scripts/validate-wave-scope.mjs +182 -17
- package/scripts/vault-integration-watcher.mjs +32 -10
- package/skills/_shared/config-reading.md +2 -2
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +145 -28
- package/skills/contract-version-bump/SKILL.md +219 -0
- package/skills/discovery/SKILL.md +4 -4
- package/skills/discovery/issue-templates.md +11 -11
- package/skills/discovery/probes-audit.md +1 -1
- package/skills/discovery/probes-feature.md +1 -1
- package/skills/discovery/probes-session.md +26 -5
- package/skills/ecosystem-health/SKILL.md +1 -1
- package/skills/ecosystem-health/wizard.md +4 -4
- package/skills/evolve/SKILL.md +1 -0
- package/skills/gitlab-ops/SKILL.md +20 -12
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/hook-development/SKILL.md +1 -1
- package/skills/mode-selector/SKILL.md +1 -1
- package/skills/npm-publish/SKILL.md +97 -0
- package/skills/plan/SKILL.md +5 -5
- package/skills/plan/mode-feature.md +4 -4
- package/skills/plan/mode-new.md +10 -10
- package/skills/plan/mode-retro.md +1 -1
- package/skills/quality-gates/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +21 -4
- package/skills/session-end/SKILL.md +108 -14
- package/skills/session-end/discovery-scan.md +4 -2
- package/skills/session-end/drift-operations.md +4 -4
- package/skills/session-end/metrics-collection.md +13 -0
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +2 -1
- package/skills/session-end/plan-verification.md +5 -2
- package/skills/session-end/vault-operations.md +1 -1
- package/skills/session-end/verification-checklist.md +1 -1
- package/skills/session-plan/SKILL.md +6 -2
- package/skills/session-plan/wave-template.md +2 -0
- package/skills/session-start/SKILL.md +149 -7
- package/skills/session-start/phase-4-5-resource-health.md +15 -2
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/vault-sync/SKILL.md +1 -1
- package/skills/vault-sync/package-lock.json +3 -3
- package/skills/vault-sync/validator.mjs +228 -40
- package/skills/wave-executor/SKILL.md +5 -2
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/wave-loop.md +163 -10
- package/templates/_shared/loop.md +4 -4
|
@@ -74,7 +74,7 @@ Read back the session plan that was agreed at the start. For EACH planned item:
|
|
|
74
74
|
- Document what was completed and what remains
|
|
75
75
|
- **Do NOT file the carryover issue here (#769).** Collect a carryover **candidate** instead — append it to the in-memory candidate list that the Phase 1.65 Handover Alignment Gate consumes. The issue is filed (only if the gate confirms it) in Phase 5 Step 3. Candidate record (JS keys as `routeCandidates` / `normalizeCandidate` read them — `source-phase`→`sourcePhase`, `origin-issue`→`originIssue`; see `plan-verification.md § Candidate Record Format`):
|
|
76
76
|
- `{ task: '<original task description>', sourcePhase: '1.2', originIssue: <IID or null>, priority: '<original>', bucket: 'partially-done' }`
|
|
77
|
-
- The eventual issue keeps the source-specific `[Carryover]` template — Title `[Carryover] <original task description>`, Labels `priority
|
|
77
|
+
- The eventual issue keeps the source-specific `[Carryover]` template — Title `[Carryover] <original task description>`, Labels `priority::<original>` + `status:ready`, Description = what's done / what's left / context for next session.
|
|
78
78
|
- Link to the original issue when applicable (record its IID as `originIssue`; a candidate with no origin issue auto-carries per the gate's routing, so nothing planned is silently forgotten).
|
|
79
79
|
|
|
80
80
|
### 1.3 Not Started Items
|
|
@@ -218,7 +218,7 @@ node scripts/emit-event.mjs --type orchestrator.handover.gated --payload \
|
|
|
218
218
|
const { autoCarry, ask } = routeCandidates(candidates);
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
-
`autoCarry` = `priority
|
|
221
|
+
`autoCarry` = `priority::critical|high` OR `bucket === 'spiral-failed'` OR `originIssue === null` — **non-deselectable** (dropping any of these would be real forgetting; consistent with the Critical Rule at `SKILL.md:853`). `ask` = the middle-band (priority `medium`/`low`/none WITH an origin issue, buckets not-started/emergent/partially-done) plus any `malformed` record. `routeCandidates` returns NORMALIZED copies for gate rendering; the coordinator retains its ORIGINAL candidate objects (with filing payloads) for Phase 5 Step 3.
|
|
222
222
|
|
|
223
223
|
3. Read STATE.md contents and extract the open questions via the sibling helper:
|
|
224
224
|
|
|
@@ -313,10 +313,10 @@ Dispatch the session-reviewer agent to verify implementation quality before the
|
|
|
313
313
|
|
|
314
314
|
| Finding class | Disposition |
|
|
315
315
|
|---|---|
|
|
316
|
-
| HIGH+ / blocking review finding | Fix inline if quick (<2 min); else create an issue (`priority
|
|
316
|
+
| HIGH+ / blocking review finding | Fix inline if quick (<2 min); else create an issue (`priority::high`, `status:ready`) and note it in the Final Report |
|
|
317
317
|
| MED / LOW review finding | Fold in-session if quick; else record under "Unresolved Review Findings" in the Final Report — DO NOT create an issue (#617) |
|
|
318
318
|
| Planned-carryover (item was in the plan, not finished) | Route as a carryover **candidate** per Phase 1.2 → the Phase 1.65 gate files it. Never forgotten: a no-origin/critical/high item auto-carries as a `[Carryover]` issue; a middle-band item with an origin issue is preselected=carry (and its origin issue stays open even if dropped). |
|
|
319
|
-
| SPIRAL / FAILED agent carryover | Route as an **auto-carry** candidate per Phase 1.6 → filed via `createSpiralCarryoverIssue` in Phase 5 Step 3 (non-deselectable) |
|
|
319
|
+
| SPIRAL / FAILED agent carryover | Route as an **auto-carry** candidate per Phase 1.6 → filed via `createSpiralCarryoverIssue` in Phase 5 Step 3 (non-deselectable; **exempt from the `issue-budget` cap** — the `[Carryover] [SPIRAL\|FAILED]` title and the `type::carryover` label bypass it, so a full budget can never swallow this filing) |
|
|
320
320
|
|
|
321
321
|
**Override-ratio telemetry (#730/H5):** whenever one or more MED/LOW review findings are routed to "Unresolved Review Findings" (rather than fixed), additionally emit a single event capturing how many findings were absorbed rather than resolved — feeding the `override_ratio` metric:
|
|
322
322
|
|
|
@@ -364,7 +364,7 @@ Rules:
|
|
|
364
364
|
|
|
365
365
|
> **Verification Reference:** See `verification-checklist.md` in this skill directory for the full quality gate checklist.
|
|
366
366
|
|
|
367
|
-
Run ALL checks listed in the verification checklist. If any check fails: fix if quick (<2 min), otherwise create a `priority
|
|
367
|
+
Run ALL checks listed in the verification checklist. If any check fails: fix if quick (<2 min), otherwise create a `priority::high` issue. Do NOT commit broken code.
|
|
368
368
|
|
|
369
369
|
### Phase 2.0a: Echo-Stub Detection (GH #42)
|
|
370
370
|
|
|
@@ -438,7 +438,7 @@ totalFindings = projectStaleness.findings.length + narrativeStaleness.findings.l
|
|
|
438
438
|
- If `totalFindings === 0`: continue, log `Vault staleness: clean (mode=strict)`.
|
|
439
439
|
- If `totalFindings > 0`: do NOT block the close. Present the findings list and surface an AskUserQuestion whose Recommended default is **warn + carryover + continue**:
|
|
440
440
|
- On Claude Code: AskUserQuestion with options:
|
|
441
|
-
1. "Warn + carryover and close (Recommended)" — file a carryover issue (labels `carryover`, `priority
|
|
441
|
+
1. "Warn + carryover and close (Recommended)" — file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] Vault staleness (strict) — <count> findings` documenting the stale projects/narratives for a follow-up session, log a Deviation entry in STATE.md `## Deviations`, then continue the close:
|
|
442
442
|
`- [<ISO timestamp>] Phase 2.3: Vault staleness strict-mode findings carried over. Findings: <count> (projects: <N>, narratives: <M>) → issue #<IID>.`
|
|
443
443
|
2. "Override and close" — proceed without a carryover issue, log a Deviation entry in STATE.md `## Deviations`:
|
|
444
444
|
`- [<ISO timestamp>] Phase 2.3: Vault staleness strict-mode findings overridden by user. Findings: <count> (projects: <N>, narratives: <M>).`
|
|
@@ -480,7 +480,7 @@ For each kept phase:
|
|
|
480
480
|
- exit code `≠ 0` ⇒ **BLOCK the close** using the same routing pattern as Phase 2.3 strict-mode. `mode: hard` here is an operator-declared repo contract (the repo deliberately chose `mode: hard`), so the block semantics are preserved — but the AUQ now ALSO offers a warn + carryover escape hatch. Present the phase name + captured summary and offer:
|
|
481
481
|
- On Claude Code: AskUserQuestion with options:
|
|
482
482
|
1. "Fix and retry Phase 2.5" (Recommended) — exit close, let the user investigate.
|
|
483
|
-
2. "Warn + carryover and close" — file a carryover issue (labels `carryover`, `priority
|
|
483
|
+
2. "Warn + carryover and close" — file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] custom-phase '<name>' (mode=hard) exited <code>` capturing the phase name + captured summary for a follow-up session, log the Deviation entry, then continue the close.
|
|
484
484
|
3. "Override and close" — proceed, log a Deviation entry in STATE.md `## Deviations`:
|
|
485
485
|
`- [<ISO timestamp>] Phase 2.5: custom-phase '<name>' (mode=hard) exited <code>, overridden by user.`
|
|
486
486
|
In addition to the Deviation entry, emit an override-ratio event so the override feeds the `override_ratio` metric (#730/H5): `node scripts/emit-event.mjs --type orchestrator.finding.overridden --payload '{"phase":"2.5","kind":"custom-phase-hard","count":N}'`.
|
|
@@ -507,7 +507,7 @@ already-computed results — no new detection logic, only aggregation:
|
|
|
507
507
|
4. Wave-level reviewer findings overridden without a fix task (`## Deviations` entries matching `reviewer finding overridden` — written by wave-executor §5/5a).
|
|
508
508
|
|
|
509
509
|
For EACH item: file a hard-terminated closure issue via `createBrokenWindowIssue()`
|
|
510
|
-
from `scripts/lib/spiral-carryover.mjs` — labels `broken-window` + `priority
|
|
510
|
+
from `scripts/lib/spiral-carryover.mjs` — labels `broken-window` + `priority::high`,
|
|
511
511
|
due-date = today + `broken-window-budget.due-days` (default 7; `glab` native
|
|
512
512
|
`--due-date`, `gh` fallback: `Due: <date>` as first body line — GitHub has no
|
|
513
513
|
native due-date field). Idempotent per task-hash — re-running a close never
|
|
@@ -576,7 +576,7 @@ Review `<state-dir>/rules/` files that are relevant to this session's work:
|
|
|
576
576
|
|
|
577
577
|
> **Ownership Reference:** See `skills/_shared/state-ownership.md`. session-end is authorized to set `status: completed` plus the optional `updated` timestamp (#184), and — as of Phase A of Epic #271 — the 5 Recommendation fields written by Phase 3.7a. No other fields.
|
|
578
578
|
|
|
579
|
-
> **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.5 → 3.6.6 → 3.6.7 → 3.6.8 → 3.7 → 3.7a → 3.7b → 3.7c → 3.7d → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; the Tail-Diät skip-plan dispatcher now dispatches the full six-phase tail mechanically, so the note is corrected to list all six). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
|
|
579
|
+
> **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.5 → 3.6.6 → 3.6.7 → 3.6.8 → 3.7 → 3.45 → 3.7a → 3.7b → 3.7c → 3.7d → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; the Tail-Diät skip-plan dispatcher now dispatches the full six-phase tail mechanically, so the note is corrected to list all six). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.45 (Telemetry Flush, #844) sits AFTER Phase 3.7 because it drains the send-queue with the just-written `sessions.jsonl` record already included, and BEFORE Phase 3.7a because it is a fire-and-forget side-effect with no dependency on the Recommendation-write ordering below it. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
|
|
580
580
|
|
|
581
581
|
> Gate: Only run if `persistence` is enabled in Session Config and `<state-dir>/STATE.md` exists.
|
|
582
582
|
1. Set frontmatter `status: completed`
|
|
@@ -622,6 +622,24 @@ Failures in either step are logged to stderr but do **not** block session close
|
|
|
622
622
|
|
|
623
623
|
This cleanup is the counterpart to the session-start Phase 1.5 recovery prompt: once a session closes cleanly, future sessions must not be offered recovery for its snapshots.
|
|
624
624
|
|
|
625
|
+
### 3.45: Telemetry Flush (advisory, #844)
|
|
626
|
+
|
|
627
|
+
> Skip silently when `persistence: false` in Session Config. There is **no dedicated config key** for this phase — the send-gate is `resolveConsent()` inside `sync.mjs` itself (fail-closed: a `disabled` / `no-consent` / headless posture makes `flush()` a no-op in <5ms, sending nothing). This phase runs late in the close, after Phase 3.7 has written `sessions.jsonl`, so any session-summary event enqueued at metrics-write time is included in the drain; the ordinal position `3.45` is kept for readability (mirrors the Phase 3.4 Runtime Ordering Note idiom of ordinal ≠ runtime order).
|
|
628
|
+
|
|
629
|
+
Drain the host-local telemetry send-queue once, fire-and-forget. The flush is **advisory** — the close must never fail, stall, or surface an error because of telemetry:
|
|
630
|
+
|
|
631
|
+
```javascript
|
|
632
|
+
import { flush } from '${PLUGIN_ROOT}/scripts/lib/telemetry/sync.mjs';
|
|
633
|
+
|
|
634
|
+
// Fire-and-forget. flush() is contractually never-throw + internally gated (resolveConsent)
|
|
635
|
+
// + 3s-timeout-bounded; the try/catch is defense-in-depth, never a real failure path.
|
|
636
|
+
try { await flush(); } catch { /* nie blockierend — der Close darf durch Telemetrie nie scheitern */ }
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Semantics.** `flush()` is fire-and-forget with an internal ~3s timeout. When the ingest endpoint is unreachable (offline), events stay in the bounded host-local queue (oldest-dropped on overflow) and are retried on a later close — nothing is lost or blocked. A one-line result MAY be surfaced in the Phase 6 close summary (`Telemetry: sent` / `queued` / `gated`), but a failure NEVER renders an error banner: under no circumstances may telemetry make `/close` fail or take materially longer than ~3s. The gate lives in the module (fail-closed via `resolveConsent`), so this phase carries no config-key check of its own beyond the `persistence: false` skip above.
|
|
640
|
+
|
|
641
|
+
Cross-reference: GitLab #844 (Epic #841); `docs/prd/2026-07-20-anonymous-usage-telemetry.md` FA3; `docs/telemetry.md`; flush API in `scripts/lib/telemetry/sync.mjs` (`flush` — fire-and-forget, gated, never-throw).
|
|
642
|
+
|
|
625
643
|
### 3.5 Session Memory
|
|
626
644
|
|
|
627
645
|
> Gate: Only run if `persistence` is enabled in Session Config AND platform is Claude Code (session memory at `~/.claude/projects/` is Claude Code-only). Learnings (Phase 3.5a) and metrics (Phase 3.7) still write to `.orchestrator/metrics/` on all platforms.
|
|
@@ -892,6 +910,60 @@ Reply with the number of your choice.
|
|
|
892
910
|
- **AUQ rule:** `.claude/rules/ask-via-tool.md` AUQ-004 — coordinator-only invocation
|
|
893
911
|
- **Companion phases:** P3.1 PROMOTION_OFFER (`enterWorktree()` in `parallel-aware-auq.md`) creates the worktree; this phase removes it.
|
|
894
912
|
|
|
913
|
+
## Phase 4b: Worktree-Orphan Sweep (#831/B5)
|
|
914
|
+
|
|
915
|
+
> Skip if `persistence: false` in Session Config. Skip silently unless `worktree-orphans.enabled: true` (opt-in; default `false`).
|
|
916
|
+
|
|
917
|
+
Sweep the repo's worktree set for branches with **0 commits ahead of the base branch** — orphans left behind by finished sessions. Distinct from Phase 4a: 4a asks "did *this* session run in a promoted worktree?", 4b asks "which worktrees from *past* sessions have nothing left in them?".
|
|
918
|
+
|
|
919
|
+
> **Ordering rationale (#490 durableCommit dependency):** Phase 4b runs AFTER the Phase 4 commit+push, NOT before — the same invariant that governs Phase 4a. Removing a worktree before commit+push would lose its `STATE.md` before the Phase 3.4 `sessions.jsonl` metrics writes are committed. See `docs/adr/0008-worktree-cleanup-ordering.md`.
|
|
920
|
+
|
|
921
|
+
### The module proposes; the coordinator disposes
|
|
922
|
+
|
|
923
|
+
> **Authoritative impl:** `scripts/lib/session-end/worktree-orphan-sweep.mjs` — `checkWorktreeOrphans({ repoRoot, mainCheckoutRoot, config, execFileFn })`. Import and call; do NOT re-implement from this doc.
|
|
924
|
+
|
|
925
|
+
`checkWorktreeOrphans()` **executes zero mutating commands.** Its complete argv set is four read-only shapes — `worktree list --porcelain`, `rev-list --count --end-of-options <base>..<branch>`, and (via the reused Phase 4a helper `isWorktreeClean`) `status --porcelain` and `status --short --branch` — all via the injection-safe arg-array form (`execFileSync('git', ['-C', dir, …])`, #577 HARDEN-001), never a template-literal shell string. It returns `null` (silent no-op) or ONE object `{ severity: 'warn', message, candidates: [{ wtPath, branch, sessionId, aheadCount: 0 }] }`.
|
|
926
|
+
|
|
927
|
+
**`--end-of-options` is load-bearing, not decoration.** `base-branch` comes from Session Config, and a value shaped like a git flag (e.g. `--glob=refs/heads/*`) is otherwise parsed by `rev-list` as an OPTION rather than a revision range — which exits 0 and prints `0`, silently marking EVERY worktree as a 0-ahead orphan and offering the operator "Löschen" for worktrees full of live work. That is not an error path the conservative default catches, because `0` parses fine. `--end-of-options` turns the payload into a hard git error that DOES fall into the conservative `continue`, and `_isSafeBaseBranch` in the config parser rejects leading-dash values at the source. No attacker is needed for this — a typo reaches the same outcome.
|
|
928
|
+
|
|
929
|
+
**The gate is opt-in and fails CLOSED:** the module returns `null` unless `enabled === true`. It accepts either the full config object or the already-indexed `worktree-orphans` block, so neither call shape can accidentally open the gate.
|
|
930
|
+
|
|
931
|
+
**A dirty worktree is never a candidate.** Orphan-ness is not decided by commit count alone — `isWorktreeClean()` is consulted first, and any uncommitted, staged or untracked work (or any git error while checking) excludes the worktree entirely. A worktree that is 0-ahead but holds live work is exactly the case where a deletion prompt would cost real data.
|
|
932
|
+
|
|
933
|
+
The return field is named `candidates`, not `orphans` or `removals`, and the name is load-bearing: **the coordinator decides, the module never does.** Grounding: `.claude/rules/parallel-sessions.md` § PSA-003 — *"Did I create this file/commit/change? If not, it is not mine to touch."* A sweep probe created none of the worktrees it inspects.
|
|
934
|
+
|
|
935
|
+
**Nothing is removed without explicit operator confirmation.** The rendered banner always ends with the literal clause `nothing was removed.` — the operator-visible proof of the invariant.
|
|
936
|
+
|
|
937
|
+
**Conservative default (safety-critical):** any git error, unparseable `rev-list` output, detached HEAD, unresolvable branch, or ambiguity of any kind → that worktree is NOT reported as a candidate. A failing sibling never suppresses a healthy finding, and silence is never to be read as "safe to delete".
|
|
938
|
+
|
|
939
|
+
### The AUQ is rendered by the coordinator, never by the module
|
|
940
|
+
|
|
941
|
+
`AskUserQuestion` is unavailable inside dispatched subagents (`.claude/rules/ask-via-tool.md` AUQ-004), so the module returns data only and the **coordinator** renders the picker — one call per candidate.
|
|
942
|
+
|
|
943
|
+
**Option order is locked and is itself a safety property (#580-AUQ-001): the non-destructive option goes FIRST and is marked `(Recommended)`, so an accidental Enter keypress can never destroy a worktree.**
|
|
944
|
+
|
|
945
|
+
`[ Behalten (Recommended) / Löschen / Manuell ]`
|
|
946
|
+
|
|
947
|
+
- **Behalten (Recommended)** — leave the worktree in place; re-surfaces next session.
|
|
948
|
+
- **Löschen** — operator explicitly authorises removal; the coordinator performs it, subject to PSA-003.
|
|
949
|
+
- **Manuell** — operator handles it outside the session; no further prompting this session.
|
|
950
|
+
|
|
951
|
+
```js
|
|
952
|
+
import { checkWorktreeOrphans } from '${PLUGIN_ROOT}/scripts/lib/session-end/worktree-orphan-sweep.mjs';
|
|
953
|
+
|
|
954
|
+
const sweep = checkWorktreeOrphans({
|
|
955
|
+
repoRoot: process.cwd(),
|
|
956
|
+
config: config['worktree-orphans'],
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
if (sweep) {
|
|
960
|
+
console.warn(sweep.message);
|
|
961
|
+
// → coordinator renders the AUQ per sweep.candidates entry.
|
|
962
|
+
// [ Behalten (Recommended) / Löschen / Manuell ]
|
|
963
|
+
// Nothing is removed unless the operator picks "Löschen".
|
|
964
|
+
}
|
|
965
|
+
```
|
|
966
|
+
|
|
895
967
|
## Phase 5: Issue Cleanup
|
|
896
968
|
|
|
897
969
|
> **VCS Reference:** Use CLI commands per the "Common CLI Commands" section of the gitlab-ops skill.
|
|
@@ -914,8 +986,8 @@ Reply with the number of your choice.
|
|
|
914
986
|
The call is idempotent: if the issue has no `status:*` labels, no update CLI call is made. Failures from `stripStatusLabels` are non-fatal — log and proceed with close.
|
|
915
987
|
|
|
916
988
|
2. **Update in-progress issues**: ensure labels reflect actual state using the issue update command
|
|
917
|
-
3. **Create carryover issues — from the Phase 1.65 gate's carry-list ONLY (#769):** file an issue for each item on the carry-list produced by the Handover Alignment Gate — i.e. the non-deselectable **auto-carry** class (`priority
|
|
918
|
-
- **Template stays source-specific:** 1.2 Partially-Done → `[Carryover] <task>` (labels `priority
|
|
989
|
+
3. **Create carryover issues — from the Phase 1.65 gate's carry-list ONLY (#769):** file an issue for each item on the carry-list produced by the Handover Alignment Gate — i.e. the non-deselectable **auto-carry** class (`priority::critical|high`, SPIRAL/FAILED, or no-origin-issue candidates) PLUS the middle-band items the operator LEFT SELECTED in triage. Do NOT file anything the gate dropped, and do NOT file directly from Phase 1.2/1.3/1.4/1.6 — those phases only collected candidates.
|
|
990
|
+
- **Template stays source-specific:** 1.2 Partially-Done → `[Carryover] <task>` (labels `priority::<original>`, `status:ready`); 1.4 unfinished Emergent → a **normal** issue (NOT the `[Carryover]` template); 1.6 SPIRAL/FAILED → fire the deferred `createSpiralCarryoverIssue({ taskDescription, kind, context, priority: 'high', vcs })` (idempotent task-hash dedup — payload comes from the candidate's `_spiral` annotation set in Phase 1.6 step 5). 1.3 files no NEW issue: a carried 1.3 candidate simply keeps its ORIGINAL issue `status:ready`.
|
|
919
991
|
- **Dropped middle-band items:** file NO `[Carryover]` duplicate; the origin issue stays open and unchanged. Record each drop in the Phase 6 Final Report under `### Dropped at Handover Gate` with its origin-issue reference and a reason slot.
|
|
920
992
|
- **Fail-open / gate skipped:** when Phase 1.65 skipped fail-open, the carry-list is ALL candidates (status quo) and there is no drop-list.
|
|
921
993
|
- **Mark answered open questions `[x]` durably — atomic with the filing above (#769):** now, on the completed side of the Quality Gate, persist each answered open question captured in-memory at Phase 1.65 Step 4 to STATE.md via the lock-guarded sibling helper (PSA-005). Co-locating this write with the carryover-issue filing is the load-bearing correctness invariant: an earlier Quality-Gate abort leaves every question `- [ ]` on disk, so it correctly re-surfaces via `readOpenQuestions().filter(!answered)` on re-close — the `[x]` mark now reflects a COMPLETED handover, never a mid-close state a later abort would invalidate. Any implied-work candidate an answered question enqueued in Phase 1.65 is filed by the carry-list step above, so the mark and its issue land together:
|
|
@@ -930,13 +1002,32 @@ Reply with the number of your choice.
|
|
|
930
1002
|
|
|
931
1003
|
Fail-open: a `markOpenQuestionAnsweredOnDisk` failure is non-fatal — log a WARN and proceed with the close; the question simply stays `- [ ]` and roundtrips to the next session.
|
|
932
1004
|
|
|
1005
|
+
3b. **Drain the issue-budget overflow — exactly ONE collector artefact (issue-budget):** when `.orchestrator/runtime/issue-budget.json` has a non-empty `overflow[]`, the session hit its `issue-budget.max-per-session` cap and every over-cap creation was PARKED rather than filed. Fold the whole list into a single artefact so nothing is silently dropped.
|
|
1006
|
+
|
|
1007
|
+
**Ordering (load-bearing):** run this as the LAST issue-creating action of Phase 5 — after step 3, after "Discovery Issue Creation", after step 4 — and re-read the counter file at that moment. Those steps can themselves push new entries into `overflow[]`; draining early would leave them unfiled.
|
|
1008
|
+
|
|
1009
|
+
```js
|
|
1010
|
+
import { readBudgetState, budgetStatePath } from '${PLUGIN_ROOT}/scripts/lib/issue-budget.mjs';
|
|
1011
|
+
const state = readBudgetState(repoRoot, sessionId); // { sessionId, count, exempt, overflow: [...] }
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
- **`issue-budget.overflow: collect-issue` (default)** — create exactly ONE issue:
|
|
1015
|
+
- Title: `[Backlog-Sammel] <session-id>, <N> zurückgestellte Punkte`
|
|
1016
|
+
- Labels: `type::backlog`, `priority::low`
|
|
1017
|
+
- Body: a Markdown checklist with one `- [ ]` line per `overflow[]` entry (`title` when present, otherwise the truncated `command`, plus its `at` timestamp).
|
|
1018
|
+
- This collector issue is itself EXEMPT from the cap (`[Backlog-Sammel]` is in the exemption list in `scripts/lib/issue-budget.mjs`), so it always lands even at count == max.
|
|
1019
|
+
- **`issue-budget.overflow: vault-note`** — create NO issue. Write one Markdown file `vault/00-inbox/<session-id>-backlog-sammel.md` (path relative to `vault-integration.vault-dir`) with valid vault frontmatter and the same checklist body.
|
|
1020
|
+
- After the artefact exists, reset `overflow` to `[]` in the counter file and record the collector issue ID / note path in the Phase 6 Final Report under `### Zurückgestellt (issue-budget)`.
|
|
1021
|
+
- **Never exempt-by-accident:** the cap never applied to `priority::critical`, the carryover class (`[Carryover]`, SPIRAL/FAILED, `type::carryover`), or `broken-window` closure issues, so nothing on the Phase 1.65 carry-list can ever appear in `overflow[]`. The promises at Phase 1.8 ("SPIRAL / FAILED agent carryover … non-deselectable") and the Critical Rule "ALWAYS create issues for unfinished PLANNED work" stay intact by construction.
|
|
1022
|
+
- Fail-open: a missing or malformed counter file means "no overflow" — log a WARN and continue the close.
|
|
1023
|
+
|
|
933
1024
|
#### Discovery Issue Creation (if discovery ran in Phase 1.5)
|
|
934
1025
|
|
|
935
1026
|
For each finding with severity `critical` or `high` from Phase 1.5:
|
|
936
1027
|
1. Create a VCS issue using the detected platform CLI:
|
|
937
1028
|
- Title: `[Discovery] <description>` (truncated to 70 chars)
|
|
938
1029
|
- Body: `**Probe:** <probe>\n**File:** <file>:<line>\n**Severity:** <severity>\n**Confidence:** <confidence>%\n**Recommendation:** <recommendation>`
|
|
939
|
-
- Labels: `type:discovery`, `priority
|
|
1030
|
+
- Labels: `type:discovery`, `priority::<severity>` (critical→critical, high→high)
|
|
940
1031
|
2. Log each created issue ID for the Final Report
|
|
941
1032
|
3. Update `discovery_stats.issues_created` count
|
|
942
1033
|
|
|
@@ -978,7 +1069,7 @@ Present to the user:
|
|
|
978
1069
|
- Wave 1 (Discovery): [duration] — [N agents] — [K files]
|
|
979
1070
|
- Wave 2 (Impl-Core): [duration] — [N agents] — [K files]
|
|
980
1071
|
- ...
|
|
981
|
-
- Tests: [passing/total]
|
|
1072
|
+
- Tests: [passing/total] · Δ this session: +[added] / −[removed] / ~[consolidated] · tests:src LOC ratio [x.xx] (advisory ceiling 1.60)
|
|
982
1073
|
- TypeScript: 0 errors
|
|
983
1074
|
- Commits: [N] pushed to [branch]
|
|
984
1075
|
- Mirror: [synced/skipped]
|
|
@@ -1000,6 +1091,8 @@ Present to the user:
|
|
|
1000
1091
|
- Notes: [any context for next session]
|
|
1001
1092
|
```
|
|
1002
1093
|
|
|
1094
|
+
> **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 `wc -l` over `tests/` divided by `wc -l` over `scripts/` + `hooks/`. 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.
|
|
1095
|
+
|
|
1003
1096
|
> **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.
|
|
1004
1097
|
|
|
1005
1098
|
## Sub-File Reference
|
|
@@ -1016,6 +1109,7 @@ Present to the user:
|
|
|
1016
1109
|
| `learning-patterns.md` | Phases 3.5a + 3.6 extraction heuristics, confidence updates, passive decay, and JSONL write procedure |
|
|
1017
1110
|
| `phase-3-6-tail.md` | Phase 3.6.x tail — full unabridged detail procedures for all six tail phases: 3.6.3 Memory-Proposals Collection (`collectProposals` + AUQ multiSelect + `promoteAndClear`, composing `writeApproved` + `clearProposalsJsonl` behind a mechanical write-before-clear guard, #828), 3.6.4 Expired-Learnings Sweep (Epic #723 B4), 3.6.5 Auto-Dream nudge (`shouldDispatchAutoDream`, #614), 3.6.6 Skill-Applied Judge (#645 L3 — `runSkillJudge`, coordinator-writes), 3.6.7 Auto-Dialectic nudge (`shouldDispatchAutoDialectic`, #614), 3.6.8 Reconciliation Rule Proposals (#696 FA3 — `runReconcile` + AUQ + `writeApprovedRules`). Loaded on demand by the SKILL.md skip-plan dispatcher (#724) — only phases with `run: true` in the `planTailPhases()` plan execute |
|
|
1018
1111
|
| `scripts/lib/session-end/phase-skip.mjs` | Phase 3.6.x tail skip-plan aggregator (#724) — `planTailPhases({repoRoot, config, sessionId, platform})` → `{plan, skippedReport}`; side-effect-free (reconcile/sweep via dry-run — no writes), never-throws (per-phase probe error fail-opens to `run: true`); wraps the six existing signal helpers with config gates first, then input detection |
|
|
1112
|
+
| (inline) Phase 3.45 | Telemetry Flush (advisory, #844) — `flush()` from `scripts/lib/telemetry/sync.mjs` drains the host-local send-queue fire-and-forget; no config key (send-gate is `resolveConsent()` inside the module, fail-closed); skip when `persistence: false`; never-throw + ~3s-bounded, offline → bounded oldest-dropped queue, optional `Telemetry: sent/queued/gated` close-summary line, NEVER an error banner; runs late in the close after Phase 3.7 |
|
|
1019
1113
|
| `session-metrics-write.md` | Phase 3.7 JSONL append, vault-mirror invocation, durable narrative mirror (`mirrorNarrative`, #675), and behavior matrix |
|
|
1020
1114
|
| `phase-3-7a-recommendations.md` | Phase 3.7a full procedural body — computeV0Recommendation call, STATE.md field write, data source guarantee, error mode |
|
|
1021
1115
|
| `phase-3-7a-recommendations.md` § 3.7b | Phase 3.7b full procedural body — `withDurableCommit` invocation for `sessions.jsonl` + `STATE.md` (#490 AC2), `enabled:false` local no-op, autopilot.jsonl exclusion note |
|
|
@@ -1037,7 +1131,7 @@ Present to the user:
|
|
|
1037
1131
|
- **NEVER commit with TypeScript errors** — 0 errors is non-negotiable
|
|
1038
1132
|
- **NEVER use `git add .`** — stage files individually to avoid capturing parallel session work
|
|
1039
1133
|
- **NEVER skip issue updates** — VCS must reflect reality after every session
|
|
1040
|
-
- **ALWAYS create issues for unfinished PLANNED work** — SPIRAL/FAILED agent carryover and partially-done plan items (Phase 1.2 / 1.6) ALWAYS get a ticket; nothing planned-but-unfinished is "remembered" without one.
|
|
1134
|
+
- **ALWAYS create issues for unfinished PLANNED work** — SPIRAL/FAILED agent carryover and partially-done plan items (Phase 1.2 / 1.6) ALWAYS get a ticket; nothing planned-but-unfinished is "remembered" without one. The `issue-budget` per-session cap does NOT weaken this: `priority::critical`, the carryover class (`[Carryover]`, `[SPIRAL]`/`[FAILED]`, `type::carryover`, bare `carryover`) and `broken-window` closure issues are exempt from the cap by construction (`scripts/lib/issue-budget.mjs` `EXEMPT_RULES`). Non-exempt over-cap creations are not dropped either — they are parked and folded into one `[Backlog-Sammel]` collector in Phase 5 Step 3b.
|
|
1041
1135
|
- **DO NOT auto-file MED/LOW review findings as issues** — newly-surfaced reviewer findings (Phase 1.8 / W4 panel) at MED or LOW severity are folded in-session or recorded in the Final Report under "Unresolved Review Findings". Only HIGH+/blocking review findings get an issue. (Issue #617 — stops the self-referential low-priority backlog.)
|
|
1042
1136
|
- **ALWAYS push to origin** — local-only work is lost work
|
|
1043
1137
|
- **ALWAYS mirror to GitHub** if configured — keep mirrors in sync
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
Resolve the effective `discovery-on-close` value:
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
effectiveDiscoveryOnClose = config.discoveryOnClose ??
|
|
11
|
+
effectiveDiscoveryOnClose = config.discoveryOnClose ?? true
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
- If the user has set `discovery-on-close` explicitly in Session Config, that value always wins (backward compatible).
|
|
15
|
-
- If the field is absent (not configured), the default is
|
|
15
|
+
- If the field is absent (not configured), the default is `true` for **every** session type.
|
|
16
|
+
|
|
17
|
+
**Why this is no longer session-type aware (2026-07-29).** The previous default was `false` for `housekeeping` and `true` for `feature`/`deep`, on the theory that housekeeping is lightweight and does not need a scan. Measurement inverted that theory: a read-only diagnostic run of the housekeeping flow across six real consumer repos found the discovery probes are the only substantial project-hygiene surface in the whole system — session-start Phase 4 runs 13 probes, of which exactly two (`ci-status`, `project-hygiene`) inspect the project rather than this tool's own substrate. Defaulting the scan OFF for housekeeping meant the one session type whose entire purpose is cleanup was also the only one running without hygiene diagnostics. Repos that genuinely want the faster close still set `discovery-on-close: false` explicitly, which continues to win.
|
|
16
18
|
|
|
17
19
|
If `effectiveDiscoveryOnClose` is `false`, skip this section.
|
|
18
20
|
|
|
@@ -55,8 +55,8 @@ fi
|
|
|
55
55
|
**Reporting rules:**
|
|
56
56
|
|
|
57
57
|
- **`mode: off`** — checker reports `status: skipped-mode-off`; include a single line "CLAUDE.md drift: skipped (mode=off)" in the quality gate report. Never blocks.
|
|
58
|
-
- **`mode: warn`** — checker always exits 0. If `.errors | length > 0`, surface the list in the report under "CLAUDE.md drift warnings (mode=warn)" with check + file:line + message for each entry. Also list any `.warnings` (e.g. `#NN` the checker could not resolve via glab). Never blocks close; note that `mode:
|
|
59
|
-
- **`mode:
|
|
58
|
+
- **`mode: warn`** — checker always exits 0. If `.errors | length > 0`, surface the list in the report under "CLAUDE.md drift warnings (mode=warn)" with check + file:line + message for each entry. Also list any `.warnings` (e.g. `#NN` the checker could not resolve via glab). Never blocks close; note that `mode: strict` would have routed the same errors through the carryover path below.
|
|
59
|
+
- **`mode: strict`** (legacy alias `hard`, normalized to `strict` at parse time — #217) — checker exits 1 on errors. On exit 1: do NOT block the close. Surface the full error list, then default to **warn + carryover + continue** (Recommended): file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] CLAUDE.md drift (strict) — <E> errors` capturing the drift items for a follow-up session, log a Deviation entry in STATE.md `## Deviations`, then continue the close. Offer "Override and close" (continue without a carryover issue; log the Deviation) as an alternative via AskUserQuestion. The user can also (a) fix the drift directly in `CLAUDE.md` (or `AGENTS.md` on Codex CLI) / `_meta/`, or (b) temporarily set `mode: warn` while backfilling, or (c) disable a specific check via its `check-*` flag if it reports false positives on this codebase.
|
|
60
60
|
- **Exit 2** (infra error — missing `node`, unreadable `VAULT_DIR`, malformed args) — treat as a skipped gate with a loud warning ("CLAUDE.md drift: infrastructure error — <reason>"). Do NOT block the session close on infra failures.
|
|
61
61
|
|
|
62
62
|
**Exit-code dispatch:** The checker writes infra-error JSON to stderr (suppressed by `2>/dev/null` above), so `DC_JSON` is empty when `DC_EXIT == 2`. Always branch on `DC_EXIT` first, then `DC_STATUS`:
|
|
@@ -64,8 +64,8 @@ fi
|
|
|
64
64
|
```bash
|
|
65
65
|
if [[ "$DC_EXIT" == "2" ]]; then
|
|
66
66
|
# infra error — DC_JSON is empty, stderr was suppressed. Surface loud warning, do not block.
|
|
67
|
-
elif [[ "$DC_STATUS" == "invalid" && "$DC_MODE" == "hard" ]]; then
|
|
68
|
-
#
|
|
67
|
+
elif [[ "$DC_STATUS" == "invalid" && ( "$DC_MODE" == "strict" || "$DC_MODE" == "hard" ) ]]; then
|
|
68
|
+
# strict-mode (legacy alias `hard`): surface + warn + carryover + continue (no hard block — #724)
|
|
69
69
|
elif [[ "$DC_STATUS" == "invalid" ]]; then
|
|
70
70
|
# warn-mode report
|
|
71
71
|
else
|
|
@@ -169,6 +169,19 @@ Finalize session metrics by reading the wave data accumulated during execution:
|
|
|
169
169
|
> - `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.
|
|
170
170
|
> - `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.
|
|
171
171
|
> - `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).
|
|
172
|
+
> - `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".
|
|
173
|
+
> **`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:
|
|
174
|
+
>
|
|
175
|
+
> ```bash
|
|
176
|
+
> jq -c --argjson w N --arg s "<semantic_session_id>" '
|
|
177
|
+
> select(.event | startswith("orchestrator.quality_gate."))
|
|
178
|
+
> | select(.semantic_session_id == $s and .wave_number == $w and .counts != null)
|
|
179
|
+
> | .counts' .orchestrator/metrics/events.jsonl | tail -1
|
|
180
|
+
> ```
|
|
181
|
+
>
|
|
182
|
+
> Filtering by `semantic_session_id` is mandatory — `events.jsonl` accumulates across sessions and every past session also had a wave `N`. Take the LAST matching record (the wave's final gate run); `counts.passed` → `suite_passed`, `counts.failed` → `suite_failed`. No match = the field was not measured for that wave → omit, never zero-fill (the producer already omits `counts` rather than zero-filling when a run fail-fast'd before the test gate).
|
|
183
|
+
> **Fallback, still live:** when no event matches, fall back to the STATE.md Wave History header `— suite <passed>/<failed> on <platform>` (written by wave-executor §3a since #944). Three cases genuinely need it: pre-#954 sessions, a gate run outside the `run-quality-gate.mjs` wrapper, and the `verification-auto-fix` producer in `scripts/lib/quality-gate.mjs`, which emits `counts` but no `wave_number` (its records are mid-wave retries, so not matching the selector is correct).
|
|
184
|
+
> **`suite_platform` has no event source at all** and is read from the STATE.md header, unchanged. **Remaining work to retire the prose path fully:** (1) carry the platform on the gate event payload; (2) once a session has landed with the event path green and no fallback hits, drop the hand-written trio from `wave-loop.md` step 7. Until both hold, the trio stays written — deleting the writer before the reader is proven loses the numbers for sessions in flight.
|
|
172
185
|
> - `open_questions_asked` / `open_questions_answered` / `open_questions_deferred` (#773): the three open-question counts from the Phase 1.65 gate's AUQ Call 2 (identical to the `questions_*` payload fields on the `orchestrator.handover.gated` event). Top-level, additive, non-negative integers. Populate ONLY when the gate ran an interactive triage ("Closen + Triage" path). OMIT all three (do NOT write `0`) when the gate was skipped (fail-open / headless / disabled) or took the fast-path — absent = "not measured", `0` = "measured, zero questions". Validator accepts absent/null/non-negative-integer.
|
|
173
186
|
> - `stagnation_events`: populated ONLY when ≥1 stagnation event was logged to `events.jsonl` during this session. When `total == 0`, the field is omitted from the JSONL entry.
|
|
174
187
|
> - `grounding_injections`: populated ONLY when ≥1 `orchestrator.grounding.injected` event was logged to `events.jsonl` during this session. When `count == 0`, the field is omitted from the JSONL entry.
|
|
@@ -110,7 +110,7 @@ Read `docs-orchestrator.mode` from Session Config (default: `warn`).
|
|
|
110
110
|
|
|
111
111
|
Gap tasks: <list task IDs and target-patterns>
|
|
112
112
|
```
|
|
113
|
-
- On "Warn + carryover and close": file a carryover issue (labels `carryover`, `priority
|
|
113
|
+
- On "Warn + carryover and close": file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] Documentation gaps (strict) — <gap-count> tasks` listing the gap task IDs + target-patterns for a follow-up session, log the deviation (below), then append the report and continue the close.
|
|
114
114
|
- On "Override": log a deviation in the `## Deviations` section of STATE.md:
|
|
115
115
|
```
|
|
116
116
|
- [Phase 3.2] docs-orchestrator strict-mode gaps overridden by user. Tasks: <ids>. Timestamp: <ISO 8601>.
|
|
@@ -241,7 +241,7 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
241
241
|
|
|
242
242
|
#### Coordinator-direct procedure
|
|
243
243
|
|
|
244
|
-
1. Read Session Config: `reconcile.enabled` (default `false`), `reconcile['rule-expiry-days']` (default `null` — falls back to per-type TTL in the engine), `reconcile['confidence-floor']` (default `0.5`), `reconcile['min-rule-days']` (default `7` — floor window (days) applied to a proposed rule's `expires-at` so a near-dead or already-elapsed natural expiry never produces a born-dead rule, issue #741.1), `reconcile['min-insight-chars']` (default `24` — opt-in minimum insight length gating the eligibility placeholder-insight check, issue #741.2). If `reconcile.enabled` is not `true`, log `reconcile: disabled (reconcile.enabled=false)` and skip all remaining steps.
|
|
244
|
+
1. Read Session Config: `reconcile.enabled` (default `false`), `reconcile['rule-expiry-days']` (default `null` — falls back to per-type TTL in the engine), `reconcile['confidence-floor']` (default `0.5`), `reconcile['min-rule-days']` (default `7` — floor window (days) applied to a proposed rule's `expires-at` so a near-dead or already-elapsed natural expiry never produces a born-dead rule, issue #741.1), `reconcile['min-insight-chars']` (default `24` — opt-in minimum insight length gating the eligibility placeholder-insight check, issue #741.2), `reconcile['max-proposals-per-run']` (default `10` — volume brake, issue #900 D; the engine sorts eligible learnings by confidence DESC and proposes at most this many per run). If `reconcile.enabled` is not `true`, log `reconcile: disabled (reconcile.enabled=false)` and skip all remaining steps.
|
|
245
245
|
|
|
246
246
|
2. Invoke `runReconcile` from `scripts/lib/reconcile/engine.mjs`:
|
|
247
247
|
|
|
@@ -252,6 +252,7 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
252
252
|
ruleExpiryDays: config.reconcile['rule-expiry-days'] ?? undefined,
|
|
253
253
|
minRuleDays: config.reconcile['min-rule-days'] ?? undefined,
|
|
254
254
|
minInsightChars: config.reconcile['min-insight-chars'] ?? undefined,
|
|
255
|
+
maxProposalsPerRun: config.reconcile['max-proposals-per-run'] ?? undefined,
|
|
255
256
|
now: new Date(),
|
|
256
257
|
});
|
|
257
258
|
```
|
|
@@ -43,13 +43,16 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
43
43
|
- Test files (`*.test.*`, `*.spec.*`, `**/__tests__/**`) corresponding to a touched production file are reclassified as expected (not scope creep)
|
|
44
44
|
- Generated/lock files (`pnpm-lock.yaml`, `*.lock`, `dist/**`, `node_modules/**`) are excluded from both planned and actual sets
|
|
45
45
|
- The `.claude/`, `.codex/`, and `.cursor/` state directories are excluded — they are session artifacts, not code
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
> **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).
|
|
48
|
+
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:
|
|
47
49
|
```
|
|
48
50
|
File-level grounding:
|
|
49
51
|
- Planned: N files
|
|
50
52
|
- Touched: N files (X% coverage)
|
|
51
53
|
- Unplanned (scope creep): N files [list first 5]
|
|
52
54
|
- Untouched (planned but not edited): N files [list first 5]
|
|
55
|
+
- Scope drift: filesRatio X.X (Y actual / Z planned, threshold 2.0) — [breached | ok | skipped: <reason>]
|
|
53
56
|
```
|
|
54
57
|
6. **Append to session metrics** (`grounding` field in the Phase 1.7 JSONL entry):
|
|
55
58
|
```json
|
|
@@ -66,7 +69,7 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
66
69
|
- Document what was completed and what remains
|
|
67
70
|
- Create a VCS issue for the remaining work with:
|
|
68
71
|
- Title: `[Carryover] <original task description>`
|
|
69
|
-
- Labels: `priority
|
|
72
|
+
- Labels: `priority::<original>`, `status:ready`
|
|
70
73
|
- Description: what's done, what's left, context for next session
|
|
71
74
|
- Link to original issue if applicable
|
|
72
75
|
|
|
@@ -41,7 +41,7 @@ fi
|
|
|
41
41
|
|
|
42
42
|
- **`mode: off`** — validator reports `status: skipped-mode-off`; include a single line "Vault validation: skipped (mode=off)" in the quality gate report and move on. Never blocks.
|
|
43
43
|
- **`mode: warn`** — validator always exits 0. If `.errors | length > 0`, surface the error list in the report under "Vault validation warnings (mode=warn)" with file + path + message for each entry. Also list any `.warnings` (dangling wiki-links) in the same section. Never blocks close, but remind the user that flipping to `mode: hard` would have blocked on N files.
|
|
44
|
-
- **`mode: hard`** — validator exits 1 on errors. On exit 1: do NOT block the close. Surface the full error list in the quality gate report, then default to **warn + carryover + continue** (Recommended): file a carryover issue (labels `carryover`, `priority
|
|
44
|
+
- **`mode: hard`** — validator exits 1 on errors. On exit 1: do NOT block the close. Surface the full error list in the quality gate report, then default to **warn + carryover + continue** (Recommended): file a carryover issue (labels `carryover`, `priority::high`) titled `[Carryover] Vault validation (hard) — <N> frontmatter errors` capturing the offending files for a follow-up session, log a Deviation entry in STATE.md `## Deviations`, then continue the close. Offer "Override and close" (continue without a carryover issue; log the Deviation) as an alternative via AskUserQuestion. The user can also (a) fix the offending frontmatter, (b) add the file pattern to `vault-sync.exclude` if it is a legitimate index file (e.g. `_MOC.md`, `_overview.md`), or (c) temporarily set `vault-sync.mode: warn` while backfilling frontmatter across the vault. On exit 0 with warnings: include them in the report but do not block.
|
|
45
45
|
- **Exit 2** (infra error — missing `node`, `pnpm`, or `validator.mjs`) — treat as a skipped gate with a loud warning ("Vault validation: infrastructure error — <reason>"). Do NOT block the session close on infra failures; the goal is to surface configuration problems, not to wedge sessions when Node is unavailable.
|
|
46
46
|
|
|
47
47
|
**Success line format** (when `errors: [] && warnings: []`):
|
|
@@ -180,7 +180,7 @@ For each task from Step 1, assign exactly one role. Use these signal-to-role map
|
|
|
180
180
|
**Disambiguation rules:**
|
|
181
181
|
- If a task involves BOTH exploration AND implementation → split it: Discovery agent reads/validates, Impl-Core agent implements. Create two separate task entries.
|
|
182
182
|
- If a task is "fix something from a previous session" (not from this session's Impl-Core) → classify as **Impl-Core** (it is new work for this session).
|
|
183
|
-
-
|
|
183
|
+
- A "write tests for new feature code being built this session" task is created ONLY when Discovery or a qa-strategist run reported a **named gap** — a concrete bug or regression the current suite would let through, stated as such. When that gap exists, classify the task as **Quality** (not Impl-Core); tests run after implementation. "Feature X was built" is NOT by itself evidence of test demand: with no named gap, no Quality task is created — do not synthesize one to give the role something to do. A dispatched `test-writer` may correspondingly report `no-tests-needed` as a SUCCESS status, not a failure.
|
|
184
184
|
- If unsure between Impl-Core and Impl-Polish → if the task is on the critical path (other tasks depend on it), it is **Impl-Core**. If independent polish, it is **Impl-Polish**.
|
|
185
185
|
- **Docs role** is only active when `docs-orchestrator.enabled: true` in Session Config. When disabled (default), documentation-update tasks fall into **Impl-Polish** (inline doc changes alongside code) or **Finalization** (standalone doc/SSOT updates) as today.
|
|
186
186
|
|
|
@@ -359,7 +359,7 @@ When `docs-orchestrator.enabled: true`, apply the following concrete dispatch ru
|
|
|
359
359
|
- Output: Validated understanding, updated task scope if discoveries warrant it
|
|
360
360
|
- Tools: Read, Grep, Glob, Bash (read-only commands only) — do NOT use Edit or Write
|
|
361
361
|
- Scope enforcement: set `allowedPaths` to `[]` (empty) for Discovery waves. Include in agent prompts: "You are READ-ONLY. Do NOT use Edit or Write tools."
|
|
362
|
-
- Distributional claims MUST follow `.claude/rules/parallel-sessions.md` § PSA-006 — quote the executed
|
|
362
|
+
- Distributional claims AND bare repo-state numbers MUST follow `.claude/rules/parallel-sessions.md` § PSA-006 — quote the executed command + file scope + count + WHEN it was measured. Coordinators REJECT Discovery outputs that assert "N of M" / "100% of X" (deep-1647 W1-D3 incident class) or a bare count like "14 commits" / "92 learnings" (#908) without that evidence. Discovery facts age: re-verify a count before re-briefing it into a later wave.
|
|
363
363
|
|
|
364
364
|
**Impl-Core**
|
|
365
365
|
- Full implementation agents with Write/Edit/Bash access
|
|
@@ -422,6 +422,10 @@ Score the session scope to determine optimal agent counts per wave. Skip for hou
|
|
|
422
422
|
|
|
423
423
|
> Housekeeping sessions skip Discovery (tasks are predefined) and use fixed agent counts regardless of complexity.
|
|
424
424
|
|
|
425
|
+
> **The Quality column is a CAP, not a target.** Every other column sizes to briefed work; the Quality column historically sized to the tier alone, so capacity went looking for work (tests written because a slot existed, not because a gap was measured). Quality capacity must be EARNED by measured demand. Compute the effective count as `min(<tier cap>, ceil((HIGH + MED gaps from the most recent qa-strategist run) / 3))`.
|
|
426
|
+
> - **0 HIGH and 0 MED gaps → the Quality role has 0 test-writing tasks**, and its wave is skipped by the Step 2 empty-role rule. This does NOT touch the read-only review panel (security-reviewer / qa-strategist / architect-reviewer) — that panel reviews, it does not write tests, and it keeps running as configured.
|
|
427
|
+
> - **No qa-strategist signal at all** (no prior measurement this session): allocate a conservative 1-2 test-writers. Never spend the full tier cap blind — an unmeasured tier cap is a guess, and the guess has historically been too high.
|
|
428
|
+
|
|
425
429
|
The `agents-per-wave` Session Config value caps the maximum regardless of tier.
|
|
426
430
|
|
|
427
431
|
If project intelligence (learnings) suggests different sizing based on historical data, prefer the historical recommendation over the formula.
|
|
@@ -32,6 +32,8 @@ For each wave, define agents with:
|
|
|
32
32
|
|
|
33
33
|
Read `agents-per-wave` from Session Config to cap the maximum.
|
|
34
34
|
|
|
35
|
+
> **The Quality column is a CAP, not a target.** Quality capacity is need-gated: the effective count is `min(<column cap>, ceil((HIGH + MED gaps from the most recent qa-strategist run) / 3))`. 0 gaps → 0 test-writing tasks and the wave is skipped (the read-only review panel is unaffected); no qa-strategist signal at all → a conservative 1-2, never the blind cap. Full rule: `SKILL.md` § Agent Count by Tier footnote.
|
|
36
|
+
|
|
35
37
|
> **Note:** For feature and deep sessions, prefer the complexity-based agent counts from Step 3. This table provides defaults when complexity scoring is skipped (housekeeping) or as a fallback.
|
|
36
38
|
|
|
37
39
|
> \* Housekeeping sessions use single-wave serial execution (see wave-executor). Agent counts are for the single consolidated wave, not per-role.
|