aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* feat: add rename-ai-artifact tool and rename the drill-deck family to flashcardCraig Jennings2026-05-3124-168/+508
| | | | | | | | | | 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.
* feat(elisp): add coverage-summary to the Elisp bundle with missing-file ↵Craig Jennings2026-05-3110-1/+545
| | | | | | | | | | | | | | detection A line-weighted coverage total has a blind spot: a module no test loads never shows up in the SimpleCov report, so it can't drag the number down. The suite looks healthier than it is. This adds a summary that counts every source file on disk against the report and treats an absent file as 0%, weighting the project number by file instead of by line so untested modules stay visible. The script ships at languages/elisp/claude/scripts/coverage-summary.el, self-contained on stock Emacs (just the built-in json). It parses the undercover SimpleCov shape directly rather than depending on the editor's coverage engine, so it runs anywhere the bundle lands. I proved it against a real 103-file report: 93 tracked, 27 untested modules surfaced, project number 66.4%. Delivery follows the bundle convention. The script lives under the gitignored .claude/ footprint and gets auto-fixed on drift by sync-language-bundle.sh, which I made generic for any claude/scripts/* rather than coverage-specific. The Makefile targets ship as a project-owned fragment (languages/elisp/coverage-makefile.txt) that install-lang.sh seeds at the project root and sync drops into .ai/inbox/ when that convention exists. The bundle never edits the project's own Makefile. Tests: 12 ERT for the kernel (Normal/Boundary/Error per function), wired into make test via a new languages/*/tests/ discovery path, plus bats for the sync auto-fix and the inbox-drop guards. This is the Elisp pilot. The pattern is proven, so fanning out to Python, Go, and TypeScript is now a follow-up. Each one needs only its own parser and fragment. The plumbing is already generic.
* refactor(workflows): restructure startup and triage-intake into reading lanesCraig Jennings2026-05-316-225/+283
| | | | | | | | | | I split each into lanes so a reader can stop at the level that answers the question: Summary for "what does this do and what does it produce", Execution for the steps to follow, Reference for examples and edge cases, History for old decisions. Both files are large enough that an agent loading them at routing time pays for context it doesn't need yet. startup.org keeps Summary, Execution, and Reference (workflow discovery and common mistakes moved under Reference). triage-intake.org gets all four, including a History lane for its design notes. Every instruction is preserved. The triage reorder ran through a content-preservation check that compared the multiset of content lines before and after, so only heading depth and lane grouping moved. Nothing was dropped or reworded. workflow-integrity.py now counts "Summary" as a valid orientation heading, since that's the new top section both files lead with. This is the pilot from the codex backlog, scoped to the two largest workflows. Whether the lanes actually cut session token use gets evaluated before any wider rollout.
* refactor(daily-prep): delegate triage to the triage-intake engineCraig Jennings2026-05-313-545/+51
| | | | | | daily-prep's Phase 3 re-implemented email/Slack/Linear/PR scanning inline (sub-steps 3b-3g, ~280 lines): the same fan-out, classify, and reactive-task work the triage-intake engine has owned since its 2026-05-26 plugin refactor. I collapsed those to four steps: 3b runs the engine, 3c surfaces today's reactive items as Day's Priorities thin links, 3d re-sorts by urgency, 3e writes the audit footer from the engine's per-source coverage. Source coverage carries because the engine's Phase 0 globs both .ai/workflows/ and .ai/project-workflows/ plugins, so the work account's Gmail/Slack/Linear/GHE plugins are still scanned, and a source change now lives in one plugin instead of being duplicated here. I adapted the downstream references (the Prep-Doc-Structure rule, the Heads-up FYI source, the Recommended Approach Pattern reframed as engine-applied), dropped the orphaned Linear-digest note, and added a Living Document entry. The file goes 825 to 576 lines, and the prep-doc contract (Day's Priorities, Heads-up, Sources-checked footer) is unchanged.
* chore(rules): resolve category-3 deepsat rule copies as left-aloneCraig Jennings2026-05-311-1/+3
| | | | Diffed the testing.md / verification.md copies in the deepsat coding-rulesets and orchestration_dashboard_mvp trees against canonical. Both are byte-identical to each other and stale (testing 221 lines behind, verification 40), with only 5 lines unique to the copies. Left untouched per the standing decision: they're team-owned, and canonicalizing would create a cross-repo dependency on the private rulesets, with the orchestration copy team-visible. Audit-only; no files modified.
* chore(rules): audit language-rule duplication, leave copies project-localCraig Jennings2026-05-311-1/+5
| | | | | | Audited the python-testing / typescript-testing / elisp rule copies across the project mirrors. Four are in sync with canonical; gloss and chime are byte-identical to each other and purely stale (44 + 1 lines behind, with zero project-specific additions). No intentional divergence exists anywhere. Disposition: leave them project-local. The language-rule copies in code projects are the bundle's deliberate copy-and-sync model, and sync-language-bundle.sh auto-fixes drifted bundle rules on each startup, so gloss and chime self-heal on their next boot. Symlinking would fight that model, and the work/deepsat copies stay untouched as team territory. Audit-only; findings recorded in the task.
* feat(aiignore): add .aiignore and the recursive-read conventionCraig Jennings2026-05-314-1/+61
| | | | | | Agents (and any future inventory tool) doing a naive recursive read of a project pick up node_modules, __pycache__, build output, and token artifacts even when those are gitignored, because a recursive read sees the disk, not git. I added a gitignore-syntax .aiignore at the repo root with the default skip list, and a protocols.org "Recursive Reads" subsection documenting the convention, the defaults to assume absent a file, and the lockfile policy (skip on agent reads, independent of git-tracking). I did not wire the walking scripts (audit.sh, diff-lang.sh, sync-language-bundle.sh): they do targeted finds over .ai/.claude/bundle dirs, never whole-tree walks, so honoring .aiignore there would be dead code. That belongs in a future catalog tool.
* feat(scripts): add workflow-integrity checker + testsCraig Jennings2026-05-313-1/+266
| | | | | | Startup's drift check catches index-vs-directory mismatches. This goes deeper: scripts/workflow-integrity.py runs six checks over the canonical .ai/workflows/: each file is indexed-or-a-plugin-of-an-indexed-engine, each index entry resolves to a file, each .ai/scripts/ reference resolves, each plugin maps to an indexed parent, each non-plugin workflow has an orientation section, and no trigger phrase is claimed by two workflows. Exit 1 on any finding. scripts/tests/workflow-integrity.bats covers the clean canonical state plus a fixture per breakage class. make test already globs scripts/tests/*.bats, so it's wired in. I calibrated against the 38 current workflows (clean). The orientation check accepts the real heading variety (Overview / Purpose / When to Use|Run / Status) and exempts plugins.
* chore(todo): tag solo-capable tasks and record their decisionsCraig Jennings2026-05-311-7/+13
| | | | Reviewed the open-task list for solo-ness (whether I can complete a task end to end and verify it without input). Tagged seven :solo: — five that already qualified (workflow test harness, daily-prep delegation, rule-duplication audit, .aiignore, coverage-summary) and two unblocked by a decision today: the category-3 rule copies (leave team-tree copies alone, don't reach into team repos) and the token-tier pilot (approved the four-lane structure for both startup.org and triage-intake.org). The google-docs token-rotation helper stays unmarked, held until a real rotation lets me write and verify in one pass. Each decision is recorded in its task body.
* docs(spec-review): enumerate implementation tasks in Phase 6Craig Jennings2026-05-313-1/+51
| | | | From a pearl handoff: Phase 6 logged deferred and v1 work only in passing, so the implementer handoff was a re-read of the spec rather than a paste. I added a step that lifts the spec's Implementation phases section into a drop-in todo.org block: one [#B] TODO per phase plus a test-surface entry mirroring the Acceptance criteria. A spec with no phase decomposition fails the step, surfacing the shape problem as a finding before Ready rather than inventing phases. Added Exit Criterion 6 and a review-history entry.
* chore(ai): archive session record and resolved tasksCraig Jennings2026-05-312-39/+121
|
* feat(workflows): add monitor-inbox workflow + inbox-status scriptCraig Jennings2026-05-3110-0/+420
| | | | | | Handoffs that arrive mid-session used to sit unseen until the next startup or a manual check. Today's burst of cross-project handoffs made that gap obvious. I added monitor-inbox.org, the cadence-and-decision layer over process-inbox: check the inbox at every task boundary, decide act-now (just do it) versus file (ask, with filing as option 1), and reply to the sender. An opt-in background-monitor /loop recipe covers unattended watching. inbox-status (with bats tests) is the cheap check the cadence calls. It lists unprocessed handoffs and exits nonzero when any are pending, using the same artifact exclusions as the wrap-up sanity check. protocols.org gets a short cadence note so the habit fires every session, and INDEX.org lists the new workflow. The act-vs-file rule (act-now is silent, filing asks with file as option 1, ambiguity asks) is the decision protocol we settled today.
* docs(protocols): surface cmail-action send as the default email pathCraig Jennings2026-05-304-70/+82
| | | | An org-drill session asked to send a follow-up email first claimed it couldn't, then hand-built MIME through msmtp, because nothing told it cmail send exists. I added a "Sending Email" subsection to protocols.org (read every session): cmail (c@cjennings.net) is the default for personal mail, dmail for work, and cmail-action send is the tool, with one-liner examples for body-file, attachments, Cc/Bcc, and threaded replies. I also rewrote send-email.org Step 4, replacing the inline-Python heredoc that taught the hard way with the cmail-action send call.
* feat(cmail): add --cc/--bcc and threading headers to cmail-action sendCraig Jennings2026-05-304-10/+160
| | | | cmail-action send couldn't do a proper reply (no Cc/Bcc, no In-Reply-To/References), so an org-drill session that needed to reply to an upstream maintainer hand-rolled a raw MIME message through msmtp instead. I extended build_message (the pure function) with cc, bcc, in_reply_to, and references, wired the matching --cc/--bcc (repeatable), --in-reply-to, and --references flags through cmd_send, and wrote the tests first. send_message derives recipients from the To/Cc/Bcc headers and strips Bcc, so no manual recipient list is needed.
* feat(session-context): resolve the active path per AI_AGENT_IDCraig Jennings2026-05-3011-7/+179
| | | | | | A single .ai/session-context.org races when two agents share a project: each agent's writes clobber the other's session log. I added .ai/scripts/session-context-path, which resolves the active path from AI_AGENT_ID: unset gives the legacy .ai/session-context.org singleton (so every existing one-agent session is unchanged), set gives .ai/session-context.d/<id>.org with the id sanitized to filename-safe characters. This is Codex's Phase 1 slice from the runtime-neutral spec: the race fix on its own, no broader refactor. startup.org's existence check and wrap-it-up.org's rename now resolve through the helper, each with a singleton fallback so older checkouts that haven't synced the script still work. Wrap folds the agent id into the archive name so two agents wrapping in the same minute don't collide. protocols.org documents the rule. Verified with 5 bats cases and a two-agent simulation showing distinct paths per id.
* fix(startup): skip the .ai/ template sync when rulesets has uncommitted WIPCraig Jennings2026-05-303-21/+59
| | | | | | From jr-estate's handoff: Phase A's =rsync -a --delete= copies the rulesets working tree by disk presence, so a downstream session that starts while rulesets has in-flight WIP pulls that WIP into its own =.ai/workflows/= and =.ai/scripts/=, where it reads as drift the user never authored. I guarded the three rsyncs behind a =git status --porcelain= check on the synced source paths (=claude-templates/.ai/{protocols.org,workflows/,scripts/}=). It syncs when those are clean and skips with a message when dirty, catching up on the next clean session. The check is scoped to those paths, so unrelated rulesets dirt (a stray session-context.org, scratch files) doesn't block the sync. The handoff's secondary anomaly (two workflow files that didn't reach jr-estate) was a timeline artifact, not a Phase A bug. Both were added in 664bf01 on 2026-05-29, after jr-estate's rsync had already run, so they correctly didn't exist to copy yet.
* fix(lint-org): suppress verbatim-asterisk misplaced-heading false positivesCraig Jennings2026-05-305-49/+80
| | | | | | org-lint reads an =** Foo= verbatim span in body prose as a possible misplaced heading, but verbatim markup is never a real heading. lint-org kept surfacing these as judgment items, so they recurred in lint-followups.org on every wrap and could never be acted on, since the todo.org content was already correct. I added lo--verbatim-asterisk-at-line-p, which mirrors the markdown-bold detector: it checks the reported line and the one before it, since org-lint marks the blank line after the offender. A match is now suppressed silently, the same way the cj-comment false positives already are. I flipped the two tests that pinned the old judgment behavior, and confirmed todo.org lints clean (judgment=0). This resolves the checker-bug report I filed in the inbox earlier, which I removed.
* chore(inbox): file lint-org false-positive bug, drop unfixable follow-upsCraig Jennings2026-05-302-4/+25
| | | | The wrap-up lint pass kept re-flagging two verbatim-asterisk misplaced-heading items (=** DONE= and =** Startup Pull Ordering= shown as verbatim references in prose) that aren't real misplaced headings and can't be fixed in todo.org. Filed a checker-bug report in the inbox to suppress that class going forward, and dropped the two unactionable items from lint-followups.org.
* chore(ai): archive session record + lint follow-upsCraig Jennings2026-05-302-0/+52
|
* fix(drill-deck): cut leakage false positives and codify source/date conventionsCraig Jennings2026-05-3010-16/+274
| | | | | | | | Health ran the new leakage check on a 43-card deck and hit two false-positive classes. The check read the whole card body, so a =Source: <label> — <url>= citation line inflated the front/back overlap whenever the URL slug repeated the question's words. Range/category cards ("What are the HbA1c ranges across normal, prediabetes, and diabetes?") tripped it too, because the question's categories echo in the answer even though the recalled content is the numbers. drill-deck-stats.py now routes leakage through an is_leaky helper. It strips =Source:= and created-date lines before computing overlap, and exempts a card when the answer carries a numeric range or threshold the question lacks. leakage_ratio itself is unchanged, so the genuine-restatement case still flags. Two body conventions now hold: a =Source:= citation goes at the end of a card after two blank lines, and no created/added date goes on a card. drill-to-anki.py now strips =Created:= / =:CREATED:= lines from the back as a backstop, and the workflow's Phase C removes them from the source during the rewrite. I added tests for the source-strip, the numeric carve-out, and the created-line strip, and documented all of it in drill-deck-review.org.
* fix(startup): exclude Python cache from script sync and restore script exec bitsCraig Jennings2026-05-3010-2/+4
| | | | | | From health's handoff: the startup =.ai/scripts/= sync was dragging pytest build artifacts into every consuming project. =rsync -a= copies by disk presence, not git status, so the =__pycache__/= and =.pytest_cache/= that rulesets' own pytest leaves in =claude-templates/.ai/scripts/tests/= rode along to each project's tree even though the root =.gitignore= already keeps them out of rulesets' commits. Phase A's scripts rsync now excludes =__pycache__=, =.pytest_cache=, and =*.pyc=. A project that already received the cache has to remove it once by hand, since =--delete= leaves excluded paths in place. I noted that in the startup doc. Health also flagged that =inbox-send.py= kept needing a manual chmod. The cause wasn't rsync dropping the bit. Four shebang scripts (=inbox-send.py=, =cj-scan.py=, =cj-remove-block.py=, =eml-view-and-extract-attachments.py=) were committed mode 100644, so rsync faithfully copied the wrong mode. I set the exec bit on all four so the synced copies are runnable.
* chore(ai): update session logCraig Jennings2026-05-301-0/+12
|
* feat(drill-deck): add authoring-quality checks and a card-authoring sectionCraig Jennings2026-05-306-144/+826
| | | | | | | | I researched spaced-repetition best practices (Wozniak's twenty rules, Matuschak's prompt-writing guide, Nielsen, the Anki and FSRS docs) and folded the findings into the drill-deck pipeline. drill-deck-stats.py now checks authoring quality on top of structure. Two checks block: answer leakage (a question that echoes >= 80% of its own answer's content words tests recognition, not recall) and duplicate / near-duplicate fronts (confusable cards interfere). Three checks warn without blocking, surfacing rewrite candidates without failing the gate: overloaded backs, list-shaped backs, and binary yes/no prompts. The fuzzy thresholds live in constants at the top of the script, so a real deck that trips false positives can be tuned. I pulled the card-parsing into a parse_cards helper that captures each card's body, and added focused tests for every new helper plus CLI coverage of the leaky, duplicate, and notes-only cases. drill-deck-review.org gains a Card Authoring Principles section (the why behind the canonical shapes, with sources), a person-card splitting path bounded by the :ID:-preservation rule, a Phase B cost-benefit-removal and leech-reformulation disposition, and a scheduling-is-Anki-side note so a future editor doesn't try to encode FSRS retention in the org source. I left out cloze cards (would need a second note type), per-card tractability targeting and retention encoding (Anki-side telemetry that never reaches the source), and on-face source-stamping (the converter strips those drawers by design). Each is noted with its reason.
* chore(ai): refresh session log and workflow-state markerCraig Jennings2026-05-302-1/+70
|
* test(scripts): cover drill-to-anki internals, broadcast, and daily-prepCraig Jennings2026-05-3012-2/+822
| | | | | | | | 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.
* chore(scripts): add drill-deck stats, diff-ids, and sync wrapperCraig Jennings2026-05-3014-48/+1340
| | | | | | 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.
* feat(drill-to-anki): default to phone sync dir and basename deck nameCraig Jennings2026-05-304-50/+114
| | | | | | Two default-behavior tweaks from real use. Output now defaults to ~/sync/phone/anki/ instead of ~/sync/org/drill/. The .apkg is a mobile-Anki artifact the phone picks up from its sync dir, while the org source stays in the project. Deck name now defaults to the raw input basename, case preserved. That drops the #+TITLE preference, which leaked tool-name jargon like "DeepSat org-drill flashcards" into the Anki deck list. The --deck and --output flags still override both. I dropped the now-unused title_from_org helper and added a test covering the two defaults. genanki is stubbed in the test since uv resolves it only at runtime.
* chore: sync .ai mirror with canonical (drill-deck-review)Craig Jennings2026-05-302-0/+212
|
* feat(workflows): add drill-deck-review + extend drill-to-anki scriptCraig Jennings2026-05-304-0/+266
| | | | | | | | I added a drill-deck-review workflow that walks an org-drill deck end-to-end: question-form audit on every heading (so the heading is the prompt, not the answer), content-accuracy audit via subagent against project source-of-truth, source rewrite preserving SRS state, regenerate to ~/sync/phone/anki/. The workflow covers three card families (acronym/concept, person, talking-point) and codifies the person-card pattern as "Who is X? Tell me about their Y." where X is a role descriptor that doesn't name the person and Y is the topical anchor from the answer body. The drill-to-anki.py script picks up a new strip_org_metadata helper that drops :PROPERTIES: drawers and SCHEDULED/DEADLINE/CLOSED planning lines from the rendered Anki back. Org-drill needs both in the source for SRS state and review scheduling; Anki cards shouldn't show them. INDEX entry under "On-demand utilities" wires the trigger phrases.
* chore(todo): fix lint findings on todo.orgCraig Jennings2026-05-302-16/+7
| | | | Relocate two LAST_REVIEWED property drawers to sit directly under their headings (and after the CLOSED line on the DONE task) so org reads them as real properties instead of inert body text. Convert the dangling #16 custom-id link to a fuzzy heading link, since the target heading carries no CUSTOM_ID. The two remaining misplaced-heading warnings are verbatim org markup inside prose, so they're false positives and stay as-is.
* docs(ai): populate Summary section in archived session recordCraig Jennings2026-05-291-1/+41
| | | | | | | | git mv in the prior wrap-up commit (0237465) renamed the index entry without re-staging the disk content, so the archive landed with an empty Active Goal and missing Decisions, Findings, Files Modified, and Next Steps sections. This follow-up commits the populated Summary that was already on disk at wrap-up time.
* chore(ai): archive session record + lint follow-upsCraig Jennings2026-05-292-0/+7
| | | | | | | Multi-arc session: no-approvals batch, codex backlog triage, page-signal + broadcast infra, voice profile Phase 1 + Phase 2 with SKILL.md split, process-inbox formalization, wrap-it-up inbox sanity check.
* fix(wrap-it-up): inbox sanity check exempts lint-followups.orgCraig Jennings2026-05-292-8/+22
| | | | | | | | | | | | | | | | The inbox sanity check I added in 8424e8f counted lint-followups.org as unprocessed, which surfaced as a false alarm during this session's wrap-up. lint-org writes its judgment items into inbox/lint-followups.org earlier in the same wrap-up workflow by design. The file is a pipeline artifact for the next morning's daily-prep, not a handoff that needs the value gate. The find filter now excludes .gitkeep, lint-followups.org, and PROCESSED-* prefixes. The Validation Checklist line names the same expected-artifacts list explicitly. Smoke test: post-fix count is 0 in the rulesets project (where lint-org just wrote 5 judgment items into inbox/lint-followups.org).
* docs(cross-project-broadcast): codify capability-and-rule cadence guidelineCraig Jennings2026-05-292-2/+28
| | | | | | | | | | | | | | | | | | | | | | | Adds one bullet to When NOT to Use and a new Cadence Guideline section that names the per-commit-broadcast anti-pattern explicitly. The new section lays out the five reasons broadcasts stay capability-and-rule-level rather than commit-level: cost per broadcast across the fleet, signal-to-noise on project-internal commits, the rsync-already-does-the-work observation, aggregation winning over per-commit pings, and the train-projects-to-ignore-inbox risk. The end-of-session bundling guidance lands too. If a session ships several broadcastable changes, bundle them into one broadcast at session end instead of firing one per commit. Source: session-end conversation 2026-05-29 surveying today's cross-project changes. Today's session shipped 13 cross-project items (4 new workflows, 6 workflow updates, 2 new scripts, 1 new bin tool). A per-commit broadcast cadence would have fired 13 inbox files across 23 targets, or 299 total inbox files. One consolidated broadcast (or no broadcast at all, since Craig already coordinated manually) covered the same ground.
* docs(wrap-it-up): add inbox sanity check + checklist lineCraig Jennings2026-05-292-0/+40
| | | | | | | | | | | | | | | | | | | | | | | | Wrap-up never knew about the inbox. After process-inbox.org landed today as the formal workflow for handoff intake, wrap-it-up.org needs to surface inbox/ when it's non-empty so the wrap doesn't silently defer handoffs to next session. This commit adds a new Inbox sanity check sub-step under Step 3 (project hygiene). The check runs find inbox -maxdepth 1 -type f filtered to skip .gitkeep and PROCESSED-* prefixes. A non-zero count surfaces with the file list and a recommendation to run process-inbox.org or explicitly defer each item. A zero count or no inbox/ directory makes the check a silent no-op. It also adds one line to the Validation Checklist: inbox is empty (excluding .gitkeep and PROCESSED-* prefixes), OR each remaining item has an explicit deferral logged in the valediction. This is the only integration gap surfaced today. The other gaps considered (LAST_INBOX_PROCESS marker stamp, sync-check, make status) didn't justify wrap-up changes. The marker is process-inbox's own responsibility. sync-check is project-specific to rulesets. make status doesn't generalize across projects.
* chore(session): pre-stage signal-mcp config + snapshot session-contextCraig Jennings2026-05-292-0/+136
| | | | | | | | | | | | | | | | | | | | | | | | | | This commit is end-of-day housekeeping for the rulesets session that ran 2026-05-28 through 2026-05-29. mcp/servers.json gains the signal-mcp stdio entry pointing at the locally-cloned rymurr/signal-mcp install at ~/.local/share/signal-mcp/. The entry uses Craig's primary +15103169357 as --user-id, which will need updating to the Google Voice number once that signal-cli registration lands (TODO scheduled today). The configuration is staged but not registered. The make install-mcp pipeline was interrupted earlier by the GPG pinentry (Craig on vacation, away from desk). Running make install-mcp at the desktop with the GPG passphrase will complete the registration. .ai/session-context.org captures the live session narrative covering the no-approvals batch (6 :quick:solo: TODOs), the codex backlog triage, the page-signal infra, the voice profile Phase 1 plus Phase 2, the SKILL.md / voice-profile.org structural split, and the inbox processing pass. No wrap-up performed. The session-context stays under that name pending the wrap-it-up workflow when Craig is back at the desk. The substantive work in this session shipped across many prior commits, each with its own focused message.
* chore(intake): file startup-rsync-dirty TODO, stamp :LAST_INBOX_PROCESS:, ↵Craig Jennings2026-05-292-0/+20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | clear processed inbox The process-inbox workflow walked the four inbox arrivals (one from home, one from jr-estate, two from health): - 2026-05-28-0858 home spec-naming-convention: implemented in bb28cfa. Deleted. - 2026-05-29-0832 jr-estate startup-rsync-carried-dirty: filed as [#B] :feature: TODO under Rulesets Open Work. Three options captured (skip-when-dirty, clean-files-only, clean-ref-based) with my recommendation noted. Deleted. - 2026-05-29-1111 health org-drill-anki initial: superseded by 1114. Deleted without filing. - 2026-05-29-1114 health org-drill-anki updated: implemented in 506ab6e. Deleted. The startup-rsync-dirty TODO also captures an anomaly the jr-estate handoff flagged: even with --delete, two files that DO exist in rulesets canonical (cross-project-broadcast.org, page-signal.org) did NOT propagate. This is likely a timing issue (those files were added after the jr-estate Phase A rsync ran). The anomaly is worth confirming when the TODO is picked up. notes.org Workflow State stamped :LAST_INBOX_PROCESS: 2026-05-29. Inbox now empty.
* feat(scripts): add drill-to-anki.py template script (org-drill to Anki .apkg)Craig Jennings2026-05-292-0/+428
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Generalizes the health-drill-to-anki.py converter into a template script under claude-templates/.ai/scripts/. Every project's startup rsync now picks it up at .ai/scripts/drill-to-anki.py. The converter walks an org-drill file. Top-level * Section headings become Anki tags. Each ** Card name :drill: entry becomes a card with the heading as Front and the body as Back (newlines converted to <br>). Parameterized from the original health-specific version: - Input file is a positional argument (was hardcoded health-drill.org). - Deck name defaults to the org file's #+TITLE if present, else the input basename in Title Case (was hardcoded "Health Drill"). - Output path defaults to ~/sync/org/drill/<input-basename>.apkg (was hardcoded health-drill.apkg in the same dir). - Deck and model IDs are derived from the deck name via SHA-256, so re-running against the same source produces stable IDs. Anki imports update existing cards in place rather than duplicating. Dependencies via PEP 723 inline script metadata. The shebang is #!/usr/bin/env -S uv run --script. First run resolves and caches genanki>=0.13. Subsequent runs are instant. There is no venv to maintain and no PEP 668 friction. Smoke tested against ~/projects/health/health-drill.org. The script wrote 43 cards into the Health Drill deck, matching the original script's output. Inbox source: 2026-05-29-1114-from-health-todo-b-org-drill-anki-export-updated.org. Craig confirmed all projects will likely have org-drill files, justifying template-tier promotion.
* feat(workflows): add -spec.org precondition to spec-review and spec-responseCraig Jennings2026-05-294-0/+64
| | | | | | | | | | | | | | | | | | | | | | | | Both workflows now check that the file under review (or the spec being responded to) ends with -spec.org before proceeding. If it does not, the workflow stops and surfaces the mismatch with the rename suggestion. The suffix is the identifier per Craig's spec-naming convention: every design, decision, or planning document under a project's docs/ directory ends with -spec.org. The .org extension alone is not enough because docs/ holds non-spec org files too (tutorials, frozen inventories, reference material). spec-review.org gained a top-level Precondition section between When to Use and Approach. spec-response.org gained the same Precondition section in the parallel position, with a note that the review-file convention <spec-basename>-review.org means a misnamed spec produces a mis-pointed review file too. Inbox source: 2026-05-28-0858-from-home-spec-naming-convention-apply-to-spec.org. Home renamed two docs to the new convention and asked rulesets to update the template workflows so the next startup rsync in every project picks up the guard.
* docs(voice): Phase 2 corpus findings: email + PR registers added to ↵Craig Jennings2026-05-291-9/+45
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | voice-profile.org Phase 2 of the writing voice corpus adds four sub-corpora to the profile: personal email (1139 messages, 283k words), work email (22 messages, small sample), PR descriptions (9 PRs), and PR review comments (3 comments, very small sample). Mu queries on local maildirs and gh API calls on the cjennings github identity. Signatures, quoted replies, and forwarded blocks stripped before analysis. No corpus files written to disk. The headline finding is the register split. Phase 1's commit-prose signal does not generalize cleanly. Em-dashes and semicolons are concentrated in commit prose (3.49 and 3.16 per 1000). Conversational and PR prose run an order of magnitude lower (em-dash 0.28 in personal email, 0.00 in PR review comments). Semicolon shows the same shape (0.64 in personal email, 0.00 in PR comments). The personal-mode rules on those still earn their place, but the basis shifts. The rules mostly enforce what is already true for non-commit registers. Contractions invert the pattern: commits 3.57 per 1000, personal email 38.52, PR review comments 50.78. The Phase 1 curiosity (I'm and I'll surprisingly rare relative to standalone I in commits) is resolved as a register effect. Personal email shows I'm at 6.04 per 1000 vs standalone I at 36.91, near natural English. Craig's voice is heavily-contracted in conversational prose and uniquely suppresses contractions in commit prose. The contraction rule is strongly confirmed in the registers where contractions are most expected. Updates land in: - Top-level Corpus section: a new Phase 2 subsection with the four sub-corpora and a cross-register findings table. - Curiosities (resolved by Phase 2) section: I'm/I'll rarity puzzle answered. - §7 (AI vocabulary) Basis: cross-register watch-word measurements. Comprehensive is concentrated in commits. Leverage shows up modestly in personal email. - §13 (em-dash) Basis: register split documented. - §32 (first-person) Basis: standalone I rates across all five registers. - §33 (semicolon) Basis: register split parallels em-dash. - §34 (contractions) Basis: register inversion documented, Phase 1 curiosity resolved. - §38 (terse cut) Basis: single-sentence-paragraph rate across registers, highest in PR descriptions. AI tells stay near zero across all five corpora. Leverage 18 occurrences in personal email is the only non-zero hit on the watch-list outside commits.
* docs(voice): scrub prose em-dashes from voice-profile.orgCraig Jennings2026-05-291-20/+20
| | | | | | | | | | | | | | | | | | This hygiene sweep covers the profile's prose sections (Problem, Basis, History, Phase 1 findings, Phase 2 list) where em-dashes had carried over from the original SKILL.md text. 21 prose em-dashes were replaced with context-appropriate punctuation (periods, colons, parentheses, or rewords). Eight em-dashes are preserved as legitimate exceptions: the literal symbol reference in §13 Rule, §13 Before example (shows source text with em-dashes), §36 Before example (felt-experience tic), §38 heading "Terse Cut — Rhetorical Padding" (paired with SKILL.md heading verbatim), §39 Before example (WARN output format), §40 example dialogue (shows what a kind correction reads like). The profile now follows its own rule for prose voice. The known follow-up flagged in 10d0bc1's commit message is closed.
* docs(voice): complete the SKILL.md / voice-profile.org structural splitCraig Jennings2026-05-292-360/+1297
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Backfills the remaining 40 patterns into the paired source-of-truth shape Pattern 13 demonstrated in 296b2e6. Each pattern in SKILL.md now collapses to a mode-tagged header plus a one-line Rule plus a profile pointer. The full Problem, Basis, Before/After, Detection, and History land in the corresponding voice-profile.org §N section. Sizes: - voice/SKILL.md: 678 to 437 lines. - voice/references/voice-profile.org: 134 to 1311 lines. - Net: 1297 insertions, 360 deletions across both files. Mode-tag conventions: general-only patterns (#1-12, 14-16, 18-31) tagged [general]. Prose+personal patterns (#33-38, 41) tagged [prose · personal]. Personal-only patterns (#32, 39, 40) tagged [personal]. Pattern 13 keeps its dual-mode tag (general overuse-reduction vs prose/personal zero-tolerance). Corpus-confirmed Basis entries cite the Phase 1 measurements for patterns 7, 17, 22, 32, 33, 34, 38. All other patterns are tagged observation-derived with the appropriate source (Wikipedia Signs of AI Writing, Strunk and White, Orwell, Plain English, Garner, or Craig's commits.md / interaction.md rules). Seven em-dashes were caught in the new SKILL.md Rule lines during a post-migration spot-check and rewritten as periods or colons. The em-dash on Pattern 13's Rule line stays because the rule references the symbol literally. Known follow-up: the profile's prose sections (Problem, Basis, History) still contain em-dashes carried over from the original SKILL.md Problem paragraphs. They don't break the loading model but they do violate the rule the file documents. A separate hygiene pass can scrub them. The em-dash sweep was not bundled here so the migration shape stays the deliverable. Bulk migration was dispatched to a subagent with strict format requirements. Spot-checks of patterns 1, 13, 17, 32, and 41 confirmed the shape lands correctly across all four mode-tag conventions.
* docs(voice): split SKILL.md and voice-profile.org into paired ↵Craig Jennings2026-05-292-12/+60
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | source-of-truth, Pattern 13 worked example This is option C from the structural-split proposal. SKILL.md becomes the thin rule-set. voice-profile.org becomes the canonical home for rationale, basis, examples, and per-pattern history. Pattern 13 is migrated as the worked example to confirm the form. The remaining 40 patterns are scheduled for a backfill pass. SKILL.md gains a Source of Truth section near the top stating the pairing rule. Every change to a pattern must land in both files. A SKILL.md edit without a profile update is incomplete. A profile update without a SKILL.md edit is fine. Pattern 13 in SKILL.md collapses to a single Rule line plus mode tags plus a pointer to voice/references/voice-profile.org §13. The Problem paragraph, the Mode-dependent-strength paragraph, the Note-on-basis paragraph, and the Before/After examples all move to the profile. voice-profile.org gains a How this combines with SKILL.md preamble that names the pairing rule explicitly. Pattern §13 lands with all the migrated content plus a History section recording the prior commits (original SKILL.md entry, the 2026-05-26 prose-mode addition in 4fac2a0, the 2026-05-29 basis note in c3cf9a5, and this migration). If Craig confirms the shape, the next pass backfills patterns 1 through 12 and 14 through 41 in the same form.
* feat(signal): page-signal CLI wrapper + workflows + cross-project broadcast ↵Craig Jennings2026-05-2910-15/+889
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | helper Three coupled additions ship together. claude-templates/bin/page-signal is a bash wrapper around signal-cli send. It defaults to --note-to-self for safety. The wrapper supports --file for attachments, --to <+number> for outbound (explicit per call, no defaults, no batch), --quiet, and --json. Exit codes: 0 sent, 1 signal-cli failure, 2 usage error, 3 signal-cli not installed. claude-templates/.ai/workflows/page-signal.org carries the discrimination rules and safety rails. When desktop notify covers it, don't reach for Signal. Long-running task completion is the canonical case. Outbound to other contacts requires explicit Craig instruction per send. A known-limitation note covers the current notification gap. signal-cli registered on Craig's primary number means messages don't fire notifications until the pending Google Voice registration lands. claude-templates/.ai/workflows/cross-project-broadcast.org and its helper cross-project-broadcast.py fan out a single message file to every AI project's inbox in one operation. Discovery is fingerprint-based: any directory under ~/code, ~/projects, ~/.emacs.d with both .ai/protocols.org and a top-level inbox/ is broadcastable. Senders are auto-excluded. Verified discovery against 23 broadcastable targets. Makefile's install target gains a general bin/ loop. The previous version hardcoded bin/ai. The new version iterates over every executable under claude-templates/bin/ and symlinks each into ~/.local/bin/. install-hooks (existing Claude hook installer) is unchanged. install-githooks (sync-check pre-commit hook setup, added earlier today) is unchanged. The bin/ loop now picks up bin/page-signal automatically. INDEX entries for both new workflows landed under Tools and meta. No bats tests on the new scripts. page-signal was smoke-tested with a live send. The send succeeded. The notification gap is covered by the workflow's known-limitation note. cross-project-broadcast.py was smoke-tested via --list against the live project set. Tests can be added when the broadcast pattern proves out across multiple use cases.
* docs(voice): apply Phase 1 deltas to SKILL.md per Craig's confirmationCraig Jennings2026-05-291-3/+9
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Three rationale-honesty edits land. Craig confirmed (vacation chat 2026-05-29) that the em-dash and semicolon zero-tolerance rules are intentional self-discipline, not codified habit. He also confirmed he's trying to use "comprehensive" sparingly despite its high natural rate in his prose. Pattern 13 (em-dash) gained a Note-on-basis paragraph naming the 3.49-per-1000-words corpus rate. The note states the rule is self-discipline, not habit-reflection. The zero-tolerance directive itself is unchanged. Pattern 33 (semicolons) gained the same shape. The note names the 3.16-per-1000-words rate. The directive is unchanged. One em-dash in the existing Problem paragraph that contradicted the rule was replaced with a comma. Pattern 7 (AI vocabulary watch-list) gained "comprehensive" as an entry. A note explains the rationale. The corpus shows 42 uses vs zero-or-one for every other watch-word, but Craig is consciously reducing his use, so flag-and-suggest is the right action. Three of the six proposed deltas from voice-profile.org are now applied. The other three (new positive patterns: single-sentence paragraph cadence, parenthetical density, declarative-default register) await Craig's call.
* docs(voice): land Phase 1 voice profile derived from git-commit corpusCraig Jennings2026-05-291-0/+89
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Phase 1 of the writing voice profile TODO (filed 7a861ed). The work covers corpus assembly, statistics, and a cross-check against the 41 SKILL.md patterns. Email, PR, Slack, and long-form sources deferred to Phase 2. Corpus: 5355 commits, 1895 with non-trivial bodies, 128608 words across 33 repos. Strong findings: - Pattern 17 (no emojis), Pattern 7 (AI vocabulary), Pattern 22 (filler), Pattern 32 (first-person), Pattern 34 (contractions), and Pattern 38 (terse cut) are all confirmed by direct corpus measurement. - Pattern 13 (em-dash zero-tolerance) and Pattern 33 (semicolons to period) contradict the corpus. Craig USES em-dashes at 3.49 per 1000 words and semicolons at 3.16 per 1000 words, rates comparable to AI-generated prose. The rules are self-discipline, not habit-reflection. SKILL.md should say so honestly. - Pattern 7 watch-word "comprehensive" appears 42 times in the corpus while every other watch-word clocks zero or one. "comprehensive" is genuine Craig vocabulary. The rule should pull it from the watch-list or flag only when it co-occurs with other AI tells. New patterns the corpus suggests adding: single-sentence-paragraph cadence (41.1% of paragraphs are exactly one sentence), parenthetical density (23 opening parens per 1000), declarative-default register (0.33 question marks per 1000). Six concrete SKILL.md edits proposed in the doc, none applied. The deltas await Craig's call. Phase 2 sources are documented in the doc body.
* docs(todo): file [#C] :spec: TODO to build Craig's writing voice profile ↵Craig Jennings2026-05-291-0/+52
| | | | | | | | | | | | | | | | | | | | | | | | | | from real corpora Files a TODO under Rulesets Open Work to mine Craig's actual writing (sent email across all three accounts, commit messages, PR bodies, org files he authored, slack threads, long-form artifacts) into a grounded voice profile. The voice/SKILL.md patterns today are observation-derived. Some are spot-on. Others are intuition. A corpus pass would tell us which patterns are genuinely Craig's voice, which were guesses, and which Craig-specific positive traits the current ruleset misses entirely. Output: voice/references/voice-profile.org with findings cited to evidence samples, plus a reconciliation pass against voice/SKILL.md to confirm, strengthen, weaken, add, or remove patterns based on what the corpus shows. Approach phased into corpus assembly, analysis (subagent-friendly), draft profile, reconcile-with-user. The body includes a privacy note: raw corpus stays out of commits if the project's remote ever stops being private. There's no urgency. The work is useful but optional, hence [#C].
* docs(todo): schedule Signal MCP server install for 2026-05-29Craig Jennings2026-05-291-112/+132
| | | | | | | | | | | | | | | | | | | | | | | Files a [#B] :feature: TODO under Rulesets Open Work to install rymurr/signal-mcp as an MCP server. The MCP gives Claude native tool access to send_message_to_user, send_message_to_group, and receive_message instead of shelling out to the page-signal wrapper. The differentiator is receive_message. Claude can listen for replies and act on them, enabling page-as-confirm flows and structured Q&A across devices. Dependency: signal-cli has to be registered with the Google Voice number first. Sending from Craig's primary number to itself doesn't notify (Signal treats it as one account on linked devices). The MCP server takes --user-id at startup, one account per instance, so it has to point at the GV account. Implementation touches mcp/servers.json (stdio entry), mcp/README.org (dependency note), and possibly mcp/secrets.env.gpg if any auth material needs encrypting. Verification: make install-mcp followed by make check-mcp shows signal-mcp ok. Smoke-test via a tool call. The task is scheduled for 2026-05-29.
* feat(mcp): add uninstall + --check + README section for MCP pipelineCraig Jennings2026-05-284-7/+143
| | | | | | | | | | | | | | | | | | | | | | | | | | | Three coupled additions close the MCP pipeline thread. mcp/install.py grew --uninstall and --check modes via argparse. The default install behavior is unchanged. --uninstall iterates over servers.json and runs `claude mcp remove <name> -s user` for each, skipping anything not registered. Idempotent. --check is the dry-run drift report. For each server, classify as ok (in both servers.json and `claude mcp list`), MISSING (configured but not registered), or EXTRA (registered but not in servers.json). Exit non-zero only on MISSING since EXTRA entries are often deliberate (the claude.ai web servers register out-of-band). Smoke test against the live config: 9 ok, 0 missing, 3 EXTRA, exit 0. Two new Makefile targets: - make uninstall-mcp invokes the --uninstall mode. - make check-mcp invokes the --check mode. README.org gained an MCP section under Two install modes covering all three targets, the OAuth-token-on-disk story, and a pointer to mcp/README.org for the full pipeline. Closes TODO #7 (uninstall + --check) and TODO #8 (README MCP section).
* fix(sync-check): ignore generated python and elisp artifactsCraig Jennings2026-05-284-44/+56
| | | | | | | | | | | | | | Pre-commit caught a false-positive on its first real-world run. The .ai/scripts/__pycache__ directory created by make test (pytest writes .pyc and .pytest_cache, emacs writes .elc) was flagged as drift against the canonical's clean tree. Added matching --exclude patterns to the diff -rq check and the --fix rsync calls. Patterns: __pycache__, *.pyc, *.pyo, .pytest_cache, *.elc. The --fix excludes prevent rsync's --delete from wiping the mirror's __pycache__ when the canonical has none. Four new bats tests cover the exclusions. All 12 pass.