aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-28 13:32:31 -0400
committerCraig Jennings <c@cjennings.net>2026-06-28 13:32:31 -0400
commit749566cc1feca12e338d1a7b5e4bd659752304cc (patch)
treef6717277d25d8243eedeaf278fdb9bbb0d3012cc
parent6be62aee7437fd8fe8d6eff991869b09529d3924 (diff)
downloadrulesets-749566cc1feca12e338d1a7b5e4bd659752304cc.tar.gz
rulesets-749566cc1feca12e338d1a7b5e4bd659752304cc.zip
chore(todo): reconcile tasks and cluster the flashcard tooling
Task audit. I folded two of Craig's cj decisions: coverage-summary.el stays a local-only helper (reframed from bug to chore, docs work only), and the flashcard-stats refutation exemption goes generic, a header-declared check-exemption that supersedes the old two-option fix. I noted that the unattended-cron task's v1 precondition has landed, so it's buildable now rather than blocked. And I grouped the three flashcard tasks (apkg converter, refutation mode, multi-tag reconcile) under one parent, since they all edit the same two scripts and want building together.
-rw-r--r--.ai/notes.org2
-rw-r--r--todo.org52
2 files changed, 29 insertions, 25 deletions
diff --git a/.ai/notes.org b/.ai/notes.org
index 16aaebb..03347d2 100644
--- a/.ai/notes.org
+++ b/.ai/notes.org
@@ -78,7 +78,7 @@ Format:
Markers maintained by workflows to record when they last ran. Read by other workflows that gate their behavior on freshness.
-:LAST_AUDIT: 2026-06-24
+:LAST_AUDIT: 2026-06-28
:LAST_INBOX_PROCESS: 2026-06-28 (11 handoffs → simplification mode in /refactor, locating-craig.md rule, suspend.org workflow, commit-gate hardening + bundled-test deny hook, readability-audit template workflow, dot-stripped project names; bug-priority matrix made binding in todo-format.md + home/work handoffs)
Format: one =:MARKER: YYYY-MM-DD= line per workflow. Workflows overwrite their own marker on completion.
diff --git a/todo.org b/todo.org
index 175d244..ec65e91 100644
--- a/todo.org
+++ b/todo.org
@@ -176,22 +176,37 @@ Make the tooling agent-agnostic instead of Claude-specific. Three threads from C
*** 2026-06-24 Wed @ 00:21:20 -0400 Partial — agent-neutral wording sweep + thread-3 note landed
Thread 2's wording half shipped in 6ad0442 (=refactor(rules): use agent-neutral language in shared rules=): agent-as-actor phrasing replaced with "the agent" across interaction.md, cross-project.md, triggers.md, working-files.md. Thread 3's note reached =.emacs.d=, whose 2026-06-23 inbox FYI confirms it received and filed the "multi-LLM support" ai-term handoff. Remaining and still TODO: thread 1 (give the agent a name), and thread 2's structural half (extract agent-neutral content into a shared source with a Codex entry-file pointer, then have Codex review the workflows for literal-reading gaps).
-** TODO [#C] apkg → org-drill converter :feature:solo:
+** TODO [#C] Flashcard tooling improvements :feature:
+:PROPERTIES:
+:CREATED: [2026-06-28 Sun]
+:LAST_REVIEWED: 2026-06-28
+: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 → =** <Front> :drill:= with Back as body; card tag → =* Section= grouping (best-effort); Back HTML → org (=<br>= → newlines, unescape entities, strip =<hr id="answer">=); 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.
-** TODO [#C] flashcard-stats refutation / claim-prompt mode :feature:
+*** TODO [#C] flashcard-stats refutation / claim-prompt mode :feature:
:PROPERTIES:
:CREATED: [2026-06-22 Mon]
+:LAST_REVIEWED: 2026-06-28
+:END:
+A refutation card (heading is a bare false claim, body is the rebuttal) is valid org-drill but trips two BLOCKING =flashcard-stats.py= checks as false positives: non-prompt-heading (a declarative claim has no =?= or imperative verb) and answer-leakage (claim words reappear in the rebuttal). =flashcard-sync='s gate then blocks the whole deck.
+
+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:
-A refutation card (heading is a bare false claim, body is the rebuttal) is valid org-drill but trips two BLOCKING =flashcard-stats.py= checks as false positives: non-prompt-heading (a declarative claim has no =?= or imperative verb) and answer-leakage (claim words reappear in the rebuttal). =flashcard-sync='s gate then blocks the whole deck. Fix (pick one): a file-level =#+DECK_KIND: refutation= keyword that skips those two checks for the file, or a per-card =:claim:= tag exempting individual cards. Option 1 is simpler and matches how the deck works (the whole file is one family). Also document the family in =flashcard-review.org= and add tests (refutation-marked 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]]. Backlog. From home 2026-06-21.
-#+begin_src cj: comment
- we need to make it more generic than this. there will be other cards like this in the future. let's not block against the information when it exists in the org header.
-#+end_src
+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 <tag>= (emit only cards carrying that tag) and =--guid-salt <s>= (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.
** TODO [#C] Guard against hardcoded host identity in synced files :feature:solo:
:PROPERTIES:
@@ -200,16 +215,12 @@ A refutation card (heading is a bare false claim, body is the rebuttal) is valid
:END:
A =CLAUDE.md= / notes file that asserts mutable environment identity as a fixed fact ("This machine is ratio", a current OS, an IP, "the laptop") is false on every machine the synced/tracked file lands on but one. It bit a real archsetup session: a stale "this machine is ratio" line made the agent reason backwards all session while on velox. Proposal: a claude-rule — don't assert mutable host/env identity as a fixed fact in a tracked/synced project file; derive it at runtime and name the command (=uname -n= for host; the =hostname= binary is often absent). Optionally a codify- or startup-time lint flagging "this machine is <name>" / "the current host is" style claims. Decide rule-only vs rule+lint. Proposal: [[file:docs/design/2026-06-21-host-identity-guard-proposal.org][proposal]]. From archsetup 2026-06-21.
-** TODO [#C] coverage-summary.el install location vs CI reachability :bug:
+** TODO [#C] coverage-summary.el documented as a local-only helper :chore:
:PROPERTIES:
:CREATED: [2026-06-22 Mon]
-:LAST_REVIEWED: 2026-06-24
+:LAST_REVIEWED: 2026-06-28
:END:
-The elisp bundle installs =coverage-summary.el= into =.claude/scripts/=, which is gitignored in code projects, so CI can't run =make coverage-summary= against it. emacs-wttrin flagged this (its copy's header was rewritten to claim a tracked =scripts/= home). Decide: ship =coverage-summary.el= to a tracked =scripts/= dir so CI reaches it, or keep =.claude/scripts/= and document it as a local-only helper. If moved, reconcile the bundle install path + the =make coverage-summary= fragment + the script's header comment. Surfaced 2026-06-21 during the coverage-summary autoloads bugfix (commit fb86736).
-
-#+begin_src cj: comment
-we can document it as a local-only helper.
-#+end_src
+The elisp bundle installs =coverage-summary.el= into =.claude/scripts/=, gitignored in code projects, so CI can't run =make coverage-summary= against it. Decision (Craig, 2026-06-28): keep it in =.claude/scripts/= and document it as a local-only helper — don't ship it to a tracked =scripts/= dir, don't expect CI to run it. Remaining work (docs only, no move): state the local-only status in the script's header comment and wherever =make coverage-summary= is described, so the gitignored install reads as intentional rather than a gap. Note: emacs-wttrin rewrote its copy's header to claim a tracked =scripts/= home, which now contradicts this decision and should be reverted on their side. Surfaced 2026-06-21 during the coverage-summary autoloads bugfix (commit fb86736).
** VERIFY [#C] Check that memories are sync'd across machines via git :spec:
:PROPERTIES:
@@ -408,16 +419,6 @@ Review [[file:docs/design/2026-06-16-autonomous-batch-execution-spec.org]] (cove
:LAST_REVIEWED: 2026-06-24
:END:
Proposal from the home project (2026-06-17): promote the self-hosted ntfy-over-Tailscale phone channel it built and verified on ratio into a general two-way agent-comms tool rulesets owns. Full proposal: [[file:docs/design/2026-06-17-ntfy-agent-comms-proposal.org]] (as-built runbook stays in the home project at =working/phone-notifications/spec.org=). What rulesets would decide: canonicalize =phone-notify= (send) plus a new =phone-recv= (check-since) as synced bin scripts; the per-machine config/secret convention (token in =~/.config/phone-notify/config= chmod 600 today, vs GPG-encrypted in dotfiles); a reference =ntfy-inbound-handler= plus systemd user-unit for event-driven delivery (Tier A subscriber routes inbound to inbox/notify, Tier B inbound spawns an agent session, Tier C notify a live session — harness research); approval-button workflows for the commits.md gates when Craig is away from the desk (tap-to-approve, the high-value concrete use); and the relationship to the retired cross-agent-comms scripts (ntfy may be the transport they lacked). Worked via =spec-create=. Blocks the triage-intake phone-push task below.
-
-** 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 <tag>= (emit only cards carrying that tag) and =--guid-salt <s>= (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.
-
** TODO [#C] triage-intake.org auto mode — push each sweep to phone (ntfy) :feature:solo:
:PROPERTIES:
:CREATED: [2026-06-20 Sat]
@@ -428,8 +429,11 @@ The work project (2026-06-18) added a "Push each sweep to Craig's phone (ntfy) â
** TODO [#D] Fully-unattended scheduled inbox check (/schedule cron pass) :feature:
:PROPERTIES:
:CREATED: [2026-06-23 Tue]
+:LAST_REVIEWED: 2026-06-28
:END:
-vNext from the inbox-consolidation spec. =auto inbox zero= (v1) is the interactive =/loop= recurring check that waits for Craig's yes before executing. A fully-unattended =/schedule= cron pass that fires while Craig is away needs its own contract before it can ship: read-only vs may-mutate =todo.org= / =~/org/roam/inbox.org=, how a find surfaces asynchronously when Craig isn't at the session, how dedup state persists across runs that don't share a session, and what session/auth context a cron run carries. Design it after v1 consolidation lands. From the inbox-consolidation spec-review (Codex finding 1). See [[file:docs/inbox-workflow-consolidation-spec.org][spec]].
+vNext from the inbox-consolidation spec. =auto inbox zero= (v1) is the interactive =/loop= recurring check that waits for Craig's yes before executing. A fully-unattended =/schedule= cron pass that fires while Craig is away needs its own contract before it can ship: read-only vs may-mutate =todo.org= / =~/org/roam/inbox.org=, how a find surfaces asynchronously when Craig isn't at the session, how dedup state persists across runs that don't share a session, and what session/auth context a cron run carries. From the inbox-consolidation spec-review (Codex finding 1). See [[file:docs/inbox-workflow-consolidation-spec.org][spec]].
+
+Update 2026-06-28: the "design after v1 consolidation lands" precondition is cleared — the inbox engine consolidation (24ca58d) and the monitor-inbox 15-min loop (edb545d) both shipped. Now actionable backlog rather than blocked; design the unattended contract when prioritized.
** TODO [#D] Warn-only pre-commit hook for tooling-path enumeration :feature:
:PROPERTIES: