aboutsummaryrefslogtreecommitdiff
path: root/scripts/tests
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 /scripts/tests
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 'scripts/tests')
-rw-r--r--scripts/tests/rename-ai-artifact.bats119
1 files changed, 119 insertions, 0 deletions
diff --git a/scripts/tests/rename-ai-artifact.bats b/scripts/tests/rename-ai-artifact.bats
new file mode 100644
index 0000000..f00c92f
--- /dev/null
+++ b/scripts/tests/rename-ai-artifact.bats
@@ -0,0 +1,119 @@
+#!/usr/bin/env bats
+#
+# Tests for scripts/rename-ai-artifact.sh — rename an .ai artifact (workflow or
+# script) across the canonical + mirror trees, rewriting every reference and
+# leaving archived session records (history) untouched.
+#
+# Strategy: build a synthetic git repo mirroring the canonical/mirror layout in
+# a temp dir, copy the real script in, and run it there. The script resolves
+# the repo root from git, so it operates entirely on the fixture. The verify
+# step (workflow-integrity / sync-check) is best-effort and skips when those
+# scripts are absent, which they are in the fixture.
+
+REAL_REPO="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)"
+SCRIPT_SRC="$REAL_REPO/scripts/rename-ai-artifact.sh"
+
+setup() {
+ REPO="$(mktemp -d -t rename-bats.XXXXXX)"
+ mkdir -p "$REPO/scripts"
+ cp "$SCRIPT_SRC" "$REPO/scripts/rename-ai-artifact.sh"
+ # Canonical + mirror trees.
+ for base in "$REPO/claude-templates/.ai" "$REPO/.ai"; do
+ mkdir -p "$base/workflows" "$base/scripts" "$base/sessions"
+ printf '* Summary\nThe foo workflow.\nTriggers: "run the foo workflow"\n' > "$base/workflows/foo.org"
+ printf '#+TITLE: Index\n- =foo.org= — the foo workflow.\n- =foobar.org= — unrelated, must survive.\n' > "$base/workflows/INDEX.org"
+ printf '* Summary\nThe foobar workflow (must not be touched by a foo rename).\n' > "$base/workflows/foobar.org"
+ printf '#!/usr/bin/env python3\n# foo-helper for the foo workflow\nprint("foo-helper")\n' > "$base/scripts/foo-helper.py"
+ printf 'Old session mentioning foo and foo.org — this is history.\n' > "$base/sessions/2026-01-01-old.org"
+ done
+ printf 'See foo.org and foo-helper.py. Also foobar.org stays.\n' > "$REPO/notes.org"
+ ( cd "$REPO" && git init -q && git add -A && git -c user.email=t@t -c user.name=t commit -qm init )
+}
+
+teardown() {
+ rm -rf "$REPO"
+}
+
+run_rename() { # OLD NEW
+ ( cd "$REPO" && bash scripts/rename-ai-artifact.sh "$1" "$2" )
+}
+
+# --- Normal: workflow rename across both trees ---
+
+@test "rename: moves a workflow in both canonical and mirror" {
+ run run_rename foo.org bar.org
+ [ "$status" -eq 0 ]
+ [ -f "$REPO/claude-templates/.ai/workflows/bar.org" ]
+ [ -f "$REPO/.ai/workflows/bar.org" ]
+ [ ! -e "$REPO/claude-templates/.ai/workflows/foo.org" ]
+ [ ! -e "$REPO/.ai/workflows/foo.org" ]
+}
+
+@test "rename: rewrites references in the index and prose" {
+ run_rename foo.org bar.org
+ grep -q "=bar.org=" "$REPO/.ai/workflows/INDEX.org"
+ grep -q "run the bar workflow" "$REPO/.ai/workflows/bar.org"
+ grep -q "bar.org" "$REPO/notes.org"
+ ! grep -q "foo.org" "$REPO/notes.org"
+}
+
+# --- The kernel: history is untouched, near-miss names survive ---
+
+@test "rename: archived session records are left as history" {
+ run_rename foo.org bar.org
+ grep -q "mentioning foo and foo.org" "$REPO/.ai/sessions/2026-01-01-old.org"
+ grep -q "mentioning foo and foo.org" "$REPO/claude-templates/.ai/sessions/2026-01-01-old.org"
+}
+
+@test "rename: a longer name sharing the prefix is not corrupted" {
+ run_rename foo.org bar.org
+ # foobar.org and its index row must be intact — token-boundary matching.
+ [ -f "$REPO/.ai/workflows/foobar.org" ]
+ grep -q "=foobar.org=" "$REPO/.ai/workflows/INDEX.org"
+ grep -q "foobar.org stays" "$REPO/notes.org"
+}
+
+# --- Script artifact (no .org extension to strip cleanly) ---
+
+@test "rename: renames a script artifact and its references" {
+ run run_rename foo-helper.py baz-helper.py
+ [ "$status" -eq 0 ]
+ [ -f "$REPO/.ai/scripts/baz-helper.py" ]
+ [ -f "$REPO/claude-templates/.ai/scripts/baz-helper.py" ]
+ grep -q "baz-helper.py" "$REPO/notes.org"
+ ! grep -q "foo-helper" "$REPO/notes.org"
+}
+
+@test "rename: also rewrites the underscore module-name variant" {
+ # A python test imports the hyphenated script under an underscored module
+ # name, the way importlib.spec_from_file_location does. Renaming the script
+ # must update both the hyphen path and the underscore module name.
+ for base in "$REPO/claude-templates/.ai" "$REPO/.ai"; do
+ printf 'spec_from_file_location("foo_helper", "foo-helper.py")\n' \
+ > "$base/scripts/uses-helper.py"
+ done
+ ( cd "$REPO" && git add -A && git -c user.email=t@t -c user.name=t commit -qm add )
+ run run_rename foo-helper.py baz-helper.py
+ [ "$status" -eq 0 ]
+ grep -q 'spec_from_file_location("baz_helper", "baz-helper.py")' "$REPO/.ai/scripts/uses-helper.py"
+ ! grep -q "foo_helper" "$REPO/.ai/scripts/uses-helper.py"
+}
+
+# --- Errors ---
+
+@test "rename: refuses when the source artifact does not exist" {
+ run run_rename nope.org bar.org
+ [ "$status" -ne 0 ]
+ [[ "$output" == *"not found"* ]]
+}
+
+@test "rename: refuses when the target already exists" {
+ run run_rename foo.org foobar.org
+ [ "$status" -ne 0 ]
+ [[ "$output" == *"exists"* ]]
+}
+
+@test "rename: refuses bad usage (missing args)" {
+ run bash "$REPO/scripts/rename-ai-artifact.sh" only-one-arg
+ [ "$status" -ne 0 ]
+}