<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/Makefile, 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(runtime): add the thin-pointer AGENTS.md agent entry file</title>
<updated>2026-07-13T21:06:13+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-13T21:06:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=6cd3aa32b9f3b033c3890f08ab430e757696e5e3'/>
<id>urn:sha1:6cd3aa32b9f3b033c3890f08ab430e757696e5e3</id>
<content type='text'>
claude-templates/AGENTS.md points any harness at protocols.org, the rules locations, and the /name-to-file resolution rule, and tells it to degrade per each rule's documented fallback instead of skipping gates. make install links it to ~/.codex/AGENTS.md (new CODEX_DIR stanza) and install-ai.sh seeds a project-owned copy at bootstrap, never overwriting an existing one. The rulesets root carries a tracked symlink as dogfood. Five new bats tests cover the link idiom and the seeding. This resolves the generic-instruction-file blocker from the runtime spec, and velox picks up the global link on its next session's make install.
</content>
</entry>
<entry>
<title>chore: prune dangling bin symlinks, point paging at signal-mcp</title>
<updated>2026-06-30T16:51:31+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-30T16:51:31+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=a266250c1e24c9b2f6f246206c1b726dbb84c4bf'/>
<id>urn:sha1:a266250c1e24c9b2f6f246206c1b726dbb84c4bf</id>
<content type='text'>
The install bin loop linked every script under claude-templates/bin but never pruned orphans, so a deleted script left a dangling ~/.local/bin symlink. page-signal hit exactly this: the wrapper was removed 2026-06-12 but its symlink stayed, resolving to a dead target on the daily drivers. I added a prune step that drops symlinks pointing into claude-templates/bin whose target is gone, so any removed script self-cleans on the next install.

I also added a protocols section naming the two paging channels: notify --persist at the machine, and the signal-mcp send_message_to_user tool when away from it. The pager account was never deregistered (only the page-signal script went away), so the live path is the MCP tool, and the section retires the script for good.
</content>
</entry>
<entry>
<title>feat(languages): add bash/shell bundle</title>
<updated>2026-06-24T01:13:26+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-24T01:13:26+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=36262858461711bcb104896007a513691113fee8'/>
<id>urn:sha1:36262858461711bcb104896007a513691113fee8</id>
<content type='text'>
Shell-heavy projects had no bundle that fit. archangel and archsetup are bash repos, and installing elisp or python gave them the wrong language rules. I added languages/bash on the go bundle's shape.

The bundle ships bash.md and bash-testing.md rules, a PostToolUse hook that runs shellcheck on edited shell files and blocks on a violation, a shellcheck pre-commit githook, settings.json wiring, gitignore-add.txt, and a "Bash/shell project" CLAUDE.md. The hook covers .sh, .bash, and extensionless files with a shell shebang, since the CLI tools that fill a shell repo carry no extension. shellcheck is the gate. shfmt stays out of the blocking path because shell has no canonical formatting style, and forcing tabs-vs-spaces would impose a contested choice. Both the hook and the githook are shellcheck-clean against their own rule.

I extended the Makefile test target to discover languages/*/tests/*.bats, so the bundle's 8 hook tests run with the rest of the suite. The README bundle table was stale, listing elisp only. I corrected it to the five bundles now shipping.
</content>
</entry>
<entry>
<title>feat(install-lang): seed a neutral CLAUDE.md when a bundle ships none</title>
<updated>2026-06-24T01:00:11+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-24T01:00:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=71db71b9d47ffbeaf1d1c859fa3e3bebb7b2ea29'/>
<id>urn:sha1:71db71b9d47ffbeaf1d1c859fa3e3bebb7b2ea29</id>
<content type='text'>
install-lang only seeded CLAUDE.md if the chosen bundle shipped one. elisp and go do, python and typescript don't. A project installing a template-less bundle got no CLAUDE.md, and a multi-bundle install inherited whichever bundle shipped one. A bash project that installed elisp and python ended up headed "Elisp project," worse than no header.

I added a language-neutral default (languages/default-CLAUDE.md) that names no language, so single-language, multi-bundle, and wrong-bundle installs all get an accurate "fill this in" header instead of a false one. Per-bundle templates still win where present. The seed-on-first-install, no-overwrite logic is unchanged.

I hardened the Makefile LANGUAGES glob to directories only so the new template file doesn't show up as a selectable language. lint covers the default. The install-lang tests cover the fallback, the bundle-wins branch, and no-overwrite.

A bash bundle is still the real gap for shell-heavy projects, filed as a backlog task.
</content>
</entry>
<entry>
<title>feat(install): adopt the statusline script into the managed set</title>
<updated>2026-06-11T16:35:45+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-11T16:35:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=3df14fc985ddad041c290c732b5b5b8eae41f68e'/>
<id>urn:sha1:3df14fc985ddad041c290c732b5b5b8eae41f68e</id>
<content type='text'>
An archsetup session added a statusLine entry to the tracked settings.json on 2026-06-11 (Craig's request), pointing at ~/.claude/statusline-command.sh, but the script itself lived outside the repo on one machine. This commits the settings entry and brings the script into .claude/, linked by make install like the rest of the config, so it reaches every machine on the next session.

Two fixes over the original: uname -n instead of hostname (Arch doesn't ship hostname by default, so the host rendered empty with stderr noise), and the tilde replacement is escaped (unquoted, bash expands the replacement ~ straight back to $HOME, which defeated the abbreviation). scripts/tests/statusline-command.bats covers the format, branch handling, and the no-stderr contract.
</content>
</entry>
<entry>
<title>fix(install): link default hooks in make install</title>
<updated>2026-06-11T16:32:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-11T16:32:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=d576fc217ba304b48dfb1c54b92bc1849397fd9b'/>
<id>urn:sha1:d576fc217ba304b48dfb1c54b92bc1849397fd9b</id>
<content type='text'>
session-clear-resume.sh shipped 2026-06-02 with its settings.json entry, but make install didn't cover hooks and nothing re-ran install-hooks, so the symlink only existed on machines that had linked it by hand. Everywhere else the hook errored silently on every /clear.

make install now links DEFAULT_HOOKS alongside skills, rules, config, and bin scripts, so the startup workflow's install step propagates new hooks machine-wide. Opt-in hooks stay manual. scripts/tests/install-hooks-link.bats covers the new section. The SessionStart-on-clear todo task closes with this: the hook feature already existed, and the gap was distribution.
</content>
</entry>
<entry>
<title>feat(typescript): add coverage-summary to the TypeScript bundle</title>
<updated>2026-05-31T18:57:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T18:57:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=ee903e4b63257573773e93d10612250e3634cae9'/>
<id>urn:sha1:ee903e4b63257573773e93d10612250e3634cae9</id>
<content type='text'>
Last language in the coverage-summary fan-out, after Elisp, Python, and Go. Same kernel: count every source file on disk that's absent from the coverage report as 0% and weight the project number by file, so an untested file stays visible instead of being averaged away.

The script at languages/typescript/claude/scripts/coverage-summary.js parses an Istanbul json-summary report (the coverage-summary.json that c8, Vitest, and Jest all emit), takes per-file statements covered over total, and reports a file-weighted number plus the missing files. It walks the source dir for .ts/.js, skipping test files, declarations, and node_modules. Node built-ins only, so it runs via node with no install, and it doesn't reimplement the per-file table nyc already prints.

Tests are black-box, run with node's own test runner: a temp tree plus a json-summary report, the script invoked via node, output asserted. They cover missing-file detection, all-tracked, test-file and node_modules exclusion, and the missing-report error. make test gained a node --test discovery path for languages/*/tests, guarded so environments without Node skip it cleanly. As with Python, the TypeScript bundle had no gitignore-add.txt, which would have left the script un-gitignored on install, so I added one.

This finishes the fan-out: coverage-summary now ships in all four bundles, each parsing its own tool's report behind the same file-weighted, missing-as-0% kernel. I proved the Go and TypeScript scripts by running them (Go against a live profile, TS against a synthetic report and the CLI). Python and TypeScript weren't run against a live coverage tool, since neither coverage.py nor nyc is installed here, so the first adopter of each should check against a real report.
</content>
</entry>
<entry>
<title>feat(go): add coverage-summary as a Go bundle coverage slice</title>
<updated>2026-05-31T18:07:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T18:07:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=47ca509e69b6a1472a735a4b9521a952e7434491'/>
<id>urn:sha1:47ca509e69b6a1472a735a4b9521a952e7434491</id>
<content type='text'>
Third language in the coverage-summary fan-out, after Elisp and Python. Same kernel: count every source file on disk that's absent from the coverage profile as 0% and weight the project number by file, so an untested file stays visible instead of being averaged away.

The script at languages/go/claude/scripts/coverage-summary.go parses a cover.out profile, maps each import-path-qualified entry back to an on-disk relative path using the module path from go.mod, and reports a file-weighted number plus the missing files. It's standard library only, so it runs anywhere via go run, and it doesn't reimplement the per-function table that go tool cover -func already prints. I proved it against a real go test -coverprofile run, not just a synthetic fixture, since the Go toolchain is installed here.

Two findings to flag. Modern go test ./... already lists every module package in the profile at 0% even when untested, so for in-module code the missing-file list is usually empty. The detection earns its keep on build-tagged files and dirs outside ./.... And this is a coverage-only slice of a Go bundle that doesn't otherwise exist yet: there's no go.md rule file, so sync-language-bundle.sh can't fingerprint it (detection keys on a bundle's own .claude/rules). The script installs via make install-lang LANG=go but won't be sync-maintained until the Go bundle gets real rules and a CLAUDE.md. Building that out is the natural companion task.

Tests are black-box: a Go test in its own throwaway module runs the script via go run against temp fixtures and checks output, so the shipped script dir stays test-free. They cover missing-file detection, all-tracked, _test.go exclusion, and the missing-report error. make test gained a go test discovery path for languages/*/tests, guarded so environments without Go skip it cleanly.
</content>
</entry>
<entry>
<title>feat(python): add coverage-summary to the Python bundle</title>
<updated>2026-05-31T17:31:35+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-31T17:31:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=af478a42b18c4d5e0712c4cb43036126d36c56b5'/>
<id>urn:sha1:af478a42b18c4d5e0712c4cb43036126d36c56b5</id>
<content type='text'>
Second language in the coverage-summary fan-out, after the Elisp pilot. Same kernel: a module no test imports never appears in coverage.py's report, so a line-weighted total skips it silently and the suite looks healthier than it is. This counts every source file on disk that's absent from the report as 0% and weights the project number by file, so untested modules stay visible.

The script at languages/python/claude/scripts/coverage-summary.py parses coverage.py's JSON (files[path].summary.covered_lines / num_statements), resolves report paths against the report's directory since coverage records them relative to where it ran, and recurses the source dir for *.py. Unlike the Elisp version it doesn't print a per-file table, because coverage.py's own coverage report already does. The script adds the missing-file accounting that report lacks. It uses only the standard library, parsing the report rather than importing coverage.

The Python run confirmed the plumbing from the pilot is genuinely generic. install-lang and sync deliver the script and the project-owned coverage-makefile.txt with no Python-specific code. The one gap I had to close: the Python bundle shipped without a gitignore-add.txt, so the .claude/ footprint wasn't ignored and the script would have been committable. Added one mirroring the Elisp footprint plus Python artifacts (__pycache__, .coverage, coverage.json). make test gained a languages/*/tests/test_*.py discovery path alongside the existing Elisp ERT one.

Tests: 12 pytest covering the parser, the file-weighted number, and the missing-file detection including subpackage recursion, plus an install-lang check that the script lands in the gitignored footprint. I proved it against a report matching coverage.py's documented schema and the CLI end to end, but not against a live coverage json run, because coverage.py isn't installed in this repo's env. The first project to adopt it should sanity-check against a real report.
</content>
</entry>
</feed>
