Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@ htmlcov/
collection.db
collection.db.bak*

# Sync drift/diff reports (written to cwd by sync; regenerated on demand)
# Sync diff reports (only written when `sync --diff-report PATH` is passed)
sync-diff-*.md
sync-schema-diff-*.md
sync-schema-observed-*.json

# Claude Code code-graph index cache
.codegraph/
Expand Down
86 changes: 86 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# KARDS Collection Manager

Local-first manager for a player's KARDS collection and saved decks:
syncs the official card set into a local database, tracks owned
quantities, and exports collection/deck data.

## Language

**Collection**:
The full set of KARDS cards together with the player's owned quantity per
card — a replica of the in-game collection, whose only added value is
richer filtering and management than the game offers. A card the player
owns zero copies of is still part of the Collection. There is no separate
word for the card set without quantities.
_Avoid_: Catalog

**Faction**:
The power a card belongs to (Soviet, USA, Britain, Germany, Japan, France,
Italy, Poland, Finland), named the way the game's data and API name it.
Use Faction everywhere internal: code, database, GraphQL, business logic.

**Nation**:
The user-facing label for a Faction — what the player sees on screen and in
exports (the "Nation" column, localized per language). Same concept as
Faction, different layer: Nation is presentation, Faction is the internal
code. A KARDS client TXT deck file also labels its sections by Nation.
Do not use Nation in internal code; do not rename user-facing "Nation" to
"Faction".

**Deck**:
A saved deck brought in from a KARDS client TXT file. Because a deck comes
from the game client, it is evidence of ownership: the player owns at
least as many copies of each card as the deck uses. A Collection quantity
below a deck's count means the Collection is stale — never that the deck
is invalid. A deck using fewer copies than owned is normal.

**Quantity**:
The number of copies of a card the player owns. Managed by the player,
never touched by sync. Capped by rarity exactly as in the game
(Standard 4, Limited 3, Special 2, Elite 1) — a quantity above the cap is
a data error, not player freedom, and every write path enforces the cap.

**Baseline**:
The committed snapshot of the API contract shape that sync checks the
live response against. Drift is measured relative to the Baseline;
accepting drift promotes the observed shape to become the new Baseline.

**Spawnable**:
A card that cannot be obtained in packs or crafted — it only appears
in-game when another card creates it. Spawnable cards are part of the
Collection as reference material: the player looks up what a spawned card
does. They are hidden by default behind a view toggle; owning them is
meaningless but nothing enforces a zero quantity — the Collection mirrors
the game, it does not police it.

**Exile**:
A cross-faction link on a card: the card belongs to one faction but may be
played in decks of another faction (its exile faction), reflecting the
game's exile-forces mechanic. Deck import falls back to the exile link when
a card is not found under its own faction.

**Diff**:
The comparison of card content between the local database and a fresh API
pull: new cards, changed stats/text, reserve transitions, removed cards.
The player reviews and approves a Diff before it is applied.
_Avoid_: Drift (that word is for contract shape changes)

**Drift**:
A change in the *shape* of the API contract against the committed baseline:
a field added or removed, a new faction/type/rarity/ability value. Drift
halts sync until the player reviews and accepts the new baseline.
_Avoid_: Diff (that word is for card content changes)

**Reserved**:
A card state set by the game (not the player): the card has been moved to
the reserve pool. Cards transition into and out of reserve over time, so
sync reports these transitions as their own category rather than as a
generic field change.

**Ability**:
A named game mechanic a card has (guard, blitz, smokescreen, …). One concept
regardless of source: regular abilities come from the card JSON in the
GraphQL API; extra abilities are manually curated for mechanics the API does
not expose. The split may be revisited — the game recently introduced its own
categorization — but for now source is the only distinction.
_Avoid_: Attribute
30 changes: 14 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ make help # list available make targets
make sync # install runtime dependencies
make sync-dev # install runtime/dev dependencies
make run # show kardscm CLI help
make sync-diff # preview catalog sync without DB changes
make sync-diff # preview card-set sync without DB changes
make web # start the local web UI
make web-admin # start admin web UI with DB backup
make test # pytest with coverage
Expand Down Expand Up @@ -102,7 +102,7 @@ collection.db
```text
KARDS deck TXT
-> importing.parser
-> commands.add_deck (or) commands.import_deck
-> commands.add_deck
-> storage.database
-> collection.db
```
Expand Down Expand Up @@ -142,8 +142,9 @@ Schema initialization also handles:

## Sync And API Drift

`kardscm sync` fetches the catalog, computes a diff, asks for category approval,
and writes only after approval. Rejected syncs leave the DB unchanged.
`kardscm sync` fetches the card set, computes a diff, asks for category approval,
and writes only after approval. Rejected syncs leave the DB unchanged. The diff
is shown on screen; nothing is written unless `--diff-report PATH` is passed.

The API baseline lives at:

Expand All @@ -153,13 +154,10 @@ kardscm/data/api_baseline.json

During sync, the raw GraphQL response *shape* is compared with this committed
baseline. A contract change **halts the sync** (it raises
`ApiContractDriftError` before any DB write) and produces local files for
review:

```text
sync-schema-diff-*.md
sync-schema-observed-*.json
```
`ApiContractDriftError` before any DB write). The halt prints the drift to the
terminal (CLI) or renders it in the sync modal (web); no files are written. The
observed shape is stashed in the database under the `drift_observed_snapshot`
metadata key so it can be promoted later.

A contract change means: a top-level or JSON key added or removed, a key
becoming sparse, a new `faction`/`type`/`rarity`/ability value, or a sharp drop
Expand All @@ -168,9 +166,10 @@ drift and never halts.

Workflow when a sync halts on drift:

1. Review the generated schema diff and observed snapshot.
1. Review the drift printed by the halt.
2. Update code/constants/locales if the new shape needs handling.
3. Run `uv run kardscm baseline accept` to promote the latest observed snapshot.
3. Run `uv run kardscm baseline accept` to promote the stashed observed
snapshot — exactly the shape you reviewed — and clear it.
4. Commit the baseline update with the related code or data change, then re-run
the sync.

Expand All @@ -182,9 +181,8 @@ data-derived contract snapshot.

## Extra Abilities

Some KARDS mechanics are visible in the game client but are not exposed as
official GraphQL attributes. Those are tracked as manually curated
extra-ability flags.
Some KARDS abilities are visible in the game client but are not exposed by the
official GraphQL API. Those are tracked as manually curated extra-ability flags.

Relevant files:

Expand Down
58 changes: 31 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
`kardscm` is a local collection and deck manager for
[KARDS](https://www.kards.com/).

It syncs the official card catalog into a local SQLite database, lets you keep
It syncs the official card set into a local SQLite database, lets you keep
your owned card quantities up to date, saves decks from KARDS client TXT files,
and exports collection or deck data to XLSX or JSON.

Expand All @@ -29,13 +29,13 @@ responsible for complying with the
[KARDS Terms of Use](https://www.kards.com/terms-of-use).

This repository ships no card data, card art, or pre-built database. The local
SQLite catalog is built on the user's own machine.
SQLite collection is built on the user's own machine.

## What It Does

- Syncs the full KARDS card catalog, including reserved and spawnable cards.
- Syncs the full KARDS card set, including reserved and spawnable cards.
- Preserves user-managed card quantities across syncs.
- Shows catalog changes before applying them: new cards, changed stats/text,
- Shows card-set changes before applying them: new cards, changed stats/text,
reserve transitions, and removed cards.
- Exports the collection to XLSX or JSON.
- Updates card quantities from an edited XLSX export.
Expand Down Expand Up @@ -77,7 +77,7 @@ repo and ran `make sync`, prefix each command with `uv run` (or
activate `.venv` first) so the console script is found:

```bash
# 1. Sync the card catalog into collection.db.
# 1. Sync the card set into collection.db.
uv run kardscm sync

# 2. Open the local browser UI and edit quantities.
Expand Down Expand Up @@ -118,22 +118,26 @@ kardscm sync --yes
kardscm sync --diff-report ./sync-report.md
```

`sync` fetches the official catalog, compares it with the local database, and
`sync` fetches the official card set, compares it with the local database, and
shows a diff before writing changes. Any rejected prompt aborts the sync and
leaves the database unchanged. A Markdown report is written whenever there are
changes.
leaves the database unchanged. The diff is shown on screen; nothing is written
to disk unless you pass `--diff-report`.

`--diff-only` writes the report without modifying the database. `--yes`
`--diff-only` prints the diff without modifying the database. `--yes`
(short: `-y`) auto-approves every category for scripted runs.

The diff is shown on screen and no file is written. Pass `--diff-report PATH`
to also save it as Markdown — useful for scripted runs where nobody is
watching the terminal.

Sync also checks the live GraphQL response *shape* against the committed
baseline `kardscm/data/api_baseline.json`. A genuine contract change — a field
added or removed, a field becoming sparse, a new `faction`/`type`/`rarity`/
ability value, or a sharp drop in card count — **halts the sync** and writes
`sync-schema-diff-*.md` and `sync-schema-observed-*.json` to the current
directory. Normal content growth (new card sets, more cards) is not a contract
change and never blocks. After reviewing a halt, run `kardscm baseline accept`
to adopt the new shape, then sync again.
ability value, or a sharp drop in card count — **halts the sync** and prints
what changed. Normal content growth (new card sets, more cards) is not a
contract change and never blocks. The observed shape is stored in the database,
so after reviewing a halt you can run `kardscm baseline accept` to adopt
exactly the shape you reviewed, then sync again.

## Collection Export And Update

Expand Down Expand Up @@ -215,7 +219,7 @@ kardscm web --admin # short: -A
kardscm --lang ru web --admin
```

Admin mode is for trusted local correction of catalog data. It exposes editable
Admin mode is for trusted local correction of collection data. It exposes editable
card stats, categories, ability flags, extra-ability flags, reserved state, and
localized title/text for the active locale.

Expand All @@ -242,11 +246,12 @@ kardscm deck export -f json -o deck.json
back to exile links when needed, checks collection quantities, and can update
or replace existing data:

- `--update` / `-u`: raise collection quantities to match the deck
- `--update` / `-u`: raise collection quantities to the deck's counts
- `--replace` / `-r`: overwrite an existing saved deck with the same name

`deck import` still exists as a simpler single-file import path, but day-to-day
use should prefer `deck add`.
`deck add` only reports a quantity shortfall when the deck needs more copies
than the collection records — a deck using fewer copies than you own is fine.
Quantities are capped per rarity exactly as in the game.

## Deck File Format

Expand Down Expand Up @@ -280,26 +285,25 @@ Rules:
## API Baseline

The committed baseline at `kardscm/data/api_baseline.json` is the contract that
sync drift is checked against. A contract change halts the sync; after reviewing
the drift report, promote the new shape (run from a clone; pipx users can drop
the `uv run` prefix):
sync drift is checked against. A contract change halts the sync and prints what
changed; the observed shape is stored in the database. After reviewing the drift
and updating any constants or translations, promote the reviewed shape (run from
a clone; pipx users can drop the `uv run` prefix):

```bash
uv run kardscm baseline accept
```

`baseline accept` adopts the latest `sync-schema-observed-*.json` from the sync
drift report — after you have reviewed it and updated any constants or
translations. A from-scratch baseline is created automatically on the first sync
`baseline accept` promotes the shape from the last halted sync — exactly what
you reviewed, not whatever the API happens to serve at accept time — and then
clears it. A from-scratch baseline is created automatically on the first sync
when none exists.

## Output Files

- `collection.db`: local SQLite database
- `collection.db.bak*`: local backups
- `sync-diff-*.md`: sync reports
- `sync-schema-diff-*.md`: API drift reports
- `sync-schema-observed-*.json`: observed API snapshots
- `sync-diff-*.md`: sync reports, only when `--diff-report` is passed
- export files: whatever path you pass with `-o`

Generated local data is intentionally not part of the repository.
Expand Down
Loading
Loading