aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.ai/scripts/drill-to-anki.py27
-rwxr-xr-xclaude-templates/.ai/scripts/drill-to-anki.py27
-rw-r--r--claude-templates/.ai/workflows/INDEX.org2
-rw-r--r--claude-templates/.ai/workflows/drill-deck-review.org210
4 files changed, 266 insertions, 0 deletions
diff --git a/.ai/scripts/drill-to-anki.py b/.ai/scripts/drill-to-anki.py
index 50e1afd..543ccd8 100755
--- a/.ai/scripts/drill-to-anki.py
+++ b/.ai/scripts/drill-to-anki.py
@@ -100,6 +100,32 @@ def title_from_org(org_text: str) -> str | None:
return None
+def strip_org_metadata(body_lines: list[str]) -> list[str]:
+ """Drop :PROPERTIES: drawers and SCHEDULED/DEADLINE/CLOSED planning lines.
+
+ Org-drill needs these in the source file (SRS state lives in the
+ PROPERTIES drawer; SCHEDULED carries the next-review date), but they
+ are noise on the back of an Anki card.
+ """
+ cleaned: list[str] = []
+ in_drawer = False
+ planning_re = re.compile(r"^\s*(SCHEDULED|DEADLINE|CLOSED):\s")
+ drawer_start_re = re.compile(r"^\s*:PROPERTIES:\s*$")
+ drawer_end_re = re.compile(r"^\s*:END:\s*$")
+ for line in body_lines:
+ if in_drawer:
+ if drawer_end_re.match(line):
+ in_drawer = False
+ continue
+ if drawer_start_re.match(line):
+ in_drawer = True
+ continue
+ if planning_re.match(line):
+ continue
+ cleaned.append(line)
+ return cleaned
+
+
def parse(org_text: str) -> list[tuple[str, str, str]]:
"""Return [(front, back_html, tag), ...] for every :drill: card."""
cards: list[tuple[str, str, str]] = []
@@ -130,6 +156,7 @@ def parse(org_text: str) -> list[tuple[str, str, str]]:
break
body_lines.append(nxt)
i += 1
+ body_lines = strip_org_metadata(body_lines)
while body_lines and not body_lines[0].strip():
body_lines.pop(0)
while body_lines and not body_lines[-1].strip():
diff --git a/claude-templates/.ai/scripts/drill-to-anki.py b/claude-templates/.ai/scripts/drill-to-anki.py
index 50e1afd..543ccd8 100755
--- a/claude-templates/.ai/scripts/drill-to-anki.py
+++ b/claude-templates/.ai/scripts/drill-to-anki.py
@@ -100,6 +100,32 @@ def title_from_org(org_text: str) -> str | None:
return None
+def strip_org_metadata(body_lines: list[str]) -> list[str]:
+ """Drop :PROPERTIES: drawers and SCHEDULED/DEADLINE/CLOSED planning lines.
+
+ Org-drill needs these in the source file (SRS state lives in the
+ PROPERTIES drawer; SCHEDULED carries the next-review date), but they
+ are noise on the back of an Anki card.
+ """
+ cleaned: list[str] = []
+ in_drawer = False
+ planning_re = re.compile(r"^\s*(SCHEDULED|DEADLINE|CLOSED):\s")
+ drawer_start_re = re.compile(r"^\s*:PROPERTIES:\s*$")
+ drawer_end_re = re.compile(r"^\s*:END:\s*$")
+ for line in body_lines:
+ if in_drawer:
+ if drawer_end_re.match(line):
+ in_drawer = False
+ continue
+ if drawer_start_re.match(line):
+ in_drawer = True
+ continue
+ if planning_re.match(line):
+ continue
+ cleaned.append(line)
+ return cleaned
+
+
def parse(org_text: str) -> list[tuple[str, str, str]]:
"""Return [(front, back_html, tag), ...] for every :drill: card."""
cards: list[tuple[str, str, str]] = []
@@ -130,6 +156,7 @@ def parse(org_text: str) -> list[tuple[str, str, str]]:
break
body_lines.append(nxt)
i += 1
+ body_lines = strip_org_metadata(body_lines)
while body_lines and not body_lines[0].strip():
body_lines.pop(0)
while body_lines and not body_lines[-1].strip():
diff --git a/claude-templates/.ai/workflows/INDEX.org b/claude-templates/.ai/workflows/INDEX.org
index aa64e2e..9c9c32c 100644
--- a/claude-templates/.ai/workflows/INDEX.org
+++ b/claude-templates/.ai/workflows/INDEX.org
@@ -84,6 +84,8 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e
- Triggers: "page me on signal", "signal me when X is done", "send a signal note about X"
- =cross-project-broadcast.org= — fan out a single message to every AI project's inbox via the discovery helper =cross-project-broadcast.py= + the existing =inbox-send.py=. Use sparingly for capability announcements and shared rule changes; not for project-specific handoffs.
- Triggers: "broadcast this to every project", "notify every project about X", "fan out this announcement", "let every project know X is available"
+- =drill-deck-review.org= — review an org-drill flashcard file, restructure cards to question-form headings (no answer hints), audit content accuracy against project source-of-truth via subagent, rewrite source preserving SRS state, regenerate the Anki =.apkg= to =~/sync/phone/anki/=. Person cards use "Who is X? Tell me about their Y."; talking-points cards stay as-is. Script behavior: =drill-to-anki.py= strips =:PROPERTIES:= drawers + =SCHEDULED:= / =DEADLINE:= planning lines from Anki output.
+ - Triggers: "review the drill deck", "update the drill deck", "refresh the Anki cards", "let's run the drill-deck-review workflow"
- =page-me.org= — set a timed notification.
- Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm")
- =status-check.org= — proactive long-running-job updates.
diff --git a/claude-templates/.ai/workflows/drill-deck-review.org b/claude-templates/.ai/workflows/drill-deck-review.org
new file mode 100644
index 0000000..a891f62
--- /dev/null
+++ b/claude-templates/.ai/workflows/drill-deck-review.org
@@ -0,0 +1,210 @@
+#+TITLE: Drill Deck Review Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+DATE: 2026-05-30
+
+* Overview
+
+Take an org-drill flashcard file and bring it into the canonical shape — every card a question that doesn't give the answer away, every fact current — then regenerate the Anki =.apkg= and drop it where the phone can sync it.
+
+The workflow has three substantive passes (question-form audit, content-accuracy audit, source rewrite) followed by a mechanical regenerate-and-place step. Content review is dispatched to a subagent because it's bounded research across project source-of-truth files; the structural rewrite stays in the main thread because it touches the SRS state we don't want to lose.
+
+* When to Use This Workflow
+
+Trigger phrases:
+
+- "Review the drill deck"
+- "Update the drill deck"
+- "Refresh the Anki cards"
+- "Let's run the drill-deck-review workflow"
+
+Typical timing:
+
+- After a wave of personnel changes (titles, roles, employment status)
+- After a major milestone (a demo ships, a contract closes, a submission goes in)
+- When org-drill review surfaces a card with stale or wrong content
+- When the Anki deck on the phone hasn't been regenerated in weeks
+
+* Inputs
+
+- *Source file*: the org-drill file. Common locations:
+ - =deepsat.org= at the work project root (symlinked from =~/sync/org/drill/=)
+ - =health-drill.org= in the health project
+ - Any =:drill:= deck under =~/sync/org/drill/=
+- *Source-of-truth docs for content accuracy*: project-specific. Typical set:
+ - Project-root =knowledge.org=, =status.org=, =notes.org=
+ - =todo.org= for the freshest signal on people / partnerships / projects
+ - =deepsat/assets/= (or equivalent) for meeting transcripts when a specific fact needs confirmation
+- *Output location*: =~/sync/phone/anki/<basename>.apkg= (the phone-sync target). The script's default is =~/sync/org/drill/=; override with =--output= per this workflow.
+
+* Canonical Card Shape
+
+** Heading (the question)
+
+Every card heading is a question that doesn't reveal the answer. Not the topic name, not the acronym, not the person's name — a question that tests recall.
+
+Three card families have different question shapes:
+
+*** Acronym / concept cards
+"What does X stand for and what is it?" or "What is X and why does it matter?" Promote the question that was already in the body up to the heading.
+
+ Before:
+ : ** AFRL :drill:
+ : What does AFRL stand for and what is it?
+ : *** Answer
+ : Air Force Research Laboratory. ...
+
+ After:
+ : ** What does AFRL stand for and what is it? :drill:
+ : Air Force Research Laboratory. ...
+
+*** Person cards
+Format: "Who is X? Tell me about their Y." where X is a role descriptor that doesn't name the person, and Y is whatever the answer body covers (background, role, limitations, scope). The answer body opens by naming the person, then continues.
+
+ Before:
+ : ** Vrezh Mikayelyan :drill:
+ : Who is Vrezh? What are his key limitations?
+ : *** Answer
+ : Developer (also called "Reg"). Armenia-based ...
+
+ After:
+ : ** Who is DeepSat's Armenia-based developer? Tell me about his background and limitations. :drill:
+ : Vrezh Mikayelyan. Armenia-based, full-time as of April 2026. Worked with Hayk at Bazoomq on Armenia's first satellite ...
+
+ Note: pick a role descriptor that genuinely identifies one person. If multiple people share the role description, add a single distinguishing detail (e.g., "the one who works evenings", "the Vineti alum"). Don't pile on parentheticals.
+
+*** Talking-points cards
+Already in question form ("Introduce Yourself", "What is DeepSat?", "What do you do at DeepSat?"). Leave the heading alone. Still strip the =*** Answer= sub-header and audit the body content for staleness.
+
+** Body (the answer)
+
+- *No =*** Answer= sub-header.* The body /is/ the answer; the heading /is/ the question. The sub-header was a workaround for topic-as-heading cards.
+- *Body opens by naming the topic.* "Air Force Research Laboratory. Air Force's R&D arm." or "Vrezh Mikayelyan. Armenia-based, full-time as of ..." The Anki back shows this directly under the front question; restating the topic makes the back read as a complete answer.
+- *PROPERTIES drawer stays.* Org-drill needs the =:ID:=, =:DRILL_LAST_INTERVAL:=, =:DRILL_EASE:= etc. for SRS state. The Anki output strips it (see the script change).
+- *=SCHEDULED:= / =DEADLINE:= planning lines stay.* Same reason. The Anki output strips them.
+
+* Approach: Phases
+
+** Phase A: Question-form audit (per card)
+
+Walk every =** ... :drill:= card and flag the ones that don't already match the canonical shape:
+
+- Heading is the topic / acronym / person's name → flag for rewrite.
+- Heading is already a question but the body still has a =*** Answer= sub-header → flag for sub-header removal.
+- Heading is a question /and/ the body is clean → no action.
+
+Output of Phase A: a list of cards needing rewrite, with the proposed new heading for each. For person cards, this means proposing the role descriptor up front so Phase C is mechanical.
+
+** Phase B: Content-accuracy audit (subagent)
+
+Dispatch a subagent with this contract (adapt the source-of-truth list per project):
+
+#+begin_example
+Audit the answer bodies of every :drill: card in <SOURCE> against
+<SOURCE-OF-TRUTH FILES> and surface every fact that is stale, wrong,
+or out-of-date. Don't rewrite the cards; report only items that need
+to change.
+
+Output shape per finding:
+ CARD: <heading as it appears>
+ CURRENT: <quote the stale phrase from the answer body>
+ UPDATE: <what it should say instead, with citation>
+ CONFIDENCE: high / medium / low
+
+Categories to look for:
+- Personnel: title changes, employment-status changes, departures, new joiners
+- Partner status: contracts that closed or fell through, partnerships that advanced or stalled, named individuals changing roles
+- Project facts: milestone shifts, submission states, exercise / demo dates
+- External contacts: title or affiliation changes
+- Company facts: head count, funding, customer status
+
+Skip cards where you find no staleness. Cap at 2,000 words.
+#+end_example
+
+Include any user-supplied seed fixes in the dispatch (e.g., "Vrezh is now full-time, drop the 'Reg' diarization error"). The subagent folds them into its report so they land in the same disposition table.
+
+Output of Phase B: a structured per-card list of content updates with confidence levels. High-confidence findings get baked in during Phase C. Medium-confidence findings are reviewed inline before baking. Low-confidence findings are surfaced but skipped unless the user calls them in.
+
+** Phase C: Source rewrite
+
+Take Phase A's question-rewrite plan and Phase B's content-update list, apply them to the source file. Preserve every card's =:PROPERTIES:= drawer (especially =:ID:=) and =SCHEDULED:= line verbatim — those carry SRS state that must survive the rewrite.
+
+Rewrite shape per card:
+
+#+begin_example
+** <question-form heading> :drill:
+[SCHEDULED line if present]
+:PROPERTIES:
+[ID + DRILL_* lines unchanged]
+:END:
+<answer body, opening with the topic name>
+#+end_example
+
+Drop the =*** Answer= sub-header entirely. The body that was under =*** Answer= becomes the body of the card. If the original body had a question above =*** Answer= (the pre-rewrite norm), drop that question — the new heading carries it.
+
+For the file as a whole, use a single =Write= rather than per-card =Edit= calls. One pass through the source, one write back. Per-card edits multiply tool calls by N and risk drift.
+
+** Phase D: Regenerate the Anki deck
+
+#+begin_src bash
+.ai/scripts/drill-to-anki.py <source.org> --output ~/sync/phone/anki/<basename>.apkg
+#+end_src
+
+The script writes the =.apkg= with stable deck/model IDs derived from the deck name, so re-importing into Anki updates existing cards rather than duplicating them.
+
+** Phase E: Verify
+
+- Spot-check the new =.apkg= size against the prior version. Significant size changes are expected when many cards were rewritten; a wildly smaller file may mean the parser dropped cards (PROPERTIES drawer mishandling, etc.).
+- Open the source in Emacs (or read with =head -100=) and confirm a few cards visually: question heading, no =*** Answer=, PROPERTIES preserved, body opens with topic name.
+- If the user keeps an org-drill session open, mention they'll want to revert the buffer to pick up the rewrite.
+
+** Phase F: Commit
+
+Two clusters:
+
+- *Source rewrite*: the org file (e.g., =deepsat.org=). Commit subject: =chore(drill): restructure cards to question-form headings + content refresh=. Body lists the content-update categories (Vrezh full-time, DCVC passed, etc.) and notes that =*** Answer= sub-headers were dropped.
+- *Workflow / script changes* (if any): if this run prompted updates to =drill-deck-review.org= or =drill-to-anki.py= in the rulesets repo, commit those separately with =chore(workflows):= or =chore(scripts):= subjects.
+
+Push both. The =.apkg= itself lives under =~/sync/phone/= which is outside the repo — no commit needed there; Syncthing (or whatever sync mechanism) handles propagation.
+
+* Anki Script Behavior
+
+The =drill-to-anki.py= script (under =.ai/scripts/=) has these contracts that this workflow depends on:
+
+1. *Strips =:PROPERTIES:= drawers* from the card body before rendering. Org-drill needs them in source; Anki cards shouldn't show them.
+2. *Strips =SCHEDULED:= / =DEADLINE:= / =CLOSED:= planning lines* from the card body. Same reason.
+3. *Does NOT strip =*** Answer= sub-headers.* If the source still has them, the Anki cards will show them. This workflow's Phase C removes them at the source.
+4. *Front of each Anki card* = the heading text without the =:drill:= tag.
+5. *Back of each Anki card* = the cleaned body (after #1 and #2), joined with =<br>= and HTML-escaped.
+6. *Stable IDs* derived from the deck name + a salt, so re-importing the same deck name updates cards rather than duplicating.
+
+If you find the script doing something else (e.g., not stripping PROPERTIES), update the script before regenerating. Don't work around a script bug in the source rewrite — the next deck will hit the same problem.
+
+* Output Path Convention
+
+- Default in the script: =~/sync/org/drill/<basename>.apkg= (matches the convention where org sources live in project repos and symlink into =~/sync/org/drill/=).
+- Default in this workflow: =~/sync/phone/anki/<basename>.apkg= (the phone-syncable Anki target). Override the script default with =--output= every time.
+
+Both paths can coexist. The =~/sync/org/drill/= dir holds Anki exports alongside the org sources (build-artifact convention); =~/sync/phone/anki/= holds the version that syncs to the phone (consumption-target convention). For most decks, only the =/sync/phone/= copy is actually consumed, so this workflow writes there directly and skips the intermediate.
+
+* Common Mistakes
+
+1. *Per-card =Edit= calls instead of one =Write=.* Multiplies tool calls and risks drift between cards. Read once, rewrite in memory, write once.
+2. *Dropping the PROPERTIES drawer in source.* Org-drill stores SRS state there; losing it resets every card's review history.
+3. *Rewriting person headings to include the name.* "Who is Vrezh Mikayelyan?" gives away the answer. The whole point is to test name recall from a role description.
+4. *Forgetting to strip =*** Answer= sub-headers.* The Anki output will show them as visible card content. The source rewrite must drop them.
+5. *Skipping the content-accuracy pass.* The structural rewrite alone leaves stale facts in place. The drill cards become a memorization tool for the wrong information.
+6. *Outputting to the script's default path.* The phone won't pick up =~/sync/org/drill/<deck>.apkg=. Always pass =--output ~/sync/phone/anki/<deck>.apkg=.
+7. *Treating subagent output as gospel.* Medium- and low-confidence findings need human review before baking. The subagent surfaces; the main thread decides.
+
+* Living Document
+
+Update this workflow as patterns emerge. Specifically:
+
+- New card family beyond acronym / person / talking-point → document the heading shape for it.
+- New source-of-truth doc beyond the standard set → add to Phase B's dispatch contract.
+- Script behavior changes → mirror them in the "Anki Script Behavior" section.
+
+** Updates and Learnings
+
+*** 2026-05-30: First run
+Built against =deepsat.org= after Craig flagged that the existing apkg surfaced PROPERTIES drawers + =*** Answer= headers on the back of every card, and that the person-card content (Vrezh in particular) had drifted. The Phase B subagent surfaced 8 high-confidence content updates plus several medium-confidence enrichments. Validated by running the rewrite and regenerating =deepsat.apkg= to =~/sync/phone/anki/=.