@exaudeus/workrail 3.36.0 → 3.37.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 (44) hide show
  1. package/dist/config/config-file.js +2 -0
  2. package/dist/console-ui/assets/{index-n8cJrS4v.js → index-o-p__sHJ.js} +1 -1
  3. package/dist/console-ui/index.html +1 -1
  4. package/dist/daemon/workflow-runner.d.ts +1 -0
  5. package/dist/daemon/workflow-runner.js +3 -6
  6. package/dist/manifest.json +23 -15
  7. package/dist/trigger/notification-service.d.ts +42 -0
  8. package/dist/trigger/notification-service.js +164 -0
  9. package/dist/trigger/trigger-listener.js +7 -1
  10. package/dist/trigger/trigger-router.d.ts +3 -1
  11. package/dist/trigger/trigger-router.js +4 -1
  12. package/docs/design/agent-behavior-patterns-discovery.md +312 -0
  13. package/docs/design/agent-engine-communication-discovery.md +390 -0
  14. package/docs/design/agent-loop-architecture-alternatives-discovery.md +531 -0
  15. package/docs/design/agent-loop-error-handling-contract.md +238 -0
  16. package/docs/design/complete-step-approach-validation-discovery.md +344 -0
  17. package/docs/design/daemon-stuck-detection-discovery.md +174 -0
  18. package/docs/design/mcp-server-disconnect-discovery.md +245 -0
  19. package/docs/design/mcp-server-epipe-crash.md +198 -0
  20. package/docs/design/notification-design-candidates.md +131 -0
  21. package/docs/design/notification-design-review.md +84 -0
  22. package/docs/design/notification-implementation-plan.md +181 -0
  23. package/docs/design/spawn-agent-failure-modes.md +161 -0
  24. package/docs/design/spawn-agent-result-handling-implementation-plan.md +186 -0
  25. package/docs/design/stdio-simplification-design-candidates.md +341 -0
  26. package/docs/design/stdio-simplification-design-review.md +93 -0
  27. package/docs/design/stdio-simplification-implementation-plan.md +317 -0
  28. package/docs/design/structured-output-tools-coexist-findings.md +288 -0
  29. package/docs/discovery/coordinator-script-design.md +745 -0
  30. package/docs/discovery/coordinator-ux-discovery.md +471 -0
  31. package/docs/discovery/spawn-agent-failure-modes.md +309 -0
  32. package/docs/discovery/workflow-selection-for-discovery-tasks.md +336 -0
  33. package/docs/discovery/worktrain-status-briefing.md +325 -0
  34. package/docs/discovery/worktrain-status-design-candidates.md +202 -0
  35. package/docs/discovery/worktrain-status-design-review-findings.md +86 -0
  36. package/docs/ideas/backlog.md +608 -0
  37. package/docs/ideas/daemon-structured-output-vs-tool-calls.md +344 -0
  38. package/docs/ideas/design-candidates-backlog-consolidation.md +85 -0
  39. package/docs/ideas/design-review-findings-backlog-consolidation.md +39 -0
  40. package/docs/ideas/implementation_plan_backlog_consolidation.md +117 -0
  41. package/docs/plans/authoring-doc-staleness-enforcement-candidates.md +251 -0
  42. package/docs/plans/authoring-doc-staleness-enforcement-review.md +99 -0
  43. package/docs/plans/authoring-doc-staleness-enforcement.md +463 -0
  44. package/package.json +1 -1
@@ -0,0 +1,251 @@
1
+ # Authoring Doc Staleness Enforcement -- Design Candidates
2
+
3
+ **Main design doc:** `docs/plans/authoring-doc-staleness-enforcement.md`
4
+ **Status:** Raw investigative material for main-agent review. NOT a final decision.
5
+ **Date:** 2026-04-16
6
+
7
+ ---
8
+
9
+ ## Problem Understanding
10
+
11
+ ### Tensions
12
+ 1. **Completeness signal vs false positive noise**: Git-date staleness fires on cosmetic code touches. If it fires too often, it gets suppressed. The check must be signal-dense enough to trust.
13
+ 2. **Self-maintaining check vs rich check**: A feature-ID coverage check is perfectly self-maintaining. A staleness check with per-rule dates requires 32-rule backfill and ongoing date updates.
14
+ 3. **Single responsibility vs one-stop validation**: Adding new checks to `validate-authoring-spec.js` keeps one command but dilutes responsibility. Separate scripts are cleaner but add CI wiring overhead.
15
+ 4. **validate:authoring-spec not in CI yet**: Any check is advisory-only until wired into CI. The CI gap is load-bearing for all existing AND new checks.
16
+
17
+ ### Likely seam
18
+ The gap is NOT in the runtime (correctly enforces the closed set). The gap is between:
19
+ - `src/application/services/compiler/feature-registry.ts` -- source of truth for what features exist
20
+ - `spec/authoring-spec.json` -- documentation target
21
+
22
+ The seam is the CI validation boundary, where this relationship can be asserted mechanically.
23
+
24
+ ### What makes it hard
25
+ - The check needs to exist before it's obvious it's missing
26
+ - Wiring into CI so it actually blocks PRs (not just a script nobody runs)
27
+ - Getting false positive rate low enough that the check stays trusted
28
+ - Bootstrap problem: existing rules need review dates before staleness check is meaningful
29
+
30
+ ---
31
+
32
+ ## Philosophy Constraints
33
+
34
+ | Principle | How it applies |
35
+ |---|---|
36
+ | Make illegal states unrepresentable | Feature coverage check makes 'undocumented feature in registry' a CI-time violation |
37
+ | Validate at boundaries | CI IS the boundary. validate:authoring-spec not being in CI means no boundary today |
38
+ | YAGNI with discipline | Per-rule lastReviewed is YAGNI right now. Spec-level date is sufficient for first iteration |
39
+ | Errors are data | Scripts should aggregate violations and fail once at the end, not fail-fast mid-loop |
40
+ | Architectural fixes over patches | The feature-registry-as-source-of-truth approach is architectural; a manual checklist is a patch |
41
+
42
+ **Philosophy conflict:** YAGNI vs Make illegal states unrepresentable. Per-rule dates are more rigorous but are not yet needed. Resolution: start with spec-level dates (YAGNI), evolve to per-rule when false positives prove noisy.
43
+
44
+ ---
45
+
46
+ ## Impact Surface
47
+
48
+ - `scripts/validate-authoring-spec.js` -- extended with staleness flag or referenced as precedent
49
+ - `scripts/validate-feature-coverage.js` -- new script (Candidate A)
50
+ - `spec/authoring-spec.json` -- must add `wr.features.capabilities` rule to pass coverage check
51
+ - `spec/authoring-spec.schema.json` -- may need optional `lastReviewed` field on rules (Candidate C)
52
+ - `.github/workflows/ci.yml` -- `validate-workflows` job must add new npm script calls
53
+ - `package.json` -- new npm scripts for the new checks
54
+ - `AGENTS.md` -- new enforcement checklist section
55
+
56
+ Nearby contracts that must stay consistent:
57
+ - `validate:registry` job in CI (add new validation alongside, not replacing)
58
+ - `stamp-workflow` npm script pattern (precedent for lastReviewed date tooling)
59
+
60
+ ---
61
+
62
+ ## Candidates
63
+
64
+ ### Candidate A: Minimal -- wire existing script + add feature coverage check
65
+
66
+ **Summary:** Add `validate:authoring-spec` to CI and add `scripts/validate-feature-coverage.js` that regex-extracts `wr.features.*` IDs from `feature-registry.ts` and asserts each appears in `authoring-spec.json`.
67
+
68
+ **Tensions resolved:** Completeness signal (hard fail on undocumented features). CI enforcement (existing structural check finally blocked). **Accepted:** No staleness signal.
69
+
70
+ **Boundary:** CI validation gate in the `validate-workflows` job.
71
+
72
+ **Why that boundary is best fit:** All existing workflow checks live in `validate-workflows`. This keeps the enforcement surface coherent.
73
+
74
+ **Failure mode:** Regex breaks if `feature-registry.ts` is reformatted. Guard: assert extracted count > 0, fail if zero with "regex may be broken" message.
75
+
76
+ **Repo pattern relationship:** Directly adapts `validate-authoring-spec.js` structure and `validate:registry` CI pattern.
77
+
78
+ **Gain:** Lowest cost. Fixes `wr.features.capabilities` gap immediately. One new 50-line script.
79
+ **Give up:** No time-based signal. Can't detect when a documented feature's implementation diverges from its spec entry.
80
+
81
+ **Scope:** Best-fit. Narrow enough for one PR, closes the primary gap.
82
+
83
+ **Philosophy:** Honors Make illegal states unrepresentable, Validate at boundaries, YAGNI. No conflicts.
84
+
85
+ **Pseudocode:**
86
+ ```js
87
+ // scripts/validate-feature-coverage.js
88
+ function extractFeatureIds(registrySource) {
89
+ const matches = [...registrySource.matchAll(/id:\s*['"]([^'"]+)['"]/g)];
90
+ const ids = matches.map(m => m[1]).filter(id => id.startsWith('wr.features.'));
91
+ if (ids.length === 0) {
92
+ throw new Error('Extracted 0 feature IDs -- regex may be broken');
93
+ }
94
+ return ids;
95
+ }
96
+
97
+ function collectSpecText(spec) {
98
+ const texts = [];
99
+ const visit = (rule) => {
100
+ texts.push(rule.id, rule.rule, ...(rule.checks ?? []), ...(rule.antiPatterns ?? []));
101
+ for (const ref of rule.sourceRefs ?? []) texts.push(ref.path, ref.note ?? '');
102
+ };
103
+ for (const topic of [...(spec.topics ?? []), ...(spec.plannedTopics ?? [])]) {
104
+ for (const rule of topic.rules) visit(rule);
105
+ }
106
+ return texts.join('\n');
107
+ }
108
+
109
+ function main() {
110
+ const registrySource = fs.readFileSync(REGISTRY_PATH, 'utf8');
111
+ const spec = JSON.parse(fs.readFileSync(SPEC_PATH, 'utf8'));
112
+ const featureIds = extractFeatureIds(registrySource);
113
+ const specText = collectSpecText(spec);
114
+ const uncovered = featureIds.filter(id => !specText.includes(id));
115
+ if (uncovered.length > 0) {
116
+ console.error('FAIL: feature IDs in registry with no spec coverage:', uncovered);
117
+ process.exit(1);
118
+ }
119
+ console.log(`OK: all ${featureIds.length} features covered`);
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ### Candidate B: Staleness extension to existing validator
126
+
127
+ **Summary:** Extend `validate-authoring-spec.js` with `--check-staleness` flag that runs `git log -1 --format=%as -- <path>` on each TS sourceRef file and compares against the spec-level `lastReviewed` date. Warn (not fail) when stale. Add as advisory-only CI step.
128
+
129
+ **Tensions resolved:** Staleness detection (time-based signal for drift after initial doc). **Accepted:** False positive noise (cosmetic touches trigger it), spec-level date is coarse (all rules share one review date).
130
+
131
+ **Boundary:** Extended `validate-authoring-spec.js` invoked with `--check-staleness` flag. Separate npm script `validate:authoring-staleness` to keep it decoupled from hard-fail checks.
132
+
133
+ **Why that boundary is best fit:** Reuses existing script infrastructure. Advisory-only mode prevents it from blocking legitimate non-docs PRs on day 1.
134
+
135
+ **Failure mode:** Contributor updates a comment in `response-supplements.ts`, staleness fires on response-supplements rules even though guidance is still accurate. Check gets ignored. **Mitigation:** Document the false positive expectation explicitly. Only promote to hard-fail after per-rule dates are backfilled (Candidate C).
136
+
137
+ **Repo pattern relationship:** Directly adapts the `stamp-workflow` lastReviewed date pattern. Git-log date queries are a standard shell/CI pattern.
138
+
139
+ **Gain:** Catches implementation drift after documentation was written. Provides a time-based hygiene signal.
140
+ **Give up:** False positive cost at spec-level granularity. Requires spec `lastReviewed` to be manually bumped after review passes.
141
+
142
+ **Scope:** Best-fit as a complement to A. Slightly too broad as standalone solution.
143
+
144
+ **Philosophy:** Honors Determinism, Validate at boundaries. Mild conflict with Make illegal states unrepresentable (advisory only, not hard invariant).
145
+
146
+ **Pseudocode:**
147
+ ```js
148
+ // Addition to validate-authoring-spec.js
149
+ function getGitLastModifiedDate(filePath) {
150
+ try {
151
+ return execSync(`git log -1 --format="%as" -- "${filePath}"`,
152
+ { cwd: repoRoot, encoding: 'utf8' }).trim() || null;
153
+ } catch { return null; }
154
+ }
155
+
156
+ function checkStaleness(spec) {
157
+ const reviewDate = new Date(spec.lastReviewed);
158
+ const stale = [];
159
+ for (const topic of spec.topics) {
160
+ for (const rule of topic.rules) {
161
+ const ruleDate = rule.lastReviewed ? new Date(rule.lastReviewed) : reviewDate;
162
+ for (const ref of rule.sourceRefs ?? []) {
163
+ if (!ref.path.match(/\.(ts|js)$/)) continue;
164
+ const modified = getGitLastModifiedDate(path.join(repoRoot, ref.path));
165
+ if (modified && new Date(modified) > ruleDate) {
166
+ stale.push({ ruleId: rule.id, path: ref.path, modified, reviewed: ruleDate.toISOString().slice(0,10) });
167
+ }
168
+ }
169
+ }
170
+ }
171
+ return stale;
172
+ }
173
+ // Called from main() when --check-staleness flag is present. Warns on findings.
174
+ // Exits non-zero only if WORKRAIL_STRICT_STALENESS=1 env var is set.
175
+ ```
176
+
177
+ ---
178
+
179
+ ### Candidate C: Per-rule lastReviewed with schema enforcement
180
+
181
+ **Summary:** Add optional `lastReviewed` ISO date string to each rule in `authoring-spec.schema.json`, backfill all 32 existing rules, and have staleness check use per-rule dates instead of spec-level date.
182
+
183
+ **Tensions resolved:** Surgical staleness (only the specific rule covering a changed file goes stale). Lower false positive rate than B. **Accepted:** One-time 32-rule backfill cost, ongoing date update obligation.
184
+
185
+ **Boundary:** Schema validation layer (Ajv enforces valid dates) + staleness check.
186
+
187
+ **Failure mode:** Per-rule dates become stale because contributors forget to update them. The per-rule precision advantage erodes and the check becomes as noisy as Candidate B.
188
+
189
+ **Repo pattern relationship:** Adapts stamp-workflow pattern at per-rule granularity. Requires a `stamp-authoring-spec.js` helper script (analogous to `stamp-workflow.ts`) to be useful in practice.
190
+
191
+ **Gain:** Surgical signal. Only the rules covering changed files flag stale.
192
+ **Give up:** Highest maintenance cost. Bootstrap obligation.
193
+
194
+ **Scope:** Too broad for initial implementation. Correct long-term direction.
195
+
196
+ **Philosophy:** Honors Make illegal states unrepresentable, Prefer explicit domain types. Conflicts with YAGNI.
197
+
198
+ ---
199
+
200
+ ### Candidate D: knownFeatureIds sidecar array in authoring-spec.json
201
+
202
+ **Summary:** Add `knownFeatureIds: string[]` array to `authoring-spec.json`, manually sync with registry, validate that each listed ID has a spec rule.
203
+
204
+ **Tensions resolved:** Schema-enforced coverage (Ajv validates the list). **Accepted:** Reintroduces manual sync problem -- two places to update when adding a feature.
205
+
206
+ **Boundary:** Schema validation (Ajv).
207
+
208
+ **Failure mode:** Contributor adds feature to registry, forgets to update `knownFeatureIds`. Check passes silently because it only validates listed IDs have coverage. Completeness hole.
209
+
210
+ **Repo pattern relationship:** Departs from self-maintaining approach. No precedent.
211
+
212
+ **Gain:** Simpler validation logic.
213
+ **Give up:** Self-maintenance. This IS the problem we're trying to solve -- a sidecar list just moves the symptom.
214
+
215
+ **Scope:** Too narrow -- solves wrong problem.
216
+
217
+ **Philosophy:** Conflicts with Architectural fixes over patches. Honors Type safety.
218
+
219
+ ---
220
+
221
+ ## Comparison and Recommendation
222
+
223
+ ### Recommendation: A + B implemented sequentially (C as future evolution)
224
+
225
+ **First PR (Candidate A):** Wire `validate:authoring-spec` into CI + add `validate-feature-coverage.js`. Fixes `wr.features.capabilities` gap immediately. Hard-fail on undocumented features.
226
+
227
+ **Second PR (Candidate B):** Add `--check-staleness` advisory mode. Wire into CI as a warning step. Uses spec-level `lastReviewed`.
228
+
229
+ **Future (Candidate C):** Upgrade B to per-rule granularity if false positives prove noisy.
230
+
231
+ **Candidate D:** Strictly inferior to A. Dismissed.
232
+
233
+ ---
234
+
235
+ ## Self-Critique
236
+
237
+ **Strongest counter-argument:** Advisory CI steps that never become blocking are eventually ignored. If Candidate B stays advisory forever, it has no enforcement value. Mitigation: the AGENTS.md checklist explicitly names the condition for promoting staleness to hard-fail.
238
+
239
+ **Narrower option that lost:** Just adding `validate:authoring-spec` to CI without the feature coverage script. Lost because the structural check already passes on current spec -- adding it to CI adds presence but doesn't catch the `wr.features.capabilities` gap.
240
+
241
+ **Broader option that might be justified:** Candidate C now. Evidence needed: a specific false positive from spec-level staleness that gets ignored by reviewers. Without that evidence, C is YAGNI.
242
+
243
+ **Assumption that would invalidate the design:** Regex extraction of feature IDs from `feature-registry.ts` becomes unreliable as the file evolves. If the `FEATURE_DEFINITIONS` array is refactored (e.g., moved to separate files, or IDs become programmatically generated), the regex breaks silently (zero IDs extracted). The guard (`if ids.length === 0, fail`) converts this from a silent false negative to a loud failure.
244
+
245
+ ---
246
+
247
+ ## Open Questions for the Main Agent
248
+
249
+ 1. Is `wr.features.capabilities` intentionally undocumented (internal-only feature) or is this a confirmed gap that should be fixed?
250
+ 2. Should `validate:authoring-spec` be added to the existing `validate-workflows` job in CI, or warrant its own separate job?
251
+ 3. Is the advisory-to-blocking promotion path for the staleness check something the project owner wants to commit to in the first PR, or leave open?
@@ -0,0 +1,99 @@
1
+ # Authoring Doc Staleness Enforcement -- Design Review Findings
2
+
3
+ **Reviewing:** Hybrid A+ (Candidate A feature coverage + Candidate B staleness with per-rule fallback)
4
+ **Date:** 2026-04-16
5
+
6
+ ---
7
+
8
+ ## Tradeoff Review
9
+
10
+ | Tradeoff | Criteria impact | What tips it | Hidden assumption |
11
+ |---|---|---|---|
12
+ | Text search (floor, not quality gate) | Passes Criterion 1 as stated | First incident of misleading minimal coverage | Authors add meaningful coverage by intent, not because the check forces depth |
13
+ | No staleness signal in PR 1 | Criterion 2 deferred to PR 2 | Second PR never ships | Follow-up PR ships within weeks |
14
+ | Regex brittleness (mitigated by guard) | Handled -- guard makes failure loud | IDs become dynamically generated | feature-registry.ts remains the single source |
15
+
16
+ ---
17
+
18
+ ## Failure Mode Review
19
+
20
+ | Failure mode | Handling | Missing mitigation | Risk |
21
+ |---|---|---|---|
22
+ | Regex extracts zero IDs | Guard clause fails CI loudly | Could assert `>= N` minimum count | Low |
23
+ | CI wiring omitted from PR | Not self-enforcing -- depends on author following checklist | Add explicit ci.yml step to AGENTS.md checklist | **HIGH** -- most likely silent failure |
24
+ | Staleness fires on cosmetic touches | Advisory mode (warn, don't fail) | Add action comment in CI step | Medium |
25
+ | feature-registry.ts is split | Not handled | Add assumption comment at top of script | Low-medium |
26
+
27
+ ---
28
+
29
+ ## Runner-Up / Simpler Alternative Review
30
+
31
+ **Runner-up (Candidate C -- per-rule dates):** Worth borrowing one element: the `rule.lastReviewed ?? spec.lastReviewed` fallback pattern. Costs zero extra implementation and creates the organic migration path from B to C. **Incorporated into Hybrid A+.**
32
+
33
+ **Simpler alternative (just add validate:authoring-spec to CI):** Too narrow -- does not catch `wr.features.capabilities`. Insufficient for Criterion 1.
34
+
35
+ **No hybrid opportunity was found that reduces complexity** relative to the already-minimal Hybrid A+.
36
+
37
+ ---
38
+
39
+ ## Philosophy Alignment
40
+
41
+ | Principle | Alignment |
42
+ |---|---|
43
+ | Make illegal states unrepresentable | STRONG -- CI-time violation for undocumented features |
44
+ | Validate at boundaries | STRONG -- CI wiring closes the enforcement gap |
45
+ | Errors are data | STRONG -- aggregate-then-fail pattern followed |
46
+ | Determinism | STRONG -- git log + regex are deterministic |
47
+ | Architectural fixes over patches | TENSION (acceptable) -- text search is a floor, not structural link |
48
+ | YAGNI | STRONG -- spec-level date, no premature structural fields |
49
+
50
+ ---
51
+
52
+ ## Findings
53
+
54
+ ### Red (blocking)
55
+ None. No blocking issues identified.
56
+
57
+ ### Orange (important, requires action)
58
+ **O1: CI wiring is the highest-risk gap**
59
+ If `validate:authoring-spec` and `validate:feature-coverage` are not added to `.github/workflows/ci.yml` in the same PR that introduces the scripts, both checks become permanently advisory-only. This is the most likely silent failure mode. The AGENTS.md checklist must include an explicit ci.yml verification step.
60
+
61
+ ### Yellow (track but not blocking)
62
+ **Y1: Staleness check deferred to PR 2**
63
+ Criterion 2 (staleness signal) is not met by the first PR. Risk is low if PR 2 ships promptly. Mitigation: create a GitHub issue for PR 2 at the same time as PR 1.
64
+
65
+ **Y2: feature-registry.ts assumption undocumented**
66
+ If the registry is refactored, the regex may break in a way the zero-guard doesn't catch. Add a comment at the top of `validate-feature-coverage.js` documenting this assumption.
67
+
68
+ **Y3: Coverage is text presence, not quality**
69
+ A one-line antiPattern mention satisfies the check. The AGENTS.md checklist is the quality gate; the script is only the floor gate. Document this distinction explicitly in the script's comment header.
70
+
71
+ ---
72
+
73
+ ## Recommended Revisions
74
+
75
+ 1. **Add ci.yml verification to AGENTS.md checklist** (Orange O1). The checklist should read: 'Verify that `.github/workflows/ci.yml` `validate-workflows` job runs both `validate:authoring-spec` and `validate:feature-coverage`.'
76
+
77
+ 2. **Add assumption comment to validate-feature-coverage.js** (Yellow Y2):
78
+ ```js
79
+ // Assumption: wr.features.* IDs are defined as string literals in
80
+ // src/application/services/compiler/feature-registry.ts.
81
+ // If the registry is refactored (split files, dynamic IDs), update this script.
82
+ ```
83
+
84
+ 3. **Create GitHub issue for PR 2 (staleness check)** when PR 1 merges (Yellow Y1). Do not treat it as deferred-indefinitely.
85
+
86
+ 4. **Include per-rule lastReviewed fallback in Candidate B implementation** (Hybrid A+ improvement from runner-up analysis):
87
+ ```js
88
+ const ruleDate = rule.lastReviewed ? new Date(rule.lastReviewed) : new Date(spec.lastReviewed);
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Residual Concerns
94
+
95
+ 1. **Quality floor vs quality gate**: The coverage check verifies presence, not adequacy. This is a deliberate choice (YAGNI), but it means a PR could satisfy CI with a one-word mention. The AGENTS.md checklist is the only quality gate for coverage depth.
96
+
97
+ 2. **Advisory staleness drift**: If Candidate B stays advisory forever (never promoted to hard-fail), it provides diminishing value as contributors learn to ignore it. The upgrade condition (promote to hard-fail after per-rule dates are backfilled) should be explicitly tracked as a GitHub issue.
98
+
99
+ 3. **wr.features.capabilities gap**: This is confirmed undocumented, but the design doc raises the open question of whether it's intentionally undocumented. Before PR 1 lands, confirm with the project owner that `wr.features.capabilities` should be in `authoring-spec.json`. If it's intentionally excluded (e.g., internal/experimental), the feature coverage check would need an allowlist mechanism.