Summary
Stage 1 of #646 ships two curated presets — preset-react-spa and preset-react-native. Stage 2 (stage-2-generate-path, #691) gave react-native a stub-driven generate-path oracle-coverage test, but react-spa has none. The hand-authored preset-react-spa/eslint-rules/require-error-boundary.ts has never been run through the synthesize → L4 → emit pipeline, even at stub level. This issue closes that asymmetry.
Grounded current state (verified against staging 2026-06-26)
- react-native — covered.
packages/core/synthesizer/generate.test.ts exercises a recipe-less RN ResearchPlan (fixtures/rn-research-plan.json) through synthesizeGenerate(...) and asserts: (a) L4 accepts, (b) the emitted rule set fires (FlatList → FlashList), (c) oracle coverage against RULES.react-native.md (IDs R12/R14/R15/R18). The LLM GenerateClient is a deterministic stub (generate-stubs.ts:stubGenerateRN, "test use only") — so this proves the pipeline handles RN's documented rule set, not that a live LLM reproduces it (live Path A is non-deterministic and tested separately).
- react-spa — not covered. No
react-spa fixture, no stubGenerateReactSPA, no oracle-coverage test against RULES.react-spa.md. grep -rn 'react-spa' packages/core/synthesizer/*.test.ts fixtures/ → empty.
Why it was deferred (not a bug)
Stage 2 acceptance required only "≥1 proving-case stack" (stage-2-generate-path/kickoff.md:66). react-native (4-rule set) satisfied the ≥1 floor; react-spa (single gap rule require-error-boundary) was a listed candidate but not required once RN was exercised. The result is a silent asymmetry — exactly the "stranded hand-maintained preset" failure README §Methodology warns about: one Stage-1 preset is generator-pipeline-covered, the other is hand-only.
Scope (small — NOT an epic)
Mirror the RN treatment for react-spa:
- Add a frozen
fixtures/react-spa-research-plan.json (oracle = RULES.react-spa.md).
- Add
stubGenerateReactSPA to generate-stubs.ts returning the react-spa rule set (require-error-boundary + any plugin-sourced rules).
- Add a
generate.test.ts block (or sibling test) asserting: L4 accepts the plan; require-error-boundary fires on a paired bad/good fixture; emitted set covers every RULES.react-spa.md oracle ID.
Estimated ~1–2 h. Each new rule/fixture follows principle 02 (paired negative) and the existing capability-commit discipline.
Out of scope
- Live-LLM proof of Path A (non-deterministic; not unit-tested — same boundary as RN).
- Path B (LLM authors AST source), Gate 3 (Stryker), Gate 5 (two-AI review) — their own deferred triggers per open-questions.md §13.10 areas 3/4/5.
Acceptance criteria
- A react-spa generate-path test exists and is green; covers all
RULES.react-spa.md oracle IDs (parity with the RN test's T7 oracle-coverage block).
make self-audit stays green.
Context
Summary
Stage 1 of #646 ships two curated presets —
preset-react-spaandpreset-react-native. Stage 2 (stage-2-generate-path, #691) gave react-native a stub-driven generate-path oracle-coverage test, but react-spa has none. The hand-authoredpreset-react-spa/eslint-rules/require-error-boundary.tshas never been run through thesynthesize → L4 → emitpipeline, even at stub level. This issue closes that asymmetry.Grounded current state (verified against
staging2026-06-26)packages/core/synthesizer/generate.test.tsexercises a recipe-less RNResearchPlan(fixtures/rn-research-plan.json) throughsynthesizeGenerate(...)and asserts: (a) L4 accepts, (b) the emitted rule set fires (FlatList → FlashList), (c) oracle coverage againstRULES.react-native.md(IDs R12/R14/R15/R18). The LLMGenerateClientis a deterministic stub (generate-stubs.ts:stubGenerateRN, "test use only") — so this proves the pipeline handles RN's documented rule set, not that a live LLM reproduces it (live Path A is non-deterministic and tested separately).react-spafixture, nostubGenerateReactSPA, no oracle-coverage test againstRULES.react-spa.md.grep -rn 'react-spa' packages/core/synthesizer/*.test.ts fixtures/→ empty.Why it was deferred (not a bug)
Stage 2 acceptance required only "≥1 proving-case stack" (
stage-2-generate-path/kickoff.md:66).react-native(4-rule set) satisfied the≥1floor;react-spa(single gap rulerequire-error-boundary) was a listed candidate but not required once RN was exercised. The result is a silent asymmetry — exactly the "stranded hand-maintained preset" failure README §Methodology warns about: one Stage-1 preset is generator-pipeline-covered, the other is hand-only.Scope (small — NOT an epic)
Mirror the RN treatment for react-spa:
fixtures/react-spa-research-plan.json(oracle =RULES.react-spa.md).stubGenerateReactSPAtogenerate-stubs.tsreturning the react-spa rule set (require-error-boundary+ any plugin-sourced rules).generate.test.tsblock (or sibling test) asserting: L4 accepts the plan;require-error-boundaryfires on a paired bad/good fixture; emitted set covers everyRULES.react-spa.mdoracle ID.Estimated ~1–2 h. Each new rule/fixture follows principle 02 (paired negative) and the existing capability-commit discipline.
Out of scope
Acceptance criteria
RULES.react-spa.mdoracle IDs (parity with the RN test's T7 oracle-coverage block).make self-auditstays green.Context
packages/core/synthesizer/generate.test.ts(RN oracle-coverage),generate-stubs.ts:stubGenerateRN.