<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/claude-rules, 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-07-29T02:28:29+00:00</updated>
<entry>
<title>feat(publish): isolate the pre-commit review and loop it to approval</title>
<updated>2026-07-29T02:28:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-29T02:28:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=80624606b8a60d97d8c7600882ddc35f5225469f'/>
<id>urn:sha1:80624606b8a60d97d8c7600882ddc35f5225469f</id>
<content type='text'>
The review already ran before every commit. It ran in the context that wrote the diff, which is the part that didn't work. A self-review checks the change against the author's own model of it and can't check the model, so the errors that survive are the ones that were never in the diff: a scope inherited from whoever reported the problem, a blast radius estimated instead of measured.

So the reviewer is now a subagent, on every commit including a one-line one. It gets the diff, a one-line claim of what the change does, and the ticket where one exists. It doesn't get the conversation or my reasoning, because those are the model it exists not to hold. The ticket stays in for the opposite reason: it was written before the work and is the only thing that can contradict my claim about my own diff.

Findings loop back to the same reviewer until it approves, rather than to a fresh one that can't tell an addressed finding from one that never existed. Three rounds, or a finding that recurs after being reported fixed, and it stops for a human instead. The unattended callers park the task rather than wait for an answer nobody is awake to give.

I gave the stance a substantiation floor. An agent told to attack will manufacture findings to satisfy the instruction, and a manufactured finding costs a round and teaches the author to discount the next review.

subagents.md said three separate times not to dispatch work this small, so it now carries an Isolation Override: the size heuristics assume the main thread could do the job equally well, and they lapse when its own context is what makes its answer untrustworthy. Field 2 of the Prompt Contract inverts there. Paste your context in and you hand over the very model you spawned the agent to escape.

I wrote it by running it on itself: three rounds and thirteen findings. Two were things I'd have shipped. Withholding the ticket made my own claim self-certifying, and the override reaffirmed the contract field that would have destroyed the isolation.
</content>
</entry>
<entry>
<title>docs: file rightsizing follow-up, fix the first-person referent</title>
<updated>2026-07-28T01:52:02+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-28T01:52:02+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=16756135ff2b70a47aded26e8ccf33dc249a9f17'/>
<id>urn:sha1:16756135ff2b70a47aded26e8ccf33dc249a9f17</id>
<content type='text'>
I filed the remaining rightsizing work as a DOING task, because what's left is decisions I haven't made rather than work to execute: whether verification.md's honesty core survives the over-verification warning, what to do with interaction.md now that it's the largest rule, whether to cut the TDD rationalization table, and which of my approval gates are preference versus guardrail. The three working docs are a commit behind the two splits, so they get reconciled first.

I also fixed the first-person rule I added earlier today. It said to write in the first person without saying whose voice that is, so it read as an agent narrating work on my behalf. The "I" is mine. I'm the author of record on every commit and review in these repos and they go out under my name, so the voice is me writing about my own work, and an agent never appears in the prose as a separate party. That lives in commits.md, which is symlinked machine-wide, so it covers every project from now on. Pattern #32 in the voice skill carried the same ambiguity and now matches.

I filed work's recurring-loop proposal with three findings, and work's reply corrected the weakest of them. I'd offered either a rare heartbeat or failure-breaks-silence, and both are needed: a failed subagent covers a scan-level failure, but only a heartbeat catches the scheduler dying, since then nothing spawns and there's no failure to report. CronCreate expires recurring jobs after seven days, so a fully silent loop is guaranteed to die quietly.

The session archive covers the day. Always-loaded rules went from about 57,800 tokens to 28,949, path-scoping and the de-duplication were both confirmed against a live session, and two bugs in my own tooling turned up, one of which had killed a work session mid-flight.
</content>
</entry>
<entry>
<title>refactor(rules): split testing.md, fix the approval gate, require first person</title>
<updated>2026-07-27T19:13:32+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-27T19:13:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=2f45b6e0010ce375e2c52c91a6537c6d6e8bc0a5'/>
<id>urn:sha1:2f45b6e0010ce375e2c52c91a6537c6d6e8bc0a5</id>
<content type='text'>
Three changes to the same layer.

I split testing.md the way I split commits.md, by what has to be resident rather than by size. What stays is the standing directive: TDD is the default, write the failing test first, and every unit needs Normal, Boundary, and Error cases. That has to fire before any code is written, which is exactly when no skill has been summoned, so it can't ride a trigger. Everything else moved to the testing-standards skill: characterization recipes, the per-category detail, property-based and mutation testing, the pyramid, integration rules, naming, the test-quality and mocking rules, coverage targets, the spike exception, and the anti-patterns. 2,824 words down to 347.

I fixed the approval gate in the publish flow. It decided whether to ask for approval by checking whether .ai/ is tracked, using that as a proxy for "team repo." The proxy was wrong in the direction that matters: rulesets, home, and work all track .ai/ while all three are private single-user repos, so the rule skipped the gate on the three projects I use most. It now checks whether any remote is on a host other than cjennings.net, which is the thing that actually decides whether someone else reads the log. Every current project resolves to gate-applies, which matches how the flow has actually been run.

I also added a first-person directive to the always-loaded core. One already existed for commit bodies and PR prose, but it moved into the publish skill with everything else, and it never covered code comments at all. Now everything I author in or about the repo is first person, with one carve-out: a comment describing what the code does stays third person, because there the code is the actor and not me.

Separately I split the publish skill internally. PR descriptions and the three review shapes moved to references/pull-requests.md, since a plain commit never needs them.

Always-loaded rules are now about 28,900 tokens, down from 57,800 this morning.

One risk on the record. testing.md's margin is thinner than commits.md's was. If testing-standards fails to trigger while I'm writing tests I lose the mocking-boundary rules, which is a quality regression rather than a permanent one, but it is a real bet where commits.md's was not. I also moved the TDD rationalization table rather than cutting it. The posts argue that kind of over-argument is counterproductive now, but removing your defense against me skipping TDD is your call, not mine.
</content>
</entry>
<entry>
<title>refactor(rules): split commits.md into invariants plus a publish skill</title>
<updated>2026-07-27T18:58:35+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-27T18:58:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=931f364864441676eb250cecd074c4876012a1cd'/>
<id>urn:sha1:931f364864441676eb250cecd074c4876012a1cd</id>
<content type='text'>
commits.md was 12,800 tokens, larger than the next four rules combined, and it loaded in every session in every project whether or not anything was being published. Most of it is procedure that only applies when a commit or PR is in play.

I split it by blast radius rather than by size. What stays always-loaded is what must hold whether or not I'm publishing, and where a violation is permanent and reaches other people: author identity, the no-AI-attribution ban, the generated-document byline rule, and the public-artifact content-scope rules. That core is 1,027 words, about 2,342 tokens.

What moved into the publish skill is everything about how a message gets written, reviewed, approved, and published. The pre-flight reconcile, the code-review gate, the draft and voice and approval gate, conventional-commit format, Voice and Focus, PR description structure, the three review shapes, merge strategy, and the pre-commit checklist.

The failure modes are why the line falls there. If the skill doesn't load I don't know the publish flow and have to be told, which is visible and recoverable. I don't silently commit with AI attribution, because that guard never moved. Only the recoverable half is on the skill-triggering bet.

I verified the split by using it. The skill registered mid-session and I invoked it to publish this commit, and it loaded with the flow intact.

Content is conserved: 5,561 words in, 5,898 across both files, the difference being the frontmatter and the pointer I added to the core. I repointed five cross-references in voice, review-code, inbox.org, and no-approvals.org that named sections which moved.

One thing I left alone. The skill is a single 4,871-word file, and both posts argue a long skill should split internally rather than sit as one blob. It loads on demand now, which is the win worth taking today. Splitting it further is its own change.
</content>
</entry>
<entry>
<title>fix(rules): honor declared file-type scopes with paths frontmatter</title>
<updated>2026-07-27T18:02:19+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-27T18:02:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=0adcb1afcf64a4638f110a3ab0c616c48d86ba4f'/>
<id>urn:sha1:0adcb1afcf64a4638f110a3ab0c616c48d86ba4f</id>
<content type='text'>
Three rules already declared a narrow scope. todo-format and org-tables name org files, emacs names elisp. But they declared it in an "Applies to:" line, which is prose. Claude Code scopes a rule by a paths field in YAML frontmatter. None of them had one, so all three loaded into every session in every project regardless of what their own first line said. That's 5,896 words of a 25,386-word rules surface, resident whether or not the session ever opened an org or elisp file.

They now carry the frontmatter. The prose line stays, because it's what a human reads. The frontmatter is what the loader reads. Keeping both means they can disagree, so lint.sh now warns when a rule names a concrete extension in prose without matching frontmatter. That check flags exactly these three and nothing else.

The heading check needed teaching too. It read line 1 and expected a Markdown heading, which frontmatter displaces. It now skips a leading frontmatter block and the blank line after it. A file with no heading and no frontmatter still warns.

I haven't confirmed that paths frontmatter applies to user-level rules rather than project-level ones only. The docs describe it as a property of rules files without drawing that distinction. If it turns out to be project-only the frontmatter is inert and the rules load as before, so the downside is nothing. I'll check with /context next session.
</content>
</entry>
<entry>
<title>feat: add peer-reasoning contract and fix two silent probe defects</title>
<updated>2026-07-27T15:48:12+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-27T15:48:12+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=6c1ea8bbc3775fe7c481f41b4f30e0c0110a9339'/>
<id>urn:sha1:6c1ea8bbc3775fe7c481f41b4f30e0c0110a9339</id>
<content type='text'>
I added a Collaborative Peer Reasoning section to the interaction rules. It governs how an interpretation forms before any rule about presenting choices: infer first and clarify only at material forks, test a conclusion against its strongest alternative, let a correction update the downstream model instead of just the wording. The file's framing line widened to match.

Two probes were failing silently. The startup KB nudge looked up the best-practices node by grepping file content for its slug. A roam node's slug lives in its filename, so the lookup always returned empty. The nudge pointed at nothing in every project and every session, for as long as it shipped. It matches the filename now, through find rather than a glob so zsh doesn't abort on no match.

The browser rule told agents to open URLs with a form ending in &amp;&gt;/dev/null &amp;. That discards the "Opening in existing browser session." line confirming the tab opened. Warm and cold start now split: foreground and read the confirmation when Chrome is already up, detach only when it isn't. The confirmation is on stdout, verified rather than assumed.

I filed two tasks from handoffs. The sentry triage split needs a work-vs-personal classification mechanism before its wording can move, because the current rule excludes by category and category can't express that split. The publish-lock design is approved but carries three open gaps. The load-bearing one is a lock held across an unbounded human approval pause.

I also swept the old processed handoffs out of inbox/. History keeps them.
</content>
</entry>
<entry>
<title>feat: enforce clean wraps and inbox-safe sync</title>
<updated>2026-07-25T20:35:14+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-25T20:35:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=f2609d9f9ad33486bef43211d753ba53e1e24181'/>
<id>urn:sha1:f2609d9f9ad33486bef43211d753ba53e1e24181</id>
<content type='text'>
Centralize repository-state checks, bind teardown to a certified clean HEAD, and allow inbox-only refreshes. Guard installed symlinks from cross-project writes and add regression coverage.
</content>
</entry>
<entry>
<title>docs: define speedrunnable backlog tasks</title>
<updated>2026-07-25T20:34:56+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-25T20:34:56+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=3203bd803b6a05d10781634e7e18742879c1046a'/>
<id>urn:sha1:3203bd803b6a05d10781634e7e18742879c1046a</id>
<content type='text'>
Clarify that speedrunnable means the solo autonomy tag; quick remains only an effort hint.
</content>
</entry>
<entry>
<title>feat(languages): ship the missing python and typescript hooks</title>
<updated>2026-07-24T01:48:44+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-24T01:48:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=c238afbd4150ef53737f16e7dd84a565d2838ecc'/>
<id>urn:sha1:c238afbd4150ef53737f16e7dd84a565d2838ecc</id>
<content type='text'>
The python and typescript bundles carried rules and a coverage script but no pre-commit hook, so any project installing one got no credential scan on commit. Both now ship all four components the README documents: the shared secret scan, a validate-on-edit hook, settings wiring, and a seed CLAUDE.md. Verified against a real repo: a commit carrying an AWS key is refused.

install-lang now warns when a bundle is missing a documented component. That's the half that keeps this from recurring. Whoever adds the sixth bundle will forget something too, and today the installer prints success either way.

Two things fell out of the build. node --check is unusable on TypeScript: it ignores --experimental-strip-types, so it rejects valid TS and accepts broken TS. The hook uses tsc filtered to syntactic diagnostics instead. And completing the bundles means every pair now collides on settings.json and pre-commit, so no two compose without FORCE=1. The earlier "bundles already compose" reading rested on these two being incomplete. Filed for a real decision; clock-panel is the project that wants it.

Also stamps :LAST_REVIEWED: at task creation, with a lint checker to catch misses. Writing a task is reviewing it, so leaving the stamp off pushed every fresh task to the top of the next review batch to be re-derived by someone with less context than its author had. Tonight's sweep sent the staleness count from 13 to 22 while the list got more accurate.
</content>
</entry>
<entry>
<title>feat(workflows): clear temp/ at wrap, plus three rule and template fixes</title>
<updated>2026-07-23T13:41:44+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-23T13:41:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=10ea44b6de3be1872f7f0bd4501ccf3878105bc4'/>
<id>urn:sha1:10ea44b6de3be1872f7f0bd4501ccf3878105bc4</id>
<content type='text'>
Four proposals from other projects, reviewed and applied together.

wrap-it-up gains a Clear temp/ step in Step 3. temp/ is gitignored everywhere, so nothing there survives deletion. The step confirms before removing anything that reads as in-progress rather than throwaway, and skips where temp/ isn't gitignored. This closes the last open clause of the working/temp work from 2026-07-20.

interaction.md drops the fenced-code-block carve-out. Chat output carries no markup at all now, fences included. Fences don't invert the way inline spans do, but the carve-out kept pulling them back in.

sentry.org folds in four notes from its first two live runs elsewhere. Two are traps for anyone reviewing a night by git log: archive-done touches .gitignore on its first run, and a mirror-only project's quiet fires leave no commits at all. The others make property sweeps sanctioned quiet-fire work and split the task audit into a mechanical hourly subset plus a nightly judgment half.

The notes.org template now lints clean, down from four flags in every project on every sweep. Two of those were mechanical, so lint-org --fix anywhere would have rewritten the template and drifted it from canonical.
</content>
</entry>
</feed>
