<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/claude-templates/bin, 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-25T20:35:14+00:00</updated>
<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>feat(notify): reserve "page me" for desktop, "text me" for Signal</title>
<updated>2026-07-20T21:47:39+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-20T21:47:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=61454894349e75b593ede5901a7b348ad580d3ce'/>
<id>urn:sha1:61454894349e75b593ede5901a7b348ad580d3ce</id>
<content type='text'>
"page me" and "text me" now name distinct channels: page is the desktop notification, text is the Signal phone push, and "text and page me" fires both. Before, "page me" defaulted to desktop and the phone was an unnamed "on my phone" variant, so you couldn't name the phone without naming the machine.

I renamed the Signal tool agent-page to agent-text to match, with a deprecated agent-page shim delegating to it so callers and other machines don't break before their next install. The dispatch and tests are otherwise unchanged. I rewrote protocols.org "Reaching Craig", page-me.org, and work-the-backlog's away-run logic around the three phrasings, and updated the runbook.
</content>
</entry>
<entry>
<title>feat(pager): document the Signal pager and add the receive timer</title>
<updated>2026-07-20T21:03:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-20T21:03:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=302b062680c8fbd9743d4e083154ac5fc314955a'/>
<id>urn:sha1:302b062680c8fbd9743d4e083154ac5fc314955a</id>
<content type='text'>
The pager worked but was undocumented and drifting stale: signal-cli warned the account had gone 47 days without a receive, and no runbook existed to hand a future session or a non-Claude runtime.

I added the runbook under docs/design/ and a roam-sync-shaped receive timer that drains the queue every 15 minutes, keeping the account warm and surfacing reply messages. I also generalized agent-page: it now sends directly from any machine holding the pager account and relays to velox only when it doesn't. ratio is linked as a device of the account, so a page lands even when velox is down.

The receive script no-ops cleanly where the account isn't registered, since the units stow onto every machine via the shared common package.
</content>
</entry>
<entry>
<title>refactor(ai-launcher): extract pure decision cores from the git/tmux paths</title>
<updated>2026-07-20T01:47:44+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-20T01:47:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=2b619f1769cae4a96202ca521a9ed4a9db65bc8f'/>
<id>urn:sha1:2b619f1769cae4a96202ca521a9ed4a9db65bc8f</id>
<content type='text'>
I moved the decision logic out of the launcher's I/O-coupled functions so it's unit-testable on plain inputs, the hardening the audit called for. Three pure cores now carry logic that was buried in git and tmux calls: _git_prep_action classifies a repo's state into none/pull/report, _order_windows sorts the window listing, and _match_window_id resolves a name to a window id. _git_is_dirty replaces the staged/unstaged/untracked check that was copy-pasted across three functions.

prep_git_single and auto_pull_if_clean both route their pull-or-report choice through _git_prep_action, so the "never auto-pull a dirty repo" rule lives in one place with its own test instead of two parallel branches. sort_windows keeps its two-pass move (park high, then reassign) but hands the ordering to _order_windows, and find_window_id is now a thin tmux wrapper over _match_window_id.

Each core gets a Normal/Boundary/Error set: the git-prep truth table including diverged and behind-while-dirty, the window ordering including whole-line-not-substring matching. The 42 launcher tests stay green and a live black-box run still prints the right launch command for claude and codex, so the extraction is behavior-neutral. I also ran shfmt (-i 2 -ci) to match the house style the other bin/ scripts follow.
</content>
</entry>
<entry>
<title>test(ai-launcher): net current behavior and make bin/ai source-testable</title>
<updated>2026-07-20T01:40:51+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-20T01:40:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=113e8d868fdd95b269c0cc2ce4ec265aa76b539c'/>
<id>urn:sha1:113e8d868fdd95b269c0cc2ce4ec265aa76b539c</id>
<content type='text'>
Brings the session launcher under characterization tests ahead of the hardening refactor, so the pure-core extractions and footgun fixes land against a green net. The tmux/git-coupled functions can't be reached by running the launcher black-box, so I wrapped dispatch in main() behind a run-vs-sourced guard: run directly it dispatches as before, sourced it exposes the functions to the tests.

The net pins current behavior (record-not-spec) with a Normal/Boundary/Error set per unit: git_status_indicator across no-upstream/ahead/behind/dirty/clean, candidate discovery and filtering, selection parsing, and functional tests over create_window, find_window_id, and sort_windows ordering against a throwaway tmux server. Every tmux call runs on a private socket so nothing touches the live ai session, and the throwaway repos disable git auto-maintenance to dodge the teardown race that flaked rename-ai.

Also cleared the two pre-existing shellcheck warnings on the touched paths: quoted the "@{u}" revspecs (SC1083) and marked the intentionally-literal display tilde (SC2088). Both are behavior-neutral, and the existing runtime tests confirm the launch path is unchanged.
</content>
</entry>
<entry>
<title>feat(bin): launch install-ai from PATH</title>
<updated>2026-07-19T01:08:56+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-19T01:08:56+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=d2b1bef686fd6c22a6303c13829f88bdd110fa7b'/>
<id>urn:sha1:d2b1bef686fd6c22a6303c13829f88bdd110fa7b</id>
<content type='text'>
Added claude-templates/bin/install-ai, a thin launcher that resolves its own path through the symlink chain and execs scripts/install-ai.sh. make install's bin loop symlinks it into ~/.local/bin/install-ai, the same mechanism that links ai and agent-page, so the fresh-project bootstrapper now runs as install-ai from anywhere. The symlink always points at the canonical, so no separate dotfiles copy is needed. Three launcher bats cover the wrapper, including invocation through a symlink from another directory (the case a naive dirname "$0"/.. would break).
</content>
</entry>
<entry>
<title>feat(launcher): bare ai picks the agent before the projects</title>
<updated>2026-07-14T04:21:32+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-14T04:21:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=3eed2e185773451574c51a4bad591a2753dac662'/>
<id>urn:sha1:3eed2e185773451574c51a4bad591a2753dac662</id>
<content type='text'>
The interactive flow now opens with a runtime picker: claude first (Enter-Enter preserves the old two-keystroke habit), codex labeled as ChatGPT, and one local:&lt;model&gt; line per ollama model, queried live with a short timeout so a dead server just drops those lines. The pick feeds the existing runtime map, then the annotated project multi-select runs as before. --runtime and AI_RUNTIME skip the question, single-directory mode is unchanged, and --print-runtimes exposes the choice list for the two new bats tests.
</content>
</entry>
<entry>
<title>feat(launcher): wire the local runtime — codex --oss over ollama</title>
<updated>2026-07-14T04:05:00+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-14T04:05:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=1b98a9ed2acff1605895700302ff276ba88baa5b'/>
<id>urn:sha1:1b98a9ed2acff1605895700302ff276ba88baa5b</id>
<content type='text'>
ai --runtime local now launches codex against the machine's ollama (explicit --local-provider=ollama, model from AI_LOCAL_MODEL, default gpt-oss:120b). Verified end to end with a codex exec completion through the local model. The dependency check probes AGENT_BIN now that AGENT_CMD carries flags. This makes codex-over-ollama the de-facto answer to the runtime spec's first-supported-local-CLI blocker.
</content>
</entry>
<entry>
<title>feat(pager): add agent-page, the runtime-neutral phone pager</title>
<updated>2026-07-13T23:30:48+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-13T23:30:48+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=49be354ff18d6739e478ee4f5ab46de6cb9e4f81'/>
<id>urn:sha1:49be354ff18d6739e478ee4f5ab46de6cb9e4f81</id>
<content type='text'>
The Signal pager reconcile found one pager identity, registered in velox's signal-cli, reachable from other machines only by hand-rolled ssh. agent-page wraps that: direct signal-cli on velox, ssh relay over the tailnet from everywhere else, a desktop-fallback hint when the relay fails, and the UUID target baked in so nobody pages the unregistered phone number again. Four bats tests stub ssh/uname/signal-cli to verify command construction; the real path was verified with a live phone push today. protocols.org's Paging Craig section now teaches both channels (notify for the desk, agent-page for the phone) and demotes signal-mcp to a velox-local nicety; page-me.org, work-the-backlog, and the INDEX carry the same two-channel story. Every project inherits on its next startup sync and make install.
</content>
</entry>
<entry>
<title>feat(launcher): add ai --runtime for choosing the agent CLI</title>
<updated>2026-07-13T21:40:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-13T21:40:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=04c3b29a968d62eac1a4123cf03606fc667c2d14'/>
<id>urn:sha1:04c3b29a968d62eac1a4123cf03606fc667c2d14</id>
<content type='text'>
The ai launcher can now start codex sessions: --runtime claude|codex (or AI_RUNTIME), a runtime-to-CLI map with local reserved behind a clear error until the local-model evaluation picks a CLI, and a runtime-aware dependency check. A new --print-launch mode prints the pane launch command without touching tmux or fzf, and is the seam the six new bats tests drive. Both current CLIs take the opening line as a positional prompt, so only the command name varies.
</content>
</entry>
</feed>
