docs(01-02): complete pure-function library plan
Five pure-function modules (types + 4 logic) implemented strict-TDD. 46 passing Jest tests across 4 spec files. Pitfall #8 (boost-cap-at-18) and Pitfall #9 (no hpCurrent in output) both enforced by spec. 3 documented deviations: - Rule 3: created apply-attribute-boost.ts dependency (Plan 01-01 work not yet merged into worktree base; content matches 01-01 spec exactly) - Rule 1: corrected L5 expected step list (PF2e CRB has class/skill feats only at even levels; plan said feat-class+feat-skill at L5) - Rule 1: TS-strict type-guard helpers for EvalResult narrowing Requirements completed: LVL-02, LVL-06, LVL-09, LVL-10, LVL-01, LVL-13, LVL-14
This commit is contained in:
217
.planning/phases/01-level-up-pf2e-regelkonform/01-02-SUMMARY.md
Normal file
217
.planning/phases/01-level-up-pf2e-regelkonform/01-02-SUMMARY.md
Normal file
@@ -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/<feature>/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
|
||||
Reference in New Issue
Block a user