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
@@ -91,7 +91,7 @@ gh issue list --limit 100 --json number,title,labels,updatedAt,assignees --jq '.
91
91
  # Flag:
92
92
  # - Issues with no activity in stale-issue-days (default: 30 days)
93
93
  # - Issues assigned but with no associated branch
94
- # - Issues labeled priority:high or priority:critical that are stale
94
+ # - Issues labeled priority::high or priority::critical that are stale
95
95
 
96
96
  # Check for associated branches:
97
97
  git branch -r | grep -i "<issue_number>"
@@ -109,7 +109,7 @@ Has Branch: true | false
109
109
  Priority: <priority label or NONE>
110
110
  ```
111
111
 
112
- **Default Severity:** Low. Medium if `priority:high` or `priority:critical` is stale.
112
+ **Default Severity:** Low. Medium if `priority::high` or `priority::critical` is stale.
113
113
 
114
114
  ---
115
115
 
@@ -231,14 +231,35 @@ Actual: <what was found or NOT found>
231
231
 
232
232
  5. Token efficiency — CLAUDE.md size:
233
233
  ```bash
234
- # Count lines in CLAUDE.md
235
- wc -l CLAUDE.md
234
+ # Delegate the threshold — do NOT re-implement it here. This probe used to
235
+ # carry its own numbers (`wc -l` > 150 warn / > 250 high); both the unit and
236
+ # the value were wrong. The ceiling is `DEFAULT_MAX_LINES = 80` and it applies
237
+ # to NON-EXEMPT effective lines: the runtime-critical `## Session Config` block
238
+ # is machine-parsed configuration, not trimmable prose, so a raw `wc -l` gate
239
+ # flags a compliant lean-root file (this plugin's own CLAUDE.md: 209 raw lines,
240
+ # 61 non-exempt — a `wc -l` rule fires, the lint passes).
241
+ REPO_ROOT="$(git rev-parse --show-toplevel)"
242
+ node "${PLUGIN_ROOT}/scripts/lib/claude-md-budget-lint.mjs" \
243
+ --repo-root "$REPO_ROOT" --mode warn --json
244
+ # → {"status":"ok"|"invalid","file":…,"lineCount":…,"exemptLines":…,
245
+ # "effectiveLineCount":…,"maxLineCharsSeen":…,"hasProvenance":…,
246
+ # "violations":[{"rule":"max-lines"|"max-line-chars"|…,"message":…}]}
247
+ # Flag ONLY on a violations[] entry — never on lineCount vs a local number.
248
+ # `--mode warn` keeps the exit code 0, so read the JSON, not `$?`.
249
+
250
+ # Ceiling value, when a finding needs to quote it (e.g. a 2x high tier):
251
+ node --input-type=module -e "
252
+ import { DEFAULT_MAX_LINES } from '${PLUGIN_ROOT}/scripts/lib/claude-md-budget-lint.mjs';
253
+ process.stdout.write(String(DEFAULT_MAX_LINES));
254
+ "
236
255
 
237
- # Flag if > 150 lines (warning) or > 250 lines (high)
256
+ # Heuristics the lint does NOT measure still eyeball these:
238
257
  # Identify sections > 30 lines that could move to <state-dir>/rules/ or <state-dir>/docs/
239
258
  # Check for inline code blocks > 10 lines (should be in separate files)
240
259
  ```
241
260
 
261
+ **Dependencies (probe 5):** requires `${PLUGIN_ROOT}/scripts/lib/claude-md-budget-lint.mjs` (#722 Epic A). Degrades gracefully when the helper is absent (pre-#722 plugin install) — skip the size finding with a note, do not fall back to a hand-rolled `wc -l` threshold, which is the drift this delegation removes.
262
+
242
263
  6. Token efficiency — .claudeignore coverage:
243
264
  ```bash
244
265
  # Check if .claudeignore exists
@@ -91,7 +91,7 @@ and skip this section.
91
91
  Using the detected VCS CLI (per gitlab-ops "Common CLI Commands" and "Dynamic Project Resolution" sections):
92
92
 
93
93
  1. Resolve the project ID or owner/repo slug for each cross-repo
94
- 2. Query open issues with `priority:critical` or `priority:high` labels (limit 5 per repo)
94
+ 2. Query open issues with `priority::critical` or `priority::high` labels (limit 5 per repo)
95
95
  3. Collect results across all configured repos
96
96
 
97
97
  ## CI Pipeline Status
@@ -78,8 +78,8 @@ CI pipeline identifiers (format "id" or "id:label", comma-separated, blank to sk
78
78
  ### Prompt 2c — Critical Issue Labels
79
79
 
80
80
  ```
81
- Critical issue labels (comma-separated, e.g. "priority:critical,severity:blocker", blank to skip):
82
- > priority:critical, severity:blocker
81
+ Critical issue labels (comma-separated, e.g. "priority::critical,severity:blocker", blank to skip):
82
+ > priority::critical, severity:blocker
83
83
  ```
84
84
 
85
85
  - Raw label strings as they appear in the VCS issue tracker.
@@ -121,7 +121,7 @@ ecosystem-health:
121
121
  pipelines:
122
122
  - id: main
123
123
  - id: deploy-production # Deploy
124
- critical-issue-labels: ["priority:critical", "severity:blocker"]
124
+ critical-issue-labels: ["priority::critical", "severity:blocker"]
125
125
  ```
126
126
 
127
127
  **Idempotency:** If an `ecosystem-health:` key already exists in Session Config,
@@ -141,7 +141,7 @@ exits 0. Re-run to edit: remove the existing block first, then re-run.
141
141
  { "id": "main" },
142
142
  { "id": "deploy-production", "label": "Deploy" }
143
143
  ],
144
- "criticalIssueLabels": ["priority:critical", "severity:blocker"]
144
+ "criticalIssueLabels": ["priority::critical", "severity:blocker"]
145
145
  }
146
146
  ```
147
147
 
@@ -211,6 +211,34 @@ For each extracted pattern, check if a learning with same `type` + `subject` alr
211
211
  - **If exists:** propose confidence update (+0.15 if confirmed by new evidence, -0.2 if contradicted)
212
212
  - **If new:** propose as new learning with confidence 0.5
213
213
 
214
+ This match is **exact string equality on `type` + `subject`** — it is blind to two records that say the same thing in different words, and it cannot detect a contradiction at all. The `-0.2 if contradicted` branch above has therefore had no producer since it was written. Step 3.3b is that producer.
215
+
216
+ ### Step 3.3b: Relation Judgment (#1016)
217
+
218
+ > **Cadence: once per candidate.** Step 3.2b's zero-patterns check and Step 3.4's single AUQ are once-per-run; Step 3.5's write is once-per-run. This step is the only per-candidate one in Phase 3 — the pool build happens once, the judgment runs for each pattern that seeds a pool.
219
+
220
+ > **Runs in `/evolve`, never in a wave.** The pool build is O(N²) over the candidate + corpus union (~13 ms at N=100 records; the viability boundary is ~N=2000). `/evolve` is operator-invoked and off the dispatch hot path — that is the whole reason this lives here and not in `skills/wave-executor/`. Do not invoke it from a wave prompt, an inter-wave checkpoint, or a hook.
221
+
222
+ Skip this step entirely when `.orchestrator/metrics/learnings.jsonl` is absent or holds fewer than 2 entries — with no corpus there is no relation to judge.
223
+
224
+ 1. **Pool.** Call `buildCandidatePools(records, { now })` from `scripts/lib/learnings/candidates.mjs`, passing the union of this run's extracted candidates and the on-disk corpus. It returns `{pools, duplicates, stats}`: `duplicates` are the exact-`learning_key` groups (already certain — no judgment needed), and each `pools[]` entry is `{seed, candidates}` where `candidates[].record` is a bounded, per-seed, non-transitive neighbour set. No clustering, no transitive closure: a neighbour of a neighbour is not a neighbour.
225
+
226
+ 2. **Judge, per candidate that seeds a pool.** `buildJudgmentInput({candidate, neighbours})` then `judgeCandidate(input, { judge })`, both from `scripts/lib/learnings/judgment.mjs`. `buildJudgmentInput` returns `null` for a candidate with no usable `id` — skip that candidate, do not judge it. `judge` is the injected verdict provider: on Claude Code 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).
227
+
228
+ 3. **Apply, through the one choke point.** `applyVerdict(verdict, effects)` is the only place a judgment may become an effect. In `/evolve` every effect handler is a *proposal recorder*, never a writer: `refine` / `supersede` / `merge` record a proposed change, and `proposeContradiction` records a contradiction pair. `applyVerdict` resolves all four handlers before invoking any of them, so an unwired handler refuses the whole batch rather than applying the decisions that happened to come first.
229
+
230
+ 4. **Fail closed.** `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 "no relation exists". The candidate keeps its Step 3.3 exact-match verdict and nothing about it is surfaced as a relation. Never fall back to a default decision, never repair-retry a malformed verdict, and never render an unreadable judgment to the operator — surfacing a relation IS the claim, so a voided judgment must not reach the AUQ at all.
231
+
232
+ 5. **Route into the existing gate.** Every surviving decision becomes an OPTION in Step 3.4's AskUserQuestion, never an action:
233
+ - `contradict` → a contradiction pair, presented as its own category beside "duplicate". If the operator selects it, it feeds the `-0.2 if contradicted` branch in Step 3.3 above, applied by Step 3.5(3) — which deliberately does NOT reset `expires_at`.
234
+ - `supersede` / `merge` → an omit-the-loser (or replace-both-with-one) proposal. If selected, the operator's next generation simply omits those ids and Step 3.5(5) archives them — never a hand-delete. The merged record must carry both sources' provenance in its own `evidence`.
235
+ - `refine` → an edit proposal against the existing record's `insight` / `evidence`.
236
+ - `skip` / `abstain` → nothing is surfaced.
237
+
238
+ **The brandmauer holds here, unchanged (#693 FA2/FA3).** The judgment computes; it never writes. Every `.claude/rules/` write and every `learnings.jsonl` write stays behind the operator's Step 3.4 selection and Step 3.5's `--prune` invocation.
239
+
240
+ **Named ceiling (revisit trigger).** A `supersede` or `merge` executed through Step 3.5(5) is tagged `_archive_reason: "superseded"` with a `_superseded_by` tombstone **only when the two records share `type` + non-empty `subject`** — that is `pruneLearnings()`'s own consolidation pass. A cross-wording pair (the exact case this step exists to find) does not share a subject, so its loser is archived `pruned` instead: still in the corpus, still resolvable by id, but the archive record does not name its replacement. Revisit when the CLI grows per-record drop routing, or when an archive audit needs to answer "what replaced this?" for cross-wording merges.
241
+
214
242
  ### Step 3.4: Present Findings via AskUserQuestion
215
243
 
216
244
  Present extracted patterns to the user for confirmation. Use AskUserQuestion with `multiSelect: true`:
@@ -244,7 +272,7 @@ If user selects "Skip all" or selects nothing, abort gracefully: "No learnings s
244
272
 
245
273
  For confirmed learnings, use atomic rewrite strategy:
246
274
 
247
- 1. Read ALL existing lines from `.orchestrator/metrics/learnings.jsonl` (if exists) into memory. If not found, check `<state-dir>/metrics/learnings.jsonl` as a legacy fallback. If legacy data is found, it will be migrated to the v2 path on write (step 8).
275
+ 1. Read ALL existing lines from `.orchestrator/metrics/learnings.jsonl` (if exists) into memory. If not found, check `<state-dir>/metrics/learnings.jsonl` as a legacy fallback. If legacy data is found, it will be migrated to the v2 path on write (step 5).
248
276
  2. Apply confidence updates for confirmed existing learnings:
249
277
  - Increment confidence by +0.15
250
278
  - Cap at 1.0
@@ -261,14 +289,70 @@ For confirmed learnings, use atomic rewrite strategy:
261
289
  - `source_session`: **non-empty kebab-slug string** identifying the session from which the pattern was extracted (e.g. `main-2026-04-27-1942`). MUST be a string — never an object, array, number, or null. If multiple sessions contributed, use the earliest. If unknown, use `"unknown"` (the string). **Never** pass `String(<object>)` — that yields `"[object Object]"` and breaks the YAML mirror downstream (#307). Optional pre-write validation: `jq -e 'select(.source_session | type == "string" and length > 2)'`.
262
290
  - `created_at`: current ISO 8601 date
263
291
  - `expires_at`: preserve the candidate's derived expiry when supplied; otherwise derive from `LEARNING_TTL_DAYS[type]` via `deriveExpiresAt()` (falling back to the schema default) rather than hard-coding a 30-day horizon
264
- 5. **Verify write**: Read back the first line of the written file to confirm valid JSON. If read-back fails or is not valid JSON, report error to user.
265
- 6. **Prune:** remove entries where `expires_at` < current date OR `confidence` <= 0.0
266
- 7. **Consolidate duplicates (NULL-SUBJECT SAFE):** if same `type` + `subject` appears more than once
267
- AND `subject` is a non-empty string, keep the entry with highest confidence.
268
- Entries with null/empty/missing `subject` are NEVER collapsed each is keyed by its unique `id`
269
- and always preserved. (Fix for issue #284: empty-subject dedupe collapse.)
270
- 8. Write entire result back to `.orchestrator/metrics/learnings.jsonl` with `>` (atomic rewrite, NOT append `>>`)
271
- 9. **Vault mirror (conditional):** Check `$CONFIG."vault-integration".enabled` via jq. If the field is missing or `false`, skip this step entirely — skill behavior is unchanged.
292
+ - `file_paths` (optional): repo-relative path(s) scoping the learning to specific files/directories. Required for a learning to ever become `/reconcile`-eligible (issue #900; see `docs/rule-authoring.md` § "Learning Type-Taxonomy, TTL & Provenance Standard"). For a `fragile-file` candidate, `file_paths: [subject]` is mechanically derivable — `subject` already IS the file path.
293
+ 5. **Write the next generation through the archive-safe pipeline NEVER a `>` redirect (#1017).**
294
+
295
+ Steps 6–8 (prune, consolidate, rewrite) are **not prose you execute by hand**. They are
296
+ `pruneLearnings()` in `scripts/lib/learnings/expiry-sweep.mjs`, the same module (and the same
297
+ crash-safe ordering, KEEP-batch probe, and `.bak-<ISO>` snapshot) the expiry sweep uses. Until
298
+ #1017, this step said "write entire result back with `>`" with no archive append at all, which
299
+ deleted 11 of 13 `learning-id` provenance targets referenced by rendered `.claude/rules/*.md`.
300
+ Do not hand-roll a `jq | ... > learnings.jsonl` pass; it bypasses every #721 safety net.
301
+
302
+ Write the full next-generation entry set (existing entries **with** the step-2/3 confidence
303
+ updates, **plus** the step-4 new learnings) as JSONL to a temp sidecar **via the Write tool**
304
+ (not a shell `>` redirect — the destructive-command guard blocks it), then invoke the
305
+ `--prune` subcommand of the sweep CLI:
306
+
307
+ ```bash
308
+ NEXT=".orchestrator/metrics/.learnings-next.jsonl" # written by the step above
309
+ node scripts/sweep-expired-learnings.mjs --prune --apply --json --entries "$NEXT" && rm -f "$NEXT"
310
+ ```
311
+
312
+ `--file` / `--archive` default to the canonical store + archive paths — pass them only when
313
+ operating on a non-default pair. The command prints ONE JSON line; capture it as `$PRUNE` and
314
+ report its `{scanned, kept, archived, byReason}` in the final summary. Preview first with
315
+ `--prune --dry-run --json` (same counts, zero writes) whenever the next generation was
316
+ hand-assembled.
317
+
318
+ > **This step is `/evolve`'s only store-write path.** Until #1017 the invocation lived here as
319
+ > an inline `node --input-type=module -e` block, which is a mechanism hiding inside prose: no
320
+ > `--help`, no exit-code contract, no test. Do not re-inline it, and do not hand-roll a
321
+ > `jq | ... > learnings.jsonl` pass — that bypasses every #721 safety net.
322
+
323
+ **Exit codes are the no-op rule.** `0` = applied (or a clean no-op). `1` = input error: the
324
+ sidecar is absent, carries a malformed line, or holds no records — the store and the archive
325
+ were **not touched**;
326
+ re-write the sidecar and re-run. `2` = the prune itself failed inside the lib. On any non-zero
327
+ exit, surface the error and stop — never retry with a shell rewrite, and never delete `$NEXT`
328
+ (the `&&` above already withholds the `rm`, so the assembled generation survives for a retry).
329
+
330
+ `pruneLearnings()` — the function the subcommand calls — performs steps 6 + 7 + 8 mechanically
331
+ and archives **every** record that
332
+ leaves the store, tagged with `_archived_at` + an `_archive_reason` from the closed enum
333
+ `expired | pruned | superseded | merged`:
334
+
335
+ - **6. Prune** — `expires_at` < now → `expired`; `confidence <= 0.0` → `pruned`.
336
+ - **7. Consolidate duplicates (NULL-SUBJECT SAFE)** — same `type` + non-empty `subject`: the
337
+ highest-confidence entry wins; each loser is archived `superseded` with a
338
+ `_superseded_by: <winning id>` tombstone. Entries with null/empty/missing `subject` are NEVER
339
+ collapsed — each is keyed by its unique `id` and always preserved (issue #284).
340
+ - **8. Rewrite** — via `rewriteLearnings()`: full schema validation, a `.bak-<ISO>` snapshot
341
+ (keep-3 rotation), then an atomic tmp+rename. Any id you drop from the temp sidecar without
342
+ an explicit reason is archived `pruned` automatically — the store can no longer lose a record
343
+ silently, whatever the next generation omits.
344
+
345
+ No `graceDays` here, deliberately: `/evolve` re-stamps `expires_at` on every reinforced learning
346
+ in steps 2–3 of THIS run, strictly before the prune, so an entry still expired at prune time is
347
+ one the analyzer just declined to reinforce. (The sweep's 14-day grace exists to protect entries
348
+ from being archived *before* that reinforcement pass runs — a hazard that cannot occur here.)
349
+
350
+ Report the returned `{scanned, kept, archived, byReason}` alongside the counts in the final
351
+ summary line. On a non-zero exit, do NOT retry with a shell rewrite — surface the error. The
352
+ old "read back the first line to confirm valid JSON" check is redundant here: `rewriteLearnings()`
353
+ round-trip-validates EVERY line before any byte reaches disk (#662), and the `malformed` guard
354
+ above rejects an unparseable sidecar before the store is touched at all.
355
+ 6. **Vault mirror (conditional):** Check `$CONFIG."vault-integration".enabled` via jq. If the field is missing or `false`, skip this step entirely — skill behavior is unchanged.
272
356
 
273
357
  If `enabled` is `true`:
274
358
 
@@ -389,20 +473,26 @@ If user selects "Boost confidence", "Reduce confidence", "Delete specific learni
389
473
 
390
474
  ### Step 4.4: Apply Changes
391
475
 
392
- Use the same atomic rewrite strategy as Phase 3, Step 3.5:
476
+ Use the same archive-safe pipeline as Phase 3, Step 3.5 — **never** a hand-rolled `>` rewrite (#1017):
393
477
 
394
478
  1. Read all lines from `learnings.jsonl`
395
479
  2. Apply the selected operation to selected learnings:
396
480
  - **Boost:** +0.15 confidence (cap 1.0), reset expires_at to +`learning-expiry-days`
397
481
  - **Reduce:** -0.2 confidence
398
- - **Delete:** remove selected entries
482
+ - **Delete:** omit the selected entries from the next generation — do NOT delete them by hand.
483
+ `pruneLearnings()` detects every **record** that left the store — reconciled by `id`, or by a
484
+ content fingerprint when a record carries no usable `id` — and archives it with
485
+ `_archive_reason: "pruned"`, so a `learning-id` referenced by a rendered rule stays resolvable.
399
486
  - **Extend:** reset expires_at to current date + `learning-expiry-days`
400
- 3. Prune entries where `expires_at` < current date OR `confidence` <= 0.0
401
- 4. Consolidate duplicates (same `type` + non-empty `subject`): keep highest confidence.
402
- Null-subject entries are preserved individually (keyed by `id`). See SKILL.md #284 fix note.
403
- 5. Write entire result back with `>` (atomic rewrite)
487
+ 3. Steps 3–5 of the old prose (prune / consolidate / rewrite) are `pruneLearnings()` run the
488
+ **exact** Step 3.5(5) invocation, writing the post-operation entry set to the `--entries`
489
+ sidecar. It prunes
490
+ (`expires_at` < now `expired`; `confidence <= 0.0` → `pruned`), consolidates duplicates
491
+ (same `type` + non-empty `subject`, highest confidence wins, loser archived `superseded` with
492
+ `_superseded_by`; null-subject entries preserved individually per #284), and rewrites through
493
+ `rewriteLearnings()` with its `.bak-<ISO>` snapshot.
404
494
 
405
- Report: "Updated N learnings. Total active: K."
495
+ Report: "Updated N learnings. Total active: K. Archived: A (<byReason>)."
406
496
 
407
497
  ---
408
498
 
@@ -533,13 +623,22 @@ Cross-reference: PRD #506 AC1-AC4 + EARS gates. Vault Integration: dialectic doe
533
623
  - **ALWAYS** use uuid-v4 for new learning IDs (generate via `uuidgen` or equivalent bash command)
534
624
  - **ALWAYS** preserve a candidate-supplied `expires_at`; otherwise derive it from `LEARNING_TTL_DAYS[type]` via `deriveExpiresAt()` rather than hard-coding `learning-expiry-days`
535
625
  - **ALWAYS** present findings to user before writing — no silent writes
536
- - **ALWAYS** use atomic rewrite (read all, modify, write all with `>`) — never append with `>>`
626
+ - **ALWAYS** route store writes through `pruneLearnings()` / `rewriteLearnings()` — never a shell
627
+ `>` rewrite and never an append `>>`. Those helpers own the schema validation, the `.bak-<ISO>`
628
+ snapshot, and the atomic tmp+rename; a hand-rolled redirect owns none of them (#721, #1017)
629
+ - **ALWAYS** let a removed entry land in `learnings-archive.jsonl` — a record may leave the STORE,
630
+ but it may never leave the CORPUS. Rendered `.claude/rules/*.md` cite `learning-id` as provenance;
631
+ a hard delete turns that citation into a dangling pointer (#1017 measured 11 of 13 dead)
537
632
  - **ALWAYS** cap confidence at 1.0 — never exceed
538
633
 
539
634
  ## Anti-Patterns
540
635
 
541
636
  - **DO NOT** write learnings without user confirmation — always present via AskUserQuestion first (on Codex CLI where AskUserQuestion is unavailable, present as a numbered Markdown list)
542
- - **DO NOT** append to `learnings.jsonl` always use atomic rewrite (read all, modify, write all)
637
+ - **DO NOT** append to `learnings.jsonl` with `>>`, and **DO NOT** rewrite it with `>` call
638
+ `pruneLearnings()` (Step 3.5(5)); a shell redirect bypasses validation, backup, and the archive
639
+ - **DO NOT** hard-delete a learning. Every record that leaves the store is archived with an
640
+ `_archive_reason` (`expired` | `pruned` | `superseded` | `merged`) and, for the last two, a
641
+ `_superseded_by` / `_merged_into` tombstone naming its replacement
543
642
  - **DO NOT** create duplicate learnings — always check type + subject match first
544
643
  - **DO NOT** set confidence above 1.0 or forget to cap it
545
644
  - **DO NOT** fabricate patterns — only extract from actual session data with verifiable evidence
@@ -6,7 +6,7 @@ model: haiku
6
6
  model-preference: sonnet
7
7
  model-preference-codex: gpt-5.4-mini
8
8
  model-preference-cursor: claude-sonnet-4-6
9
- description: Use this skill when performing VCS operations on GitLab or GitHub repositories — creating, updating, or closing issues and MRs, applying label taxonomy, running `glab`/`gh` CLI commands, or resolving project IDs dynamically. Acts as the single source of truth for CLI command syntax and label conventions; consuming skills reference this rather than duplicating logic. Triggers: "create a GitLab issue", "list open MRs", "apply priority label", "how do I resolve the project ID", "what's the carryover issue template". <example>Context: session-end needs to file a carryover issue for an incomplete task. user: "/close" assistant: "Creating carryover issue via glab with the Carryover Template from gitlab-ops — labels: carryover, priority:high."</example>
9
+ description: Use this skill when performing VCS operations on GitLab or GitHub repositories — creating, updating, or closing issues and MRs, applying label taxonomy, running `glab`/`gh` CLI commands, or resolving project IDs dynamically. Acts as the single source of truth for CLI command syntax and label conventions; consuming skills reference this rather than duplicating logic. Triggers: "create a GitLab issue", "list open MRs", "apply priority label", "how do I resolve the project ID", "what's the carryover issue template". <example>Context: session-end needs to file a carryover issue for an incomplete task. user: "/close" assistant: "Creating carryover issue via glab with the Carryover Template from gitlab-ops — labels: carryover, priority::high."</example>
10
10
  ---
11
11
 
12
12
  # VCS Operations Reference
@@ -99,13 +99,21 @@ done
99
99
 
100
100
  ## Label Taxonomy
101
101
 
102
- **Taxonomy convention (decided 2026-07-05, #727):** labels use the SINGLE-COLON form exclusively (`priority:high`, `status:ready`, `area:vcs`, `type:chore`, `from:<agent>`). The `::`-scoped form (`priority::high`, GitLab scoped-labels) is DEPRECATED baseline-scaffold legacy and MUST NOT be introduced — this repo mirrors to GitHub, which has no scoped-label semantics (no mutual-exclusion enforcement), so `::` yields zero benefit on the mirror while a migration would break every existing label reference and issue.
102
+ **Taxonomy convention `priority` REVERSED to scoped `::` (supersedes #727 for this one axis).**
103
+
104
+ - **`priority::<level>` is canonical.** #727's stated rationale was that "this repo mirrors to GitHub, which has no scoped-label semantics … while a migration would break every existing label reference and issue." Both halves were checked on 2026-07-25 and neither holds:
105
+ - **Issues are not mirrored at all.** `aiat-poc-infra/docs/github-mirror-runbook.md:1,5` describes a git **push-mirror** with GitHub as "read-only downstream"; `docs/gitlab-team-org-2026-06-21.md:45` confirms there is no two-way GitLab issue sync. Nothing crosses the boundary that a label rename could break.
106
+ - **GitHub already uses the scoped form.** `gh api "repos/AIAT-AIandBusinessgrowth/aiat-barrierefrei-engine/labels"` returns `priority::high`, `priority::low`, `priority::med`, `priority::medium` across 77 open issues, and **zero** `priority:high`. Same pattern on `aiat-doc-vlm`. GitHub treats `::` as an ordinary string; it merely does not enforce mutual exclusion.
107
+ - Volume agrees independently: **416 `priority::` against 249 `priority:` and 7 bare** at the time of the decision. Chasing the minority spelling would mean re-labelling the majority.
108
+ Producers were migrated FIRST (this change); the label-data migration follows separately, because migrating data before producers means the divergence returns within a day.
109
+ - **`area:` / `type:` / `status:` / `from:` stay SINGLE-COLON** — but NOT on #727's rationale, which is disproven above. They stay because nothing measured argues for flipping them, and because each axis is its own migration cost. Flipping them is a separate decision and is explicitly NOT made here. Note that `status` in particular is the worst-disciplined axis on the instance (354 assignments, only 48 percent scoped, 5 genuine value conflicts), so any future flip there needs a conflict-resolution pass first.
110
+ - **Readers accept both spellings.** Every consumer that MATCHES a label compares through `scripts/lib/label-scope.mjs` `normalizeLabel()`, which collapses `::` to `:` — so issues still carrying `priority:high` keep being counted until the data migration lands. Only WRITES are canonical.
103
111
 
104
112
  ### Priority Labels
105
- - `priority:critical` — blocking production or users
106
- - `priority:high` — important, schedule this sprint
107
- - `priority:medium` — plan for next sprint
108
- - `priority:low` — backlog, nice-to-have
113
+ - `priority::critical` — blocking production or users
114
+ - `priority::high` — important, schedule this sprint
115
+ - `priority::medium` — plan for next sprint
116
+ - `priority::low` — backlog, nice-to-have
109
117
 
110
118
  ### Status Labels
111
119
  - `status:ready` — defined, ready to pick up
@@ -151,11 +159,11 @@ GitHub has no native issue-blocking relation at all — the body-ordering-note f
151
159
  # Issues
152
160
  glab issue list --per-page 50 # All open issues
153
161
  glab issue list --label "status:ready" --per-page 10 # Ready to work on
154
- glab issue list --label "priority:high" --per-page 10 # High priority
162
+ glab issue list --label "priority::high" --per-page 10 # High priority
155
163
  glab issue list --closed --per-page 10 # Recently closed
156
164
  glab issue view <IID> # View issue details
157
165
  glab issue view <IID> --comments # With comments
158
- glab issue create --title "title" --label "priority:high,status:ready"
166
+ glab issue create --title "title" --label "priority::high,status:ready"
159
167
  glab issue update <IID> --label "status:in-progress" # WARNING: --label REPLACES the full set — see caveat below
160
168
  glab issue close <IID> # then VERIFY: glab issue view <IID> must show state=closed
161
169
  glab issue note <IID> -m "Comment text" # Add comment
@@ -188,11 +196,11 @@ glab api "projects/$(glab repo view --output json | python3 -c "import json,sys;
188
196
  # Issues
189
197
  gh issue list --limit 50 # All open issues
190
198
  gh issue list --label "status:ready" --limit 10 # Ready to work on
191
- gh issue list --label "priority:high" --limit 10 # High priority
199
+ gh issue list --label "priority::high" --limit 10 # High priority
192
200
  gh issue list --state closed --limit 10 # Recently closed
193
201
  gh issue view <NUMBER> # View issue details
194
202
  gh issue view <NUMBER> --comments # With comments
195
- gh issue create --title "title" --label "priority:high,status:ready"
203
+ gh issue create --title "title" --label "priority::high,status:ready"
196
204
  gh issue edit <NUMBER> --add-label "status:in-progress"
197
205
  gh issue close <NUMBER>
198
206
  gh issue comment <NUMBER> --body "Comment text" # Add comment
@@ -257,6 +265,9 @@ What should be achieved and why.
257
265
  ### Context for next session
258
266
  [relevant context, file paths, decisions made]
259
267
 
268
+ ### Revisit-Trigger
269
+ [the concrete condition or event that reopens this — e.g. "when <metric/state> passes <threshold>", "at the next <session type/release>". A deferral with no named trigger is not a deferral — never a bare "later"/"low prio"/"TBD".]
270
+
260
271
  ### Open Questions
261
272
  _(optional — include only when unanswered questions remain in STATE.md `## Open Questions` at close; omit this section entirely otherwise)_
262
273
  - [ ] [unanswered question 1] (source: W<N>/<agent>, prio: high|medium|low)
@@ -266,13 +277,15 @@ _(optional — include only when unanswered questions remain in STATE.md `## Ope
266
277
  Relates to #ORIGINAL_IID
267
278
  ```
268
279
 
280
+ `### Revisit-Trigger` is **mandatory** for the `/close` carryover template above: a carryover deferred without a concrete, checkable reopen condition is a rot risk — "later" reliably means "never". (The SPIRAL/FAILED escalation carryover built by `scripts/lib/spiral-carryover.mjs` is a deliberately separate, machine-triaged template and carries no trigger field.)
281
+
269
282
  ### Discovery Finding
270
283
 
271
284
  ```markdown
272
285
  ## [Discovery] <finding title>
273
286
 
274
287
  **Probe:** <probe_name>
275
- **Severity:** <priority:critical|high|medium|low>
288
+ **Severity:** <priority::critical|high|medium|low>
276
289
  **Category:** <code|infra|ui|arch|session|audit|vault|feature>
277
290
 
278
291
  ### Finding
@@ -302,7 +315,7 @@ Relates to #ORIGINAL_IID
302
315
  - [ ] Quality gates pass after fix
303
316
  ```
304
317
 
305
- Labels: `type:discovery`, `priority:<level>`, `area:<inferred>`, `status:ready`
318
+ Labels: `type:discovery`, `priority::<level>`, `area:<inferred>`, `status:ready`
306
319
 
307
320
  ## Template-First Enforcement (PSA-005 + #519)
308
321
 
@@ -93,7 +93,7 @@ gitlab-portfolio:
93
93
  enabled: true
94
94
  mode: warn # warn | strict | off
95
95
  stale-days: 30
96
- critical-labels: ["priority:critical", "priority:high"]
96
+ critical-labels: ["priority::critical", "priority::high"]
97
97
  ```
98
98
 
99
99
  | Field | Default | Meaning |
@@ -101,7 +101,7 @@ gitlab-portfolio:
101
101
  | `enabled` | `false` | Master switch. |
102
102
  | `mode` | `warn` | `warn` / `strict` / `off` — failure handling; `off` ≡ disabled. |
103
103
  | `stale-days` | `30` | Issues older than N days are flagged stale. |
104
- | `critical-labels` | `["priority:critical","priority:high"]` | Label substrings that classify an issue as critical (case-insensitive). |
104
+ | `critical-labels` | `["priority::critical","priority::high"]` | Label substrings that classify an issue as critical (case-insensitive). |
105
105
 
106
106
  ### Security
107
107
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  You are the Interrogator — a staff engineer who pressure-tests a plan, design, or PRD by playing devil's advocate. Where `/brainstorm` is a cooperative Design Facilitator that *narrows* an ambiguous design space, you are the adversarial stress test that tries to *break* a plan the user already believes in. You don't collect wishes; you hunt contradictions. You don't expand scope; you expose the assumptions hiding inside it.
6
6
 
7
- You respond in {{owner.language}} when that matches the user's language. You meet people at their abstraction level — product language with stakeholders, interface and data-model language with engineers.
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 meet people at their abstraction level — product language with stakeholders, interface and data-model language with engineers.
8
8
 
9
9
  The user invited the grilling. Relentlessness is the service they asked for, not rudeness. Be sharp, be specific, never be a yes-man — but every challenge points at the plan, never at the person.
10
10
 
@@ -40,6 +40,49 @@ Apply these continuously throughout the grill — they are the substance of the
40
40
 
41
41
  **Apply the tactics that bite.** Not every tactic fits every target — a tooling or meta plan may have no glossary to collide with, a greenfield idea may have no code to contradict yet. Run the tactics that have real material; never manufacture a conflict to tick a box. A forced question violates the fewer-sharper-questions discipline as surely as a skipped real one does.
42
42
 
43
+ ## Output Levels
44
+
45
+ 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 grill.
46
+
47
+ **How to read a budget.** A *turn* is every chat line you author between the user's last answer and your next question — the evidence you read out of the code, the contradiction you name, the one sentence of recommendation reasoning. Raw Grep/Read 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 contradiction disappears, it just gets stated in fewer words.
48
+
49
+ This is the tightest of the orchestrator's budgets by design. A turn is structurally one sentence plus one question; a budget generous enough to hold five questions would license exactly the volley § One question at a time forbids. The grill summary file, if the user asks for one, carries no budget — it is the artifact, not the conversation.
50
+
51
+ **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.
52
+
53
+ **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:
54
+ - input validation, and the reporting of invalid input;
55
+ - error handling, error messages, and failure disclosure — a swallowed error is never "concise";
56
+ - security findings, warnings, and destructive-action confirmations (PSA-003);
57
+ - 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;
58
+ - anything the operator explicitly asked to see;
59
+ - a kill assumption's four fields — *Fails-if*, *Evidence-this-week*, *Kill-criterion*, *Cheapest-test* (Tactic 5) — and the Tiger / Paper Tiger / Elephant sort (Tactic 6). Those are the findings themselves, not narration about them; a three-field workup is an incomplete answer, not a short one.
60
+
61
+ ### output-level: ultra
62
+ - Meaning: telegraphic — the evidence, the contradiction, the question. No narration.
63
+ - Budget: ≤6 lines per turn; ≤1 line per tactic finding; ≤1 line of recommendation reasoning before the tool call. A kill-assumption or pre-mortem turn is exempt (see the never-traded list) but stays at one line per field.
64
+ - Shape: quote the code as `<file>:<line> — <what it does>`, then the collision, then the question. Never restate the user's answer back at them.
65
+ - Escalation: `expand <topic>` — see § Escalation above.
66
+
67
+ ### output-level: full
68
+ - Meaning: terse but complete — framing trimmed, evidence preserved. This is the default.
69
+ - Budget: ≤14 lines per turn; ≤3 lines per tactic finding; ≤2 lines of recommendation reasoning before the tool call.
70
+ - Shape: the steelman in one line, then the attack, then the question. Every claim about behaviour keeps its `<file>:<line>` — that citation IS the evidence; what gets trimmed is the commentary on it.
71
+ - Escalation: `expand <topic>` — see § Escalation above.
72
+
73
+ ### output-level: lite
74
+ - Meaning: verbose — the reasoning behind each challenge is spelled out. Chosen for learning, not for speed.
75
+ - Budget: ≤35 lines per turn; ≤8 lines per tactic finding. Still a ceiling — `lite` is not "unbounded".
76
+ - Shape: explain which tactic you are applying and why it bites here, name the branches of the decision tree you are deferring, define unfamiliar terms on first use.
77
+ - Escalation: `expand <topic>` — see § Escalation above.
78
+
79
+ ### Companion dials
80
+
81
+ Same file, same lookup, same fallback-to-default rule:
82
+
83
+ - `efficiency.preamble` — `minimal` (default): at most one clause before a tool call, and only when the next step is non-obvious; never "Let me read the model." immediately followed by reading it. `verbose`: one sentence before each Grep/Read naming the contradiction you expect to find.
84
+ - `tone.style` — `direct` (this soul's baseline: name the contradiction plainly and make the user resolve it), `neutral` (state the collision without advocacy; still recommend when asked), `friendly` (same content, softer framing; never softer facts). No setting makes you a yes-man — the challenge always lands, only its wording moves.
85
+
43
86
  ## Values
44
87
 
45
88
  - **Skeptical by default** — a plausible claim is not a verified one; the cheap challenge now saves the expensive correction later
@@ -292,7 +292,7 @@ enabled=$(jq -r '.strictMode // false' "$CONFIG_FILE" 2>/dev/null)
292
292
 
293
293
  ## Our in-house examples (read these, not the upstream `examples/`)
294
294
 
295
- - `hooks/pre-bash-destructive-guard.mjs` — policy-driven command blocker, 13 rules in `.orchestrator/policy/blocked-commands.json`
295
+ - `hooks/pre-bash-destructive-guard.mjs` — policy-driven command blocker, 14 rules in `.orchestrator/policy/blocked-commands.json`
296
296
  - `hooks/enforce-scope.mjs` — wave-scope boundary enforcement using `.orchestrator/wave-scope.json`
297
297
  - `hooks/on-session-start.mjs` — banner + session init
298
298
  - `hooks/post-edit-validate.mjs` — validates edits after the fact
@@ -216,7 +216,7 @@ Missing signal fields contribute 0 to the score — no NaN propagation.
216
216
  ## Open Questions (for Phase B-1 follow-up)
217
217
 
218
218
  - Learnings freshness window — default 30d? Configurable per-type or a single global TTL?
219
- - Backlog priority weighting — rule-based (`priority:critical = +0.2` confidence bonus) vs.
219
+ - Backlog priority weighting — rule-based (`priority::critical = +0.2` confidence bonus) vs.
220
220
  learned from historical completion rates?
221
221
  - Alternative-generation algorithm — top-N non-selected modes scored by partial signal match, or
222
222
  fixed set derived from v0 heuristic branches?
@@ -29,7 +29,23 @@ Create at https://www.npmjs.com/settings/<user>/tokens → Generate New Token
29
29
 
30
30
  **Never** put the token in the tracked `.npmrc` (it holds `ignore-scripts=true` per SEC-020 and is committed), never persist it into `~/.npmrc`, never echo it into logs.
31
31
 
32
- ## Publish flow
32
+ ## Canonical path: `scripts/release.mjs` (Release als ein Dispatch, #978)
33
+
34
+ Since v3.19.0 the release is ONE dispatch — the script mechanizes every step below plus the
35
+ version-surface sync this skill previously left to operator memory (the gap that let v3.18.0
36
+ ship tagged but unpublished):
37
+
38
+ ```bash
39
+ node scripts/release.mjs --set-version X.Y.Z # rewrite all 12 version literals (10 files) + codex cachebuster + lock sync
40
+ # … author CHANGELOG entry + README highlights (enforced by --check) …
41
+ node scripts/release.mjs --check # preflight: surfaces, CHANGELOG, tag/registry collision, CI green, leakage gate
42
+ node scripts/release.mjs --publish # token publish → registry verify → tag AFTER publish → push origin+github
43
+ ```
44
+
45
+ The tag is created only AFTER a registry-verified publish — never before. The manual flow
46
+ below remains as the fallback and as documentation of what the script does.
47
+
48
+ ## Publish flow (manual fallback)
33
49
 
34
50
  ```bash
35
51
  # 1. Pre-flight (first publish: expect E404 = name free; upgrade: expect the previous version)
@@ -376,9 +376,9 @@ The Epic issue's number is not known during § 5.5.1. Once Phase 6 creates it, s
376
376
 
377
377
  Score each issue using three factors:
378
378
 
379
- 1. **Technical dependencies (highest weight):** Issues that other issues depend on get `priority:critical` or `priority:high`. Identify dependency chains: DB schema before API, API before frontend, shared libs before consumers, infrastructure before application.
379
+ 1. **Technical dependencies (highest weight):** Issues that other issues depend on get `priority::critical` or `priority::high`. Identify dependency chains: DB schema before API, API before frontend, shared libs before consumers, infrastructure before application.
380
380
 
381
- 2. **Business value (medium weight):** Issues the user marked as core MVP features in the PRD get `priority:high`. Nice-to-haves and polish items get `priority:medium` or `priority:low`.
381
+ 2. **Business value (medium weight):** Issues the user marked as core MVP features in the PRD get `priority::high`. Nice-to-haves and polish items get `priority::medium` or `priority::low`.
382
382
 
383
383
  3. **Risk (tiebreaker) — Impact × Risk 2×2 triage:** Classify each issue by Impact (high/low) and Risk (high/low) before applying the bump:
384
384
  - **High-Impact + Low-Risk → Implement.** Proceed directly; apply the one-level priority bump from the tiebreaker rule.
@@ -387,7 +387,7 @@ Score each issue using three factors:
387
387
  - **Low-Impact + High-Risk → Reject.** Do not create an issue for this candidate; note the rejection rationale in the PRD's Risks & Dependencies section instead.
388
388
 
389
389
  Assign labels from the standard taxonomy:
390
- - `priority:critical` / `priority:high` / `priority:medium` / `priority:low`
390
+ - `priority::critical` / `priority::high` / `priority::medium` / `priority::low`
391
391
  - `type:feature` / `type:enhancement` / `type:bug` / `type:chore` / `type:discovery`
392
392
  - `status:ready`
393
393
  - `area:<inferred from content>` (e.g., `area:api`, `area:frontend`, `area:infra`)
@@ -422,8 +422,8 @@ If user selects "Adjust priorities" or "Remove issues", handle the adjustments i
422
422
  For each approved issue:
423
423
 
424
424
  1. Create via VCS CLI using comma-separated labels in a single `--label` flag:
425
- - **GitLab**: `glab issue create --title "[Plan] <title>" --label "type:feature,priority:high,status:ready" --description "<body>"`
426
- - **GitHub**: `gh issue create --title "[Plan] <title>" --label "type:feature,priority:high,status:ready" --body "<body>"`
425
+ - **GitLab**: `glab issue create --title "[Plan] <title>" --label "type:feature,priority::high,status:ready" --description "<body>"`
426
+ - **GitHub**: `gh issue create --title "[Plan] <title>" --label "type:feature,priority::high,status:ready" --body "<body>"`
427
427
  2. Brief pause (1s) between creations for rate limiting
428
428
  3. After all issues are created, set dependency links:
429
429
  - **GitLab**: use `glab api` to set `blocks`/`is-blocked-by` relations. On HTTP 403 (non-Premium/Ultimate): `relates_to` + body-ordering-note fallback — see gitlab-ops SKILL.md § "Issue Linking (`blocks` / `is_blocked_by`)".
@@ -111,9 +111,9 @@ Source: PRD Section 3 (Acceptance Criteria).
111
111
 
112
112
  Apply this scoring:
113
113
 
114
- 1. **Technical dependencies** — Issues that block other issues get `priority:critical` or `priority:high`. DB before API, API before UI, shared before consumers.
115
- 2. **Core acceptance criteria** — Issues covering primary happy-path scenarios get `priority:high`.
116
- 3. **Edge cases / nice-to-haves** — Defensive scenarios, error handling, optional behaviors get `priority:medium` or `priority:low`.
114
+ 1. **Technical dependencies** — Issues that block other issues get `priority::critical` or `priority::high`. DB before API, API before UI, shared before consumers.
115
+ 2. **Core acceptance criteria** — Issues covering primary happy-path scenarios get `priority::high`.
116
+ 3. **Edge cases / nice-to-haves** — Defensive scenarios, error handling, optional behaviors get `priority::medium` or `priority::low`.
117
117
  4. **Risk factor — Impact × Risk 2×2 triage:** Classify by Impact (high/low) × Risk (high/low): **High-Impact + Low-Risk → Implement** (bump priority up one level); **High-Impact + High-Risk → Experiment** (smallest spike first); **Low-Impact + Low-Risk → Defer** (backlog, no issue in this set); **Low-Impact + High-Risk → Reject** (no issue — note rationale in § Risks & Dependencies). Full quadrant definitions: `SKILL.md` § 6.2 Auto-Prioritize.
118
118
 
119
119
  ### Labels
@@ -121,7 +121,7 @@ Apply this scoring:
121
121
  Apply per gitlab-ops skill label taxonomy:
122
122
 
123
123
  - **Type:** `type:feature` for new capabilities, `type:enhancement` for extensions of existing features.
124
- - **Priority:** `priority:critical` / `priority:high` / `priority:medium` / `priority:low` from auto-prioritize above.
124
+ - **Priority:** `priority::critical` / `priority::high` / `priority::medium` / `priority::low` from auto-prioritize above.
125
125
  - **Area:** Infer from affected code paths (e.g., `area:api`, `area:frontend`, `area:infra`).
126
126
  - **Appetite:** Map from Wave 1 Q5 scope answer (`appetite:1w`, `appetite:2w`, `appetite:6w`).
127
127