<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets, 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-08-01T03:44:54+00:00</updated>
<entry>
<title>chore: record the sync-narrowing and standup-script work</title>
<updated>2026-08-01T03:44:54+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-08-01T03:44:54+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=8b5d34c3a504e39b7a0e0e678acb803f71aa9317'/>
<id>urn:sha1:8b5d34c3a504e39b7a0e0e678acb803f71aa9317</id>
<content type='text'>
Archives the day's record and folds in this wrap's lint sweep.
</content>
</entry>
<entry>
<title>chore: track the parked sync-model handoff</title>
<updated>2026-08-01T03:42:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-08-01T03:42:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=1c222cb563d729870ef9bd3b24805df9a76dd4b6'/>
<id>urn:sha1:1c222cb563d729870ef9bd3b24805df9a76dd4b6</id>
<content type='text'>
Design input for the last-synced manifest, which is the other half of the propagation fix and still unbuilt. Its point: the locally-edited-and-diverging state isn't rare, because the propagation rule tells projects to patch locally as a stopgap. So an overwrite-and-report has to carry the diff, not just a backup path. A reader needs to tell in one look whether the canonical already contains their patch or whether their change was thrown away and needs re-sending.

working/ is tracked from creation, and this sat untracked while the narrowing shipped around it.
</content>
</entry>
<entry>
<title>chore: file voice pattern #48 and stamp the inbox pass</title>
<updated>2026-07-31T18:46:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-31T18:46:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=cb31fd252e8b028232da2ab538d5b58ccc6e029a'/>
<id>urn:sha1:cb31fd252e8b028232da2ab538d5b58ccc6e029a</id>
<content type='text'>
Work proposed a rule for the corrective form of antithesis, the "X rather than Y" shape, distinct from the additive form #9 already covers. Their evidence held up and I'm a live instance of it, so the task carries my two additions: it belongs in the attestation high-recurrence set immediately, and the frequency cap needs mirroring into the always-loaded chat rules. The voice skill only runs on publish artifacts, and I noticed this in conversation, so the rule as proposed can't reach the surface I complained about. That second half is now approved and folded in.
</content>
</entry>
<entry>
<title>fix(sync): narrow the dirty-rulesets guard to per-file excludes</title>
<updated>2026-07-31T18:36:21+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-31T18:36:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=f69dc22c80156f92c6e3519e87bc014558d77dbf'/>
<id>urn:sha1:f69dc22c80156f92c6e3519e87bc014558d77dbf</id>
<content type='text'>
One uncommitted file under the synced template paths used to skip all three rsyncs for every project. On 2026-07-30 that froze every template everywhere for a full day, and five workflow files went stale in one project alone with nothing reporting it. The blast radius had no relation to what I was editing.

Each dirty path now becomes an --exclude on its own rsync. rsync honors an exclude on both sides, so a withheld file is neither overwritten nor deleted downstream. Every clean file still propagates. A dirty protocols.org skips only its own single-file transfer. A rename withholds both names, since sweeping the old copy would delete a file the project still runs mid-rename.

The run now names what it held back, so a project can see it's one file behind.

The behind-upstream guard stays all-or-nothing. That staleness lives in the destination, so there's no single source file to narrow to.

I verified it against the real checkout mid-change: 48 workflows propagated while the two files dirty at the time were withheld and named. The same run under the old guard would have synced nothing.
</content>
</entry>
<entry>
<title>fix(daily-prep): gate the priorities review on a script per standup</title>
<updated>2026-07-31T18:19:18+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-31T18:19:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=a212eeb7c92f291045344f2769c9a7f85f2a38d5'/>
<id>urn:sha1:a212eeb7c92f291045344f2769c9a7f85f2a38d5</id>
<content type='text'>
A prep wrote "Bring:" topic lists under both standup headers. Every rule requiring a Yesterday/Today/Blockers brief was already on the page. More prose in Phase 6 wouldn't have caught that, because the prose is what got skipped. So the requirement now sits on the Phase 8 gate: the prep isn't complete until every standup carries its own script.

The Standups section now says the lines are the words I speak, in complete first-person sentences. Two standups get two scripts, because the altitude rules admit different material to each. A topics-vs-script contrast sits with the shape rules, since prose decays back into bullets and an example doesn't.

The script is a draft I edit at the gate, never words put in my mouth. A topic list fails safe by forcing me to compose. A wrong script reads fluently enough to be spoken unchanged.

Refs: inbox/2026-07-31-1129-from-work-proposal-for-daily-prep-org-standup.org
</content>
</entry>
<entry>
<title>chore: archive work records and park incoming handoffs</title>
<updated>2026-07-31T11:33:44+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-31T11:33:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=cbcd972fbea65c315713113572f9334efe8d1ca3'/>
<id>urn:sha1:cbcd972fbea65c315713113572f9334efe8d1ca3</id>
<content type='text'>
Records for the helper-launcher and sync-extraction work, plus four sets of
handoffs received from other projects. Each set is parked beside the decision it
still needs: sentry's CronCreate correction and its pass-3 reframe, the
per-project channel declaration model, the template-sync revert finding, and the
nag-event vocabulary.

Nothing here is code. The handoffs are kept rather than deleted because two of
them turn on the difference between two readings of the same rule, and that
difference is the useful part.
</content>
</entry>
<entry>
<title>refactor(startup): extract the template sync into a tested script</title>
<updated>2026-07-31T11:07:34+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-31T11:07:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=f57105773366aebdceeafdb51dc3584c42b0e4e4'/>
<id>urn:sha1:f57105773366aebdceeafdb51dc3584c42b0e4e4</id>
<content type='text'>
The .ai/ sync is how every workflow, protocol and script change reaches every project, and until now it was untested inline bash in startup.org. Yesterday it failed in two ways at once and nothing anywhere reported either.

An uncommitted edit of mine under the synced paths skipped all three rsyncs for a full day, so no project got anything. Five workflow files went stale in .emacs.d alone. One of them was a telegram plugin whose staleness crashed telega-server on every triage run, which is the only reason anyone noticed.

Work found the second failure. The --delete rsync silently reverts a local patch to a rulesets-owned file, so they hit a bug, fixed it, restarted, and came back running the broken version. Their own log still said it was patched.

I want both guards changed. Neither change is safe to make against inline bash that runs in every session, so this commit only moves the logic and pins what it does.

sync-templates is a faithful extraction, rough edges included. I verified it differentially rather than by reading: the old block and the new script over the same fixtures produce identical stdout, exit status and resulting tree.

Two of the fifteen characterization tests pin the failures above. "ONE dirty file blocks ALL THREE rsyncs" is the blast radius in one assertion, and the narrowing lands by turning it red on purpose. "a locally-edited template is silently overwritten, with no record kept" pins work's regression, asserting the output is indistinguishable from an ordinary sync and that no warning exists anywhere.

A third test pins a false-success path I found but deliberately did not fix: the success line prints unconditionally, so a run whose rsyncs all failed still reports a clean sync. That matters next rather than now, because a last-synced manifest written from that branch would stamp success onto a sync that did nothing. The script carries the warning at the line itself.

startup.org keeps a fallback for the case where it holds this file but the script is missing. It announces the skip and names the recovery command, because that state cannot heal itself: the fallback runs instead of the sync, so no later sync can deliver what is missing.
</content>
</entry>
<entry>
<title>feat(ai): add --helper for a second session in a live project</title>
<updated>2026-07-30T12:42:47+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-30T12:42:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=84bd121683add4440a3652b12b70e6e2d45f5384'/>
<id>urn:sha1:84bd121683add4440a3652b12b70e6e2d45f5384</id>
<content type='text'>
A helper is a second agent session in a project that already has a live one. It reads freely, makes only scoped single-heading edits, and never touches git. Until now the only way to get one was to tell a session by hand it was a helper, and nothing checked whether that was true.

--helper runs agent-roster first, so the claim gets verified rather than asserted. It opens its own tmux window and hands the session helper-mode.org instead of the normal startup workflow. Git prep is skipped because pulls belong to the primary. With no other agent live it warns and falls back to a primary launch.

When the roster can't answer, it opens a helper anyway. I picked that direction because a helper that turns out to be alone merely does less, while a primary that turns out not to be alone runs pulls and rsync underneath a live session.

Wrap-up gains a Step 0 branch. A helper archives its own context file and stops, and the primary's next commit carries its edits. An orphaned helper whose primary already exited runs the full wrap, because otherwise its edits strand as a dirty tree nobody owns.

That branch resolves the project root before consulting the roster. agent-roster defaults to $PWD and only matches agents at or inside it, so a call from a subdirectory would miss a primary at the root and report the helper orphaned. That is the one path that commits and pushes.

The agent id is sanitized to the charset session-context-path keeps, because it's interpolated into the command typed into the pane. A helper launching a helper gets a fresh id rather than inheriting its parent's. Two agents resolving to one context file is the collision the contract exists to prevent.

Startup still has no roster check, so a bare claude launched into a busy project runs full primary startup regardless. helper-mode.org and protocols.org now say so instead of listing the routing as though it works.
</content>
</entry>
<entry>
<title>chore: archive completed work and refresh the lint follow-up queue</title>
<updated>2026-07-29T11:38:34+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-29T11:38:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=73e8c01fa49f48766a283e2db933e38951f656ab'/>
<id>urn:sha1:73e8c01fa49f48766a283e2db933e38951f656ab</id>
<content type='text'>
Three finished tasks moved to Resolved: the sentry cycle rename, the
references/ link removal, and the parked telegram down-is-launch fix.
The lint sweep re-ran and its judgment items were refreshed.
</content>
</entry>
<entry>
<title>docs: file the wrap-org-table row-splitting bug</title>
<updated>2026-07-29T11:26:11+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-29T11:26:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=ecd5d7b47d06c724086a1fa799fc984131493892'/>
<id>urn:sha1:ecd5d7b47d06c724086a1fa799fc984131493892</id>
<content type='text'>
wrap-org-table.el turns one logical table row into two, by writing a rule between its continuation lines. The predicate at wot--continuation-group-p requires every line past the first to carry an empty cell. A row that overflows every column has a fully-populated continuation, so the group is rejected and each physical line becomes its own row.

lint doesn't miss this. It causes it. lint-org.el:424 calls the same predicate, so it reports the tool's own correct output as missing rules between rows and names the tool as the fix. Follow that advice and the row splits, and lint then reports the result clean.

The fix is the round trip, not a detector. work implemented the predicate-based detection I recommended and showed it can't discriminate at any threshold: loose it condemns every table, tight it still mixes a wrapped row against two rows legitimately sharing a rule. Reflow twice and diff is true by construction and needs nobody to decide what a group means.

The task separates what I measured from what I inferred. The inference was refuted three times while the reproductions held, so the split is load-bearing rather than tidiness. Its review loop bounded out at three rounds. The open questions are what the reviewer and I could not settle between us.

work reported it. I hit the same failure on 2026-07-27 and left it in a session summary instead of filing it, which is why they met it three weeks later.
</content>
</entry>
</feed>
