<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/review-code, 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>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>docs(review): drop praise from approve summaries, print inline text at the gate</title>
<updated>2026-07-11T06:56:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-11T06:56:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=99bd213002f2bd24192d15ff9ae69a0379b2f5be'/>
<id>urn:sha1:99bd213002f2bd24192d15ff9ae69a0379b2f5be</id>
<content type='text'>
An approve summary now carries no praise, not even a bare positive like "Clean." or "Solid fix." Lead with the substantive pointer (the design note pinned inline) and close with the verdict. This removes the bare-positive carve-out from review-code's Posted Summary Voice and from voice pattern #40, and updates the worked examples.

The review gate must also print the full prose of every inline comment, never a description of it. The exact words post under my name, so I have to see them before I approve. Made explicit in review-code Phase 5 and commits.md Shape 1.

From a DeepSat review session where I approved "One design note inline, not a blocker. Approving."
</content>
</entry>
<entry>
<title>feat(review-code): gate deep-dive checks on a project review profile</title>
<updated>2026-06-09T19:15:52+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-09T19:15:52+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=5bd8247acf4757e0868883ed65d395fec3fb7a0c'/>
<id>urn:sha1:5bd8247acf4757e0868883ed65d395fec3fb7a0c</id>
<content type='text'>
The skill ran one flat criteria set on every change, so a concurrent, DB-backed service got the same review as a shell script, and the checks that matter for the service (concurrency, performance, auth, API compatibility) weren't in the set at all.

Phase 3.5 splits the checks into a universal core that always runs plus nine modules that activate on the project's review profile. A project declares the profile with a "Review profile:" line in its CLAUDE.md. Absent that, the skill auto-detects from framework, ORM, concurrency, and manifest signals and recommends declaring one. The security and dependency modules defer their depth to /security-check.
</content>
</entry>
<entry>
<title>docs(review-code): require plain-text terminal output in Phase 5</title>
<updated>2026-06-02T18:50:39+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-02T18:50:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=9bc7e6730f5ca27bdff30089a34bca1e224da9e4'/>
<id>urn:sha1:9bc7e6730f5ca27bdff30089a34bca1e224da9e4</id>
<content type='text'>
The skill's chat echo (report, criterion table, verdict, draft summaries) was rendering bold and backtick spans as reverse video, which is hard to read in the terminal. Phase 5 now requires plain text for everything echoed to chat, while the artifact posted to GitHub keeps normal markdown. It's the same constraint as interaction.md's no-reverse-video rule, repeated at the print step where the violation actually happens.
</content>
</entry>
<entry>
<title>docs(skills): add voice pattern 40, praise/correction asymmetry</title>
<updated>2026-05-25T20:11:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-25T20:11:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=a5b955f7c74ad5e27bb73c6d2143359cc66b2455'/>
<id>urn:sha1:a5b955f7c74ad5e27bb73c6d2143359cc66b2455</id>
<content type='text'>
Voice gains pattern #40: strip the "why" from praise on an approve, since the author already knows why their change is good and the justification reads as flattery. Always keep the why on a finding or change-request, delivered gently. Behavior only changes when the reason lands.

review-code now runs a praise/correction gate before posting any summary, and its inline-comment guidance is tightened so the why-it-matters survives the brevity cuts. The reviewer states the stakes (a user hits a 500, a screen reader announces nothing), not just the mechanism.
</content>
</entry>
<entry>
<title>docs(skills): keep review-code re-review approvals bare</title>
<updated>2026-05-22T23:17:45+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-22T23:17:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=1e216dd170a46d99ef400ca6bf98f97b23c1db9b'/>
<id>urn:sha1:1e216dd170a46d99ef400ca6bf98f97b23c1db9b</id>
<content type='text'>
Extended the posted-summary voice guidance: a re-review confirming requested changes is just "Approving" plus at most a bare positive. An approve summary must not carry a clause describing what the change does or why it works — that's the banned pattern, since the author already knows the rationale and restating it reads as padding.
</content>
</entry>
<entry>
<title>docs(skills): scope review-code's CI-trust and CLAUDE.md-citation rules</title>
<updated>2026-05-22T19:07:44+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-22T19:07:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=5ff5088b0a3978f35b49e35c71add4764697c257'/>
<id>urn:sha1:5ff5088b0a3978f35b49e35c71add4764697c257</id>
<content type='text'>
Two clarifications to review-code where it appeared to contradict other rules.

The "trust CI, don't run builds" rule read as a blanket license to skip verification. I scoped it to reviewing a diff, not shipping one. A pre-commit or pre-push flow still owes the local verification verification.md requires. Reading a PR doesn't duplicate CI. Producing one doesn't get to skip it.

The CLAUDE.md-adherence audit could put a CLAUDE.md citation into a team-visible PR comment, which commits.md says not to do. I added two modes. A private review cites CLAUDE.md directly. A public review translates the rule into the engineering reason and doesn't name the file, since a teammate can act on the reason but not on a file they can't reach.
</content>
</entry>
<entry>
<title>docs(skills): keep review-code praise honest and unforced</title>
<updated>2026-05-22T18:48:59+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-22T18:48:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=a4389e8259a68d6e237485f6de4f05c7d1c6cc5d'/>
<id>urn:sha1:a4389e8259a68d6e237485f6de4f05c7d1c6cc5d</id>
<content type='text'>
Two related changes to review-code's strengths guidance. The mandatory "three minimum" could force filler on a tiny diff or padded praise on a weak PR, so I relaxed it to up to three specific strengths, with an honest "nothing notable" allowed when the diff doesn't earn them. I also reframed the old "No Strengths section" anti-pattern as "skipping strengths out of laziness": a substantive diff still demands them, a weak one doesn't.

The other change tells reviewers to name the good thing and stop, without explaining why it's good. Explaining praise reads as sycophantic since the author already knows the rationale. Elaboration is for findings, not compliments.
</content>
</entry>
</feed>
