<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/.ai/scripts/tests/test_drill_deck_diff_ids.py, branch main</title>
<subtitle>Claude Code skills, rules, and language bundles
</subtitle>
<id>https://git.cjennings.net/rulesets/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/rulesets/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/'/>
<updated>2026-05-31T17:19:34+00:00</updated>
<entry>
<title>feat: add rename-ai-artifact tool and rename the drill-deck family to flashcard</title>
<updated>2026-05-31T17:19:34+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T17:19:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=ddf48dc7ac780da1aacdff4e03f1d7da255b8f39'/>
<id>urn:sha1:ddf48dc7ac780da1aacdff4e03f1d7da255b8f39</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>test(scripts): cover drill-to-anki internals, broadcast, and daily-prep</title>
<updated>2026-05-30T18:27:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-30T18:27:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=a6313954fc297ee4a6c1c42ba903730a364cd5df'/>
<id>urn:sha1:a6313954fc297ee4a6c1c42ba903730a364cd5df</id>
<content type='text'>
I backfilled the gaps left after the flashcard work landed. drill-to-anki.py had tests only for its two default helpers. I added coverage for the core parser and its pieces: parse (section-to-tag mapping, drawer-only body, blank trimming, multiline join, no-card input), strip_org_metadata (drawer and planning-line stripping, unclosed drawer), section_to_tag, escape_html, and the deterministic stable_id. I also filled the remaining drill-deck-stats / drill-deck-diff-ids branches (missing-title and PROPERTIES-mismatch warnings, the appeared-IDs note path).

I added test_cross_project_broadcast.py for the two scripts that had none here: is_broadcastable / discover (SEARCH_ROOTS pointed at a tmp tree) / sender_project / inbox_send_path, plus an ERT suite for daily-prep-agenda.el (dp-iso-date, dp-bucket with the clock pinned, dp-format-entry, and dp-collect end to end on a temp org file).

daily-prep-agenda.el needed one change to be loadable under ERT: its batch entrypoint fired on any load. I gated it behind dp--cli-invocation-p, the same readable-files check lint-org.el already uses, so requiring the file for tests no longer runs the extractor. A real invocation with a file argument still fires. A no-argument run now no-ops instead of printing an empty header.
</content>
</entry>
<entry>
<title>chore(scripts): add drill-deck stats, diff-ids, and sync wrapper</title>
<updated>2026-05-30T18:17:47+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-30T18:17:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=0234e52b727b34ade93961eb05b5638685f4406f'/>
<id>urn:sha1:0234e52b727b34ade93961eb05b5638685f4406f</id>
<content type='text'>
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.
</content>
</entry>
</feed>
