You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds player history support by ingesting historical FIDE rating data.
Introduces a new cli module for crawling and ingesting historical CSV
snapshots, a HistoryDb persistence layer for player history and
federation summary views, and Smithy-defined history API
endpoints served by a new HistoryService.
The crawler is refactored to use hash-based incremental diffing (with
cached hash Refs) so only changed players emit events, and ingestion is
skipped entirely when data is already up to date.
Then inspect with `grep`/`tail`/`head`. Only re-run sbt if code was modified.
23
-
- Only run `sbt compile`/`sbt test` after MCP shows no compilation issues.
24
-
- After code changes: `sbt lint` (scalafmt + scalafix), then `sbt test`.
25
-
-**Do not add yourself as co-author** — no `Co-Authored-By: Claude ...` in commits.
20
+
- Always save any plan/optimization session in `<project-root>/.claude/docs/` directory instead of just writing in the console
21
+
- Omit `Co-Authored-By` lines from git commits
26
22
27
-
## Tool references
23
+
## Skills
28
24
29
-
### MCP — Always verify APIs via MCP before using them
25
+
**Always** use these skills:
30
26
31
-
Metals MCP at `.metals/mcp.json`. Query definitions/types/symbols, check compilation errors before running sbt. Fix MCP issues before running sbt to avoid long feedback loops.
32
-
33
-
**Override Grep with Metals MCP when the question is "what does the compiler resolve this to?"**
34
-
35
-
Grep is the default and works for most searches. But it fails silently on these Scala-specific scenarios — use Metals instead:
36
-
37
-
| Scenario | Tool |
38
-
|---|---|
39
-
| What type is this expression / what does it return? |`mcp__metals__inspect`|
40
-
| Which given/implicit is resolved at this call site? |`mcp__metals__inspect`|
41
-
| Which overloaded method is called here? |`mcp__metals__inspect`|
42
-
| What's the underlying type of an opaque type? |`mcp__metals__inspect`|
43
-
| What does a wildcard import bring into scope? |`mcp__metals__inspect`|
44
-
| Who calls this method / all implementations of a trait? (semantic, not textual) |`mcp__metals__get-usages`|
45
-
46
-
Other Metals tools: `glob-search` (find symbols by name), `get-docs` (ScalaDoc), `compile-file` (single-file compile check), `list-modules`, `list-scalafix-rules`.
47
-
48
-
**Signal to switch:** When you grep and get 10+ candidates with no way to disambiguate — that means you need Metals, not a better regex. Fall back to Grep/Glob for non-Scala files, string literals, config values, SQL, or when Metals is unavailable.
27
+
- General Scala 3/FP standards are in the `/scala3-fp` skill.
28
+
- cats-effect best practices are in the `cats-effect-io` and `cats-effect-resource`
29
+
- typed error with cats-mtl best practices are in the `cats-mtl-typed-errors`
30
+
- SBT and Metals workflow is in the `/scala-sbt` skill.
49
31
50
32
## Modules Structure
51
33
@@ -83,100 +65,21 @@ HTTP server and orchestration. Ember server with Smithy4s routes (`PlayerService
83
65
### gatling (`modules/gatling`)
84
66
Load testing simulations (Gatling). Warmup (2k users), Stress (12k users), and Capacity (incremental ramp) scenarios against `localhost:9669/api`. Independent of other modules.
85
67
86
-
## Functional Programming Standards
87
-
88
-
-**No mutable state**: avoid `var`, mutable collections, and `return`.
89
-
-**Prefer immutable case classes** over builder patterns.
90
-
- If mutability is unavoidable, scope it inside a method — never as class fields.
91
-
- Minimize side-effects; push them to the edges (entry points).
92
-
- Use ADTs (`enum`) for modeling closed sets of variants.
93
-
94
-
## Scala 3 Syntax
95
-
96
-
-**Braceless syntax** — the project uses `rewrite.scala3.removeOptionalBraces = yes`.
97
-
- Use Scala 3 `enum`, `given`/`using`, `extension`, `opaque type`, and `derives` where appropriate.
98
-
- Use `for`/`yield` (not `flatMap` chains) for sequencing effects — this is the idiomatic pattern in this codebase.
99
-
- Prefer `match` expressions over nested `if`/`else`.
100
-
101
-
## Code Style
102
-
103
-
- Braceless syntax — no curly braces for `if`, `match`, `for`, class/object bodies.
104
-
-`maxColumn = 110`.
105
-
- Wildcard imports: `import cats.syntax.all.*`
106
-
-`align.preset = none` — do not align on `=` or `=>`.
107
-
108
-
### Code Style: Flat `for`-comprehensions
109
-
110
-
Prefer flat `for`/`yield` over nested `match`/`case` inside effectful blocks. Lift `Either`/`Option` into `F` so the `for` stays linear:
111
-
112
-
```scala
113
-
// BAD — nested match in MID-CHAIN breaks the for-comprehension flow
114
-
for
115
-
result <- service.doSomething(...)
116
-
value <- result match// BAD: match in middle, more steps follow
117
-
caseRight(v) => v.pure[F]
118
-
caseLeft(err) =>Sync[F].raiseError(err)
119
-
next <- process(value)
120
-
response <-Ok(next.asJson)
121
-
yield response
122
-
123
-
// ALSO BAD — .flatMap with case inside for-comprehension
124
-
for
125
-
body <- req.req.as[Body]
126
-
result <- service.getItem(id).flatMap {
127
-
caseSome(item) =>Ok(item.asJson)
128
-
caseNone=>NotFound(...)
129
-
}
130
-
yield result
131
-
132
-
// GOOD — plain match at TERMINAL position (pure response mapping, no side effects)
133
-
for
134
-
body <- req.req.as[Body]
135
-
result <- service.doSomething(body)
136
-
response <- result match
137
-
caseRight(value) =>Ok(value.asJson)
138
-
caseLeft(err) =>BadRequest(err.asJson)
139
-
yield response
140
-
```
68
+
### Project-specific opaque types
69
+
Defined in `core/domain/Ids.scala` and `core/domain/Types.scala`.
70
+
Prefer to use Iron to define new type than opaque
141
71
142
-
### Code style rules
143
-
-**No fully-qualified names in code.** Always use imports.
144
-
-**Context bounds: use `{A, B, C}` syntax** (Scala 3.6 aggregate bounds), not colon-separated.
145
-
-**Opaque types for domain values.** AI writes 90%+ of code, so write-cost is near zero while compile-time safety is free. Use opaque types with smart constructors for all entity IDs, constrained strings, and bounded numbers. Defined in `core/domain/Ids.scala` and `core/domain/Types.scala`.
146
-
-**Type-level constraints flow E2E.** Encode invariants in types (opaque types, `NonEmptyList`, refined types) and propagate them through **all** layer signatures: route → service → repository. Never downgrade a constraint to a weaker type and re-validate internally — that hides the requirement from callers and defeats compile-time safety. Unwrap/weaken only at the true system boundary: SQL interpolation, Java SDK calls, job parameter serialization.
147
-
-**`.toString` over `.value.toString`.** Opaque types erase at runtime, so `s"...$opaqueId"` and `opaqueId.toString` just work — no need to unwrap first.
148
-
-**`NonEmptyList` over `List` + `.get`/`.head`.** When a method logically requires non-empty input (batch embeddings, `IN` clauses, etc.), use `NonEmptyList[T]` in the **signature** — including repository methods — instead of `List[T]` with a runtime `.toNel.get` or `.head`. Callers use `NonEmptyList.fromList` to handle the empty case at the call site.
149
-
-**No premature helpers.** If the logic can be composed from <5 Scala/cats operators, always inline at call site — never extract a helper. If >=5 operators, **ask the user** before extracting (in plan mode or popup dialog). When consensus is reached on a new helper, add/link it in this document so future sessions know to use it. **Always use helpers already listed here** (e.g., `AsyncOps`) — don't expand them inline. Before writing any new helper, search the codebase for existing ones that do the same thing.
150
-
-**Generic over specific (stdlib/cats only).** Prefer composing well-tested Scala/cats operators generically over type-specific. "Generic" means leveraging stdlib type classes, not extracting custom helper functions — those still follow the <5 operator rule.
151
-
-**Proactive naming review.** When modifying code, flag misleading, stale, or inconsistent names to the user. **Scope follows the compiler iteratively** — same as smell detection: start with changed files, then follow compilation errors outward. For **internal names** (classes, properties, methods) — recommend renaming directly. For **external names** (request/response DTOs, DB-serialized JSONB fields) — suggest the better name but note migration implications.
152
-
-**Proactive code smell detection.** Scope follows the compiler iteratively: (1) find smell in current file, (2) fix it, (3) compile → if it fails because other files import the changed symbol, fix those too, (4) repeat until compilation passes. If **reading unrelated code** (not in the compilation chain) and spotting a violation — **add it to the smell list** (see Code Smell Tracking below), do not fix. This applies to all rules: type safety, error handling, control flow, naming, logging, etc.
153
-
154
-
### Error Handling
155
-
156
-
-**No error swallowing.** Unless the business scenario *explicitly* requires a default value, using `.getOrElse`, blanket `try`/`catch`, `.recover`, `.handleErrorWith`, or `.orElse` to silently discard failures is forbidden. Every error must be surfaced — either raised via `Raise` or propagated in the type.
157
-
-**Prefer cats-mtl typed errors over `Either`.** Use `Raise[F, E]` / `Handle[F, E]` to short-circuit errors in `F` directly, keeping for-comprehensions flat. Avoid `IO[Either[E, A]]` return types — they force callers to unwrap and nest. Reserve `Either` for pure validation logic or values that cross serialization boundaries.
158
-
159
-
```scala
160
-
// BAD — Either in return type, callers must unwrap
-**Trusted vs untrusted data paths.** Internal data (config, DB-persisted values, intra-service calls) is trusted — failures are bugs, raise directly (`MonadThrow`). External data (user input, third-party APIs) is untrusted — capture failures via `Raise` so callers `Handle` them explicitly at the boundary.
168
-
-**Trust the compiler.** What the signature declares, trust unconditionally. Do not defensively re-validate what the type system already guarantees (e.g., don't `require(nel.nonEmpty)` on a `NonEmptyList`, don't null-check an opaque type). Every act of distrust costs tokens exponentially — the agent opens the implementation, reads dependents, context bloats, and subsequent steps degrade.
169
-
-**Signature completeness.** Every function must honestly declare its effects (`IO`/`F[_]`), failure modes (via `Raise[F, E]` constraint or `Option`), and use domain types for parameters. A signature like `def fetchUser(id: String): User` is dishonest — it hides effects and error paths. Prefer `def fetchUser[F[_]: {Async, Raise[AppError]}](id: UserId): F[User]`.
170
-
171
-
### NoOp Service Implementations
72
+
### Proactive code smell detection
172
73
173
-
When providing stub/noop implementations of service traits:
174
-
-**Data-related operations MUST fail** — raise the error or return a failed effect. Silent success would mask missing wiring.
175
-
-**Data-irrelevant operations CAN succeed** — metrics, logging, telemetry noops return `().pure[F]` since skipping them is safe.
74
+
Scope follows the compiler iteratively:
75
+
(1) find smell in current file
76
+
(2) fix it
77
+
(3) compile → if it fails because other files import the changed symbol, fix those too
78
+
(4) repeat until compilation passes. If **reading unrelated code** (not in the compilation chain) and spotting a violation — **add it to the smell list** (see Code Smell Tracking below), do not fix. This applies to all rules: type safety, error handling, control flow, naming, logging, etc.
176
79
177
80
### Code Smell Tracking
178
81
179
-
When spotting code smells in **unrelated code** (not in the current compilation chain), add them to the persistent smell list file at `<project-root>/memory/code_smells.md` instead of just warning in the response.
82
+
When spotting code smells in **unrelated code** (not in the current compilation chain), add them to the persistent smell list file at `<project-root>/.claude/memory/code_smells.md` instead of just warning in the response.
180
83
181
84
**Rules:**
182
85
-**Max 10 entries.** If adding an 11th, delete the oldest entry (FIFO eviction).
@@ -190,55 +93,15 @@ When spotting code smells in **unrelated code** (not in the current compilation
190
93
For non-trivial tasks, generate a plan before writing code:
191
94
1. Identify affected modules and files.
192
95
2. List the changes needed in each file.
193
-
3. Note any schema/migration impacts (Smithy, ES mappings).
96
+
3. Note any schema/migration impacts.
194
97
4. Confirm the plan before implementing.
195
98
196
99
After implementing a group of related changes, review for:
197
100
- Correctness (does it compile and pass tests?)
198
101
- Consistency (does it follow existing patterns in the codebase?)
199
102
- Simplicity (is there a simpler way to achieve the same result?)
200
103
201
-
## Things to Avoid
202
-
203
-
- Do NOT use `Future`, `Await`, `Thread.sleep`, or blocking calls on the IO thread pool.
204
-
- Do NOT use Ox, ZIO, or direct-style concurrency libraries — this project is cats-effect.
205
-
- Do NOT manually define ES field mappings — they are generated from Smithy schemas.
206
-
- Do NOT reformat snapshot JSON files (`elastic/src/test/resources/snapshot/mappings/`).
207
-
- Do NOT wrap `Option[Boolean]` in `Some()` for Smithy trait properties.
208
-
209
-
210
-
## Rule Hierarchy — Four Layers of Constraint
211
-
212
-
Not all rules are equal. When adding or interpreting rules in this file, classify them by enforcement layer:
213
-
214
-
| Layer | Enforced by | Examples | Agent action |
215
-
|---|---|---|---|
216
-
|**1. Compiler**| Type system, exhaustiveness, opaque types |`Raise[F, E]` in signature, sealed trait match | No rule needed — compiler rejects violations |
217
-
|**2. Actionable rules**| This file — clear if/then criteria | "No error swallowing", "NonEmptyList over List + .head" | Follow unconditionally; violations are code smells |
218
-
|**3. Process constraints**| Filesystem as cross-session memory | Code smell tracking in `code_smells.md`| Agent discovers → records → human prioritizes |
219
-
|**4. Advisory**| Agent suggests, human decides | "Consider adding runtime assertion here", deploy impact | Flag to user, do not act unilaterally |
220
-
221
-
Push constraints **up** whenever possible: prefer a compiler-enforced type over an actionable rule, prefer an actionable rule over an advisory suggestion.
222
-
223
-
## Rule Quality Standard
224
-
225
-
Rules in this file must be precise enough that the agent never needs to read extra context to guess intent. Every ambiguity = extra file reads = token bloat = degraded reasoning.
226
-
227
-
```
228
-
// BAD — vague, requires judgment calls
229
-
"Always use tagless final style"
230
-
"Be pragmatic about error handling"
231
-
232
-
// GOOD — unambiguous if/then, no interpretation needed
233
-
"ParserService should be a typeclass constraint in the class constructor:
234
-
class FileProcessor[F[_]: {Async, ParserService}]
235
-
NOT as a parameter:
236
-
def process[F[_]](parser: ParserService[F])"
237
-
```
238
-
239
-
When writing new rules: lead with the concrete pattern (what to do), then the anti-pattern (what not to do), then rationale (why). If a rule can't be expressed as a clear if/then, it belongs in Layer 4 (advisory), not Layer 2.
240
-
241
-
### Fixing a bug
104
+
### Fixing a Bug
242
105
243
106
1. Write a failing test reproducing the bug
244
-
3. Verify the test fails, apply fix, clean again, verify all tests pass
107
+
2. Verify the test fails, apply fix, clean again, verify all tests pass
Copy file name to clipboardExpand all lines: README.md
+19Lines changed: 19 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -116,3 +116,22 @@ Then run:
116
116
```bash
117
117
docker compose up -d
118
118
```
119
+
120
+
## Run CLI without building
121
+
122
+
You can use the pre-built CLI Docker image from [GitHub Container Registry](https://github.com/lenguyenthanh/fide/pkgs/container/fide-cli) to ingest historical CSV files into the database.
123
+
124
+
Assuming CSV files are in `./csv-data` and the docker-compose Postgres is running:
0 commit comments