diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-31 12:19:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-31 12:19:34 -0500 |
| commit | ddf48dc7ac780da1aacdff4e03f1d7da255b8f39 (patch) | |
| tree | 99926b681a9ea6d4210d0dcd1bd8e8a6d47d7d9e /.ai/scripts | |
| parent | b46619cd17ed4e36f2e59c1b600078521b2049ef (diff) | |
| download | rulesets-ddf48dc7ac780da1aacdff4e03f1d7da255b8f39.tar.gz rulesets-ddf48dc7ac780da1aacdff4e03f1d7da255b8f39.zip | |
feat: add rename-ai-artifact tool and rename the drill-deck family to flashcard
Renaming an .ai artifact by hand is the kind of mechanical job that gets done incompletely: the canonical copy moves but the mirror doesn't, a reference in the INDEX is missed, a trigger phrase points at the old name. I'd also assumed a rename was costly because references scatter, when the index update is trivial and the drift check already guards it. So I built the discipline into a script instead of re-deriving it each time.
scripts/rename-ai-artifact.sh takes old and new basenames, moves the file in both the canonical and mirror trees, and rewrites every reference repo-wide on a token boundary so renaming "foo" can't corrupt "foobar" or "foo-bar". It rewrites the underscore module-name variant too (a hyphenated script imported as foo_bar via importlib), leaves the archived session records under sessions/ alone because they're history, and runs workflow-integrity + sync-check at the end to prove no drift. rename-artifact.org documents it and indexes the triggers.
Then I used the tool to do the rename that prompted it: the org-drill deck workflow and its helpers are now flashcard-named, since "flashcard" is the word you'd actually search for. The renamed set is flashcard-review.org plus flashcard-stats.py, flashcard-sync, flashcard-to-anki.py, and flashcard-diff-ids.py, with their tests, every reference, and the INDEX entry updated. The deck is still an org-drill deck under the hood, so the ":drill:" tag handling and the "drill deck" trigger phrases stay. I added "review/update the flashcards" alongside them.
Tests: 9 bats for the rename tool (including the prefix-collision and history-preservation edges), and the renamed script suites all pass under make test.
Diffstat (limited to '.ai/scripts')
| -rwxr-xr-x | .ai/scripts/flashcard-diff-ids.py (renamed from .ai/scripts/drill-deck-diff-ids.py) | 4 | ||||
| -rwxr-xr-x | .ai/scripts/flashcard-stats.py (renamed from .ai/scripts/drill-deck-stats.py) | 8 | ||||
| -rwxr-xr-x | .ai/scripts/flashcard-sync (renamed from .ai/scripts/drill-deck-sync) | 26 | ||||
| -rwxr-xr-x | .ai/scripts/flashcard-to-anki.py (renamed from .ai/scripts/drill-to-anki.py) | 6 | ||||
| -rw-r--r-- | .ai/scripts/tests/flashcard-sync.bats (renamed from .ai/scripts/tests/drill-deck-sync.bats) | 12 | ||||
| -rw-r--r-- | .ai/scripts/tests/test_flashcard_diff_ids.py (renamed from .ai/scripts/tests/test_drill_deck_diff_ids.py) | 6 | ||||
| -rw-r--r-- | .ai/scripts/tests/test_flashcard_stats.py (renamed from .ai/scripts/tests/test_drill_deck_stats.py) | 6 | ||||
| -rw-r--r-- | .ai/scripts/tests/test_flashcard_to_anki.py (renamed from .ai/scripts/tests/test_drill_to_anki.py) | 6 |
8 files changed, 37 insertions, 37 deletions
diff --git a/.ai/scripts/drill-deck-diff-ids.py b/.ai/scripts/flashcard-diff-ids.py index bd2c4cc..152bb70 100755 --- a/.ai/scripts/drill-deck-diff-ids.py +++ b/.ai/scripts/flashcard-diff-ids.py @@ -9,7 +9,7 @@ IDs are usually fine (new cards added on purpose) but worth surfacing. Exits 0 when clean, 1 when any IDs disappeared or appeared. Usage: - drill-deck-diff-ids.py <before.org> <after.org> + flashcard-diff-ids.py <before.org> <after.org> """ from __future__ import annotations @@ -68,7 +68,7 @@ def main() -> int: disappeared = before_ids - after_ids appeared = after_ids - before_ids - print(f"drill-deck-diff-ids: {before_path.name} → {after_path.name}") + print(f"flashcard-diff-ids: {before_path.name} → {after_path.name}") print() print(f"IDs in BEFORE: {len(before_ids)}") print(f"IDs in AFTER: {len(after_ids)}") diff --git a/.ai/scripts/drill-deck-stats.py b/.ai/scripts/flashcard-stats.py index 04c3468..1fa5afb 100755 --- a/.ai/scripts/drill-deck-stats.py +++ b/.ai/scripts/flashcard-stats.py @@ -6,7 +6,7 @@ Reports counts and flags two tiers of issue. Blocking WARNs (exit 1): - PROPERTIES drawer count not matching card count - Cards missing :ID: (risks SRS-state loss across rewrites) -- `*** Answer` sub-headers (should be 0 per drill-deck-review.org) +- `*** Answer` sub-headers (should be 0 per flashcard-review.org) - Non-prompt headings (topic-as-heading not yet rewritten) - #+TITLE missing, or carrying source-tool jargon ("org-drill") - Answer leakage: a card whose question echoes most of its own answer @@ -20,14 +20,14 @@ Non-blocking NOTEs (exit unaffected): - Binary yes/no prompts (low retrieval effort — candidate to reformulate) Exits 0 when no blocking warnings are present, 1 otherwise, 2 on bad usage. -Use as a gate before regenerating the Anki deck or running drill-deck-sync. +Use as a gate before regenerating the Anki deck or running flashcard-sync. The fuzzy checks (leakage, duplicate, overloaded) are tuned by the LEAKAGE_* and BACK_WORD_LIMIT constants below; loosen them if a real deck trips false positives. Usage: - drill-deck-stats.py <file.org> + flashcard-stats.py <file.org> """ from __future__ import annotations @@ -297,7 +297,7 @@ def main() -> int: elif SOURCE_TOOL_RE.search(title): warn(f"#+TITLE contains source-tool jargon ('{title}'); the deck name shows in Anki — drop 'Org-Drill' for a name that reads well on the consumption side") if answer_count: - warn(f"{answer_count} cards have *** Answer sub-headers (drop per drill-deck-review.org)") + warn(f"{answer_count} cards have *** Answer sub-headers (drop per flashcard-review.org)") if prop_count != len(cards): warn(f"PROPERTIES count {prop_count} does not match card count {len(cards)}") if no_id: diff --git a/.ai/scripts/drill-deck-sync b/.ai/scripts/flashcard-sync index 8e51cdd..f5ba7fb 100755 --- a/.ai/scripts/drill-deck-sync +++ b/.ai/scripts/flashcard-sync @@ -1,23 +1,23 @@ #!/usr/bin/env bash -# drill-deck-sync: stats check + regenerate Anki apkg + place at ~/sync/phone/anki/ +# flashcard-sync: stats check + regenerate Anki apkg + place at ~/sync/phone/anki/ # -# Wraps drill-deck-stats.py + drill-to-anki.py (and optionally -# drill-deck-diff-ids.py) for the canonical "rewrote the deck, now ship -# it" step in the drill-deck-review workflow. +# Wraps flashcard-stats.py + flashcard-to-anki.py (and optionally +# flashcard-diff-ids.py) for the canonical "rewrote the deck, now ship +# it" step in the flashcard-review workflow. # # Usage: -# drill-deck-sync <source.org> -# drill-deck-sync <source.org> --diff-against <previous-version.org> +# flashcard-sync <source.org> +# flashcard-sync <source.org> --diff-against <previous-version.org> # # Exits non-zero when the stats check warns, when --diff-against shows -# any disappeared / appeared IDs, or when drill-to-anki.py fails. The +# any disappeared / appeared IDs, or when flashcard-to-anki.py fails. The # Anki apkg is not written when any gate fails. set -euo pipefail usage() { cat >&2 <<'EOF' -usage: drill-deck-sync <source.org> [--diff-against <previous-version.org>] +usage: flashcard-sync <source.org> [--diff-against <previous-version.org>] EOF exit 2 } @@ -53,9 +53,9 @@ if [[ ! -f "$SOURCE" ]]; then fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -STATS="$SCRIPT_DIR/drill-deck-stats.py" -DIFF_IDS="$SCRIPT_DIR/drill-deck-diff-ids.py" -TO_ANKI="$SCRIPT_DIR/drill-to-anki.py" +STATS="$SCRIPT_DIR/flashcard-stats.py" +DIFF_IDS="$SCRIPT_DIR/flashcard-diff-ids.py" +TO_ANKI="$SCRIPT_DIR/flashcard-to-anki.py" for helper in "$STATS" "$DIFF_IDS" "$TO_ANKI"; do if [[ ! -f "$helper" ]]; then @@ -64,12 +64,12 @@ for helper in "$STATS" "$DIFF_IDS" "$TO_ANKI"; do fi done -echo "=== drill-deck-sync: $SOURCE ===" +echo "=== flashcard-sync: $SOURCE ===" echo echo "--- stats ---" if ! python3 "$STATS" "$SOURCE"; then echo - echo "stats check failed — fix warnings before sync, or call drill-to-anki.py directly to override" >&2 + echo "stats check failed — fix warnings before sync, or call flashcard-to-anki.py directly to override" >&2 exit 1 fi echo diff --git a/.ai/scripts/drill-to-anki.py b/.ai/scripts/flashcard-to-anki.py index 9fe954e..7227683 100755 --- a/.ai/scripts/drill-to-anki.py +++ b/.ai/scripts/flashcard-to-anki.py @@ -22,9 +22,9 @@ a mobile-Anki artifact the phone picks up from its sync dir, so it lands there rather than next to the org source. Usage: - drill-to-anki.py <input.org> - drill-to-anki.py <input.org> --deck "My Deck Name" - drill-to-anki.py <input.org> --output /path/to/deck.apkg + flashcard-to-anki.py <input.org> + flashcard-to-anki.py <input.org> --deck "My Deck Name" + flashcard-to-anki.py <input.org> --output /path/to/deck.apkg Requires genanki, which uv resolves automatically via the PEP 723 script metadata above. No venv or system install needed. diff --git a/.ai/scripts/tests/drill-deck-sync.bats b/.ai/scripts/tests/flashcard-sync.bats index e141cab..608a280 100644 --- a/.ai/scripts/tests/drill-deck-sync.bats +++ b/.ai/scripts/tests/flashcard-sync.bats @@ -1,11 +1,11 @@ #!/usr/bin/env bats -# Tests for the drill-deck-sync wrapper: argument handling + the stats gate. -# The clean end-to-end path runs drill-to-anki.py (uv-resolved genanki) and is +# Tests for the flashcard-sync wrapper: argument handling + the stats gate. +# The clean end-to-end path runs flashcard-to-anki.py (uv-resolved genanki) and is # not exercised here; these cover the guard paths that stop before that step. setup() { SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)" - SYNC="$SCRIPT_DIR/drill-deck-sync" + SYNC="$SCRIPT_DIR/flashcard-sync" TMP="$(mktemp -d)" } @@ -13,17 +13,17 @@ teardown() { rm -rf "$TMP" } -@test "drill-deck-sync: no args exits 2" { +@test "flashcard-sync: no args exits 2" { run "$SYNC" [ "$status" -eq 2 ] } -@test "drill-deck-sync: missing source file exits 2" { +@test "flashcard-sync: missing source file exits 2" { run "$SYNC" "$TMP/nope.org" [ "$status" -eq 2 ] } -@test "drill-deck-sync: stats gate failure exits 1 and writes no apkg" { +@test "flashcard-sync: stats gate failure exits 1 and writes no apkg" { cat > "$TMP/dirty.org" <<'EOF' #+TITLE: DeepSat Org-Drill Flashcards diff --git a/.ai/scripts/tests/test_drill_deck_diff_ids.py b/.ai/scripts/tests/test_flashcard_diff_ids.py index 15fb148..9554b48 100644 --- a/.ai/scripts/tests/test_drill_deck_diff_ids.py +++ b/.ai/scripts/tests/test_flashcard_diff_ids.py @@ -1,4 +1,4 @@ -"""Tests for drill-deck-diff-ids.py: :ID: extraction + SRS-state diff CLI. +"""Tests for flashcard-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. @@ -12,12 +12,12 @@ from pathlib import Path import pytest -SCRIPT = Path(__file__).resolve().parents[1] / "drill-deck-diff-ids.py" +SCRIPT = Path(__file__).resolve().parents[1] / "flashcard-diff-ids.py" @pytest.fixture(scope="module") def diff_ids(): - spec = importlib.util.spec_from_file_location("drill_deck_diff_ids", SCRIPT) + spec = importlib.util.spec_from_file_location("flashcard_diff_ids", SCRIPT) assert spec and spec.loader module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) diff --git a/.ai/scripts/tests/test_drill_deck_stats.py b/.ai/scripts/tests/test_flashcard_stats.py index d60084d..606f7c1 100644 --- a/.ai/scripts/tests/test_drill_deck_stats.py +++ b/.ai/scripts/tests/test_flashcard_stats.py @@ -1,4 +1,4 @@ -"""Tests for drill-deck-stats.py: prompt-form heuristic + CLI inventory/gate. +"""Tests for flashcard-stats.py: prompt-form heuristic + CLI inventory/gate. Plain python3 script (no third-party deps), so the pure helper imports directly; the inventory/gate behavior is exercised through the CLI. @@ -12,12 +12,12 @@ from pathlib import Path import pytest -SCRIPT = Path(__file__).resolve().parents[1] / "drill-deck-stats.py" +SCRIPT = Path(__file__).resolve().parents[1] / "flashcard-stats.py" @pytest.fixture(scope="module") def stats(): - spec = importlib.util.spec_from_file_location("drill_deck_stats", SCRIPT) + spec = importlib.util.spec_from_file_location("flashcard_stats", SCRIPT) assert spec and spec.loader module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) diff --git a/.ai/scripts/tests/test_drill_to_anki.py b/.ai/scripts/tests/test_flashcard_to_anki.py index fc17817..058b0cd 100644 --- a/.ai/scripts/tests/test_drill_to_anki.py +++ b/.ai/scripts/tests/test_flashcard_to_anki.py @@ -1,4 +1,4 @@ -"""Tests for drill-to-anki.py default-path and deck-name helpers. +"""Tests for flashcard-to-anki.py default-path and deck-name helpers. The script is a PEP 723 uv-run script that imports genanki, which uv resolves at runtime but isn't installed in the test environment. The fixture stubs @@ -14,14 +14,14 @@ from pathlib import Path import pytest -SCRIPT = Path(__file__).resolve().parents[1] / "drill-to-anki.py" +SCRIPT = Path(__file__).resolve().parents[1] / "flashcard-to-anki.py" @pytest.fixture(scope="module") def drill(): # Only stub when genanki is genuinely absent, so a real install isn't shadowed. sys.modules.setdefault("genanki", types.ModuleType("genanki")) - spec = importlib.util.spec_from_file_location("drill_to_anki", SCRIPT) + spec = importlib.util.spec_from_file_location("flashcard_to_anki", SCRIPT) assert spec and spec.loader module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) |
