From c8f8c63787685000c5fcf2ab3e8e5a15c88cab4b Mon Sep 17 00:00:00 2001 From: Janis Date: Tue, 7 Apr 2026 09:12:17 +0200 Subject: [PATCH] refactor: NCS-24 update CLAUDE.md for improved structure and clarity --- .claude/memory/MEMORY.md | 9 -- .../memory/feedback_keep_structure_updated.md | 16 ---- .claude/memory/project_structure_api.md | 51 ----------- .claude/memory/project_structure_core.md | 48 ----------- .claude/memory/project_structure_root.md | 55 ------------ CLAUDE.md | 85 +++++++++---------- docs/unresolved.md | 20 ----- 7 files changed, 38 insertions(+), 246 deletions(-) delete mode 100644 .claude/memory/MEMORY.md delete mode 100644 .claude/memory/feedback_keep_structure_updated.md delete mode 100644 .claude/memory/project_structure_api.md delete mode 100644 .claude/memory/project_structure_core.md delete mode 100644 .claude/memory/project_structure_root.md diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md deleted file mode 100644 index 81be3d8..0000000 --- a/.claude/memory/MEMORY.md +++ /dev/null @@ -1,9 +0,0 @@ -# Memory Index - -## Feedback -- [feedback_keep_structure_updated.md](feedback_keep_structure_updated.md) — Update structure memory files whenever source files are added, removed, or changed - -## Project Structure -- [project_structure_root.md](project_structure_root.md) — Top-level layout, modules list, VERSIONS map, navigation rules (skip `build/`, `.gradle/`, `.idea/`) -- [project_structure_api.md](project_structure_api.md) — `modules/api`: all files and types (Board, Piece, Square, GameState, Move, ApiResponse, PlayerInfo) -- [project_structure_core.md](project_structure_core.md) — `modules/core`: all files and types (GameContext, GameRules, MoveValidator, GameController, Parser, Renderer) diff --git a/.claude/memory/feedback_keep_structure_updated.md b/.claude/memory/feedback_keep_structure_updated.md deleted file mode 100644 index 74512b8..0000000 --- a/.claude/memory/feedback_keep_structure_updated.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: keep-structure-memory-updated -description: Always update the project structure memory files when adding, removing, or changing source files -type: feedback ---- - -After any change that adds, removes, renames, or significantly alters a source file, update the relevant structure memory file: - -- New/renamed/deleted file in `modules/api` → update `project_structure_api.md` -- New/renamed/deleted file in `modules/core` → update `project_structure_core.md` -- New module, dependency version change, or new top-level directory → update `project_structure_root.md` -- New module added → create a new `project_structure_.md` and add it to `MEMORY.md` - -**Why:** Structure memories are the primary navigation aid. Stale entries cause wasted exploration. - -**How to apply:** Treat the structure memory update as part of completing any implementation task — do it in the same session, not as a follow-up. diff --git a/.claude/memory/project_structure_api.md b/.claude/memory/project_structure_api.md deleted file mode 100644 index 5901d9b..0000000 --- a/.claude/memory/project_structure_api.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: module-api-structure -description: File and type overview for the modules/api module (shared domain types) -type: project ---- - -# Module: `modules/api` - -**Purpose:** Shared domain model — pure data types with no game logic. Depended on by `modules/core`. - -**Gradle:** `id("scala")`, no `application` plugin. No Quarkus. Uses scoverage plugin. - -**Package root:** `de.nowchess.api` - -## Source files (`src/main/scala/de/nowchess/api/`) - -### `board/` -| File | Contents | -|------|----------| -| `Board.scala` | `opaque type Board = Map[Square, Piece]` — extensions: `pieceAt`, `withMove`, `pieces`; `Board.initial` sets up start position | -| `Color.scala` | `enum Color { White, Black }` — `.opposite`, `.label` | -| `Piece.scala` | `case class Piece(color, pieceType)` — convenience vals `WhitePawn`…`BlackKing` | -| `PieceType.scala` | `enum PieceType { Pawn, Knight, Bishop, Rook, Queen, King }` — `.label` | -| `Square.scala` | `enum File { A–H }`, `enum Rank { R1–R8 }`, `case class Square(file, rank)` — `.toString` algebraic, `Square.fromAlgebraic(s)` | - -### `game/` -| File | Contents | -|------|----------| -| `GameState.scala` | `case class CastlingRights(kingSide, queenSide)` + `.None`/`.Both`; `enum GameResult { WhiteWins, BlackWins, Draw }`; `enum GameStatus { NotStarted, InProgress, Finished(result) }`; `case class GameState(piecePlacement, activeColor, castlingWhite, castlingBlack, enPassantTarget, halfMoveClock, fullMoveNumber, status)` — FEN-compatible snapshot | - -### `move/` -| File | Contents | -|------|----------| -| `Move.scala` | `enum PromotionPiece { Knight, Bishop, Rook, Queen }`; `enum MoveType { Normal, CastleKingside, CastleQueenside, EnPassant, Promotion(piece) }`; `case class Move(from, to, moveType = Normal)` | - -### `player/` -| File | Contents | -|------|----------| -| `PlayerInfo.scala` | `opaque type PlayerId = String`; `case class PlayerInfo(id: PlayerId, displayName: String)` | - -### `response/` -| File | Contents | -|------|----------| -| `ApiResponse.scala` | `sealed trait ApiResponse[+A]` → `Success[A](data)` / `Failure(errors)`; `case class ApiError(code, message, field?)`; `case class Pagination(page, pageSize, totalItems)` + `.totalPages`; `case class PagedResponse[A](items, pagination)` | - -## Test files (`src/test/scala/de/nowchess/api/`) -Mirror of main structure — one `*Test.scala` per source file using `AnyFunSuite with Matchers`. - -## Notes -- `GameState` is FEN-style but `Board` (in `core`) is a `Map[Square,Piece]` — the two are separate representations -- `CastlingRights` is defined here in `api`; the castling logic lives in `core` diff --git a/.claude/memory/project_structure_core.md b/.claude/memory/project_structure_core.md deleted file mode 100644 index 6014779..0000000 --- a/.claude/memory/project_structure_core.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: module-core-structure -description: File and type overview for the modules/core module (TUI chess engine) -type: project ---- - -# Module: `modules/core` - -**Purpose:** Standalone TUI chess application. All game logic, move validation, rendering. Depends on `modules/api`. - -**Gradle:** `id("scala")` + `application` plugin. Main class: `de.nowchess.chess.Main`. Uses scoverage plugin. - -**Package root:** `de.nowchess.chess` - -## Source files (`src/main/scala/de/nowchess/chess/`) - -### Root -| File | Contents | -|------|----------| -| `Main.scala` | Entry point — prints welcome, starts `GameController.gameLoop(GameContext.initial, Color.White)` | - -### `controller/` -| File | Contents | -|------|----------| -| `GameController.scala` | `sealed trait MoveResult` ADT: `Quit`, `InvalidFormat`, `NoPiece`, `WrongColor`, `IllegalMove`, `Moved`, `MovedInCheck`, `Checkmate`, `Stalemate`; `object GameController` — `processMove(ctx, turn, raw): MoveResult` (pure), `gameLoop(ctx, turn)` (I/O loop), `applyRightsRevocation(...)` (castling rights bookkeeping) | -| `Parser.scala` | `object Parser` — `parseMove(input): Option[(Square, Square)]` parses coordinate notation e.g. `"e2e4"` | - -### `logic/` -| File | Contents | -|------|----------| -| `GameContext.scala` | `enum CastleSide { Kingside, Queenside }`; `case class GameContext(board, whiteCastling, blackCastling)` — `.castlingFor(color)`, `.withUpdatedRights(color, rights)`; `GameContext.initial`; `extension (Board).withCastle(color, side)` moves king+rook atomically | -| `GameRules.scala` | `enum PositionStatus { Normal, InCheck, Mated, Drawn }`; `object GameRules` — `isInCheck(board, color)`, `legalMoves(ctx, color): Set[(Square,Square)]`, `gameStatus(ctx, color): PositionStatus` | -| `MoveValidator.scala` | `object MoveValidator` — `isLegal(board, from, to)`, `legalTargets(board, from): Set[Square]` (board-only, no castling), `legalTargets(ctx, from)` (context-aware, includes castling), `isCastle`, `castleSide`, `castlingTargets(ctx, color)` — full castling legality (empty squares, no check through transit) | - -### `view/` -| File | Contents | -|------|----------| -| `Renderer.scala` | `object Renderer` — `render(board): String` outputs ANSI-colored board with file/rank labels | -| `PieceUnicode.scala` | `extension (Piece).unicode: String` maps each piece to its Unicode chess symbol | - -## Test files (`src/test/scala/de/nowchess/chess/`) -Mirror of main structure — one `*Test.scala` per source file using `AnyFunSuite with Matchers`. - -## Key design notes -- `MoveValidator` has two overloaded `legalTargets`: one takes `Board` (geometry only), one takes `GameContext` (adds castling) -- `GameRules.legalMoves` filters by check — it calls `MoveValidator.legalTargets(ctx, from)` then simulates each move -- Castling rights revocation is in `GameController.applyRightsRevocation`, triggered after every move -- No `@QuarkusTest` — this module is a plain Scala application, not a Quarkus service diff --git a/.claude/memory/project_structure_root.md b/.claude/memory/project_structure_root.md deleted file mode 100644 index a6aa4d0..0000000 --- a/.claude/memory/project_structure_root.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: project-root-structure -description: Top-level project structure, modules list, and navigation notes for NowChessSystems -type: project ---- - -# NowChessSystems — Root Structure - -## Directory layout (skip `build/`, `.gradle/`, `.idea/`) - -``` -NowChessSystems/ -├── build.gradle.kts # Root: sonarqube plugin, VERSIONS map -├── settings.gradle.kts # include(":modules:core", ":modules:api") -├── gradlew / gradlew.bat -├── CLAUDE.md # Project instructions for Claude Code -├── .claude/ -│ ├── CLAUDE.MD # Working agreement (plan/verify/unresolved) -│ ├── settings.json -│ └── agents/ # architect, code-reviewer, gradle-builder, scala-implementer, test-writer -├── docs/ -│ ├── Claude-Skills.md -│ ├── Security.md -│ └── unresolved.md -├── jacoco-reporter/ # Python scripts for coverage gap reporting -└── modules/ - ├── api/ # Shared domain types (no logic) - └── core/ # TUI chess engine + game logic -``` - -## Modules - -| Module | Gradle path | Purpose | -|--------|-------------|---------| -| `api` | `:modules:api` | Shared domain model: Board, Piece, Move, GameState, ApiResponse | -| `core` | `:modules:core` | TUI chess app: game logic, move validation, rendering | - -`core` depends on `api` via `implementation(project(":modules:api"))`. - -## VERSIONS (root `build.gradle.kts`) - -| Key | Value | -|-----|-------| -| `QUARKUS_SCALA3` | 1.0.0 | -| `SCALA3` | 3.5.1 | -| `SCALA_LIBRARY` | 2.13.18 | -| `SCALATEST` | 3.2.19 | -| `SCALATEST_JUNIT` | 0.1.11 | -| `SCOVERAGE` | 2.1.1 | - -## Navigation rules -- **Always skip** `build/`, `.gradle/`, `.idea/` when exploring — they are generated artifacts -- Tests use `AnyFunSuite with Matchers` (ScalaTest), not JUnit `@Test` -- No Quarkus in current modules — Quarkus is planned for future services -- Agent workflow: architect → scala-implementer → test-writer → gradle-builder → code-reviewer diff --git a/CLAUDE.md b/CLAUDE.md index a2ecc46..8cf560e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,58 +1,49 @@ -# CLAUDE.md — NowChessSystems +# Now-Chess -## Stack -Scala 3.5.x · Quarkus + quarkus-scala3 · Hibernate/Jakarta · Lanterna TUI · K8s + ArgoCD + Kargo · Frontend TBD (Vite/React/Angular/Vue) - -### Memory - -Your memory is saved under .claude/memory/MEMORY.md. - -## Structure -``` -build.gradle.kts / settings.gradle.kts # root; include(":modules:") per service -modules//build.gradle.kts + src/ -docs/adr/ docs/api/ docs/unresolved.md -``` -Versions in root `extra["VERSIONS"]`; modules read via `rootProject.extra["VERSIONS"] as Map`. +Scala 3.5.1 · Gradle 9 ## Commands -```bash -./gradlew build -./gradlew :modules::build|test -./gradlew :modules::test --tests "de.nowchess.." + +``` +./clean # Clear build dirs — only when necessary +./compile # Compile all modules — always run +./test # Run all tests +./coverage # Check coverage ``` -## Workflow -1. **Plan** — restate requirement, list files, flag risks. Proceed unless genuine ambiguity. -2. **Tests first** — cover only new behaviour. -3. **Implement** — no scope creep. -4. **Verify** — check each requirement; confirm green build. +## Modules -## Scala/Quarkus Rules -- `given`/`using` only (no `implicit`); `Option`/`Either`/`Try` (no `null`/`.get`) -- `jakarta.*` only; reactive I/O (`Uni`/`Multi`), no blocking on event loop -- Always exclude `org.scala-lang:scala-library` from Quarkus BOM -- Unit tests: `extends AnyFunSuite with Matchers` — no `@Test`, no `: Unit` -- Integration tests: `@QuarkusTest` + JUnit 5 — `@Test` methods need explicit `: Unit` +| Module | Role | Depends on | +|--------|------|-----------| +| `api` | Model / shared types | (none) | +| `core` | Primary business logic | api, rule | +| `rule` | Game rules | api | +| `io` | Export formats | api, core | +| `ui` | Entrypoint & UI | core, io | -## Coverage -Line = 100% · Branch = 100% · Method = 100% · Regression tests · document exceptions -Check: `jacoco-reporter/scoverage_coverage_gaps.py modules/{svc}/build/reports/scoverageTest/scoverage.xml` -⚠️ Use `scoverageTest/`, NOT `scoverage/`. +## Style -## Bug Fixing -Fix failures immediately without asking. After 3 failed attempts → log in `docs/unresolved.md` + surface summary. +- Use immutable data and pure functions. +- Keep functions under 30 lines. If you need "and" to describe it, split it. +- Keep cyclomatic complexity under 15. +- Avoid comments. Let names carry intent; comment only non-obvious algorithms. +- Scan for duplicated logic before finishing. Extract it. +- Follow default Sonar style for Scala. +- Use `Option` or `Either` for fallible operations; avoid exceptions for control flow. +- Naming: types are PascalCase, functions/values are camelCase. -## Agents (new service) -Sequential: architect → scala-implementer → test-writer → gradle-builder → code-reviewer (review only, no self-fix) -Parallel: only when services are fully independent (no shared contracts/state). +## Code Quality -## Unresolved (`docs/unresolved.md`) -Append only, never delete: -``` -## [YYYY-MM-DD] -**Requirement/Bug:** **Root Cause:** **Attempted Fixes:** **Next Step:** -``` +- **Coverage:** 100% condition coverage required in `api`, `core`, `rule`, `io` (mandatory); `ui` exempt. -## Done Checklist -- [ ] Plan written · files created/modified · tests green · requirements verified · unresolved logged +## Architecture Decisions + +- **Immutable state as primary model:** GameContext (api) holds board, history, player state — immutable, passed through the system. Each move creates a new GameContext, enabling undo/redo without side effects. +- **Observer pattern for UI decoupling:** GameEngine publishes move/state events; CommandInvoker queues moves; UI listens to events, not polling. GameEngine never imports UI code. +- **RuleSet trait encapsulates rules:** Move generation, check, castling, en passant all in RuleSet impl. GameEngine calls rules as a black box; rules don't know about the rest of core. + +## Rules + +- **Tests are the spec.** Never modify tests to pass; modify requirements or code. Update tests only if requirements change. +- Never read build folders. Ask permission if needed. +- Keep this file up to date with any important decisions or conventions. \ No newline at end of file diff --git a/docs/unresolved.md b/docs/unresolved.md index 71ccda8..e69de29 100644 --- a/docs/unresolved.md +++ b/docs/unresolved.md @@ -1,20 +0,0 @@ -## [2026-03-31] Unreachable code blocking 100% statement coverage - -**Requirement/Bug:** Reach 100% statement coverage in core module. - -**Root Cause:** 4 remaining uncovered statements (99.6% coverage) are unreachable code: -1. **PgnParser.scala:160** (`case _ => None` in extractPromotion) - Regex `=([QRBN])` only matches those 4 characters; fallback case can never execute -2. **GameHistory.scala:29** (`addMove$default$4` compiler-generated method) - Method overload 3 without defaults shadows the 4-param version, making promotionPiece default accessor unreachable -3. **GameEngine.scala:201-202** (`case _` in completePromotion) - GameController.completePromotion always returns one of 4 expected MoveResult types; catch-all is defensive code - -**Attempted Fixes:** -1. Added comprehensive PGN parsing tests (all 4 promotion types) - PgnParser improved from 95.8% to 99.4% -2. Added GameHistory tests using named parameters - hit `addMove$default$3` (castleSide) but not `$default$4` (promotionPiece) -3. Named parameter approach: `addMove(from=..., to=..., promotionPiece=...)` triggers 4-param with castleSide default ✓ -4. Positional approach: `addMove(f, t, None, None)` requires all 4 args (explicit, no defaults used) - doesn't hit $default$4 -5. Root issue: Scala's overload resolution prefers more-specific non-default overloads (2-param, 3-param) over the 4-param with defaults - -**Recommendation:** 99.6% (1029/1033) is maximum achievable without refactoring method overloads. Unreachable code design patterns: -- **Pattern 1 (unreachable regex fallback):** Defensive pattern match against exhaustive regex -- **Pattern 2 (overshadowed defaults):** Method overloads shadow default parameters in parent signature -- **Pattern 3 (defensive catch-all):** Error handling for impossible external API returns