aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-31 12:19:34 -0500
committerCraig Jennings <c@cjennings.net>2026-05-31 12:19:34 -0500
commitddf48dc7ac780da1aacdff4e03f1d7da255b8f39 (patch)
tree99926b681a9ea6d4210d0dcd1bd8e8a6d47d7d9e /.ai/scripts
parentb46619cd17ed4e36f2e59c1b600078521b2049ef (diff)
downloadrulesets-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)