Files
Dimension-47/.planning/phases/01-level-up-pf2e-regelkonform/01-02-SUMMARY.md
Alexander Zielonka df29617c3e 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
2026-04-27 14:21:00 +02:00

13 KiB
Raw Blame History

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
pure-functions
jest
tdd
level-up
prereq-evaluator
recompute
pf2e-rules
phase provides
01-level-up-pf2e-regelkonform applyAttributeBoost + AbilityAbbreviation (Plan 01-01) — boost-cap-at-18 helper
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
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
Plan 01-04 LevelingService integration
Plan 01-05 wizard UI preview
Plan 01-03 progression seed
added 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)
created modified
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
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
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: ...'
LVL-02
LVL-06
LVL-09
LVL-10
LVL-01
LVL-13
LVL-14
~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 : any types 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 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 25, 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 — 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