5.5 KiB
Research: Quality Gates & Code Hygiene
Feature: 031-quality-gates-hygiene Date: 2026-03-11
R1: Vitest v8 Coverage Configuration
Decision: Use @vitest/coverage-v8 with config-based enablement under test.coverage.
Rationale: Vitest 3.x (project uses 3.2.4) supports v8 coverage natively but requires the @vitest/coverage-v8 peer package installed separately. Config-based coverage.enabled: true ensures coverage runs automatically with vitest run, integrating into the existing pnpm check script without CLI changes.
Configuration:
test: {
coverage: {
provider: "v8",
enabled: true,
thresholds: {
lines: 70,
branches: 60,
autoUpdate: true,
},
},
}
Key details:
thresholds.autoUpdate: truemodifies the config file in-place when coverage exceeds thresholds — contributors must commit the updated file.- No change to
pnpm checkscript needed —vitest runalready runs tests and will now include coverage. coveragedirectory should be added to.gitignore.
Alternatives considered:
- CLI flag
--coverage: Rejected — requires changing thecheckscript and doesn't support autoUpdate persistence. - Istanbul provider: Rejected — v8 is the default and faster for Node.js workloads.
R2: Biome Cognitive Complexity Rule
Decision: Enable noExcessiveCognitiveComplexity with default threshold 15 and refactor 5 existing violations.
Rationale: The rule uses SonarSource's Cognitive Complexity methodology. Threshold 15 is the industry standard default. The rule is not included in recommended: true — it must be explicitly configured.
Configuration:
"complexity": {
"noExcessiveCognitiveComplexity": {
"level": "error",
"options": {
"maxAllowedComplexity": 15
}
}
}
Existing violations (5 functions):
| File | Function | Score |
|---|---|---|
apps/web/src/adapters/bestiary-adapter.ts:257 |
renderEntries |
23 |
apps/web/src/persistence/encounter-storage.ts:21 |
loadEncounter |
17 |
apps/web/src/persistence/encounter-storage.ts:55 |
rehydration callback | 22 |
scripts/check-layer-boundaries.mjs:51 |
checkLayerBoundaries |
22 |
scripts/generate-bestiary-index.mjs:30 |
buildSourceMap |
19 |
All must be refactored before the rule can be enforced.
R3: Biome A11y Rules Review
Decision: Enable noNoninteractiveElementInteractions explicitly. No nursery-stage a11y rules exist in Biome 2.0.
Rationale: Biome 2.0 ships 35 a11y rules, 33 of which are recommended: true. The two non-recommended stable rules are:
noNoninteractiveElementInteractions— directly relevant to this React UI (flags interactive handlers on non-interactive elements)noRestrictedElements— not applicable (requires a config of restricted elements, used for design system enforcement)
There are no nursery-stage a11y rules available. The spec's FR-011 (enable nursery rules) resolves to enabling the one relevant non-recommended stable rule instead.
R4: pnpm audit
Decision: Add pnpm audit --audit-level=high to the pnpm check script.
Rationale: Current dependency tree passes cleanly (0 advisories). The command runs quickly and requires no additional dependencies. Placing it first in the chain provides early failure on CVEs before slower checks run.
Offline consideration: pnpm audit requires network access. If the registry is unreachable, the command fails. This is an acceptable trade-off — pre-commit enforcement is the primary goal, and offline commits are rare.
R5: Biome-Ignore Audit
Decision: Fix 1 blanket ignore, reduce 8 a11y ignores in combatant-row.tsx.
Current state (10 total ignores):
| File | Count | Type |
|---|---|---|
packages/domain/src/set-initiative.ts:65 |
1 | Blanket biome-ignore lint: |
apps/web/src/components/combatant-row.tsx |
8 | Rule-specific (4 pairs of useKeyWithClickEvents + noStaticElementInteractions) |
apps/web/src/adapters/bestiary-adapter.ts:375 |
1 | Rule-specific (noExplicitAny) |
Fix strategies:
- set-initiative.ts: Replace
biome-ignore lint:with proper type narrowing — the guard on line 64 checksaHas && bHas, so TypeScript can narrow if the comparison is restructured (e.g., early return for undefined cases, or explicitas numbercast with rule-specific ignore). - combatant-row.tsx: The 3 inner divs (lines 444-446, 481-486, 491-496) exist solely to call
e.stopPropagation(). These can be replaced with a smallStopPropagationwrapper component that usesonClickCaptureor similar pattern, or the outer row's click handler can checkevent.targetto skip when a child interactive element was clicked. The outer row div (lines 405-407) could be converted to a<div role="button" tabIndex={0} onKeyDown={...}>to satisfy both rules, or the click handler can be moved to a semantic element. - bestiary-adapter.ts: Keep as-is — rule-specific ignore for
noExplicitAnyon raw JSON is justified.
R6: Constitution Early Enforcement Principle
Decision: PATCH amendment to constitution Development Workflow section (version 2.2.0 → 2.2.1).
Rationale: This is a clarification of the existing rule "No change may be merged unless all automated checks pass." The new language makes explicit that gates must run at the earliest feasible enforcement point (pre-commit), not just in CI.
Versioning: PATCH — non-semantic clarification per the constitution's versioning policy.