session-orchestrator 3.16.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) 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 +130 -412
  6. package/README.md +17 -12
  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/docs/README.md +2 -1
  21. package/docs/USER-GUIDE.md +8 -3
  22. package/docs/ci-setup.md +121 -7
  23. package/docs/codex-setup.md +1 -1
  24. package/docs/components.md +7 -7
  25. package/docs/cursor-setup.md +22 -9
  26. package/docs/events-schema.md +5 -1
  27. package/docs/instruction-delivery.md +444 -0
  28. package/docs/pi-setup.md +1 -1
  29. package/docs/rule-authoring.md +58 -9
  30. package/docs/session-config-reference.md +306 -6
  31. package/docs/session-config-template.md +66 -3
  32. package/docs/telemetry/telemetry-claims.md +204 -0
  33. package/docs/telemetry.md +158 -0
  34. package/hooks/_lib/guard-source-loader.mjs +467 -0
  35. package/hooks/_lib/lock-bootstrap.mjs +21 -0
  36. package/hooks/_lib/vcs-create-matcher.mjs +119 -0
  37. package/hooks/config-protection.mjs +0 -0
  38. package/hooks/enforce-commands.mjs +10 -2
  39. package/hooks/hooks-codex.json +1 -1
  40. package/hooks/hooks-cursor.json +11 -2
  41. package/hooks/hooks-pi.json +10 -0
  42. package/hooks/hooks.json +21 -1
  43. package/hooks/on-session-end.mjs +178 -18
  44. package/hooks/on-session-start.mjs +23 -0
  45. package/hooks/post-bash-write-verify.mjs +977 -0
  46. package/hooks/post-subagent-discovery-validator.mjs +256 -41
  47. package/hooks/pre-bash-destructive-guard.mjs +525 -160
  48. package/hooks/pre-bash-issue-budget.mjs +167 -0
  49. package/hooks/pre-bash-sessions-ledger-guard.mjs +627 -0
  50. package/hooks/pre-bash-templates-first.mjs +96 -63
  51. package/hooks/skill-invocation-telemetry.mjs +109 -10
  52. package/hooks/subagent-telemetry.mjs +527 -37
  53. package/package.json +16 -3
  54. package/pi/prompts/contract-version-bump.md +12 -0
  55. package/rules/README.md +32 -0
  56. package/scripts/archive-closed-prds.mjs +12 -22
  57. package/scripts/autopilot-multi.mjs +103 -20
  58. package/scripts/backfill-abandoned-sessions.mjs +160 -4
  59. package/scripts/check-doc-consistency.sh +17 -1
  60. package/scripts/compute-grounding-injection.sh +18 -3
  61. package/scripts/dialectic-deriver.mjs +7 -2
  62. package/scripts/eval-session.mjs +50 -9
  63. package/scripts/fleet-instruction-scan.mjs +141 -0
  64. package/scripts/lib/auto-dialectic.mjs +11 -2
  65. package/scripts/lib/auto-dream.mjs +16 -5
  66. package/scripts/lib/autopilot/mr-draft.mjs +31 -1
  67. package/scripts/lib/autopilot/worktree-pipeline.mjs +113 -5
  68. package/scripts/lib/backlog-scan.mjs +39 -6
  69. package/scripts/lib/blocked-commands-policy.mjs +340 -0
  70. package/scripts/lib/build-live-signals.mjs +7 -4
  71. package/scripts/lib/ci-status-banner.mjs +75 -12
  72. package/scripts/lib/claude-md-budget-lint.mjs +283 -34
  73. package/scripts/lib/command-blocker.mjs +1013 -58
  74. package/scripts/lib/config/config-protection.mjs +2 -1
  75. package/scripts/lib/config/context-coverage.mjs +82 -0
  76. package/scripts/lib/config/drift-check.mjs +9 -1
  77. package/scripts/lib/config/gitlab-portfolio.mjs +1 -1
  78. package/scripts/lib/config/issue-budget.mjs +123 -0
  79. package/scripts/lib/config/moc-staleness.mjs +98 -0
  80. package/scripts/lib/config/reconcile.mjs +21 -0
  81. package/scripts/lib/config/section-extractor.mjs +121 -1
  82. package/scripts/lib/config/worktree-orphans.mjs +138 -0
  83. package/scripts/lib/config-schema.mjs +23 -3
  84. package/scripts/lib/config.mjs +32 -0
  85. package/scripts/lib/context-coverage-banner.mjs +223 -0
  86. package/scripts/lib/convergence-monitor.mjs +49 -3
  87. package/scripts/lib/description-surface.mjs +535 -0
  88. package/scripts/lib/dispatcher/enumerate.mjs +136 -30
  89. package/scripts/lib/dispatcher/rank.mjs +22 -8
  90. package/scripts/lib/ecosystem-wizard/config-writer.mjs +26 -24
  91. package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +1 -1
  92. package/scripts/lib/eval/engine.mjs +47 -5
  93. package/scripts/lib/events.mjs +59 -7
  94. package/scripts/lib/evolve/autonomy-verdict.mjs +5 -0
  95. package/scripts/lib/evolve/autopilot-effectiveness.mjs +54 -7
  96. package/scripts/lib/gates/gate-full.mjs +15 -3
  97. package/scripts/lib/gates/gate-helpers.mjs +132 -6
  98. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +28 -8
  99. package/scripts/lib/gitlab-portfolio/aggregator.mjs +8 -2
  100. package/scripts/lib/gitlab-portfolio/cli.mjs +1 -1
  101. package/scripts/lib/handover-gate.mjs +7 -3
  102. package/scripts/lib/harness-audit/categories/category4.mjs +22 -5
  103. package/scripts/lib/instruction-budget-guard.mjs +402 -51
  104. package/scripts/lib/io.mjs +345 -10
  105. package/scripts/lib/issue-budget.mjs +269 -0
  106. package/scripts/lib/issue-close-strip-labels.mjs +39 -9
  107. package/scripts/lib/label-scope.mjs +47 -0
  108. package/scripts/lib/learnings/schema.mjs +43 -3
  109. package/scripts/lib/lock-reaper.mjs +1 -2
  110. package/scripts/lib/memory-proposals/schema.mjs +36 -1
  111. package/scripts/lib/moc-staleness-banner.mjs +267 -0
  112. package/scripts/lib/peer-discovery.mjs +645 -0
  113. package/scripts/lib/pi-hook-bridge.mjs +146 -17
  114. package/scripts/lib/product-repo-detect.mjs +9 -8
  115. package/scripts/lib/project-hygiene.mjs +432 -0
  116. package/scripts/lib/quality-gate.mjs +167 -0
  117. package/scripts/lib/recommendations-v0.mjs +1 -1
  118. package/scripts/lib/reconcile/eligibility.mjs +1 -1
  119. package/scripts/lib/reconcile/emitter.mjs +23 -4
  120. package/scripts/lib/reconcile/engine.mjs +147 -39
  121. package/scripts/lib/reconcile/idempotency.mjs +114 -14
  122. package/scripts/lib/reconcile-nudge-banner.mjs +65 -9
  123. package/scripts/lib/resource-probe/evaluate.mjs +70 -4
  124. package/scripts/lib/resource-probe.mjs +19 -0
  125. package/scripts/lib/rule-loader.mjs +6 -0
  126. package/scripts/lib/scope-baseline.mjs +564 -0
  127. package/scripts/lib/scope-gate.mjs +399 -98
  128. package/scripts/lib/session-close-backfill.mjs +61 -6
  129. package/scripts/lib/session-end/phase-skip.mjs +1 -0
  130. package/scripts/lib/session-end/worktree-orphan-sweep.mjs +252 -0
  131. package/scripts/lib/session-id.mjs +221 -41
  132. package/scripts/lib/session-lock.mjs +304 -6
  133. package/scripts/lib/session-schema/constants.mjs +22 -3
  134. package/scripts/lib/session-schema/filters.mjs +88 -0
  135. package/scripts/lib/session-schema/validator.mjs +16 -0
  136. package/scripts/lib/session-schema.mjs +1 -0
  137. package/scripts/lib/sessions-integrity-banner.mjs +294 -0
  138. package/scripts/lib/sessions-staleness-banner.mjs +121 -12
  139. package/scripts/lib/skill-evolution/idempotency.mjs +135 -16
  140. package/scripts/lib/skill-evolution/mr-opener.mjs +9 -1
  141. package/scripts/lib/skill-health/join.mjs +35 -9
  142. package/scripts/lib/spiral-carryover.mjs +142 -30
  143. package/scripts/lib/state-md/mission-status.mjs +53 -3
  144. package/scripts/lib/subagents-schema.mjs +43 -9
  145. package/scripts/lib/telemetry/anon-id.mjs +141 -0
  146. package/scripts/lib/telemetry/consent.mjs +299 -0
  147. package/scripts/lib/telemetry/paths.mjs +27 -0
  148. package/scripts/lib/telemetry/queue.mjs +287 -0
  149. package/scripts/lib/telemetry/schema.mjs +384 -0
  150. package/scripts/lib/telemetry/sync.mjs +312 -0
  151. package/scripts/lib/test-runner/issue-reconcile.mjs +53 -13
  152. package/scripts/lib/tests-src-ratio.mjs +484 -0
  153. package/scripts/lib/validate/check-agents.mjs +56 -0
  154. package/scripts/lib/validate/check-hooks-symmetry.mjs +244 -10
  155. package/scripts/lib/validate/check-rules.mjs +217 -35
  156. package/scripts/lib/validate/check-test-value-bans.mjs +782 -0
  157. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  158. package/scripts/lib/validate-vendored-rules.mjs +10 -2
  159. package/scripts/lib/vault-archive.mjs +17 -2
  160. package/scripts/lib/vault-backfill/glab.mjs +8 -0
  161. package/scripts/lib/vault-mirror/process.mjs +30 -0
  162. package/scripts/lib/vault-mirror/render-sessions.mjs +293 -36
  163. package/scripts/lib/vault-status/board-writer.mjs +63 -5
  164. package/scripts/lib/vault-status/narrative-mirror.mjs +13 -7
  165. package/scripts/lib/vcs-repo-spec.mjs +362 -0
  166. package/scripts/lib/wave-resource-gate.mjs +115 -11
  167. package/scripts/lib/worktree/listing.mjs +44 -7
  168. package/scripts/mcp-server.sh +32 -6
  169. package/scripts/measure-context-overhead.sh +151 -0
  170. package/scripts/memory-propose.mjs +72 -9
  171. package/scripts/print-applicable-rules.mjs +51 -12
  172. package/scripts/release.mjs +534 -0
  173. package/scripts/run-quality-gate.mjs +123 -5
  174. package/scripts/telemetry.mjs +250 -0
  175. package/scripts/validate-wave-scope.mjs +182 -17
  176. package/scripts/vault-integration-watcher.mjs +32 -10
  177. package/skills/_shared/config-reading.md +2 -2
  178. package/skills/bootstrap/fast-template.md +1 -1
  179. package/skills/claude-md-drift-check/checker.mjs +145 -28
  180. package/skills/contract-version-bump/SKILL.md +219 -0
  181. package/skills/discovery/SKILL.md +4 -4
  182. package/skills/discovery/issue-templates.md +11 -11
  183. package/skills/discovery/probes-audit.md +1 -1
  184. package/skills/discovery/probes-feature.md +1 -1
  185. package/skills/discovery/probes-session.md +26 -5
  186. package/skills/ecosystem-health/SKILL.md +1 -1
  187. package/skills/ecosystem-health/wizard.md +4 -4
  188. package/skills/evolve/SKILL.md +1 -0
  189. package/skills/gitlab-ops/SKILL.md +20 -12
  190. package/skills/gitlab-portfolio/SKILL.md +2 -2
  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 +97 -0
  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/quality-gates/SKILL.md +1 -1
  199. package/skills/reconcile/SKILL.md +21 -4
  200. package/skills/session-end/SKILL.md +108 -14
  201. package/skills/session-end/discovery-scan.md +4 -2
  202. package/skills/session-end/drift-operations.md +4 -4
  203. package/skills/session-end/metrics-collection.md +13 -0
  204. package/skills/session-end/phase-3-2-docs-verification.md +1 -1
  205. package/skills/session-end/phase-3-6-tail.md +2 -1
  206. package/skills/session-end/plan-verification.md +5 -2
  207. package/skills/session-end/vault-operations.md +1 -1
  208. package/skills/session-end/verification-checklist.md +1 -1
  209. package/skills/session-plan/SKILL.md +6 -2
  210. package/skills/session-plan/wave-template.md +2 -0
  211. package/skills/session-start/SKILL.md +149 -7
  212. package/skills/session-start/phase-4-5-resource-health.md +15 -2
  213. package/skills/test-runner/SKILL.md +2 -2
  214. package/skills/vault-sync/SKILL.md +1 -1
  215. package/skills/vault-sync/package-lock.json +3 -3
  216. package/skills/vault-sync/validator.mjs +228 -40
  217. package/skills/wave-executor/SKILL.md +5 -2
  218. package/skills/wave-executor/circuit-breaker.md +2 -0
  219. package/skills/wave-executor/wave-loop.md +163 -10
  220. 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
 
@@ -261,6 +261,7 @@ For confirmed learnings, use atomic rewrite strategy:
261
261
  - `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
262
  - `created_at`: current ISO 8601 date
263
263
  - `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
+ - `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.
264
265
  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
266
  6. **Prune:** remove entries where `expires_at` < current date OR `confidence` <= 0.0
266
267
  7. **Consolidate duplicates (NULL-SUBJECT SAFE):** if same `type` + `subject` appears more than once
@@ -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
@@ -272,7 +280,7 @@ Relates to #ORIGINAL_IID
272
280
  ## [Discovery] <finding title>
273
281
 
274
282
  **Probe:** <probe_name>
275
- **Severity:** <priority:critical|high|medium|low>
283
+ **Severity:** <priority::critical|high|medium|low>
276
284
  **Category:** <code|infra|ui|arch|session|audit|vault|feature>
277
285
 
278
286
  ### Finding
@@ -302,7 +310,7 @@ Relates to #ORIGINAL_IID
302
310
  - [ ] Quality gates pass after fix
303
311
  ```
304
312
 
305
- Labels: `type:discovery`, `priority:<level>`, `area:<inferred>`, `status:ready`
313
+ Labels: `type:discovery`, `priority::<level>`, `area:<inferred>`, `status:ready`
306
314
 
307
315
  ## Template-First Enforcement (PSA-005 + #519)
308
316
 
@@ -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
 
@@ -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?
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: npm-publish
3
+ user-invocable: true
4
+ model: sonnet
5
+ description: Use when publishing this package to npm — a version release (npm publish), verifying the registry/pi.dev listing, or diagnosing npm auth failures (E403 2FA/token errors). Token-based flow via NPM_TOKEN in .env.local with a temp userconfig, leakage-gate greps before every publish, post-publish verification and marker/badge upkeep. Trigger on "publish to npm", "npm release", "E403 publish error".
6
+ ---
7
+
8
+ # npm-publish — Token-based publish runbook
9
+
10
+ > Companion to `docs/distribution/npm-publish-checklist.md` (the original 7-step operator runbook). This skill adds the token-auth mechanics and the failure-mode diagnosis learned during the v3.16.0 first publish (2026-07-19).
11
+
12
+ ## Why this skill exists
13
+
14
+ npm requires 2FA **or** a granular access token with "Bypass 2FA" for every publish (policy active since 2025; legacy tokens were removed Nov 2025 — only granular tokens exist). The failure mode is confusing: `npm publish` fails with **E403 and NO OTP prompt** when the account either has no 2FA enrolled or the supplied token lacks the bypass flag. Three dead ends verified empirically: plain `npm publish` (E403), `--auth-type=web` (no web flow exists for publish), PTY-forced publish (same E403). The ONLY non-interactive path is a correctly-configured granular token.
15
+
16
+ ## Token requirements (all four mandatory)
17
+
18
+ Create at https://www.npmjs.com/settings/<user>/tokens → Generate New Token → **Granular Access Token**:
19
+
20
+ 1. **Permissions: Read and write** (Packages and scopes).
21
+ 2. **Packages: "All packages"** for a FIRST publish (the package does not exist yet, so per-package selection cannot include it). After the first publish, re-create scoped to the single package — least privilege.
22
+ 3. **"Bypass two-factor authentication (2FA)" enabled** — this is the checkbox whose absence produces the E403-without-prompt. npm shows a red security warning here and recommends Trusted Publishing for CI/CD; for interactive operator-assisted releases the short-lived bypass token is acceptable.
23
+ 4. **Short expiration** — write tokens default to 7 days (90 max). Take the default.
24
+
25
+ ## Auth resolution order
26
+
27
+ 1. `NPM_TOKEN` in `.env.local` at the repo root (gitignored — verify with `git check-ignore .env.local` before writing; also confirm no `.env` pattern in the `files` whitelist of package.json).
28
+ 2. Interactive fallback: operator runs `npm publish --access public` in a real terminal (only works when account 2FA is enrolled — OTP prompt appears).
29
+
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
+
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)
49
+
50
+ ```bash
51
+ # 1. Pre-flight (first publish: expect E404 = name free; upgrade: expect the previous version)
52
+ npm view session-orchestrator version
53
+
54
+ # 2. Leakage gate — every grep MUST print 0 (from docs/distribution/npm-publish-checklist.md)
55
+ npm pack --dry-run 2>&1 | grep -cE "npm notice.* tests/"
56
+ npm pack --dry-run 2>&1 | grep -c "npm notice.*\.orchestrator/"
57
+ npm pack --dry-run 2>&1 | grep -cE "npm notice.*[[:space:]]\.claude/"
58
+ npm pack --dry-run 2>&1 | grep -c "npm notice.*\.github/"
59
+ npm pack --dry-run 2>&1 | grep -c "node_modules"
60
+ npm pack --dry-run 2>&1 | grep -ci "\.env"
61
+ npm pack --dry-run 2>&1 | grep -ci "owner\.yaml"
62
+
63
+ # 3. Publish via temp userconfig (never a persistent npmrc)
64
+ NPM_TOKEN=$(grep '^NPM_TOKEN=' .env.local | cut -d= -f2-)
65
+ TMPRC=$(mktemp) && printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$TMPRC" && chmod 600 "$TMPRC"
66
+ npm publish --access public --userconfig "$TMPRC"; rm "$TMPRC"
67
+
68
+ # 4. Verify
69
+ npm view session-orchestrator version # must print the new version
70
+ ```
71
+
72
+ Success marker: `+ session-orchestrator@<version>` on the publish output.
73
+
74
+ ## Post-publish checklist
75
+
76
+ 1. **Verify registry**: `npm view session-orchestrator version dist.unpackedSize keywords` — `pi-package` keyword must be present.
77
+ 2. **pi.dev gallery**: indexing is asynchronous — check https://pi.dev/packages later; do not block on it.
78
+ 3. **Marker upkeep** (first publish only — done in v3.16.0): README install matrix + npm badge, `site/index.html` install section, `docs/pi-setup.md` availability paragraph.
79
+ 4. **Rotate/delete the token** at https://www.npmjs.com/settings/<user>/tokens once the release is done — especially if the token value ever transited chat, a screenshot, or any log. A token pasted into a conversation is burned: rotate immediately after use.
80
+ 5. Update the release issue / CHANGELOG if the publish was part of a tracked release.
81
+
82
+ ## Failure-mode table
83
+
84
+ | Symptom | Cause | Fix |
85
+ |---|---|---|
86
+ | `E403 ... Two-factor authentication or granular access token with bypass 2fa enabled is required` — no OTP prompt | Account has no 2FA enrolled AND token (if any) lacks Bypass-2FA | Create granular token with all four requirements above, or enroll 2FA |
87
+ | Same E403 despite a fresh token | Token created without the Bypass-2FA checkbox, or Read-only, or package-scoped on a first publish | Re-create: RW + All packages + Bypass-2FA |
88
+ | `E404` on `npm view` after publish | Registry propagation (rare, seconds) or publish actually failed | Re-check the publish output for `+ <name>@<version>` |
89
+ | `ENEEDAUTH` | No login/token at all | Token flow above, or `npm login` |
90
+ | OTP prompt appears but flow is non-interactive (`!`-prefix, script) | No TTY for the prompt | Use the token flow, or a real terminal |
91
+
92
+ ## Security invariants
93
+
94
+ - `.env.local` is gitignored AND absent from the npm `files` whitelist — verify both before writing a token into it.
95
+ - Temp userconfig: `chmod 600`, deleted immediately after publish.
96
+ - The leakage gate runs before EVERY publish, not only the first.
97
+ - npm's own recommendation for unattended CI/CD is **Trusted Publishing** (OIDC) — evaluate it if publishing ever moves into CI (ref: https://docs.npmjs.com/about-access-tokens).
@@ -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
 
@@ -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
 
@@ -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