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
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## Purpose & Overview
7
7
 
8
- Files under `.claude/rules/*.md` are engineering rules injected into agent prompts. The loader — `loadApplicableRules()` in [`scripts/lib/rule-loader.mjs`](../scripts/lib/rule-loader.mjs) — reads every `*.md` file in the rules directory, parses its optional YAML frontmatter, and returns the subset applicable to a given wave. The wave-executor calls it at each wave boundary with the wave's `allowedPaths` (from `wave-scope.json`) as `scopePaths`, so a wave that touches only frontend files does not pay the token cost of backend or Swift rules.
8
+ Files under `.claude/rules/*.md` are engineering rules injected into agent prompts. The loader — `loadApplicableRules()` in [`scripts/lib/rule-loader.mjs`](../scripts/lib/rule-loader.mjs) — reads every `*.md` file in the rules directory, parses its optional YAML frontmatter, and returns the subset applicable to a given wave. The wave-executor calls it at each wave boundary with the wave's `allowedPaths` (from `wave-scope.json`) as `scopePaths` see `skills/wave-executor/wave-loop.md` § "Pre-Dispatch: Glob-Scoped Rule Injection". **The saving that scoping buys is smaller than it looks, and on Claude Code it can be negative:** measured 2026-07-30 on a real wave, the glob axis saved 4.0% of a 169,961-byte corpus, and because Claude Code already delivers every `.claude/rules/*.md` through native project-instruction loading, prepending the block on top costs +72% rather than saving anything. Scoping pays where the harness does NOT auto-load the directory (Codex CLI, Pi, Cursor). Full measurement: [`docs/instruction-delivery.md`](instruction-delivery.md).
9
9
 
10
10
  Two rule categories existed before FA1:
11
11
 
@@ -32,6 +32,7 @@ All keys are optional. Unknown keys are **ignored without error** — adding a n
32
32
  | `auto-generated` | `boolean` | no | Marks a rule produced by the FA2 reconciliation engine (not hand-authored). Triggers the never-always-on invariant (see below). | `auto-generated: true` |
33
33
  | `confidence` | `number` (0..1) | no | Confidence of the source learning that generated the rule. Mirrors the `learnings.jsonl` confidence field. | `confidence: 0.85` |
34
34
  | `tier` | `string` (`always` \| `coordinator-only` \| `wave-only`) | no | Load-context tier (issue #692). Gates which contexts the rule loads in, via the `context` param to `loadApplicableRules`. Absent = no tier gating (backward-compatible). See [Tier gating](#tier-gating-issue-692) below. | `tier: coordinator-only` |
35
+ | `review-date` | `string` (ISO 8601 date) | no | **check-rules.mjs-only, advisory (#880 FA5).** Periodic-review marker for handwritten rules. NOT read by `rule-loader.mjs` (not in `SCALAR_META_KEYS`) — zero effect on loading. Deliberately distinct from `expires-at`, which IS a live gate. See [Handwritten Rule Review Date](#handwritten-rule-review-date-880-fa5) below. | `review-date: 2026-10-23` |
35
36
 
36
37
  **Surfaced names.** The loader normalises kebab-case YAML keys to camelCase on the rule entry: `host-class` → `hostClass`, `expires-at` → `expiresAt`, `learning-key` → `learningKey`, `auto-generated` → `autoGenerated`. (`tier` is already a single lowercase token, so it is surfaced unchanged as `tier`.)
37
38
 
@@ -131,17 +132,59 @@ Absent tag = universal (vendored to every consumer repo, the default and fully b
131
132
 
132
133
  ## The Never-Always-On Invariant (Auto-Generated Rules)
133
134
 
134
- > Forward-reference to FA4 (issue #697). The CI gate described here lands with FA4 in `scripts/lib/validate/check-rules.mjs`; FA1 documents the contract that gate will enforce.
135
+ > Delivered by FA4 (issue #697) in `scripts/lib/validate/check-rules.mjs`, wired into CI via `scripts/validate-plugin.mjs`. FA1 (this doc) specifies the contract that gate enforces.
135
136
 
136
137
  Hand-authored rules are the curated, cross-cutting baseline. **Auto-generated rules are *extra* rules** — narrow, learning-derived, and time-boxed. They must never inflate the always-on instruction budget (cross-ref #668 instruction-budget). The brandmauer (firewall) is:
137
138
 
138
139
  Any rule with `auto-generated: true` **MUST**:
139
140
 
140
- 1. Carry **at least one activation axis** — `globs`, `mode`, or `host-class`. It must **NOT** be always-on.
141
+ 1. Carry **at least one activation axis** — `globs` or `host-class`. It must **NOT** be always-on.
141
142
  2. Carry a `learning-key` (provenance — which learning produced it).
142
143
  3. Carry an `expires-at` (time-box — auto-generated rules are not permanent).
143
144
 
144
- A rule that sets `auto-generated: true` but lacks an activation axis, or omits `learning-key` / `expires-at`, is a violation. The FA4 CI gate (`scripts/lib/validate/check-rules.mjs`) will fail the build on such a rule. This keeps every machine-authored rule conditional and self-expiring — the always-on surface stays the hand-curated baseline.
145
+ A rule that sets `auto-generated: true` but lacks an activation axis, or omits `learning-key` / `expires-at`, is a violation. The FA4 CI gate (`scripts/lib/validate/check-rules.mjs`) fails the build on such a rule (exit 1). This keeps every machine-authored rule conditional and self-expiring — the always-on surface stays the hand-curated baseline.
146
+
147
+ > Note: `scripts/lib/reconcile/emitter.mjs`'s own module doc additionally lists `mode` as a third accepted axis for the *emitter's* internal throw-guard (the pure function that produces a rule's activation metadata before it is ever written to disk). `check-rules.mjs`'s CI gate — which audits `.claude/rules/*.md` files already on disk — checks only `globs`/`host-class`, not `mode`. This is a pre-existing discrepancy between the emitter's internal guard and the CI gate's scope, not something #880 (below) introduced or resolved; every currently-emitted rule uses `globs` as its axis in practice, so the discrepancy has not yet produced a false pass.
148
+
149
+ ## Handwritten Rule Review Date (#880 FA5)
150
+
151
+ The invariant above only binds the **machine author** (the FA2 reconciliation engine, via the emitter's throw-guard). A human authoring a `.claude/rules/*.md` file by hand bypasses it entirely — there was previously no check at all on handwritten rules' activation scoping or on when they were last reviewed. Issue #880 found that most of this repo's handwritten rules carry no `globs`/`paths`/`host-class` frontmatter and no periodic-review marker, and nothing in the system ever prompted a re-review.
152
+
153
+ **Correction to a common misreading:** "no `globs`/`paths`/`host-class`" does **not** mean "no activation axis at all." Since issue #692, `tier:` (`always` | `coordinator-only` | `wave-only`) is a real load-context gating axis — `rule-loader.mjs`'s `applyGates()` excludes `coordinator-only` rules from wave context and `wave-only` rules from coordinator context (see [Tier gating](#tier-gating-issue-692) above). Every handwritten rule in this repo already carries a `tier:` key. Treating `tier:` as *not* an axis and flagging all of them as "no activation axis" would be both factually wrong and operationally dangerous: the obvious-looking fix — adding a `globs:`/`paths:` filter to a rule that is intentionally always-on (e.g. `security.md`, `verification-before-completion.md`) — would silently stop that rule from loading in most waves, a live behaviour change to a safety-critical directive disguised as a metadata fix. **`check-rules.mjs` therefore counts `tier:` as a valid activation axis for handwritten rules, on equal footing with `globs`/`paths`/`host-class`.**
154
+
155
+ ### The symmetric check (WARN-only, `check-rules.mjs`)
156
+
157
+ For every `.claude/rules/*.md` file **without** `auto-generated: true`, the gate checks:
158
+
159
+ 1. **Activation axis** — a non-empty `globs` array, its `paths` alias (#795), a `host-class` key, **or** a `tier` key. Missing all four → `WARN: ... no activation axis ...`.
160
+ 2. **`review-date`** (ISO 8601, e.g. `2026-10-23`) — a periodic-review marker. Missing → `WARN: ... missing a review-date ...`.
161
+
162
+ > **`globs: []` is not a fourth way to pass — and no other axis can rescue it.** An *empty* `globs` array is not the same as an *absent* one. `rule-loader.mjs` excludes such a rule unconditionally (`if (globs.length === 0) continue;`), and that check runs **after** `applyGates()` — so a co-present `tier:`, `host-class:` or `mode:` cannot bring it back. The rule matches nothing and never loads, in any context. `check-rules.mjs` therefore emits its own distinct WARN for `globs: []` that wins over the axis check, rather than reporting the (opposite) "loads always-on". Read rule 1 as a flat OR only for a *non-empty* or *absent* `globs`.
163
+
164
+ Both checks are **advisory (WARN), not a build failure** — `check-rules.mjs`'s exit code is driven **solely** by the pre-existing auto-generated hard-fail invariants above. A handwritten-rule WARN never turns CI red. Promoting this to a hard gate is a later, deliberate step (not part of #880), once every handwritten rule in the fleet has a `review-date`.
165
+
166
+ ### Why `review-date`, not `expires-at`
167
+
168
+ `expires-at` was deliberately **not** reused as the handwritten review marker. `rule-loader.mjs`'s `applyGates()` treats `expires-at` as a **live expiry gate**: once the date passes, the rule is silently **excluded** from every load (fail-open only on a malformed date — see [Fail-open on parse error](#fail-open-on-parse-error) above). Stamping `expires-at` on an always-on safety rule (`security.md`, `parallel-sessions.md`, …) as a "please review this periodically" reminder would mean that rule **actually stops loading** the day the reminder date passes — the opposite of what a review marker should do.
169
+
170
+ `review-date` is a **new, inert** frontmatter key instead:
171
+
172
+ - It is **not** in `rule-loader.mjs`'s `SCALAR_META_KEYS` allowlist (that module is contract-locked as of #880 — its allowlist was not extended). An unrecognised key is ignored without error per the existing parser contract, so `review-date` has **zero effect** on `loadApplicableRules()` / `applyGates()` — it is parsed only by `check-rules.mjs`, via a small local regex helper (`hasFrontmatterKey()`) that scans the raw frontmatter block directly.
173
+ - Format: a bare ISO 8601 date (`YYYY-MM-DD`), same shape as `expires-at`, but purely advisory.
174
+
175
+ ```markdown
176
+ ---
177
+ tier: always
178
+ review-date: 2026-10-23
179
+ ---
180
+ # Security Rules (Always-on)
181
+ ```
182
+
183
+ ### Current fleet state (as migrated by #880)
184
+
185
+ **Every** handwritten rule in this repo's own `.claude/rules/` carries a `review-date`: the #880 migration added one to the then-current set (2026-07-25, +90 days from the session date), and every rule added since ships with one from the start. None gained a new `globs:`/`paths:` axis — every file's pre-existing activation-axis state (`tier:`, with or without `globs:`) is unchanged; only the `review-date:` line was added. The auto-generated rules are untouched by this section (they already satisfy the FA4 invariant in full).
186
+
187
+ Four of them (`loop-and-monitor.md`, `lsp.md`, `mvp-scope.md`, `owner-persona.md`) carry `tier: coordinator-only` — genuinely excluded from wave context, not part of the wave-time always-on budget despite lacking `globs:`. The rest carry either `tier: always` or `tier: wave-only` + `globs:` (`bash-harness-pitfalls.md`, `cli-design.md`, `testing.md`) — the `tier: always` cohort is the genuinely unconditional, every-context, every-wave one.
145
188
 
146
189
  ## Learning Type-Taxonomy, TTL & Provenance Standard (issue #723 B6 / #733)
147
190
 
@@ -160,8 +203,8 @@ Transcribed verbatim from `LEARNING_TYPE_REGISTRY` (16 types):
160
203
  | `fragile-file` | 45 | true | true |
161
204
  | `effective-sizing` | 45 | true | false |
162
205
  | `recurring-issue` | 45 | true | true |
163
- | `workflow-pattern` | 90 | true | false |
164
- | `proven-pattern` | 90 | true | false |
206
+ | `workflow-pattern` | 90 | true | true |
207
+ | `proven-pattern` | 90 | true | true |
165
208
  | `anti-pattern` | 90 | true | true |
166
209
  | `autopilot-effectiveness` | 90 | true | false |
167
210
  | `autonomy-verdict` | 90 | false | false |
@@ -174,7 +217,11 @@ Transcribed verbatim from `LEARNING_TYPE_REGISTRY` (16 types):
174
217
 
175
218
  Capability axes:
176
219
  - **`agentProposable`** — the type may appear in `PROPOSAL_TYPES` (a wave-agent may `memory.propose()` this type). `autonomy-verdict`, `fragile-pattern`, and `stagnation-class-frequency` are `false` — these are analyzer-synthesized classes, not agent-observed, so they are never agent-proposable.
177
- - **`ruleConvertible`** — the type may appear in `CONVERT_TYPES` (the FA2 reconciliation engine may convert a learning of this type into a conditional `.claude/rules/*.md` proposal). `fragile-file`, `recurring-issue`, `anti-pattern`, `convention`, `architecture-pattern`, `design-pattern`, `fragile-pattern`, and `stagnation-class-frequency` are the eight `ruleConvertible: true` types.
220
+ - **`ruleConvertible`** — the type may appear in `CONVERT_TYPES` (the FA2 reconciliation engine may convert a learning of this type into a conditional `.claude/rules/*.md` proposal). `fragile-file`, `recurring-issue`, `anti-pattern`, `convention`, `architecture-pattern`, `design-pattern`, `fragile-pattern`, `stagnation-class-frequency`, `workflow-pattern`, and `proven-pattern` are the ten `ruleConvertible: true` types (issue #900 flipped the last two from `false` — the real corpus census showed a large volume of these records carrying usable `file_paths` scope that were structurally unconvertible before the flip).
221
+
222
+ ### Type aliasing (issue #900)
223
+
224
+ The real learnings corpus also accumulated free-form type names that were never registered — the same semantic classes as two registered types, written with a different literal. `LEARNING_TYPE_ALIASES` in `scripts/lib/learnings/schema.mjs` maps these to their canonical counterpart (`gotcha` → `anti-pattern`, `pattern` → `proven-pattern`), applied by `normalizeDialects()` on both the read and write/migration funnels — mirroring the existing `files` → `file_paths` dialect-normalization pattern one level up (a type-name alias instead of a field-name alias). No alias key may collide with a registry key (guarded by a test in `tests/lib/learnings-schema-normalization.test.mjs`).
178
225
 
179
226
  `LEARNING_TTL_DAYS[type]` derives its value from `LEARNING_TYPE_REGISTRY[type].ttlDays` for every listed type, plus a `default: 60` fallback entry for any type not present in the registry (`deriveExpiresAt()` looks up `LEARNING_TTL_DAYS[type] ?? LEARNING_TTL_DAYS.default`).
180
227
 
@@ -303,7 +350,9 @@ timeout masks real perf regressions.
303
350
 
304
351
  - [`skills/_shared/config-reading.md`](../skills/_shared/config-reading.md) § "Glob-Scoped Rule Injection (#336)" — wave-boundary injection flow + match algorithm
305
352
  - [`scripts/lib/rule-loader.mjs`](../scripts/lib/rule-loader.mjs) — `loadApplicableRules()` implementation (the contract this doc specifies)
306
- - `scripts/lib/validate/check-rules.mjs` — FA4 CI validation gate (forward-reference; lands with #697)
353
+ - `scripts/lib/validate/check-rules.mjs` — FA4 hard-fail CI gate (auto-generated invariants, #697) + FA5 warn-mode symmetric check (handwritten rules, #880)
354
+ - [`tests/lib/validate/check-rules.test.mjs`](../tests/lib/validate/check-rules.test.mjs) — auto-generated invariant coverage (FA4 #697)
355
+ - [`tests/rules/check-rules-handwritten.test.mjs`](../tests/rules/check-rules-handwritten.test.mjs) — handwritten warn-mode coverage (#880 FA5)
307
356
  - [`scripts/print-applicable-rules.mjs`](../scripts/print-applicable-rules.mjs) — `--context wave|coordinator` flag exercises the tier gate (#692)
308
357
  - [`scripts/lib/validate-vendored-rules.mjs`](../scripts/lib/validate-vendored-rules.mjs) — pre-write vendoring validator (issue #722 Epic A Wave 2)
309
358
  - [`scripts/lib/rules-sync.mjs`](../scripts/lib/rules-sync.mjs) — `syncRules()` implementation, archetype resolution (issue #722 Epic A)
@@ -313,4 +362,4 @@ timeout masks real perf regressions.
313
362
  - [`scripts/lib/reconcile/eligibility.mjs`](../scripts/lib/reconcile/eligibility.mjs) — `CONVERT_TYPES` (derived from `LEARNING_TYPE_REGISTRY`), rule-conversion eligibility gates
314
363
  - [`scripts/lib/memory-proposals/schema.mjs`](../scripts/lib/memory-proposals/schema.mjs) — `PROPOSAL_TYPES` (derived from `LEARNING_TYPE_REGISTRY`)
315
364
  - [`docs/session-config-reference.md`](session-config-reference.md#reconcile-693--696--697) § Reconcile — `reconcile.rule-expiry-days` / `min-rule-days` / `min-insight-chars` config keys that tune the emitted `expires-at` and eligibility gates
316
- - Issues: #336 (glob-scoping), #668 (instruction-budget), #692 (tier load-context gating), #693 (Rule Activation epic), #694 (FA1 foundation), #697 (FA4 validation), #722 (vendoring validation + archetype-scoped manifest), #723 B6 / #733 (type-taxonomy + provenance standard)
365
+ - Issues: #336 (glob-scoping), #668 (instruction-budget), #692 (tier load-context gating), #693 (Rule Activation epic), #694 (FA1 foundation), #697 (FA4 validation), #722 (vendoring validation + archetype-scoped manifest), #723 B6 / #733 (type-taxonomy + provenance standard), #880 (FA5 — handwritten-rule symmetric check, warn mode)