diff --git a/.planning/phases/01-level-up-pf2e-regelkonform/01-02-SUMMARY.md b/.planning/phases/01-level-up-pf2e-regelkonform/01-02-SUMMARY.md new file mode 100644 index 0000000..08a14d4 --- /dev/null +++ b/.planning/phases/01-level-up-pf2e-regelkonform/01-02-SUMMARY.md @@ -0,0 +1,217 @@ +--- +phase: 01-level-up-pf2e-regelkonform +plan: 02 +subsystem: testing +tags: [pure-functions, jest, tdd, level-up, prereq-evaluator, recompute, pf2e-rules] + +# Dependency graph +requires: + - phase: 01-level-up-pf2e-regelkonform + provides: applyAttributeBoost + AbilityAbbreviation (Plan 01-01) — boost-cap-at-18 helper +provides: + - Five pure-function modules (types.ts + 4 logic modules) covering all PF2e level-up math + - skill-increase-cap (LVL-06) — TRAINED→EXPERT@L3, EXPERT→MASTER@L7, MASTER→LEGENDARY@L15 + - prereq-evaluator (D-01..D-04) — DSL parser + evaluator + German formatter, UNKNOWN-aggressive + - recompute-derived-stats (Pitfall #8/#9 safe) — pure pipeline, never outputs current/temp HP fields + - compute-applicable-steps (D-10 ordering) — wizard step list per (level, FA, caster, choiceType) + - 46 passing Jest tests across 4 specs (≥17 prereq, 13 steps, 9 skill, 5 recompute) + - Test discipline pattern: strict RED→GREEN per module, separate commits +affects: [Plan 01-04 LevelingService integration, Plan 01-05 wizard UI preview, Plan 01-03 progression seed] + +# Tech tracking +tech-stack: + added: [] # No new dependencies — ts-jest + jest already present + patterns: + - "Pure-function lib (no NestJS, no Prisma, no I/O) under server/src/modules//lib/" + - "Discriminated-union return values with explicit type-guard helpers for TS-strict narrowing" + - "Sibling .spec.ts test files using Jest's testRegex auto-discovery" + - "TDD: spec written first (RED commit) → minimal implementation (GREEN commit)" + +key-files: + created: + - server/src/modules/leveling/lib/types.ts + - server/src/modules/leveling/lib/skill-increase-cap.ts + - server/src/modules/leveling/lib/skill-increase-cap.spec.ts + - server/src/modules/leveling/lib/prereq-evaluator.ts + - server/src/modules/leveling/lib/prereq-evaluator.spec.ts + - server/src/modules/leveling/lib/recompute-derived-stats.ts + - server/src/modules/leveling/lib/recompute-derived-stats.spec.ts + - server/src/modules/leveling/lib/compute-applicable-steps.ts + - server/src/modules/leveling/lib/compute-applicable-steps.spec.ts + - server/src/modules/leveling/lib/apply-attribute-boost.ts # Rule 3 dependency from Plan 01-01 + modified: [] + +key-decisions: + - "Type-guard helpers (isUnknown, isOk, isFail) preferred over discriminant property access for TS-strict narrowing" + - "OR-list parser uses Oxford-comma normalization (', or ' → ' or ') before tokenizing to avoid leftover 'or' prefix" + - "Feat-name heuristic restricts to ≤4 words and rejects free-text function-words (you/a/the/of/and) to prevent sentence-as-feat misclassification" + - "L5 step list deviates from plan: PF2e CRB has class/skill feats only at EVEN levels; plan's L5 list was rules-incorrect" + - "Boost-cap-at-18 enforced via applyAttributeBoost() import — math is not duplicated in recompute" + - "DerivedStats output object NEVER includes hpCurrent/hpTemp fields — Pitfall #9 enforced by spec assertion AND by absence of those literal strings in production code" + +patterns-established: + - "RED commit (failing spec) → GREEN commit (minimal impl) gate sequence per module" + - "UNKNOWN-aggressive prereq evaluation: any non-classifiable atom poisons the whole clause (per D-03)" + - "ClassProgression-driven proficiency overrides with character pre-existing rank as fallback" + - "German user-facing reason strings: 'Du benötigst...', 'Dir fehlt das Talent...', 'Voraussetzung nicht erfüllt: ...'" + +requirements-completed: [LVL-02, LVL-06, LVL-09, LVL-10, LVL-01, LVL-13, LVL-14] + +# Metrics +duration: ~30min +completed: 2026-04-27 +--- + +# Phase 1 Plan 02: Level-Up Pure-Function Library Summary + +**Five pure-function modules (skill-cap, prereq-DSL, recompute, step-ordering, shared types) implemented strict-TDD with 46 passing Jest tests, establishing the test discipline for the Level-Up subsystem and isolating bug-prone PF2e math (Pitfall #8/#9) behind a fully-tested boundary.** + +## Tasks + +| # | Task | Status | Commits | +|---|------|--------|---------| +| 0 | Add apply-attribute-boost dependency (Rule 3 — Plan 01-01 work not merged into worktree base) | Done | 7e40449 | +| 1 | types.ts — shared type vocabulary | Done | 4d2cb5e | +| 2 | skill-increase-cap (RED→GREEN) | Done | 3a4267d, f189750 | +| 3 | prereq-evaluator (RED→GREEN) | Done | 66d9d5c, da82d9b | +| 4 | recompute-derived-stats (RED→GREEN) | Done | 8dd55b6, 6011024 | +| 5 | compute-applicable-steps (RED→GREEN) | Done | 70ec7bb, de07fc8, d9ed18c | +| 6 | Full leveling test suite gate | Done (46 passing) | (verification only) | +| — | TS-strict narrowing fix for discriminated unions | Done (Rule 1) | be6eaee | + +## Test Counts + +| Spec File | Tests | +|-----------|-------| +| skill-increase-cap.spec.ts | 9 | +| prereq-evaluator.spec.ts | 19 | +| recompute-derived-stats.spec.ts | 5 | +| compute-applicable-steps.spec.ts | 13 | +| **Total** | **46** | + +The plan target of "≥50 tests" assumed Plan 01-01's 9-test apply-attribute-boost.spec.ts would be present (5 specs, ≥50 tests). In this worktree, Plan 01-01 work is not yet merged into the base — only its production module was created here as a Rule 3 dependency. After orchestrator merge, the combined count will reach 55. This plan delivers all of its own 46 tests, which fully cover VALIDATION.md rows 1-W1-06 through 1-W1-29. + +Run command: +``` +cd server && npm test -- --testPathPatterns=leveling +``` + +Output: +``` +PASS src/modules/leveling/lib/recompute-derived-stats.spec.ts +PASS src/modules/leveling/lib/skill-increase-cap.spec.ts +PASS src/modules/leveling/lib/compute-applicable-steps.spec.ts +PASS src/modules/leveling/lib/prereq-evaluator.spec.ts + +Test Suites: 4 passed, 4 total +Tests: 46 passed, 46 total +``` + +`cd server && npx tsc --noEmit -p tsconfig.json` — exits 0 for the leveling lib (errors elsewhere are pre-existing Prisma-client codegen issues out of scope per Scope Boundary rule). + +## Verification Checklist + +- [x] All 5 production modules + types.ts created in `server/src/modules/leveling/lib/` +- [x] All 4 spec files created (5th — apply-attribute-boost.spec.ts — owned by Plan 01-01) +- [x] TDD discipline: each module's spec was written first and observed failing (RED commit) before implementation (GREEN commit) +- [x] Pitfall #8 (boost-cap-at-18) enforced — recompute test for CON 18 → 19 (not 20) +- [x] Pitfall #9 (no hpCurrent in output) enforced — `expect(result).not.toHaveProperty('hpCurrent')` + literal string absent from production code +- [x] D-01 evaluable patterns covered: skill rank, OR-list, AND-list, feat name, heritage, class +- [x] D-02 non-evaluable patterns return `{unknown:true}`: spellcasting, deity, age, vision, free-text +- [x] Step ordering matches D-10 (with PF2e correction at L5 — see Deviations) +- [x] Zero `: any` types in production code (only English-prose use of word "any" in a JSDoc comment) +- [x] Zero `@nestjs/` imports in any lib file +- [x] Zero Prisma client imports in any lib file +- [x] German failure reasons in prereq-evaluator (D-15) + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 — Blocking] Created apply-attribute-boost.ts in this worktree** +- **Found during:** Task 1 (types.ts imports `AbilityAbbreviation` from `./apply-attribute-boost`) +- **Issue:** Plan 01-02 depends on Plan 01-01's `apply-attribute-boost.ts` module, but Plan 01-01's work is on a separate parallel-execution worktree branch and not yet merged into this worktree's base (`096edbf`). Without it, types.ts and recompute-derived-stats.ts cannot compile. +- **Fix:** Created `server/src/modules/leveling/lib/apply-attribute-boost.ts` with content that exactly matches the Plan 01-01 specification (verbatim from 01-01-PLAN.md lines 351-380). When orchestrator merges Plan 01-01's worktree branch, content will be byte-identical and merge will be conflict-free. +- **Files modified:** `server/src/modules/leveling/lib/apply-attribute-boost.ts` +- **Commit:** 7e40449 + +**2. [Rule 1 — Bug] Fixed L5 expected step list (PF2e rules correction)** +- **Found during:** Task 5 GREEN phase (one of 13 tests failed against the implementation) +- **Issue:** The plan's expected step list for `computeApplicableSteps(5, 'Fighter', ...)` included `feat-class` and `feat-skill`. PF2e CRB places class feats and skill feats at EVEN levels only (2, 4, 6, ..., 20). L5 is odd → no class/skill feat slot. The plan's other tests at L4 (even, has feat-class+feat-skill) and L3 (odd, NOT has feat-class) are internally consistent with the even-level rule; only L5 was misstated. +- **Fix:** Changed expected list at L5 to `['class-features', 'boost', 'skill-increase', 'feat-ancestry', 'review']` and added a comment block explaining the deviation. The implementation rule "feat-class/feat-skill on even levels" is unchanged because the project's "regelkonform" goal (CLAUDE.md / PROJECT.md) requires PF2e correctness above plan literalism. +- **Files modified:** `server/src/modules/leveling/lib/compute-applicable-steps.spec.ts` +- **Commit:** de07fc8 + +**3. [Rule 1 — Bug] Type-guard helpers for TS-strict EvalResult narrowing** +- **Found during:** Task 6 (`tsc --noEmit` after all RED/GREEN commits) +- **Issue:** TypeScript strict mode could not narrow `EvalResult = {ok:true} | {ok:false; reason} | {unknown:true; raw}` via `r.ok` access — the unknown variant has no `ok` property. Tests passed at runtime but `tsc` reported `error TS2339: Property 'ok' does not exist on type 'EvalResult'` in 8 lines of the spec and 4 lines of the production walker. +- **Fix:** Added explicit type-guard helpers (`isUnknown`, `isOk`, `isFail` in production; `isFail` in spec) that use `'ok' in r` membership checks. Replaced direct `r.ok` accesses with guard calls. Runtime behavior unchanged. +- **Files modified:** `server/src/modules/leveling/lib/prereq-evaluator.ts`, `server/src/modules/leveling/lib/prereq-evaluator.spec.ts` +- **Commit:** be6eaee + +### Authentication Gates + +None — this plan introduces no I/O. + +### Architectural Changes + +None — pure-function modules only. + +## Notes on Prereq-Evaluator Edge Cases (for future grammar tuning) + +While implementing the parser, two interesting edge cases came up that future PF2e prereq corpora may stress: + +1. **Oxford-comma "X, Y, or Z" splitting.** A naive `,\s*|\s+or\s+` regex split leaves a stray leading "or " on the last segment because the `,\s*` alternative consumes the comma but stops before the "or" keyword. Solution: pre-normalize `,\s*or\s+` → ` or ` before splitting. + +2. **Free-text-as-feat misclassification.** Without guards, a sentence like "You worship a god of fire and destruction" matches the feat fallback regex `^[A-Z][A-Za-z' \-]*$` (it's all letters/spaces and starts with uppercase). Added two heuristics: ≤4 words AND no presence of common function-words (you, a, the, of, and, to, with, from, by). Future PF2e feats with names like "Heir to the Vows of Asmodeus" would fail this guard — but no such canonical feat exists today, and the planner's UNKNOWN-aggressive intent (D-03) prefers false-unknown over false-evaluable. + +## TDD Gate Compliance + +For each of Tasks 2–5, a `test(...)` commit (RED gate) precedes the corresponding `feat(...)` commit (GREEN gate). Verified in git log: + +| Module | RED commit | GREEN commit | +|--------|-----------|---------------| +| skill-increase-cap | 3a4267d | f189750 | +| prereq-evaluator | 66d9d5c | da82d9b | +| recompute-derived-stats | 8dd55b6 | 6011024 | +| compute-applicable-steps | 70ec7bb | d9ed18c | + +The compute-applicable-steps module additionally has commit de07fc8 (test fix between RED and GREEN, separated as a deviation rather than amended into RED to preserve the audit trail of the plan-vs-PF2e-rules conflict). + +## Confirmation: types.ts Imported by All Downstream Modules + +```bash +grep -l "from './types'" server/src/modules/leveling/lib/*.ts +``` +Result: +- `compute-applicable-steps.ts` (StepKind) +- `prereq-evaluator.ts` (CharacterContext, EvalResult, Proficiency) +- `recompute-derived-stats.ts` (CharacterContext, ClassProgressionRow, DerivedStats, Proficiency, WizardChoices, PROFICIENCY_BASE_BONUS, AbilityAbbreviation) +- `skill-increase-cap.ts` (Proficiency) + +All four logic modules import from `./types` — no duplicate type definitions. + +## Self-Check: PASSED + +- `server/src/modules/leveling/lib/types.ts` — FOUND +- `server/src/modules/leveling/lib/skill-increase-cap.ts` — FOUND +- `server/src/modules/leveling/lib/skill-increase-cap.spec.ts` — FOUND +- `server/src/modules/leveling/lib/prereq-evaluator.ts` — FOUND +- `server/src/modules/leveling/lib/prereq-evaluator.spec.ts` — FOUND +- `server/src/modules/leveling/lib/recompute-derived-stats.ts` — FOUND +- `server/src/modules/leveling/lib/recompute-derived-stats.spec.ts` — FOUND +- `server/src/modules/leveling/lib/compute-applicable-steps.ts` — FOUND +- `server/src/modules/leveling/lib/compute-applicable-steps.spec.ts` — FOUND +- `server/src/modules/leveling/lib/apply-attribute-boost.ts` — FOUND (Rule 3 dependency) +- Commit 7e40449 — FOUND +- Commit 4d2cb5e — FOUND +- Commit 3a4267d — FOUND +- Commit f189750 — FOUND +- Commit 66d9d5c — FOUND +- Commit da82d9b — FOUND +- Commit 8dd55b6 — FOUND +- Commit 6011024 — FOUND +- Commit 70ec7bb — FOUND +- Commit de07fc8 — FOUND +- Commit d9ed18c — FOUND +- Commit be6eaee — FOUND