Pull new cards from Scryfall, compile IR for new cards, and sync to SQLite. Delta-only — never overwrites existing data.
Orchestrates the full delta card import pipeline. Downloads new cards from Scryfall, compiles their IR using Claude Code (free), and syncs everything to SQLite. Only adds new cards — existing card data and IR are never touched.
Optional arguments (passed after /update-cards):
| Argument | Effect |
|---|---|
| (none) | Full pipeline: hydrate + export + compile + sync |
--hydrate-only | Just pull new cards from Scryfall, don't compile |
--refresh-errata | Also check for oracle text changes on existing cards |
--dry-run | Show what would change without modifying anything |
Before making any changes, record how many cards currently have IR data. Write a temp script (NEVER use node -e on Windows):
// .temp/baseline_ir_count.cjs
const Database = require('better-sqlite3');
const db = new Database('public/data/mtg_cards.db');
const ir = db.prepare('SELECT COUNT(*) as c FROM cards WHERE behavior_json IS NOT NULL').get();
const total = db.prepare('SELECT COUNT(*) as c FROM cards').get();
console.log(JSON.stringify({ ir_count: ir.c, total_cards: total.c }));
db.close();
Save these numbers for verification in Step 6.
Run the hydrate script in delta mode (default):
python scripts/hydrate_cards.py
If --refresh-errata was requested:
python scripts/hydrate_cards.py --refresh-errata
If --dry-run was requested:
python scripts/hydrate_cards.py --dry-run
Check the output:
[DELTA] line showing how many new cards were foundNew: 0 and no errata, report "Already up to date — no new cards found." and STOPIf --hydrate-only was requested: Report the delta stats and STOP here.
Export only the new cards (from the delta file) to cards_raw.json:
python scripts/export_cards_for_ai.py --from-delta
Verify: Check that cards_raw.json was written and contains the expected number of cards.
Do NOT use compile_with_ai.py (that calls the Claude API and costs money). Instead, compile IR directly using Claude Code.
Read these files to understand the IR format and available actions:
docs/AI_CARD_COMPILATION.md — The full compilation pipeline docs, including IR schema, capability matrix workflow, and how to handle unknown actionsresults/card_batches/_action_library_toc.md — Searchable index of all 1,212+ implemented action types, organized by category (P/T mods, counters, zone movement, draw, mana, combat, keywords, damage, tokens, search, conditionals, triggers, costs, special). Always search this before inventing a new action name.capability_matrix.json — Machine-readable status of every action. Only use actions with status IMPLEMENTED or PARTIAL. Check the actions category for valid action types.cards_compiled.json — Look up Sol Ring (activated/mana ability), Beast Whisperer (triggered ability), and Swords to Plowshares (spell effect) to see canonical IR structure. Use a temp .cjs script to extract them (NEVER node -e on Windows).cards_raw.json — The new cards to compileAction lookup workflow:
_action_library_toc.md for your action name or a similar mechaniccapability_matrix.json to confirm status is IMPLEMENTED or PARTIALsrc/batch/irExecutor.js for the closest existing handlerProcess the new cards in chunks of ~20-30 at a time:
cards_raw.jsonoracle_text and type_lineabilities array following the format from the example cards and docscards_compiled.json:// .temp/merge_compiled_batch.cjs
const fs = require('fs');
const compiled = JSON.parse(fs.readFileSync('cards_compiled.json', 'utf-8'));
const newCards = {
"card-uuid-here": {
// full card object with ir field
}
};
Object.assign(compiled.cards, newCards);
compiled.meta.total_cards = Object.keys(compiled.cards).length;
compiled.meta.last_updated = new Date().toISOString();
fs.writeFileSync('cards_compiled.json', JSON.stringify(compiled, null, 2));
console.log(`Merged ${Object.keys(newCards).length} cards. Total: ${compiled.meta.total_cards}`);
Important compilation rules:
cards_raw.json plus the ir fieldcompiled_at timestamp and qa_status: "PENDING"type_line into supertypes, card_types, subtypes arrayscapability_matrix.json or _action_library_toc.md[]{ "type": "KEYWORD", "keyword": "flying" } formatmeta block in the IR: { "engine_version": "2.0.0", "parsing_confidence": "HIGH" }After all batches are merged, verify the compiled count:
// .temp/verify_compilation.cjs
const compiled = require('./cards_compiled.json');
const delta = require('./delta_new_cards.json');
const newIds = delta.new_card_ids || [];
let found = 0, missing = [];
for (const id of newIds) {
if (compiled.cards[id] && compiled.cards[id].ir) found++;
else missing.push(id);
}
console.log(`Compiled: ${found}/${newIds.length}`);
if (missing.length > 0) console.log(`Missing:`, missing.slice(0, 10));
Push the newly compiled IR back to the SQLite database:
node scripts/sync_ir_to_sqlite.cjs
What happens:
cards_compiled.jsonbehavior_json column for cards that have new IRAfter syncing IR to SQLite, regenerate the handler registry to pick up any new handlers from newly compiled cards:
node scripts/generate_handler_registry.cjs
What happens:
irExecutor.js STEP_EXECUTORS for all handler definitionscards_compiled.json for action usage across all cardsdocs/handler_registry.json, docs/handler_registry.md, docs/alias_map.mdSafety: This is a read-scan-write operation — it only reads source files and generates derived artifacts. No source data is modified. If the script detects fewer handlers than the previous run, it aborts without writing (guard against parse failures).
Confirm that existing IR was preserved and new cards were added. Write a temp script:
// .temp/verify_integrity.cjs
const Database = require('better-sqlite3');
const db = new Database('public/data/mtg_cards.db');
const ir = db.prepare('SELECT COUNT(*) as c FROM cards WHERE behavior_json IS NOT NULL').get();
const total = db.prepare('SELECT COUNT(*) as c FROM cards').get();
const nullIr = db.prepare('SELECT COUNT(*) as c FROM cards WHERE behavior_json IS NULL').get();
const solRing = db.prepare("SELECT name, length(behavior_json) as ir_len FROM cards WHERE name='Sol Ring' LIMIT 1").get();
console.log('IR count:', ir.c);
console.log('Total cards:', total.c);
console.log('NULL IR:', nullIr.c);
console.log('Sol Ring check:', solRing);
db.close();
Verification checks:
Print a summary like:
=== Card Database Update Complete ===
New cards added: 305
Cards with errata: 0
Cards compiled: 305
Compile errors: 0
IR count before: 31,541
IR count after: 31,846
Total cards: 36,889
Data integrity: VERIFIED
| Error | Recovery |
|---|---|
| Scryfall download fails | Retry python scripts/hydrate_cards.py — it's idempotent |
| Export finds 0 cards | Check delta_new_cards.json exists and has card IDs |
| Compilation errors | Review card oracle text, fix IR manually |
| Sync fails | Safe to retry — sync only does UPDATEs |
| IR count decreased | CRITICAL — investigate immediately, check backup |
| File | Purpose |
|---|---|
scripts/hydrate_cards.py | Scryfall bulk download, delta INSERT |
scripts/export_cards_for_ai.py | Export cards to JSON for compilation |
scripts/sync_ir_to_sqlite.cjs | Sync compiled IR back to SQLite |
capability_matrix.json | Valid action types for IR compilation |
delta_new_cards.json | Intermediate file listing new card IDs |
cards_raw.json | Exported card data for compilation |
cards_compiled.json | Master compilation output (~88MB) |
public/data/mtg_cards.db | SQLite runtime database |