<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/.ai/scripts, branch main</title>
<subtitle>Claude Code skills, rules, and language bundles
</subtitle>
<id>https://git.cjennings.net/rulesets/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/rulesets/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/'/>
<updated>2026-06-12T20:26:22+00:00</updated>
<entry>
<title>fix(todo-cleanup): keep --archive-done silent on a real-mode no-op</title>
<updated>2026-06-12T20:26:22+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-12T20:26:22+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=21ab10ef9e2fff9b4726695036e95e33937e1fa9'/>
<id>urn:sha1:21ab10ef9e2fff9b4726695036e95e33937e1fa9</id>
<content type='text'>
The wrap runs --archive-done twice (wrap-it-up, then open-tasks.org Phase A). The first pass archives and reports. The second finds nothing and used to print "0 subtree(s) moved", which reads as alarming next to the first pass's diff. Now a real-mode run that moves nothing and skips nothing says nothing. Check mode still previews "0 would move", and a missing-section skip still reports, since those are conditions the caller needs.
</content>
</entry>
<entry>
<title>fix(scripts): lint-org pre-registers runtime org link types</title>
<updated>2026-06-12T07:28:43+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-12T07:28:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=1dba35cc182691390d80c789502b174292b14777'/>
<id>urn:sha1:1dba35cc182691390d80c789502b174292b14777</id>
<content type='text'>
mu4e registers its link type in a live Emacs, so batch org-lint parsed [[mu4e:msgid:...]] links as fuzzy heading refs and flagged "Unknown fuzzy location" on links that work interactively. lint-org now registers each type in lo-runtime-link-types as a no-op before linting. org-link-set-parameters merges rather than replaces, so a genuinely loaded mu4e keeps its real parameters.
</content>
</entry>
<entry>
<title>fix(scripts): lint-org resolves wrap-org-table from its own directory</title>
<updated>2026-06-11T19:58:59+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-11T19:58:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=6f575e64b1e9f5d9332db73b1e6427d500f90f48'/>
<id>urn:sha1:6f575e64b1e9f5d9332db73b1e6427d500f90f48</id>
<content type='text'>
Consumers load lint-org with a bare -l and no load-path flag, so the new require of wrap-org-table failed everywhere outside make test's -L. lint-org now adds its own directory to load-path first. lint-org-cli.bats locks the bare-load contract for both scripts.
</content>
</entry>
<entry>
<title>feat(org): table standard as a rule, reflow helper, and lint check</title>
<updated>2026-06-11T19:25:55+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-11T19:25:55+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=8d790f371e54a8cc3e79a5ce72cd4dd5b3fa4513'/>
<id>urn:sha1:8d790f371e54a8cc3e79a5ce72cd4dd5b3fa4513</id>
<content type='text'>
Wide org tables overflow the page in exported PDF/docx, and hand-wrapping a cell into continuation rows is tedious and error-prone. The standard existed only as a work-project convention with nothing enforcing it.

claude-rules/org-tables.md carries the generalized standard: 120-column budget measured at render width (a link counts as its visible label and is never split), over-budget cells wrap onto continuation rows, and a rule sits under the header and every logical row.

wrap-org-table.el reflows a table to that shape mechanically. Columns shrink from natural width toward a floor of their widest atomic token, cells wrap link-safe, and rule-delimited continuation groups merge back into their logical row before re-wrapping, which makes the reflow idempotent. A table whose floors still exceed the budget reflows best-effort and stays flagged for restructuring.

lint-org.el gains an org-table-standard judgment check: width overruns and missing rules surface during the sweep with a pointer to the helper. Conformant wrapped tables don't false-flag, since the check reuses the helper's continuation-group reading. The check is judgment-only by design: reflowing is a visible layout change the sweep shouldn't make silently.
</content>
</entry>
<entry>
<title>fix(scripts): keep screenshot --launch from crashing the compositor</title>
<updated>2026-06-11T10:07:42+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-11T10:07:42+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=7095d622ab6e295143d1306bdb5c8ecd85cf0745'/>
<id>urn:sha1:7095d622ab6e295143d1306bdb5c8ecd85cf0745</id>
<content type='text'>
An XWayland client launched by --launch could send a configure request while the script tore down the headless output. Hyprland's damage path then dereferenced the removed monitor and the compositor aborted (Hyprland 0.55.2, coredump analysis in docs/design/).

The fix has two layers. --launch now forces the Wayland backend (DISPLAY unset, GDK and Qt steered to wayland) so no XWayland surface exists to race. Teardown also polls until the launched clients actually unmap before removing the output.

X11-only apps fail to map under the default, and some emacs builds are X11-only. The new --x11 flag allows XWayland for them, protected by the unmap wait. The no-window error hints at the flag.
</content>
</entry>
<entry>
<title>feat(workflows): generalize broadcast into announcement + situational modes</title>
<updated>2026-06-09T22:16:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-09T22:16:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=c91bd0b1e8183814f248b0751d88a8e422a905e8'/>
<id>urn:sha1:c91bd0b1e8183814f248b0751d88a8e422a905e8</id>
<content type='text'>
cross-project-broadcast handled tooling and rule announcements but had no shape for the situational case: a life or work event I want every project's agent to know, said once so none is missing context when I next talk to them. I renamed it to broadcast (helper and test alongside) and split it into two modes over the same fan-out plumbing. Announcement keeps the rigid capability template. Situational carries a general-not-comprehensive summary plus a fixed receiving-agent contract: record it in notes.org, hold it time-boxed or standing, apply on the project's own judgment, ask follow-ups at startup. The broadcasting agent does no per-project relevance analysis. Each receiving agent decides what the event means for its own work.
</content>
</entry>
<entry>
<title>feat(lint-org): reconcile follow-ups on write instead of appending</title>
<updated>2026-06-03T02:31:37+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-03T02:31:37+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=ac693a6b7fa7abe88f7778f8e793d5ddfd32f24e'/>
<id>urn:sha1:ac693a6b7fa7abe88f7778f8e793d5ddfd32f24e</id>
<content type='text'>
Every run appended a fresh dated "lint-org follow-ups" section with line-number-keyed entries, so the follow-ups file grew an unbounded pile of near-duplicate sections, kept entries whose finding had since resolved, and broke whenever the target file's line numbers shifted. Running an audit against a large todo.org surfaced exactly that drift: dead-link flags pointing at docs that now exist, and three stacked dated runs for one file.

Now lint-org rewrites the current file's section from the current run. Findings that no longer reproduce simply are not re-emitted, re-runs dedupe to one section, and entries key on checker plus message with the line as a trailing annotation, so a finding survives line shifts as the same entry. Other files' sections are left intact, and the strip step tolerates the old dated-header shape so existing follow-ups files migrate on first run. This changes the follow-ups file from an append-only log to the current outstanding findings per file.

task-audit's Phase C link-hygiene step now also reaps a matching dead-link entry when it fixes or verifies the link, scoped strictly to dead-link entries, so the audit and the follow-ups file stop drifting between lint runs.

Five follow-ups tests cover record-by-content, dedupe across runs, drop-on-resolve, and preserve-other-files. Mirrors synced.
</content>
</entry>
<entry>
<title>feat: add rename-ai-artifact tool and rename the drill-deck family to flashcard</title>
<updated>2026-05-31T17:19:34+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T17:19:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=ddf48dc7ac780da1aacdff4e03f1d7da255b8f39'/>
<id>urn:sha1:ddf48dc7ac780da1aacdff4e03f1d7da255b8f39</id>
<content type='text'>
Renaming an .ai artifact by hand is the kind of mechanical job that gets done incompletely: the canonical copy moves but the mirror doesn't, a reference in the INDEX is missed, a trigger phrase points at the old name. I'd also assumed a rename was costly because references scatter, when the index update is trivial and the drift check already guards it. So I built the discipline into a script instead of re-deriving it each time.

scripts/rename-ai-artifact.sh takes old and new basenames, moves the file in both the canonical and mirror trees, and rewrites every reference repo-wide on a token boundary so renaming "foo" can't corrupt "foobar" or "foo-bar". It rewrites the underscore module-name variant too (a hyphenated script imported as foo_bar via importlib), leaves the archived session records under sessions/ alone because they're history, and runs workflow-integrity + sync-check at the end to prove no drift. rename-artifact.org documents it and indexes the triggers.

Then I used the tool to do the rename that prompted it: the org-drill deck workflow and its helpers are now flashcard-named, since "flashcard" is the word you'd actually search for. The renamed set is flashcard-review.org plus flashcard-stats.py, flashcard-sync, flashcard-to-anki.py, and flashcard-diff-ids.py, with their tests, every reference, and the INDEX entry updated. The deck is still an org-drill deck under the hood, so the ":drill:" tag handling and the "drill deck" trigger phrases stay. I added "review/update the flashcards" alongside them.

Tests: 9 bats for the rename tool (including the prefix-collision and history-preservation edges), and the renamed script suites all pass under make test.
</content>
</entry>
<entry>
<title>feat(workflows): add monitor-inbox workflow + inbox-status script</title>
<updated>2026-05-31T05:07:03+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T05:07:03+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=8c0eca8375db2c2d346f5fd08ac752209349f94e'/>
<id>urn:sha1:8c0eca8375db2c2d346f5fd08ac752209349f94e</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat(cmail): add --cc/--bcc and threading headers to cmail-action send</title>
<updated>2026-05-31T03:13:21+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T03:13:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=e446dab251fb0889c516c491e0d73a6ec9f0b873'/>
<id>urn:sha1:e446dab251fb0889c516c491e0d73a6ec9f0b873</id>
<content type='text'>
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.
</content>
</entry>
</feed>
