<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/claude-templates/.ai/workflows/inbox.org, 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-27T18:58:35+00:00</updated>
<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>feat: extend the silent-until-signal heartbeat to the two MCP monitor loops</title>
<updated>2026-07-20T18:39:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-20T18:39:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=0e9958abfbe97540b348b2118a863f7995e73b9e'/>
<id>urn:sha1:0e9958abfbe97540b348b2118a863f7995e73b9e</id>
<content type='text'>
Auto triage-intake and auto inbox-zero now collapse an empty check to a single "&lt;workflow&gt; at HH:MM: nothing" line, matching the sentry quiet-fire heartbeat from Phase 1. Detection stays in-session, so the MCP-auth sources are scanned in full and only the output collapses. Inbox already had an acknowledge-only-on-empty rule, now unified to the standard format; triage's three-section sweep output gains the empty-sweep collapse.

Completes the silent-until-signal spec (Phases 2-5): the spec is the shared-policy home the three workflows reference, and a manual-testing entry covers the live-loop verification. Spec flipped to IMPLEMENTED.
</content>
</entry>
<entry>
<title>fix(roam): scope the work-denylist to durable KB-node writes, not roam tidying</title>
<updated>2026-07-19T20:35:13+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-19T20:35:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=beb7f0bb76eaf0da3afd07fb8617361361556882'/>
<id>urn:sha1:beb7f0bb76eaf0da3afd07fb8617361361556882</id>
<content type='text'>
The sentry live trial surfaced this. A sentry inbox-zero pass running from the work project parked the whole 19-item roam inbox as a cross-project boundary crossing and refused to tidy it unattended, reading knowledge-base.md's work-denylist as "don't touch roam from work."

That over-reads the rule. The denylist is a confidentiality guard on one thing: promoting a durable fact into a new agents/ node, so work-confidential material stays out of the personal cross-machine store. Roam is a shared resource, not another project's product scope. Reading it and tidying the shared roam inbox are housekeeping any project may do, work included.

knowledge-base.md gains a "Scope of the denylist" paragraph stating that and naming the mis-park as the error it closes. sentry.org's inbox-zero pass and inbox.org's roam mode each get a one-line note pointing at the rule, so a future agent doesn't re-derive the refusal. Only durable-node promotion stays work-denylisted.
</content>
</entry>
<entry>
<title>feat(sentry): wire the roam writers and wrap-up guard for sentry</title>
<updated>2026-07-19T10:01:51+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-19T10:01:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=c6383e97f7dc1113959a000f6273d30c2f20415e'/>
<id>urn:sha1:c6383e97f7dc1113959a000f6273d30c2f20415e</id>
<content type='text'>
Phase 3 of the sentry supervisor: reconcile the existing roam writers and wrap-up so sentry's locks actually guard something, and its shutdown has one enforced entry point.

The roam-write lock only helps if every roam writer takes it, so both writers now do. knowledge-base.md's write recipe and inbox.org core §5 acquire the roam-write lock around their edit and trigger roam-sync instead of committing themselves. That closes a gap the one-git-owner rule already implied but the KB recipe still violated: the recipe told agents to run git add -A &amp;&amp; commit &amp;&amp; push against a tree that's chronically dirty from live captures, which could sweep an in-flight capture into a stray commit. roam-sync stays the roam repo's only committer. Agents edit-plus-trigger under the lock, and roam-sync.sh's header now states that contract instead of the old "agents commit inline" note.

Both writers degrade as the spec settled: an absent agent-lock proceeds unlocked (today's behavior), and only a present helper reporting the lock busy after its bounded wait defers or surfaces.

wrap-it-up.org gains a Step 0 that refuses while sentry is live. It checks the single-runner lock and points at "stop sentry" rather than archiving the anchor and tearing down the buffer under a still-firing loop. Both files derive the lock name the same way (sentry-&lt;repo-basename&gt;), so the guard and the engine agree. triage-intake.org notes that it also runs as sentry's triage pass under the no-approvals contract, with its trigger phrases unchanged.
</content>
</entry>
<entry>
<title>chore: drop AI co-author from generated-document headers</title>
<updated>2026-07-09T19:02:39+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-09T19:02:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=6def7c4d63f499aba10fe93c7bb2c7e206a7d7f5'/>
<id>urn:sha1:6def7c4d63f499aba10fe93c7bb2c7e206a7d7f5</id>
<content type='text'>
Every org document an agent writes carried `#+AUTHOR: Craig Jennings &amp; Claude`. No template stamps that line. Agents copy it from a neighboring file, so one stray header propagates through everything generated afterward.

My own repos tolerate the co-author line. Employers whose policy is that work product carries employee names alone do not. An `#+AUTHOR:` line survives conversion into docx, a wiki page, or a PDF that reaches a customer.

I rewrote the header to `Craig Jennings` across the workflows, templates, specs, and design docs. The rule now lives in commits.md, so the next generated document starts correct rather than inheriting the mistake.

Archived session logs keep their original headers as a record of what happened. The two Codex-authored design docs keep their byline, because Codex wrote them and relabeling would be a false attribution rather than the removal of one.
</content>
</entry>
<entry>
<title>feat(sync): never let untracked or gitignored files block template updates</title>
<updated>2026-07-02T09:22:09+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-02T09:22:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=ed75d3c17e7605d2669456b53a03def531a607b7'/>
<id>urn:sha1:ed75d3c17e7605d2669456b53a03def531a607b7</id>
<content type='text'>
Projects were falling behind on templates because somebody sent them a task: an untracked inbox drop read as a dirty tree to the stricter gates. The policy is now stated where the gates live: dirty means tracked modifications only, and untracked or gitignored files never block a template pull, a fast-forward, or a monitoring gate.

The audit found one offender. The inbox monitor's precondition used bare porcelain, counting the very drops it exists to process. It now checks tracked changes only, which is safe because the per-item commit already stages explicitly. The rsync WIP-guard keeps counting untracked files inside the synced source paths on purpose, since a half-written template is exactly the WIP it holds back.
</content>
</entry>
<entry>
<title>feat(backlog): wire the two callers into work-the-backlog</title>
<updated>2026-07-02T05:16:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-02T05:16:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=263138a696a62f8b4b81867f1fdb49f84dc79f02'/>
<id>urn:sha1:263138a696a62f8b4b81867f1fdb49f84dc79f02</id>
<content type='text'>
inbox.org's auto mode regains its "run this batch next?" ask, now chaining into work-the-backlog as an explicit second step after routing: the eligibility query over the queued batch, file-only, paging off, cap 1. Startup and wrap-up still never execute.

The no-approvals speedrun lands as the named preset: an explicit ordered list run under autonomous-commit + always-push + paging-on, every approval front-loaded into the seven-step pre-flight. Any phrase containing "speedrun" routes to the preset, with disambiguation notes in no-approvals.org and the index. The finer Q&amp;A mechanics land with Phase 4.

I scoped the chain's task set to the queued batch rather than all of todo.org. The ask is "run this batch next?", and a batch-yes running an unrelated higher-priority task would be surprising.
</content>
</entry>
<entry>
<title>feat(backlog): extract the execution loop into work-the-backlog.org</title>
<updated>2026-07-02T05:11:06+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-02T05:11:06+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=d379a23953166a2c95d20da5098426cbf49a510f'/>
<id>urn:sha1:d379a23953166a2c95d20da5098426cbf49a510f</id>
<content type='text'>
work-the-backlog.org now owns the autonomous execution loop: the mechanical eligibility gate, the four-item defer checklist, the per-task quality bar, and the run-cap kill switch, fed a task set, session mode, and cap by its callers. I stubbed the pre-flight Q&amp;A, waiver read, end-of-set page, and metrics record with pointers to their phases.

inbox.org's auto mode drops its execute step. Per-cycle item 3 routes and queues only, so the loop has one home. This is Phase 1 of the autonomous-batch execution spec.
</content>
</entry>
<entry>
<title>feat(routing): wire the wrap-up cross-project router end to end</title>
<updated>2026-07-02T04:43:15+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-02T04:43:15+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=7c120073a7de96e67a4f51e539c45d2d22d74f81'/>
<id>urn:sha1:7c120073a7de96e67a4f51e539c45d2d22d74f81</id>
<content type='text'>
This closes the build half of the wrap-up routing spec: Phases 2 and 4 here, with the engine and discovery already shipped.

inbox.org's "File as TODO" disposition now runs route_recommend on each keeper and stamps :ROUTE_CANDIDATE: &lt;destination&gt; on strong and weak matches, so the wrap-up router has a candidate set without ever scanning the standing backlog. wrap-it-up.org Step 3 gains the optional router after the inbox sanity check, with the gate-vs-optional split named in the prose: surface the batch with destinations and confidence labels, then go or skip. An empty set stays silent.

The go path is mechanical rather than prose-driven: the new route-batch helper lists candidates read-only, and on go extracts each subtree (children ride along, markers stripped, headings promoted), delivers it via inbox-send for provenance, and removes the local copy only after a successful send, rewriting todo.org per send so a crash never strands an already-sent task locally. Overlapping candidate spans (a tagged child inside a tagged parent) are a loud conflict, left in place with a non-zero exit, because routing either span would silently take the other along.

A 13-test bats suite covers list/backlog exclusion, empty-set silence, delivery with provenance and children, promotion, drawer pruning, the no-todo.org destination, failed-send recovery with the marker intact, the nested-candidate conflict, and duplicate-marker dedupe. cross-project.md notes the router as a sanctioned cross-project write path.
</content>
</entry>
<entry>
<title>refactor(tasks): use a :blocker: tag, not a :BLOCKS: property</title>
<updated>2026-06-24T11:09:21+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-24T11:09:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=9709638f1d50193bd4636205a142a2277f92e4f4'/>
<id>urn:sha1:9709638f1d50193bd4636205a142a2277f92e4f4</id>
<content type='text'>
:BLOCKS: rulesets: was a malformed org tag, and the property form (:BLOCKED_BY: / :BLOCKS: carrying &lt;project&gt;: &lt;what&gt;) was more structure than the dependency needs. The blocking side now carries a plain :blocker: tag, mirroring :blocked: on the waiting side, with the which-project detail in the task body rather than a property. open-tasks.org reads the body for the blocking/requesting project; the scheme, the todo-format convention, and the inbox blocking-dependency handoff all move to the two-tag form. No property anywhere.
</content>
</entry>
</feed>
