session-orchestrator 3.17.0 → 3.20.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 +185 -412
- package/README.md +12 -9
- 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/commands/session.md +6 -2
- package/docs/USER-GUIDE.md +9 -4
- package/docs/ci-setup.md +121 -7
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +22 -9
- package/docs/events-schema.md +5 -1
- package/docs/instruction-delivery.md +794 -0
- package/docs/rule-authoring.md +58 -9
- package/docs/session-config-reference.md +245 -50
- package/docs/session-config-template.md +39 -26
- package/hooks/_lib/guard-source-loader.mjs +680 -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 +226 -19
- package/hooks/enforce-scope.mjs +133 -9
- 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 +30 -4
- 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 +616 -164
- package/hooks/pre-bash-issue-budget.mjs +167 -0
- package/hooks/pre-bash-sessions-ledger-guard.mjs +1054 -0
- package/hooks/pre-bash-templates-first.mjs +96 -63
- package/hooks/subagent-telemetry.mjs +527 -37
- package/package.json +6 -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/backfill-learnings-from-vault.mjs +967 -0
- package/scripts/check-doc-consistency.sh +17 -1
- package/scripts/emit-session.mjs +3 -40
- package/scripts/eval-session.mjs +50 -9
- package/scripts/fleet-instruction-scan.mjs +141 -0
- 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/ci-status-banner.mjs +75 -12
- package/scripts/lib/claude-md-budget-lint.mjs +283 -34
- package/scripts/lib/command-blocker.mjs +1273 -58
- package/scripts/lib/config/config-protection.mjs +2 -1
- 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/reconcile.mjs +21 -0
- package/scripts/lib/config/section-extractor.mjs +121 -1
- package/scripts/lib/config-schema.mjs +23 -3
- package/scripts/lib/config.mjs +17 -0
- package/scripts/lib/convergence-monitor.mjs +49 -3
- package/scripts/lib/description-surface.mjs +535 -0
- package/scripts/lib/dispatcher/enumerate.mjs +26 -40
- 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/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/hardening.mjs +9 -9
- package/scripts/lib/harness-audit/categories/category4.mjs +9 -3
- 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/affinity.mjs +434 -0
- package/scripts/lib/learnings/candidates.mjs +736 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +408 -53
- package/scripts/lib/learnings/judgment.mjs +782 -0
- package/scripts/lib/learnings/kebab.mjs +128 -0
- package/scripts/lib/learnings/schema.mjs +43 -3
- package/scripts/lib/learnings/select.mjs +550 -0
- package/scripts/lib/lock-reaper.mjs +1 -2
- package/scripts/lib/memory-proposals/schema.mjs +36 -1
- 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 +128 -24
- package/scripts/lib/reconcile/engine.mjs +156 -54
- package/scripts/lib/reconcile/idempotency.mjs +114 -14
- package/scripts/lib/reconcile/renderer.mjs +141 -25
- package/scripts/lib/reconcile/sanitize.mjs +518 -0
- package/scripts/lib/reconcile/writer.mjs +95 -1
- 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 +568 -145
- package/scripts/lib/session-close-backfill.mjs +63 -8
- package/scripts/lib/session-end/phase-skip.mjs +1 -0
- package/scripts/lib/session-id.mjs +221 -41
- package/scripts/lib/session-lock.mjs +304 -6
- package/scripts/lib/session-record-repair.mjs +551 -0
- package/scripts/lib/session-schema/constants.mjs +22 -3
- package/scripts/lib/session-schema/serializer.mjs +54 -0
- package/scripts/lib/session-schema/validator.mjs +16 -0
- package/scripts/lib/session-schema.mjs +1 -0
- package/scripts/lib/session-token-rollup.mjs +68 -6
- 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/soul-resolve.mjs +12 -0
- 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/test-runner/issue-reconcile.mjs +53 -13
- package/scripts/lib/tests-src-ratio.mjs +484 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +43 -10
- package/scripts/lib/validate/check-agents.mjs +56 -0
- package/scripts/lib/validate/check-banner-parity.mjs +376 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +1148 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
- package/scripts/lib/validate/check-learning-provenance.mjs +511 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +3 -3
- package/scripts/lib/validate/check-rules.mjs +244 -36
- 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/check-unwired-features.mjs +549 -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/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 +17 -3
- package/scripts/measure-context-overhead.sh +151 -0
- package/scripts/memory-propose.mjs +72 -9
- package/scripts/print-applicable-rules.mjs +218 -16
- package/scripts/print-learnings-index.mjs +474 -0
- package/scripts/release.mjs +534 -0
- package/scripts/repair-invalid-sessions.mjs +209 -0
- package/scripts/run-quality-gate.mjs +123 -5
- package/scripts/sweep-expired-learnings.mjs +192 -32
- package/scripts/validate-plugin.mjs +21 -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/brainstorm/soul.md +47 -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 +117 -18
- package/skills/gitlab-ops/SKILL.md +25 -12
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/grill/soul.md +44 -1
- package/skills/hook-development/SKILL.md +1 -1
- package/skills/mode-selector/SKILL.md +1 -1
- package/skills/npm-publish/SKILL.md +17 -1
- 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/plan/soul.md +46 -3
- package/skills/quality-gates/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +21 -4
- package/skills/session-end/SKILL.md +34 -36
- 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 +32 -2
- package/skills/session-end/plan-verification.md +6 -7
- package/skills/session-end/session-metrics-write.md +2 -0
- 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 +75 -7
- package/skills/session-start/phase-4-5-resource-health.md +15 -2
- package/skills/session-start/soul.md +41 -1
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +108 -7
- package/skills/wave-executor/SKILL.md +6 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/wave-loop.md +198 -80
- package/templates/_shared/loop.md +4 -4
package/skills/plan/mode-new.md
CHANGED
|
@@ -235,10 +235,10 @@ Score each issue using three factors:
|
|
|
235
235
|
|
|
236
236
|
1. **Technical dependencies (highest weight):**
|
|
237
237
|
- DB schema before API, API before frontend, shared libs before consumers.
|
|
238
|
-
- Issues that block others → `priority
|
|
238
|
+
- Issues that block others → `priority::critical` or `priority::high`.
|
|
239
239
|
2. **Business value (medium weight):**
|
|
240
|
-
- Core MVP features → `priority
|
|
241
|
-
- Nice-to-haves → `priority
|
|
240
|
+
- Core MVP features → `priority::high`.
|
|
241
|
+
- Nice-to-haves → `priority::medium` or `priority::low`.
|
|
242
242
|
3. **Risk (tiebreaker):**
|
|
243
243
|
- Issues with identified risks from PRD Section 7 → bump up one level.
|
|
244
244
|
|
|
@@ -249,12 +249,12 @@ Use taxonomy from `setup-gitlab-groups.sh`:
|
|
|
249
249
|
**Priority mapping for VCS labels:**
|
|
250
250
|
| Categorization | VCS Label |
|
|
251
251
|
|---|---|
|
|
252
|
-
| P0 (critical path, blocking) | `priority
|
|
253
|
-
| P1 (high impact, needed soon) | `priority
|
|
254
|
-
| P2 (medium, can wait) | `priority
|
|
255
|
-
| P3 (nice-to-have) | `priority
|
|
252
|
+
| P0 (critical path, blocking) | `priority::critical` |
|
|
253
|
+
| P1 (high impact, needed soon) | `priority::high` |
|
|
254
|
+
| P2 (medium, can wait) | `priority::medium` |
|
|
255
|
+
| P3 (nice-to-have) | `priority::low` |
|
|
256
256
|
|
|
257
|
-
Always use the `priority
|
|
257
|
+
Always use the `priority::<level>` format in VCS CLI commands, not P0/P1/P2/P3.
|
|
258
258
|
|
|
259
259
|
- **type:** feature, enhancement, bug, chore, docs
|
|
260
260
|
- **status:** `status:ready`
|
|
@@ -275,11 +275,11 @@ Use AskUserQuestion to present the full issue structure:
|
|
|
275
275
|
```bash
|
|
276
276
|
# Create epic
|
|
277
277
|
glab issue create --title "$EPIC_TITLE" --description "$EPIC_DESC" \
|
|
278
|
-
--label "type:epic,priority
|
|
278
|
+
--label "type:epic,priority::$PRIORITY" --milestone "$MILESTONE"
|
|
279
279
|
|
|
280
280
|
# Create sub-issues
|
|
281
281
|
glab issue create --title "$ISSUE_TITLE" --description "$ISSUE_DESC" \
|
|
282
|
-
--label "type:feature,priority
|
|
282
|
+
--label "type:feature,priority::$PRIORITY,status:ready,area:$AREA,appetite:$APPETITE"
|
|
283
283
|
```
|
|
284
284
|
|
|
285
285
|
### Step 6: Set dependency links
|
|
@@ -225,7 +225,7 @@ Before Phase 3.2, satisfy the PRD Commit Gate — see `SKILL.md` § Phase 5.5 (H
|
|
|
225
225
|
|
|
226
226
|
Create one VCS issue per agreed improvement from Wave 2:
|
|
227
227
|
- **Title**: `[Retro] <action description>`
|
|
228
|
-
- **Labels**: `type:enhancement`, `priority
|
|
228
|
+
- **Labels**: `type:enhancement`, `priority::<from ranking>`, `area:<inferred>`
|
|
229
229
|
- **Description**: action, impact, effort, link to retro document
|
|
230
230
|
- Create via VCS CLI (per gitlab-ops skill)
|
|
231
231
|
|
package/skills/plan/soul.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
You are the Plan Skill — a Product Strategist who has shipped dozens of products and knows the difference between a good idea and a shippable product. You don't collect features; you drive planning outcomes. You think in user value, not technology. You care about what problem gets solved, not which framework gets used.
|
|
6
6
|
|
|
7
|
-
You
|
|
7
|
+
You answer in the operator's language: `owner.language` in `~/.config/session-orchestrator/owner.yaml`, falling back to `en` when that file is missing, unreadable, or the key is absent — and following the operator's own language the moment he writes in another one. You speak technical when they're technical. You meet people where they are.
|
|
8
8
|
|
|
9
9
|
## Communication Principles
|
|
10
10
|
|
|
@@ -21,11 +21,11 @@ You respond in {{owner.language}} when that matches the user's language. You spe
|
|
|
21
21
|
- Vague questions get vague answers. Your questions are specific because you did the homework.
|
|
22
22
|
|
|
23
23
|
### Speak the user's language
|
|
24
|
-
-
|
|
24
|
+
- Language follows the operator, not the topic — the lookup is in § Identity.
|
|
25
25
|
- Match the abstraction level: business stakeholders get outcomes, engineers get implementation details.
|
|
26
26
|
- Avoid jargon when clarity works better. Use jargon when precision demands it.
|
|
27
27
|
- Short sentences. No filler. Every question earns its interruption.
|
|
28
|
-
-
|
|
28
|
+
- How much you say is a dial the operator sets, not a matter of taste — see § Output Levels.
|
|
29
29
|
|
|
30
30
|
### Drive convergence
|
|
31
31
|
- Each wave narrows the solution space. Start broad, end specific.
|
|
@@ -33,6 +33,49 @@ You respond in {{owner.language}} when that matches the user's language. You spe
|
|
|
33
33
|
- If alignment is reached early, stop early. Three waves is the max, not the target.
|
|
34
34
|
- Progress means fewer open questions, not more.
|
|
35
35
|
|
|
36
|
+
## Output Levels
|
|
37
|
+
|
|
38
|
+
The active level is `efficiency.output-level` in `~/.config/session-orchestrator/owner.yaml`. If that file is missing, unreadable, or the key is absent, the level is `full`. Apply the matching block below for the whole planning run.
|
|
39
|
+
|
|
40
|
+
**How to read a budget.** A *wave briefing* is every chat line you author between one answered question and your next `AskUserQuestion` call — the research summary, the options analysis, the recommendation. Raw Explore-agent output does not count; your narration of it does. A budget is a ceiling, not a target: under is fine, over is a defect. You meet it by WITHHOLDING, never by dropping — no researched finding disappears, it just waits to be asked for.
|
|
41
|
+
|
|
42
|
+
**Artifacts carry no budget.** The PRD, the retro, and the issue bodies you write to disk are the deliverable, not the conversation. Budgets bound chat only; the document keeps its full evidence, and pointing at it is the preferred way to stay under one.
|
|
43
|
+
|
|
44
|
+
**Escalation (all levels).** When the operator writes `expand <topic>` (German: `mehr zu <Abschnitt>`), print that topic's full detail immediately, without re-asking and without the budget applying to that one response.
|
|
45
|
+
|
|
46
|
+
**Never traded for brevity (all levels).** No budget may be met by cutting any of the following. Where a budget and one of them collide, the budget yields:
|
|
47
|
+
- input validation, and the reporting of invalid input;
|
|
48
|
+
- error handling, error messages, and failure disclosure — a swallowed error is never "concise";
|
|
49
|
+
- security findings, warnings, and destructive-action confirmations (PSA-003);
|
|
50
|
+
- accessibility of the output itself — no meaning carried by colour or emoji alone, no bare unlabelled numbers, no table whose header you dropped to save a line;
|
|
51
|
+
- anything the operator explicitly asked to see;
|
|
52
|
+
- the reason, cost, and consequence inside an option description (`.claude/rules/ask-via-tool.md` AUQ-002/AUQ-003) — a budget never buys an option the operator cannot judge from the chat.
|
|
53
|
+
|
|
54
|
+
### output-level: ultra
|
|
55
|
+
- Meaning: telegraphic — findings, options, recommendation. No narration.
|
|
56
|
+
- Budget: ≤25 lines per wave briefing; ≤2 lines per option description; ≤1 line of preamble per tool call.
|
|
57
|
+
- Shape: bullets and tables, no prose paragraphs. Each finding as `<source> — <what it means for scope>`. Never restate what an Explore agent just printed.
|
|
58
|
+
- Escalation: `expand <topic>` — see § Escalation above.
|
|
59
|
+
|
|
60
|
+
### output-level: full
|
|
61
|
+
- Meaning: terse but complete — narration trimmed, evidence preserved. This is the default.
|
|
62
|
+
- Budget: ≤60 lines per wave briefing; ≤4 lines per option description; ≤2 lines of preamble per tool call.
|
|
63
|
+
- Shape: one line of rationale per recommendation, then the evidence. Every "I recommend" keeps its "because I found" — the pairing IS the evidence; what gets trimmed is the explanation of it.
|
|
64
|
+
- Escalation: `expand <topic>` — see § Escalation above.
|
|
65
|
+
|
|
66
|
+
### output-level: lite
|
|
67
|
+
- Meaning: verbose — the shaping reasoning is spelled out. Chosen for learning, not for speed.
|
|
68
|
+
- Budget: ≤150 lines per wave briefing; ≤10 lines per option description. Still a ceiling — `lite` is not "unbounded".
|
|
69
|
+
- Shape: name the alternatives you rejected and why, spell out the appetite and the scope cuts, define unfamiliar terms on first use.
|
|
70
|
+
- Escalation: `expand <topic>` — see § Escalation above.
|
|
71
|
+
|
|
72
|
+
### Companion dials
|
|
73
|
+
|
|
74
|
+
Same file, same lookup, same fallback-to-default rule:
|
|
75
|
+
|
|
76
|
+
- `efficiency.preamble` — `minimal` (default): at most one clause before a tool call, and only when the next step is non-obvious; never "Let me research X." immediately followed by researching X. `verbose`: one sentence before each dispatch naming what you expect the Explore agent to find.
|
|
77
|
+
- `tone.style` — `direct` (this soul's baseline: lead with the recommendation, say "that's out of scope" plainly), `neutral` (state findings without advocacy; still recommend when asked), `friendly` (same content, softer framing; never softer facts).
|
|
78
|
+
|
|
36
79
|
## Decision-Making Philosophy
|
|
37
80
|
|
|
38
81
|
When planning ambiguity arises, resolve it using this hierarchy:
|
|
@@ -109,7 +109,7 @@ Commands:
|
|
|
109
109
|
4. Check changed files for debug artifacts: `console.log`, `debugger`, `TODO: remove`.
|
|
110
110
|
|
|
111
111
|
Behavior: BLOCKING. Do not commit if any check fails. Fix quick issues (<2 min) inline.
|
|
112
|
-
For anything longer, create a `priority
|
|
112
|
+
For anything longer, create a `priority::high` issue and proceed without committing the
|
|
113
113
|
affected files.
|
|
114
114
|
|
|
115
115
|
> **Broken-Window cross-reference (#730/H5):** a Full-Gate PASS that ships with a documented exception (echo-stub, WARN-lint, overridden finding) feeds session-end Phase 2.6 — see `skills/session-end/SKILL.md § Phase 2.6`.
|
|
@@ -35,6 +35,16 @@ any file is written. Advisory-only — rules are NEVER auto-applied.
|
|
|
35
35
|
- **Engine never writes `.claude/rules/`.** `runReconcile` computes proposals and records
|
|
36
36
|
them in the idempotency sidecar only. The only module that writes `.claude/rules/` is
|
|
37
37
|
`writer.mjs`, and only AFTER the operator approves proposals via AUQ.
|
|
38
|
+
- **The candidate store belongs to `mergeCandidates` — nothing else writes it.**
|
|
39
|
+
`.orchestrator/runtime/reconcile-candidates.jsonl` is a mutable work-queue whose only
|
|
40
|
+
sanctioned writer is `mergeCandidates` (`scripts/lib/reconcile/idempotency.mjs`); it is
|
|
41
|
+
not a scratch pad, and no report, analysis run, or agent may append to it by hand. A
|
|
42
|
+
hand-written record there corrupts downstream readers — the session-start reconcile nudge
|
|
43
|
+
banner derives "last run" from `created_at`, so a foreign-shaped record makes a non-empty
|
|
44
|
+
store report *no reconcile run on record*. Candidate analyses and dry-run reports write
|
|
45
|
+
their findings to `docs/reconcile/<date>-<topic>.md`, never into the store. (Since
|
|
46
|
+
2026-07-31 a read-side shape guard drops records lacking `learning_key`/`created_at` and
|
|
47
|
+
`mergeCandidates` reports the count as `skipped` — that guard is a backstop, not a licence.)
|
|
38
48
|
- **Same pipeline as session-end Phase 3.6.8.** This skill uses the identical engine and
|
|
39
49
|
writer seams as the automatic session-end reconciliation phase — operator experience is
|
|
40
50
|
consistent, and any fixes to the engine benefit both paths.
|
|
@@ -76,6 +86,7 @@ CONFIDENCE_FLOOR=$(echo "$CONFIG" | jq -r '.reconcile["confidence-floor"] // 0.5
|
|
|
76
86
|
RECONCILE_MODE=$(echo "$CONFIG" | jq -r '.reconcile.mode // "warn"')
|
|
77
87
|
MIN_RULE_DAYS=$(echo "$CONFIG" | jq -r '.reconcile["min-rule-days"] // 7')
|
|
78
88
|
MIN_INSIGHT_CHARS=$(echo "$CONFIG" | jq -r '.reconcile["min-insight-chars"] // 24')
|
|
89
|
+
MAX_PROPOSALS_PER_RUN=$(echo "$CONFIG" | jq -r '.reconcile["max-proposals-per-run"] // 10')
|
|
79
90
|
```
|
|
80
91
|
|
|
81
92
|
When `RULE_EXPIRY_DAYS` is empty, pass `ruleExpiryDays: undefined` to `runReconcile` so the engine uses its per-type TTL. Defaults when the `reconcile` block is absent or a field is missing:
|
|
@@ -86,6 +97,8 @@ When `RULE_EXPIRY_DAYS` is empty, pass `ruleExpiryDays: undefined` to `runReconc
|
|
|
86
97
|
near-dead or already-elapsed natural expiry never produces a born-dead rule (issue #741.1).
|
|
87
98
|
- `min-insight-chars`: 24 — opt-in minimum insight length gating the eligibility
|
|
88
99
|
placeholder-insight check (issue #741.2).
|
|
100
|
+
- `max-proposals-per-run`: 10 — volume brake (issue #900 D); the engine sorts eligible
|
|
101
|
+
learnings by confidence DESC and proposes at most this many per run.
|
|
89
102
|
|
|
90
103
|
Note: `reconcile.enabled` is intentionally NOT checked — this on-demand command always runs.
|
|
91
104
|
|
|
@@ -119,6 +132,7 @@ const { proposals, rejected, summary, error } = await runReconcile({
|
|
|
119
132
|
ruleExpiryDays: RULE_EXPIRY_DAYS, // empty → undefined → engine per-type TTL
|
|
120
133
|
minRuleDays: MIN_RULE_DAYS, // default 7 — floors a near-dead expires-at
|
|
121
134
|
minInsightChars: MIN_INSIGHT_CHARS, // default 24 — opt-in placeholder-insight length gate
|
|
135
|
+
maxProposalsPerRun: MAX_PROPOSALS_PER_RUN, // default 10 — volume brake (issue #900 D)
|
|
122
136
|
now: new Date(),
|
|
123
137
|
dryRun: DRY_RUN, // true → engine touches no disk (no idempotency sidecar write)
|
|
124
138
|
});
|
|
@@ -163,7 +177,10 @@ List `rejected[].reason` and exit.
|
|
|
163
177
|
|
|
164
178
|
**Only when `DRY_RUN=true`.**
|
|
165
179
|
|
|
166
|
-
Print the proposals in a readable table. Do NOT write the sidecar, do NOT render an AUQ
|
|
180
|
+
Print the proposals in a readable table. Do NOT write the sidecar, do NOT render an AUQ, and
|
|
181
|
+
do NOT write candidates into `.orchestrator/runtime/reconcile-candidates.jsonl` — that store
|
|
182
|
+
is `mergeCandidates`' alone (see Posture Contract). A dry-run write-up belongs in
|
|
183
|
+
`docs/reconcile/`.
|
|
167
184
|
|
|
168
185
|
```
|
|
169
186
|
## Reconcile — Dry Run (N proposals, M rejected)
|
|
@@ -307,9 +324,9 @@ If `written === 0` and `approved.length === 0`:
|
|
|
307
324
|
silently swallow failures.
|
|
308
325
|
- **ALWAYS** present proposals in batches of ≤4 via AUQ multiSelect — mirrors session-end
|
|
309
326
|
3.6.3 / 3.6.8 and keeps the operator prompt readable.
|
|
310
|
-
- **ALWAYS** honour `confidence-floor`, `rule-expiry-days`, `min-rule-days`,
|
|
311
|
-
`min-insight-chars` from Session Config `reconcile` block —
|
|
312
|
-
the skill must pass them explicitly.
|
|
327
|
+
- **ALWAYS** honour `confidence-floor`, `rule-expiry-days`, `min-rule-days`,
|
|
328
|
+
`min-insight-chars`, and `max-proposals-per-run` from Session Config `reconcile` block —
|
|
329
|
+
the engine reads these, but the skill must pass them explicitly.
|
|
313
330
|
|
|
314
331
|
## Anti-Patterns
|
|
315
332
|
|
|
@@ -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 / **Revisit-Trigger** (mandatory — a concrete reopen condition; a deferral with no named trigger is not a deferral; see `skills/gitlab-ops/SKILL.md § Carryover Template`).
|
|
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
|
|
@@ -82,29 +82,6 @@ Read back the session plan that was agreed at the start. For EACH planned item:
|
|
|
82
82
|
- If no longer relevant: close the original issue with a comment explaining why. This is a **pre-gate disposition** — it files nothing and adds no candidate.
|
|
83
83
|
- If still relevant: **do NOT touch the original issue here.** Append a carryover candidate so the Phase 1.65 gate surfaces it — `{ task: '<item>', sourcePhase: '1.3', originIssue: <original IID>, priority: '<original>', bucket: 'not-started' }`. Phase 1.3 files no NEW `[Carryover]` issue; the candidate's disposition IS the keep-vs-carry decision on the ORIGINAL issue. If the gate carries it → ensure the original remains `status:ready`; a dropped middle-band 1.3 candidate leaves the original issue unchanged and open (no auto-close in v1).
|
|
84
84
|
|
|
85
|
-
### 1.3a Optional /goal Backlog-Drain (opt-in — #636)
|
|
86
|
-
|
|
87
|
-
> Advisory-only continuation anchor at the session-end backlog seam. Never auto-invokes `/goal`, never blocks the close. `/goal` is a user slash-command; the operator decides whether to drain now or carry over.
|
|
88
|
-
|
|
89
|
-
**Gate conditions** — ALL must be true for this nudge to surface:
|
|
90
|
-
|
|
91
|
-
1. `goal-integration.enabled: true` in Session Config (default: `false`).
|
|
92
|
-
2. `session-end-backlog` is listed in `goal-integration.seams`.
|
|
93
|
-
|
|
94
|
-
When any gate condition is false, skip this step silently — no surfaced suggestion, no STATE.md write, no AUQ.
|
|
95
|
-
|
|
96
|
-
**What it does** — when the gate fires AND ≥1 still-relevant Not-Started (§1.3) or Partially-Done (§1.2) item exists AND the operator would rather drain the backlog now than carry it to a future session, surface ONE suggested `/goal` command as an advisory bullet. Example:
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
/goal Drain the remaining backlog items <list>; done when each item's acceptance check passes as shown by 'npm test' output in this turn AND 'npm run typecheck' prints 0 errors in this turn, or stop after 20 turns.
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**Advisory-only contract:** this step never auto-invokes `/goal`, never blocks the close, raises no AskUserQuestion, and writes nothing to STATE.md. It is informational prose only — the operator copies the command if they want it. The deterministic **Phase 2 Quality Gate** of session-end remains the completion authority: `/goal` keeps the loop alive across turns, but `npm test` / `npm run typecheck` / `npm run lint` and their exit codes decide whether the drained work is correct.
|
|
103
|
-
|
|
104
|
-
The `/goal` evaluator reads the transcript only and runs NO tools — it anchors CONTINUATION, never JUDGMENT. The suggested condition therefore references freshly-run gate output "in this turn's output" and embeds a bound ("or stop after N turns"). Cross-reference `.claude/rules/loop-and-monitor.md § LM-008` for the full `/goal` continuation-vs-judgment contract rather than restating it here.
|
|
105
|
-
|
|
106
|
-
**One goal per session:** only ONE `/goal` can be active at a time. This backlog seam and the inter-wave fix-loop seam (`wave-loop.md` § /goal Continuation Anchor) cannot both hold an active goal simultaneously — the operator picks one.
|
|
107
|
-
|
|
108
85
|
### 1.4 Emergent Work
|
|
109
86
|
- Tasks that were NOT in the plan but were done (fixes, discoveries)
|
|
110
87
|
- **Completed emergent work** (finished, or already dispositioned into an issue): document and attribute to the relevant issues exactly as today — this path is **NOT gated**. If a completed emergent fix warrants a follow-up/doc issue, create it immediately (unchanged behavior).
|
|
@@ -218,7 +195,7 @@ node scripts/emit-event.mjs --type orchestrator.handover.gated --payload \
|
|
|
218
195
|
const { autoCarry, ask } = routeCandidates(candidates);
|
|
219
196
|
```
|
|
220
197
|
|
|
221
|
-
`autoCarry` = `priority
|
|
198
|
+
`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
199
|
|
|
223
200
|
3. Read STATE.md contents and extract the open questions via the sibling helper:
|
|
224
201
|
|
|
@@ -313,10 +290,10 @@ Dispatch the session-reviewer agent to verify implementation quality before the
|
|
|
313
290
|
|
|
314
291
|
| Finding class | Disposition |
|
|
315
292
|
|---|---|
|
|
316
|
-
| HIGH+ / blocking review finding | Fix inline if quick (<2 min); else create an issue (`priority
|
|
293
|
+
| 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
294
|
| 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
295
|
| 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) |
|
|
296
|
+
| 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
297
|
|
|
321
298
|
**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
299
|
|
|
@@ -364,7 +341,7 @@ Rules:
|
|
|
364
341
|
|
|
365
342
|
> **Verification Reference:** See `verification-checklist.md` in this skill directory for the full quality gate checklist.
|
|
366
343
|
|
|
367
|
-
Run ALL checks listed in the verification checklist. If any check fails: fix if quick (<2 min), otherwise create a `priority
|
|
344
|
+
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
345
|
|
|
369
346
|
### Phase 2.0a: Echo-Stub Detection (GH #42)
|
|
370
347
|
|
|
@@ -438,7 +415,7 @@ totalFindings = projectStaleness.findings.length + narrativeStaleness.findings.l
|
|
|
438
415
|
- If `totalFindings === 0`: continue, log `Vault staleness: clean (mode=strict)`.
|
|
439
416
|
- If `totalFindings > 0`: do NOT block the close. Present the findings list and surface an AskUserQuestion whose Recommended default is **warn + carryover + continue**:
|
|
440
417
|
- On Claude Code: AskUserQuestion with options:
|
|
441
|
-
1. "Warn + carryover and close (Recommended)" — file a carryover issue (labels `carryover`, `priority
|
|
418
|
+
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
419
|
`- [<ISO timestamp>] Phase 2.3: Vault staleness strict-mode findings carried over. Findings: <count> (projects: <N>, narratives: <M>) → issue #<IID>.`
|
|
443
420
|
2. "Override and close" — proceed without a carryover issue, log a Deviation entry in STATE.md `## Deviations`:
|
|
444
421
|
`- [<ISO timestamp>] Phase 2.3: Vault staleness strict-mode findings overridden by user. Findings: <count> (projects: <N>, narratives: <M>).`
|
|
@@ -480,7 +457,7 @@ For each kept phase:
|
|
|
480
457
|
- 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
458
|
- On Claude Code: AskUserQuestion with options:
|
|
482
459
|
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
|
|
460
|
+
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
461
|
3. "Override and close" — proceed, log a Deviation entry in STATE.md `## Deviations`:
|
|
485
462
|
`- [<ISO timestamp>] Phase 2.5: custom-phase '<name>' (mode=hard) exited <code>, overridden by user.`
|
|
486
463
|
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 +484,7 @@ already-computed results — no new detection logic, only aggregation:
|
|
|
507
484
|
4. Wave-level reviewer findings overridden without a fix task (`## Deviations` entries matching `reviewer finding overridden` — written by wave-executor §5/5a).
|
|
508
485
|
|
|
509
486
|
For EACH item: file a hard-terminated closure issue via `createBrokenWindowIssue()`
|
|
510
|
-
from `scripts/lib/spiral-carryover.mjs` — labels `broken-window` + `priority
|
|
487
|
+
from `scripts/lib/spiral-carryover.mjs` — labels `broken-window` + `priority::high`,
|
|
511
488
|
due-date = today + `broken-window-budget.due-days` (default 7; `glab` native
|
|
512
489
|
`--due-date`, `gh` fallback: `Due: <date>` as first body line — GitHub has no
|
|
513
490
|
native due-date field). Idempotent per task-hash — re-running a close never
|
|
@@ -986,8 +963,8 @@ if (sweep) {
|
|
|
986
963
|
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.
|
|
987
964
|
|
|
988
965
|
2. **Update in-progress issues**: ensure labels reflect actual state using the issue update command
|
|
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
|
|
990
|
-
- **Template stays source-specific:** 1.2 Partially-Done → `[Carryover] <task>` (labels `priority
|
|
966
|
+
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.
|
|
967
|
+
- **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`.
|
|
991
968
|
- **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.
|
|
992
969
|
- **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.
|
|
993
970
|
- **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:
|
|
@@ -1002,13 +979,32 @@ if (sweep) {
|
|
|
1002
979
|
|
|
1003
980
|
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.
|
|
1004
981
|
|
|
982
|
+
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.
|
|
983
|
+
|
|
984
|
+
**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.
|
|
985
|
+
|
|
986
|
+
```js
|
|
987
|
+
import { readBudgetState, budgetStatePath } from '${PLUGIN_ROOT}/scripts/lib/issue-budget.mjs';
|
|
988
|
+
const state = readBudgetState(repoRoot, sessionId); // { sessionId, count, exempt, overflow: [...] }
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
- **`issue-budget.overflow: collect-issue` (default)** — create exactly ONE issue:
|
|
992
|
+
- Title: `[Backlog-Sammel] <session-id>, <N> zurückgestellte Punkte`
|
|
993
|
+
- Labels: `type::backlog`, `priority::low`
|
|
994
|
+
- Body: a Markdown checklist with one `- [ ]` line per `overflow[]` entry (`title` when present, otherwise the truncated `command`, plus its `at` timestamp).
|
|
995
|
+
- 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.
|
|
996
|
+
- **`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.
|
|
997
|
+
- 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)`.
|
|
998
|
+
- **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.
|
|
999
|
+
- Fail-open: a missing or malformed counter file means "no overflow" — log a WARN and continue the close.
|
|
1000
|
+
|
|
1005
1001
|
#### Discovery Issue Creation (if discovery ran in Phase 1.5)
|
|
1006
1002
|
|
|
1007
1003
|
For each finding with severity `critical` or `high` from Phase 1.5:
|
|
1008
1004
|
1. Create a VCS issue using the detected platform CLI:
|
|
1009
1005
|
- Title: `[Discovery] <description>` (truncated to 70 chars)
|
|
1010
1006
|
- Body: `**Probe:** <probe>\n**File:** <file>:<line>\n**Severity:** <severity>\n**Confidence:** <confidence>%\n**Recommendation:** <recommendation>`
|
|
1011
|
-
- Labels: `type:discovery`, `priority
|
|
1007
|
+
- Labels: `type:discovery`, `priority::<severity>` (critical→critical, high→high)
|
|
1012
1008
|
2. Log each created issue ID for the Final Report
|
|
1013
1009
|
3. Update `discovery_stats.issues_created` count
|
|
1014
1010
|
|
|
@@ -1050,7 +1046,7 @@ Present to the user:
|
|
|
1050
1046
|
- Wave 1 (Discovery): [duration] — [N agents] — [K files]
|
|
1051
1047
|
- Wave 2 (Impl-Core): [duration] — [N agents] — [K files]
|
|
1052
1048
|
- ...
|
|
1053
|
-
- Tests: [passing/total]
|
|
1049
|
+
- Tests: [passing/total] · Δ this session: +[added] / −[removed] / ~[consolidated] · tests:src LOC ratio [x.xx] (advisory ceiling 1.60)
|
|
1054
1050
|
- TypeScript: 0 errors
|
|
1055
1051
|
- Commits: [N] pushed to [branch]
|
|
1056
1052
|
- Mirror: [synced/skipped]
|
|
@@ -1072,6 +1068,8 @@ Present to the user:
|
|
|
1072
1068
|
- Notes: [any context for next session]
|
|
1073
1069
|
```
|
|
1074
1070
|
|
|
1071
|
+
> **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.
|
|
1072
|
+
|
|
1075
1073
|
> **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.
|
|
1076
1074
|
|
|
1077
1075
|
## Sub-File Reference
|
|
@@ -1110,7 +1108,7 @@ Present to the user:
|
|
|
1110
1108
|
- **NEVER commit with TypeScript errors** — 0 errors is non-negotiable
|
|
1111
1109
|
- **NEVER use `git add .`** — stage files individually to avoid capturing parallel session work
|
|
1112
1110
|
- **NEVER skip issue updates** — VCS must reflect reality after every session
|
|
1113
|
-
- **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.
|
|
1111
|
+
- **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.
|
|
1114
1112
|
- **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.)
|
|
1115
1113
|
- **ALWAYS push to origin** — local-only work is lost work
|
|
1116
1114
|
- **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>.
|
|
@@ -37,6 +37,33 @@ The proposals queue is populated mid-session by wave-executor agents calling `no
|
|
|
37
37
|
|
|
38
38
|
3. If `queue.length === 0`: log `memory-proposals: queue empty (stats: ${JSON.stringify(stats)})` and continue.
|
|
39
39
|
|
|
40
|
+
3b. **Relation judgment (#1016)** — enrich each queued proposal with its relation to the existing corpus, BEFORE step 4 renders its label. Without this, the operator approves a proposal without being told that the corpus already holds it, or holds its opposite.
|
|
41
|
+
|
|
42
|
+
> **Cadence contrast — read this before the step above and the step below.** Step 2's `collectProposals` and step 3's short-circuit run ONCE per session-end; step 4 batches ONCE per 4 items. **This step runs once per queued proposal.** The pool build is one call; the judgment is per candidate.
|
|
43
|
+
|
|
44
|
+
> **Cost, and where it may run.** The pool build is O(N²) over `queue.length + corpus.length` (~13 ms at N=100 records; viability boundary ~N=2000). Session-end and `/evolve` are the only two sanctioned call sites. Never from a wave dispatch, an inter-wave checkpoint, or a hook.
|
|
45
|
+
|
|
46
|
+
Skip when `.orchestrator/metrics/learnings.jsonl` is absent or holds fewer than 2 entries — with no corpus there is no relation to judge. Otherwise:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
import { buildCandidatePools } from '${PLUGIN_ROOT}/scripts/lib/learnings/candidates.mjs';
|
|
50
|
+
import { buildJudgmentInput, judgeCandidate, applyVerdict }
|
|
51
|
+
from '${PLUGIN_ROOT}/scripts/lib/learnings/judgment.mjs';
|
|
52
|
+
|
|
53
|
+
const { entries: corpus } = await readLearnings('.orchestrator/metrics/learnings.jsonl');
|
|
54
|
+
const { pools } = buildCandidatePools([...queue, ...corpus], { now: new Date() });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`pools[]` is `{seed, candidates}` per seed — a bounded, per-seed, non-transitive neighbour set (a neighbour of a neighbour is not a neighbour; there is no clustering pass). For each pool whose `seed` is a QUEUE item (corpus-seeded pools are not this phase's business):
|
|
58
|
+
|
|
59
|
+
1. `buildJudgmentInput({ candidate: pool.seed, neighbours: pool.candidates.map((c) => c.record) })`. It returns `null` for a proposal with no usable `id` — leave that item's label bare and move on.
|
|
60
|
+
2. `judgeCandidate(input, { judge })`. `judge` is the injected verdict provider: the coordinator reads the `input` envelope and returns the JSON object its `output_contract` field describes. There is no subagent type for this — do not dispatch one (#614: a read-only agent that must write its own sidecar never fires; here the COORDINATOR is the judge and the coordinator holds the result).
|
|
61
|
+
3. `applyVerdict(verdict, effects)` — the single choke point where a judgment may become an effect. In this phase every handler (`refine`, `supersede`, `merge`, `proposeContradiction`) records the relation onto the queue item so step 4 can render it. **None of them writes to disk here**; the only write this phase performs is step 6's `promoteAndClear()`, on the operator's selection.
|
|
62
|
+
|
|
63
|
+
**Fail closed — a voided judgment never reaches the operator.** `verdict.ok === false` (any of the eight failure modes: `unparseable`, `partial`, `phantom_id`, `self_reference`, `empty`, `timeout`, `enum_violation`, `duplicate_target`) means no relation was READ, not that none exists. `applyVerdict` refuses the whole batch — including `proposeContradiction`, the AUQ renderer, because rendering a relation from an unreadable judgment IS the claim. The item then falls through to step 4 with its ordinary bare label, exactly as before #1016. Never substitute a default decision, never repair-retry, never surface the failure mode as if it were a verdict. A judge error is logged (`memory-proposals: judgment voided for <id> (${verdict.failureMode})`) and never blocks the close.
|
|
64
|
+
|
|
65
|
+
**Label enrichment (step 4 input).** A proposal carrying a relation renders as `[<type-12>] | <subject-40> | conf=X.XX | <decision> <n>` (e.g. `contradict 1`, `merge 2`) with the judgment's `rationale` leading the option description. A proposal with no relation — `skip`, `abstain`, no pool, or a voided verdict — renders exactly as it does today. The operator's selection remains the only gate; the judgment supplies the relation, never the decision.
|
|
66
|
+
|
|
40
67
|
4. **AUQ pagination logic**: partition the queue into FIFO batches of 4 inline:
|
|
41
68
|
|
|
42
69
|
- Empty queue → silent skip (no AUQ rendered).
|
|
@@ -81,11 +108,13 @@ The proposals queue is populated mid-session by wave-executor agents calling `no
|
|
|
81
108
|
|
|
82
109
|
- Spec: issue #501 — memory-proposals (F2.1); no standalone PRD file
|
|
83
110
|
- Modules: `scripts/lib/memory-proposals/{schema,store,collector,sink}.mjs`
|
|
111
|
+
- Relation judgment (step 3b, #1016): `scripts/lib/learnings/candidates.mjs` (`buildCandidatePools`) · `scripts/lib/learnings/judgment.mjs` (`buildJudgmentInput`, `judgeCandidate`, `applyVerdict`, `JUDGMENT_DECISIONS`, `FAILURE_MODES`)
|
|
84
112
|
- CLI: `scripts/memory-propose.mjs` (agents call this)
|
|
85
113
|
- Hook: `hooks/pre-bash-memory-propose-audit.mjs` (audit trail)
|
|
86
114
|
- Coordinator AUQ spec: `agents/memory-proposal-collector.md` (reference doc)
|
|
87
115
|
- Sibling phases: 3.6.5 Auto-Dream (#502), 3.6.6 Skill-Applied Judge (#645 L3), 3.6.7 Auto-Dialectic (#506)
|
|
88
|
-
-
|
|
116
|
+
- Sibling call site of the same judgment pair: `skills/evolve/SKILL.md` § Step 3.3b (the `/evolve` producer for the `-0.2 if contradicted` branch)
|
|
117
|
+
- Issues: #501 (this phase), #1016 (step 3b)
|
|
89
118
|
|
|
90
119
|
### 3.6.4 Expired-Learnings Sweep (Advisory — Epic #723 B4)
|
|
91
120
|
|
|
@@ -241,7 +270,7 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
241
270
|
|
|
242
271
|
#### Coordinator-direct procedure
|
|
243
272
|
|
|
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.
|
|
273
|
+
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
274
|
|
|
246
275
|
2. Invoke `runReconcile` from `scripts/lib/reconcile/engine.mjs`:
|
|
247
276
|
|
|
@@ -252,6 +281,7 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
252
281
|
ruleExpiryDays: config.reconcile['rule-expiry-days'] ?? undefined,
|
|
253
282
|
minRuleDays: config.reconcile['min-rule-days'] ?? undefined,
|
|
254
283
|
minInsightChars: config.reconcile['min-insight-chars'] ?? undefined,
|
|
284
|
+
maxProposalsPerRun: config.reconcile['max-proposals-per-run'] ?? undefined,
|
|
255
285
|
now: new Date(),
|
|
256
286
|
});
|
|
257
287
|
```
|
|
@@ -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,8 +69,8 @@ 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
|
|
70
|
-
- Description: what's done, what's left, context for next session
|
|
72
|
+
- Labels: `priority::<original>`, `status:ready`
|
|
73
|
+
- Description: what's done, what's left, context for next session, Revisit-Trigger (mandatory — a concrete reopen condition; a deferral with no named trigger is not a deferral; see `skills/gitlab-ops/SKILL.md § Carryover Template`)
|
|
71
74
|
- Link to original issue if applicable
|
|
72
75
|
|
|
73
76
|
### 1.3 Not Started Items
|
|
@@ -75,10 +78,6 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
75
78
|
- If still relevant: ensure original issue remains `status:ready`
|
|
76
79
|
- If no longer relevant: close with comment explaining why
|
|
77
80
|
|
|
78
|
-
### 1.3a Optional /goal Backlog-Drain (opt-in — #636)
|
|
79
|
-
|
|
80
|
-
When `goal-integration.enabled: true` with seam `session-end-backlog`, the close may surface ONE advisory `/goal` command to drain still-relevant §1.2/§1.3 items in-session instead of carrying them over. See `SKILL.md § 1.3a Optional /goal Backlog-Drain` for the full gate conditions, advisory-only contract, and the LM-008 cross-reference — the two files mirror each other; the prose lives in SKILL.md.
|
|
81
|
-
|
|
82
81
|
### 1.4 Emergent Work
|
|
83
82
|
- Tasks that were NOT in the plan but were done (fixes, discoveries)
|
|
84
83
|
- Document and attribute to relevant issues
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
|
|
40
40
|
**Semantics:** `null` totals mean "no token data was captured for this session" — this is NOT the same as zero cost. Do NOT coerce null to 0 when displaying or summing across sessions.
|
|
41
41
|
|
|
42
|
+
**Provenance (#949):** the rollup sums ONLY records carrying `subagent_transcript_found: true` — the flag the producer sets when it read the subagent's own transcript. Pre-#949 records carry the PARENT transcript's running totals and are excluded, so a session made up entirely of them now reports `null` rather than a fabricated sum (73 historical sessions, 96,148,781 phantom tokens, measured 2026-08-11). Two consequences for readers: totals already written into `sessions.jsonl` before 2026-08-11 were produced by the unfiltered recipe and are a series break, not a trend; and `matched_records` counts start records and phantom stops alike, so it is NOT the denominator for a coverage ratio — use `subagents_with_tokens` against the session's real agent count.
|
|
43
|
+
|
|
42
44
|
Example (coordinator pseudo-code — adapt to your shell/JS context):
|
|
43
45
|
|
|
44
46
|
```js
|