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
13 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-level-up-pf2e-regelkonform | 02 | testing |
|
|
|
|
|
|
|
|
|
~30min | 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
- All 5 production modules + types.ts created in
server/src/modules/leveling/lib/ - All 4 spec files created (5th — apply-attribute-boost.spec.ts — owned by Plan 01-01)
- TDD discipline: each module's spec was written first and observed failing (RED commit) before implementation (GREEN commit)
- Pitfall #8 (boost-cap-at-18) enforced — recompute test for CON 18 → 19 (not 20)
- Pitfall #9 (no hpCurrent in output) enforced —
expect(result).not.toHaveProperty('hpCurrent')+ literal string absent from production code - D-01 evaluable patterns covered: skill rank, OR-list, AND-list, feat name, heritage, class
- D-02 non-evaluable patterns return
{unknown:true}: spellcasting, deity, age, vision, free-text - Step ordering matches D-10 (with PF2e correction at L5 — see Deviations)
- Zero
: anytypes in production code (only English-prose use of word "any" in a JSDoc comment) - Zero
@nestjs/imports in any lib file - Zero Prisma client imports in any lib file
- 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
AbilityAbbreviationfrom./apply-attribute-boost) - Issue: Plan 01-02 depends on Plan 01-01's
apply-attribute-boost.tsmodule, 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.tswith 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', ...)includedfeat-classandfeat-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 --noEmitafter all RED/GREEN commits) - Issue: TypeScript strict mode could not narrow
EvalResult = {ok:true} | {ok:false; reason} | {unknown:true; raw}viar.okaccess — the unknown variant has nookproperty. Tests passed at runtime buttscreportederror 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,isFailin production;isFailin spec) that use'ok' in rmembership checks. Replaced directr.okaccesses 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:
-
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+→orbefore splitting. -
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
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— FOUNDserver/src/modules/leveling/lib/skill-increase-cap.ts— FOUNDserver/src/modules/leveling/lib/skill-increase-cap.spec.ts— FOUNDserver/src/modules/leveling/lib/prereq-evaluator.ts— FOUNDserver/src/modules/leveling/lib/prereq-evaluator.spec.ts— FOUNDserver/src/modules/leveling/lib/recompute-derived-stats.ts— FOUNDserver/src/modules/leveling/lib/recompute-derived-stats.spec.ts— FOUNDserver/src/modules/leveling/lib/compute-applicable-steps.ts— FOUNDserver/src/modules/leveling/lib/compute-applicable-steps.spec.ts— FOUNDserver/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