From a760d8e2e645000deb56b4a0a7d922147b2b1ad7 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 19 Jul 2026 18:41:45 -0500 Subject: chore(todo): close the two flashcard speedrun tasks Both shipped this session (a143679 apkg converter, a14e43b multi-tag reconcile). Converted to dated event-log entries per the depth rule. --- todo.org | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) (limited to 'todo.org') diff --git a/todo.org b/todo.org index 8304470..116f881 100644 --- a/todo.org +++ b/todo.org @@ -349,12 +349,8 @@ The Codex entry-file pointer now exists: =claude-templates/AGENTS.md= (thin poin :END: Three flashcard-tooling tasks that all edit =flashcard-to-anki.py= and/or =flashcard-stats.py=, grouped so they get built together instead of colliding on the same files (prior sessions flagged the conflict risk). The Anki =#+TITLE= deck-name fix already landed (commit 060a938), so any preserved pre-fix script copy gets re-derived against the current canonical, never copied wholesale. The three children each ship independently. -*** TODO [#C] apkg → org-drill converter :feature:solo: -:PROPERTIES: -:CREATED: [2026-06-22 Mon] -:LAST_REVIEWED: 2026-06-24 -:END: -Inverse of =flashcard-to-anki.py=: read an Anki =.apkg= (zip → =collection.anki2=/=.anki21= sqlite) and emit an org-drill =.org= in the house canonical shape. Recovers orphaned decks (=deepsat-fundamentals.apkg= has no saved =.org= source) and enables phone→org round-trip. Mapping: deck name → =#+TITLE=; each note → =** :drill:= with Back as body; card tag → =* Section= grouping (best-effort); Back HTML → org (=
= → newlines, unescape entities, strip =
=); fresh =:ID:= UUID per card. Edge cases for tests: multiple decks per apkg, non-basic note types (skip/warn), HTML entities, empty back, media refs, =.anki2= vs =.anki21= schema. Lives beside the flashcard-* family in =claude-templates/.ai/scripts/= (a new file must be built in canonical — downstream =.ai/scripts/= is wiped by startup =--delete=). PEP 723 uv-run, stdlib =zipfile= + =sqlite3= (no genanki for reading). Acceptance: round-trip a known org-drill source through =flashcard-to-anki.py= then back, assert cards match. Build request: [[file:docs/design/2026-06-21-apkg-to-orgdrill-buildreq.org][buildreq]]. Backlog, not urgent. From home 2026-06-21. +*** 2026-07-19 Sun @ 18:40:36 -0500 Built apkg-to-orgdrill.py, the inverse converter +Commit a143679. Reads an Anki =.apkg= (stdlib =zipfile= + =sqlite3=, no genanki) and emits org-drill =.org= in the house shape: deck → =#+TITLE=, note Front → =** heading :drill:=, Back HTML → body (=
=→newlines, entities unescaped amp-last, answer =
= stripped), tag → =* section=, fresh =:ID:= per card. 17 tests incl. the round-trip through =flashcard-to-anki.py='s own parse(); verified end-to-end against a real genanki apkg. Grounded the schema by generating + inspecting a real apkg first. Speedrun task 1 of 2. *** TODO [#C] flashcard-stats refutation / claim-prompt mode :feature: :PROPERTIES: @@ -365,14 +361,8 @@ A refutation card (heading is a bare false claim, body is the rebuttal) is valid Design (Craig, 2026-06-28, supersedes the two proposed options): make the exemption *generic*, not refutation-specific — more card kinds like this will come. When the org header declares the relevant info, the gate honors it rather than blocking. So a general file-level header keyword (a card-kind / check-exemption declaration) tells =flashcard-stats.py= which checks not to apply, instead of a hardcoded =#+DECK_KIND: refutation= keyword or a per-card =:claim:= tag. Document the mechanism in =flashcard-review.org= and add tests (a header-declared exemption file passes despite declarative headings + claim/answer overlap). Edits =flashcard-stats.py= — coordinate with the multi-tag reconcile, same file. Proposal: [[file:docs/design/2026-06-21-flashcard-stats-refutation-proposal.org][proposal]] (its two-option fix is superseded by this generic header approach). Backlog. From home 2026-06-21. -*** TODO [#C] Reconcile flashcard multi-tag tooling into canonical :chore:quick:solo: -:PROPERTIES: -:CREATED: [2026-06-20 Sat] -:LAST_REVIEWED: 2026-06-24 -:END: -The work project edited two synced scripts locally as a stopgap (2026-06-17) and asked rulesets to fold them into the canonical so the next sync doesn't revert them. Preserved bundle: [[file:docs/design/2026-06-17-flashcard-multitag-note.md][note]], [[file:docs/design/2026-06-17-flashcard-multitag-to-anki.py][to-anki.py]], [[file:docs/design/2026-06-17-flashcard-multitag-stats.py][stats.py]]. Change: support a second org tag on drill headings (=:fundamental:drill:=) for curated subset decks. =flashcard-to-anki.py= — broaden =CARD_RE= to match a trailing tag block (a heading is a card when =drill= is among its tags), bound the card body by any L1/L2 heading, add =--tag-filter = (emit only cards carrying that tag) and =--guid-salt = (separate GUID space so a subset deck imports non-empty without disturbing the full deck's SRS state). =flashcard-stats.py= — same =CARD_RE=/=HEADING_RE= broadening plus a drill-membership guard. Use the preserved to-anki.py (the 0953 version: dropped an unused =heading_tags()= helper, tightened =CARD_RE= =(.*?)=→=(.+?)= for parity with stats). Apply to both =.ai/scripts/= and =claude-templates/.ai/scripts/=, add a multi-tag bats case to =flashcard-sync.bats= (a =:foo:drill:= heading parses; =--tag-filter foo= returns only those), verify the full deck still parses to 465 and =--tag-filter fundamental= returns 100, then sync-check + make test. Shared-asset change, so review-gated. - -Note (2026-06-24): the Anki =#+TITLE= deck-name fix landed (commit 060a938) — =default_deck_name= is now =default_deck_name(input_path, org_text)= with a new docstring. The preserved 2026-06-17 =to-anki.py= predates that, so *don't* copy it wholesale (it would revert the title-fix). Re-derive the multi-tag changes against the current canonical =flashcard-to-anki.py= and keep the =#+TITLE= behavior. +*** 2026-07-19 Sun @ 18:41:00 -0500 Reconciled the flashcard multi-tag tooling into canonical +Commit a14e43b. Broadened =CARD_RE= in both =flashcard-to-anki.py= and =flashcard-stats.py= so a heading is a card when =drill= is among its tag block (=:fundamental:drill:=), bounded card bodies by any L1/L2 heading (=HEADING_RE=), and added =--tag-filter= (subset emit) + =--guid-salt= (distinct GUID space) to to-anki, plus a drill-membership guard to stats. Re-derived against the current canonical rather than copying the preserved 2026-06-17 files, which predated the =#+TITLE= fix (060a938) and would have reverted it. parse()'s 3rd element is now the Anki-tag list; existing parse tests moved to that contract, new tests cover multi-tag / tag-filter / guid-salt / stats count, and a =flashcard-sync.bats= case guards the count. The real 465/100 check needs the work deck; verified end-to-end on a synthetic deck (2 cards → 1 with =--tag-filter fundamental=) through real genanki. Speedrun task 2 of 2. ** DONE [#C] coverage-summary.el documented as a local-only helper :chore:quick:solo: CLOSED: [2026-07-18 Sat] -- cgit v1.2.3