refactor: NCS-24 update CLAUDE.md for improved structure and clarity #20
@@ -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)
|
|
||||||
@@ -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_<module>.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.
|
|
||||||
@@ -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`
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,58 +1,50 @@
|
|||||||
# CLAUDE.md — NowChessSystems
|
# Now-Chess
|
||||||
|
|
||||||
## Stack
|
Scala 3.5.1 · Gradle 9
|
||||||
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:<svc>") per service
|
|
||||||
modules/<svc>/build.gradle.kts + src/
|
|
||||||
docs/adr/ docs/api/ docs/unresolved.md
|
|
||||||
```
|
|
||||||
Versions in root `extra["VERSIONS"]`; modules read via `rootProject.extra["VERSIONS"] as Map<String,String>`.
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
```bash
|
|
||||||
./gradlew build
|
|
||||||
./gradlew :modules:<svc>:build|test
|
|
||||||
./gradlew :modules:<svc>:test --tests "de.nowchess.<svc>.<Class>"
|
|
||||||
```
|
```
|
||||||
|
./clean # Clear build dirs — only when necessary
|
||||||
## Workflow
|
./compile # Compile all modules — always run
|
||||||
1. **Plan** — restate requirement, list files, flag risks. Proceed unless genuine ambiguity.
|
./test # Run all tests
|
||||||
2. **Tests first** — cover only new behaviour.
|
./coverage # Check coverage
|
||||||
3. **Implement** — no scope creep.
|
|
||||||
4. **Verify** — check each requirement; confirm green build.
|
|
||||||
|
|
||||||
## 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`
|
|
||||||
|
|
||||||
## 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/`.
|
|
||||||
|
|
||||||
## Bug Fixing
|
|
||||||
Fix failures immediately without asking. After 3 failed attempts → log in `docs/unresolved.md` + surface summary.
|
|
||||||
|
|
||||||
## 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).
|
|
||||||
|
|
||||||
## Unresolved (`docs/unresolved.md`)
|
|
||||||
Append only, never delete:
|
|
||||||
```
|
|
||||||
## [YYYY-MM-DD] <title>
|
|
||||||
**Requirement/Bug:** **Root Cause:** **Attempted Fixes:** **Next Step:**
|
|
||||||
```
|
```
|
||||||
|
Try to stick to these commands for consistency.
|
||||||
|
|
||||||
## Done Checklist
|
## Modules
|
||||||
- [ ] Plan written · files created/modified · tests green · requirements verified · unresolved logged
|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
- **Coverage:** 100% condition coverage required in `api`, `core`, `rule`, `io` (mandatory); `ui` exempt.
|
||||||
|
|
||||||
|
## 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.
|
||||||
@@ -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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user