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.
Files changed (221) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/rules/030-wave-execution.mdc +17 -1
  5. package/CHANGELOG.md +185 -412
  6. package/README.md +12 -9
  7. package/SECURITY.md +190 -27
  8. package/agents/AGENTS.md +20 -3
  9. package/agents/code-implementer.md +6 -6
  10. package/agents/db-specialist.md +1 -1
  11. package/agents/qa-strategist.md +31 -6
  12. package/agents/schemas/qa-strategist.schema.json +27 -0
  13. package/agents/schemas/test-writer.schema.json +60 -2
  14. package/agents/security-reviewer.md +1 -1
  15. package/agents/session-reviewer.md +1 -1
  16. package/agents/test-writer.md +29 -10
  17. package/agents/ui-developer.md +1 -1
  18. package/commands/contract-version-bump.md +28 -0
  19. package/commands/portfolio.md +1 -1
  20. package/commands/session.md +6 -2
  21. package/docs/USER-GUIDE.md +9 -4
  22. package/docs/ci-setup.md +121 -7
  23. package/docs/codex-setup.md +1 -1
  24. package/docs/components.md +6 -6
  25. package/docs/cursor-setup.md +22 -9
  26. package/docs/events-schema.md +5 -1
  27. package/docs/instruction-delivery.md +794 -0
  28. package/docs/rule-authoring.md +58 -9
  29. package/docs/session-config-reference.md +245 -50
  30. package/docs/session-config-template.md +39 -26
  31. package/hooks/_lib/guard-source-loader.mjs +680 -0
  32. package/hooks/_lib/lock-bootstrap.mjs +21 -0
  33. package/hooks/_lib/vcs-create-matcher.mjs +119 -0
  34. package/hooks/config-protection.mjs +0 -0
  35. package/hooks/enforce-commands.mjs +226 -19
  36. package/hooks/enforce-scope.mjs +133 -9
  37. package/hooks/hooks-codex.json +1 -1
  38. package/hooks/hooks-cursor.json +11 -2
  39. package/hooks/hooks-pi.json +10 -0
  40. package/hooks/hooks.json +21 -1
  41. package/hooks/on-session-end.mjs +178 -18
  42. package/hooks/on-session-start.mjs +30 -4
  43. package/hooks/post-bash-write-verify.mjs +977 -0
  44. package/hooks/post-subagent-discovery-validator.mjs +256 -41
  45. package/hooks/pre-bash-destructive-guard.mjs +616 -164
  46. package/hooks/pre-bash-issue-budget.mjs +167 -0
  47. package/hooks/pre-bash-sessions-ledger-guard.mjs +1054 -0
  48. package/hooks/pre-bash-templates-first.mjs +96 -63
  49. package/hooks/subagent-telemetry.mjs +527 -37
  50. package/package.json +6 -3
  51. package/pi/prompts/contract-version-bump.md +12 -0
  52. package/rules/README.md +32 -0
  53. package/scripts/archive-closed-prds.mjs +12 -22
  54. package/scripts/autopilot-multi.mjs +103 -20
  55. package/scripts/backfill-abandoned-sessions.mjs +160 -4
  56. package/scripts/backfill-learnings-from-vault.mjs +967 -0
  57. package/scripts/check-doc-consistency.sh +17 -1
  58. package/scripts/emit-session.mjs +3 -40
  59. package/scripts/eval-session.mjs +50 -9
  60. package/scripts/fleet-instruction-scan.mjs +141 -0
  61. package/scripts/lib/autopilot/mr-draft.mjs +31 -1
  62. package/scripts/lib/autopilot/worktree-pipeline.mjs +113 -5
  63. package/scripts/lib/backlog-scan.mjs +39 -6
  64. package/scripts/lib/blocked-commands-policy.mjs +340 -0
  65. package/scripts/lib/ci-status-banner.mjs +75 -12
  66. package/scripts/lib/claude-md-budget-lint.mjs +283 -34
  67. package/scripts/lib/command-blocker.mjs +1273 -58
  68. package/scripts/lib/config/config-protection.mjs +2 -1
  69. package/scripts/lib/config/drift-check.mjs +9 -1
  70. package/scripts/lib/config/gitlab-portfolio.mjs +1 -1
  71. package/scripts/lib/config/issue-budget.mjs +123 -0
  72. package/scripts/lib/config/reconcile.mjs +21 -0
  73. package/scripts/lib/config/section-extractor.mjs +121 -1
  74. package/scripts/lib/config-schema.mjs +23 -3
  75. package/scripts/lib/config.mjs +17 -0
  76. package/scripts/lib/convergence-monitor.mjs +49 -3
  77. package/scripts/lib/description-surface.mjs +535 -0
  78. package/scripts/lib/dispatcher/enumerate.mjs +26 -40
  79. package/scripts/lib/ecosystem-wizard/config-writer.mjs +26 -24
  80. package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +1 -1
  81. package/scripts/lib/eval/engine.mjs +47 -5
  82. package/scripts/lib/events.mjs +59 -7
  83. package/scripts/lib/gates/gate-full.mjs +15 -3
  84. package/scripts/lib/gates/gate-helpers.mjs +132 -6
  85. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +28 -8
  86. package/scripts/lib/gitlab-portfolio/aggregator.mjs +8 -2
  87. package/scripts/lib/gitlab-portfolio/cli.mjs +1 -1
  88. package/scripts/lib/handover-gate.mjs +7 -3
  89. package/scripts/lib/hardening.mjs +9 -9
  90. package/scripts/lib/harness-audit/categories/category4.mjs +9 -3
  91. package/scripts/lib/instruction-budget-guard.mjs +402 -51
  92. package/scripts/lib/io.mjs +345 -10
  93. package/scripts/lib/issue-budget.mjs +269 -0
  94. package/scripts/lib/issue-close-strip-labels.mjs +39 -9
  95. package/scripts/lib/label-scope.mjs +47 -0
  96. package/scripts/lib/learnings/affinity.mjs +434 -0
  97. package/scripts/lib/learnings/candidates.mjs +736 -0
  98. package/scripts/lib/learnings/expiry-sweep.mjs +408 -53
  99. package/scripts/lib/learnings/judgment.mjs +782 -0
  100. package/scripts/lib/learnings/kebab.mjs +128 -0
  101. package/scripts/lib/learnings/schema.mjs +43 -3
  102. package/scripts/lib/learnings/select.mjs +550 -0
  103. package/scripts/lib/lock-reaper.mjs +1 -2
  104. package/scripts/lib/memory-proposals/schema.mjs +36 -1
  105. package/scripts/lib/peer-discovery.mjs +645 -0
  106. package/scripts/lib/pi-hook-bridge.mjs +146 -17
  107. package/scripts/lib/product-repo-detect.mjs +9 -8
  108. package/scripts/lib/project-hygiene.mjs +432 -0
  109. package/scripts/lib/quality-gate.mjs +167 -0
  110. package/scripts/lib/recommendations-v0.mjs +1 -1
  111. package/scripts/lib/reconcile/eligibility.mjs +1 -1
  112. package/scripts/lib/reconcile/emitter.mjs +128 -24
  113. package/scripts/lib/reconcile/engine.mjs +156 -54
  114. package/scripts/lib/reconcile/idempotency.mjs +114 -14
  115. package/scripts/lib/reconcile/renderer.mjs +141 -25
  116. package/scripts/lib/reconcile/sanitize.mjs +518 -0
  117. package/scripts/lib/reconcile/writer.mjs +95 -1
  118. package/scripts/lib/reconcile-nudge-banner.mjs +65 -9
  119. package/scripts/lib/resource-probe/evaluate.mjs +70 -4
  120. package/scripts/lib/resource-probe.mjs +19 -0
  121. package/scripts/lib/rule-loader.mjs +6 -0
  122. package/scripts/lib/scope-baseline.mjs +564 -0
  123. package/scripts/lib/scope-gate.mjs +568 -145
  124. package/scripts/lib/session-close-backfill.mjs +63 -8
  125. package/scripts/lib/session-end/phase-skip.mjs +1 -0
  126. package/scripts/lib/session-id.mjs +221 -41
  127. package/scripts/lib/session-lock.mjs +304 -6
  128. package/scripts/lib/session-record-repair.mjs +551 -0
  129. package/scripts/lib/session-schema/constants.mjs +22 -3
  130. package/scripts/lib/session-schema/serializer.mjs +54 -0
  131. package/scripts/lib/session-schema/validator.mjs +16 -0
  132. package/scripts/lib/session-schema.mjs +1 -0
  133. package/scripts/lib/session-token-rollup.mjs +68 -6
  134. package/scripts/lib/sessions-integrity-banner.mjs +294 -0
  135. package/scripts/lib/sessions-staleness-banner.mjs +121 -12
  136. package/scripts/lib/skill-evolution/idempotency.mjs +135 -16
  137. package/scripts/lib/skill-evolution/mr-opener.mjs +9 -1
  138. package/scripts/lib/soul-resolve.mjs +12 -0
  139. package/scripts/lib/spiral-carryover.mjs +142 -30
  140. package/scripts/lib/state-md/mission-status.mjs +53 -3
  141. package/scripts/lib/subagents-schema.mjs +43 -9
  142. package/scripts/lib/test-runner/issue-reconcile.mjs +53 -13
  143. package/scripts/lib/tests-src-ratio.mjs +484 -0
  144. package/scripts/lib/tmux-layout/telemetry.mjs +43 -10
  145. package/scripts/lib/validate/check-agents.mjs +56 -0
  146. package/scripts/lib/validate/check-banner-parity.mjs +376 -0
  147. package/scripts/lib/validate/check-guard-requires-parity.mjs +1148 -0
  148. package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
  149. package/scripts/lib/validate/check-learning-provenance.mjs +511 -0
  150. package/scripts/lib/validate/check-owner-leakage.mjs +3 -3
  151. package/scripts/lib/validate/check-rules.mjs +244 -36
  152. package/scripts/lib/validate/check-test-value-bans.mjs +782 -0
  153. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  154. package/scripts/lib/validate/check-unwired-features.mjs +549 -0
  155. package/scripts/lib/validate-vendored-rules.mjs +10 -2
  156. package/scripts/lib/vault-archive.mjs +17 -2
  157. package/scripts/lib/vault-backfill/glab.mjs +8 -0
  158. package/scripts/lib/vault-mirror/process.mjs +30 -0
  159. package/scripts/lib/vault-mirror/render-sessions.mjs +293 -36
  160. package/scripts/lib/vcs-repo-spec.mjs +362 -0
  161. package/scripts/lib/wave-resource-gate.mjs +115 -11
  162. package/scripts/lib/worktree/listing.mjs +44 -7
  163. package/scripts/mcp-server.sh +17 -3
  164. package/scripts/measure-context-overhead.sh +151 -0
  165. package/scripts/memory-propose.mjs +72 -9
  166. package/scripts/print-applicable-rules.mjs +218 -16
  167. package/scripts/print-learnings-index.mjs +474 -0
  168. package/scripts/release.mjs +534 -0
  169. package/scripts/repair-invalid-sessions.mjs +209 -0
  170. package/scripts/run-quality-gate.mjs +123 -5
  171. package/scripts/sweep-expired-learnings.mjs +192 -32
  172. package/scripts/validate-plugin.mjs +21 -0
  173. package/scripts/validate-wave-scope.mjs +182 -17
  174. package/scripts/vault-integration-watcher.mjs +32 -10
  175. package/skills/_shared/config-reading.md +2 -2
  176. package/skills/bootstrap/fast-template.md +1 -1
  177. package/skills/brainstorm/soul.md +47 -1
  178. package/skills/claude-md-drift-check/checker.mjs +145 -28
  179. package/skills/contract-version-bump/SKILL.md +219 -0
  180. package/skills/discovery/SKILL.md +4 -4
  181. package/skills/discovery/issue-templates.md +11 -11
  182. package/skills/discovery/probes-audit.md +1 -1
  183. package/skills/discovery/probes-feature.md +1 -1
  184. package/skills/discovery/probes-session.md +26 -5
  185. package/skills/ecosystem-health/SKILL.md +1 -1
  186. package/skills/ecosystem-health/wizard.md +4 -4
  187. package/skills/evolve/SKILL.md +117 -18
  188. package/skills/gitlab-ops/SKILL.md +25 -12
  189. package/skills/gitlab-portfolio/SKILL.md +2 -2
  190. package/skills/grill/soul.md +44 -1
  191. package/skills/hook-development/SKILL.md +1 -1
  192. package/skills/mode-selector/SKILL.md +1 -1
  193. package/skills/npm-publish/SKILL.md +17 -1
  194. package/skills/plan/SKILL.md +5 -5
  195. package/skills/plan/mode-feature.md +4 -4
  196. package/skills/plan/mode-new.md +10 -10
  197. package/skills/plan/mode-retro.md +1 -1
  198. package/skills/plan/soul.md +46 -3
  199. package/skills/quality-gates/SKILL.md +1 -1
  200. package/skills/reconcile/SKILL.md +21 -4
  201. package/skills/session-end/SKILL.md +34 -36
  202. package/skills/session-end/discovery-scan.md +4 -2
  203. package/skills/session-end/drift-operations.md +4 -4
  204. package/skills/session-end/metrics-collection.md +13 -0
  205. package/skills/session-end/phase-3-2-docs-verification.md +1 -1
  206. package/skills/session-end/phase-3-6-tail.md +32 -2
  207. package/skills/session-end/plan-verification.md +6 -7
  208. package/skills/session-end/session-metrics-write.md +2 -0
  209. package/skills/session-end/vault-operations.md +1 -1
  210. package/skills/session-end/verification-checklist.md +1 -1
  211. package/skills/session-plan/SKILL.md +6 -2
  212. package/skills/session-plan/wave-template.md +2 -0
  213. package/skills/session-start/SKILL.md +75 -7
  214. package/skills/session-start/phase-4-5-resource-health.md +15 -2
  215. package/skills/session-start/soul.md +41 -1
  216. package/skills/test-runner/SKILL.md +2 -2
  217. package/skills/vault-sync/validator.mjs +108 -7
  218. package/skills/wave-executor/SKILL.md +6 -7
  219. package/skills/wave-executor/circuit-breaker.md +2 -0
  220. package/skills/wave-executor/wave-loop.md +198 -80
  221. package/templates/_shared/loop.md +4 -4
@@ -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:critical` or `priority:high`.
238
+ - Issues that block others → `priority::critical` or `priority::high`.
239
239
  2. **Business value (medium weight):**
240
- - Core MVP features → `priority:high`.
241
- - Nice-to-haves → `priority:medium` or `priority:low`.
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:critical` |
253
- | P1 (high impact, needed soon) | `priority:high` |
254
- | P2 (medium, can wait) | `priority:medium` |
255
- | P3 (nice-to-have) | `priority:low` |
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:<level>` format in VCS CLI commands, not P0/P1/P2/P3.
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:$PRIORITY" --milestone "$MILESTONE"
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:$PRIORITY,status:ready,area:$AREA,appetite:$APPETITE"
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:<from ranking>`, `area:<inferred>`
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
 
@@ -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 respond in {{owner.language}} when that matches the user's language. You speak technical when they're technical. You meet people where they are.
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
- - Respond in {{owner.language}} when that is the user's language. No exceptions.
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
- - Tone: {{tone.style}}. Output level: {{efficiency.output-level}}. Preamble: {{efficiency.preamble}}.
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:high` issue and proceed without committing the
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`, and
311
- `min-insight-chars` from Session Config `reconcile` block — the engine reads these, but
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:<original>` + `status:ready`, Description = what's done / what's left / context for next session.
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: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.
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:high`, `status:ready`) and note it in the Final Report |
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:high` issue. Do NOT commit broken code.
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: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:
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: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.
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:high`,
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: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`.
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:<severity>` (critical→critical, high→high)
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 ?? (sessionType === 'housekeeping' ? false : true)
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 **session-type aware**: `false` for `housekeeping`, `true` for `feature` and `deep`.
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: hard` would have routed the same errors through the carryover path below.
59
- - **`mode: hard`** — 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 (hard) — <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.
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
- # hard-mode: surface + warn + carryover + continue (no hard block — #724)
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: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.
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
- - Issue: #501
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
- 5. **Report** in the verification output:
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:<original>`, `status:ready`
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