aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts/tests/test_drill_deck_diff_ids.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-30 13:17:47 -0500
committerCraig Jennings <c@cjennings.net>2026-05-30 13:17:47 -0500
commit0234e52b727b34ade93961eb05b5638685f4406f (patch)
treeb7ee5f66a9fceb3fd4d9b1d2ba8c44e89dde76c5 /.ai/scripts/tests/test_drill_deck_diff_ids.py
parent038d59b7e548d2323f43dcd92ba14cba876d840d (diff)
downloadrulesets-0234e52b727b34ade93961eb05b5638685f4406f.tar.gz
rulesets-0234e52b727b34ade93961eb05b5638685f4406f.zip
chore(scripts): add drill-deck stats, diff-ids, and sync wrapper
I incorporated the flashcard-tooling bundle from the work project's deck-review workflow, validated there against a 93-card deck. Three scripts now live under .ai/scripts/: drill-deck-stats.py (pre-rewrite inventory plus a gate that warns on stray *** Answer headers, missing :ID:, non-prompt headings, and #+TITLE jargon like "org-drill"), drill-deck-diff-ids.py (SRS-state preservation check that flags any :ID: lost across a rewrite), and drill-deck-sync (bash wrapper chaining stats, optional diff-ids, then drill-to-anki, writing to ~/sync/phone/anki/ only when the gates pass). The drill-deck-review.org workflow gains a Helper Scripts section and references the scripts from its phases. I reconciled its output-path prose with the drill-to-anki default that just moved to ~/sync/phone/anki/, so it no longer claims the script still defaults to ~/sync/org/drill/. I added tests for both Python scripts (pure logic plus CLI gate behavior) and a bats suite for the wrapper's guard paths. The clean end-to-end sync path stays uncovered since it needs uv-resolved genanki.
Diffstat (limited to '.ai/scripts/tests/test_drill_deck_diff_ids.py')
-rw-r--r--.ai/scripts/tests/test_drill_deck_diff_ids.py88
1 files changed, 88 insertions, 0 deletions
diff --git a/.ai/scripts/tests/test_drill_deck_diff_ids.py b/.ai/scripts/tests/test_drill_deck_diff_ids.py
new file mode 100644
index 0000000..9cd8305
--- /dev/null
+++ b/.ai/scripts/tests/test_drill_deck_diff_ids.py
@@ -0,0 +1,88 @@
+"""Tests for drill-deck-diff-ids.py: :ID: extraction + SRS-state diff CLI.
+
+Plain python3 script (no third-party deps), so card_id_map imports directly;
+the disappeared/appeared reporting is exercised through the CLI.
+"""
+from __future__ import annotations
+
+import importlib.util
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+SCRIPT = Path(__file__).resolve().parents[1] / "drill-deck-diff-ids.py"
+
+
+@pytest.fixture(scope="module")
+def diff_ids():
+ spec = importlib.util.spec_from_file_location("drill_deck_diff_ids", SCRIPT)
+ assert spec and spec.loader
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+DECK_A = """* Section
+** What is DeepSat? :drill:
+:PROPERTIES:
+:ID: id-1
+:END:
+Body.
+** Who founded it? :drill:
+:PROPERTIES:
+:ID: id-2
+:END:
+Body.
+"""
+
+# id-2 dropped, id-3 added relative to DECK_A
+DECK_B = """* Section
+** What is DeepSat? :drill:
+:PROPERTIES:
+:ID: id-1
+:END:
+Body.
+** When was it founded? :drill:
+:PROPERTIES:
+:ID: id-3
+:END:
+Body.
+"""
+
+
+def test_card_id_map_extracts_id_to_heading(diff_ids, tmp_path):
+ f = tmp_path / "a.org"
+ f.write_text(DECK_A)
+ m = diff_ids.card_id_map(f)
+ assert set(m) == {"id-1", "id-2"}
+ assert m["id-1"] == "What is DeepSat?"
+
+
+def _run(before, after):
+ return subprocess.run(
+ [sys.executable, str(SCRIPT), str(before), str(after)],
+ capture_output=True, text=True,
+ )
+
+
+def test_cli_identical_decks_exit_zero(tmp_path):
+ a = tmp_path / "a.org"
+ a.write_text(DECK_A)
+ b = tmp_path / "b.org"
+ b.write_text(DECK_A)
+ r = _run(a, b)
+ assert r.returncode == 0
+ assert "preserved" in r.stdout.lower()
+
+
+def test_cli_dropped_id_warns_and_exits_one(tmp_path):
+ a = tmp_path / "a.org"
+ a.write_text(DECK_A)
+ b = tmp_path / "b.org"
+ b.write_text(DECK_B)
+ r = _run(a, b)
+ assert r.returncode == 1
+ assert "disappeared" in r.stdout.lower()
+ assert "id-2" in r.stdout