| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
| |
Renaming an .ai artifact by hand is the kind of mechanical job that gets done incompletely: the canonical copy moves but the mirror doesn't, a reference in the INDEX is missed, a trigger phrase points at the old name. I'd also assumed a rename was costly because references scatter, when the index update is trivial and the drift check already guards it. So I built the discipline into a script instead of re-deriving it each time.
scripts/rename-ai-artifact.sh takes old and new basenames, moves the file in both the canonical and mirror trees, and rewrites every reference repo-wide on a token boundary so renaming "foo" can't corrupt "foobar" or "foo-bar". It rewrites the underscore module-name variant too (a hyphenated script imported as foo_bar via importlib), leaves the archived session records under sessions/ alone because they're history, and runs workflow-integrity + sync-check at the end to prove no drift. rename-artifact.org documents it and indexes the triggers.
Then I used the tool to do the rename that prompted it: the org-drill deck workflow and its helpers are now flashcard-named, since "flashcard" is the word you'd actually search for. The renamed set is flashcard-review.org plus flashcard-stats.py, flashcard-sync, flashcard-to-anki.py, and flashcard-diff-ids.py, with their tests, every reference, and the INDEX entry updated. The deck is still an org-drill deck under the hood, so the ":drill:" tag handling and the "drill deck" trigger phrases stay. I added "review/update the flashcards" alongside them.
Tests: 9 bats for the rename tool (including the prefix-collision and history-preservation edges), and the renamed script suites all pass under make test.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
detection
A line-weighted coverage total has a blind spot: a module no test loads never shows up in the SimpleCov report, so it can't drag the number down. The suite looks healthier than it is. This adds a summary that counts every source file on disk against the report and treats an absent file as 0%, weighting the project number by file instead of by line so untested modules stay visible.
The script ships at languages/elisp/claude/scripts/coverage-summary.el, self-contained on stock Emacs (just the built-in json). It parses the undercover SimpleCov shape directly rather than depending on the editor's coverage engine, so it runs anywhere the bundle lands. I proved it against a real 103-file report: 93 tracked, 27 untested modules surfaced, project number 66.4%.
Delivery follows the bundle convention. The script lives under the gitignored .claude/ footprint and gets auto-fixed on drift by sync-language-bundle.sh, which I made generic for any claude/scripts/* rather than coverage-specific. The Makefile targets ship as a project-owned fragment (languages/elisp/coverage-makefile.txt) that install-lang.sh seeds at the project root and sync drops into .ai/inbox/ when that convention exists. The bundle never edits the project's own Makefile.
Tests: 12 ERT for the kernel (Normal/Boundary/Error per function), wired into make test via a new languages/*/tests/ discovery path, plus bats for the sync auto-fix and the inbox-drop guards.
This is the Elisp pilot. The pattern is proven, so fanning out to Python, Go, and TypeScript is now a follow-up. Each one needs only its own parser and fragment. The plumbing is already generic.
|
| |
|
|
|
|
|
|
|
|
| |
I split each into lanes so a reader can stop at the level that answers the question: Summary for "what does this do and what does it produce", Execution for the steps to follow, Reference for examples and edge cases, History for old decisions. Both files are large enough that an agent loading them at routing time pays for context it doesn't need yet.
startup.org keeps Summary, Execution, and Reference (workflow discovery and common mistakes moved under Reference). triage-intake.org gets all four, including a History lane for its design notes. Every instruction is preserved. The triage reorder ran through a content-preservation check that compared the multiset of content lines before and after, so only heading depth and lane grouping moved. Nothing was dropped or reworded.
workflow-integrity.py now counts "Summary" as a valid orientation heading, since that's the new top section both files lead with.
This is the pilot from the codex backlog, scoped to the two largest workflows. Whether the lanes actually cut session token use gets evaluated before any wider rollout.
|
| |
|
|
|
|
| |
Startup's drift check catches index-vs-directory mismatches. This goes deeper: scripts/workflow-integrity.py runs six checks over the canonical .ai/workflows/: each file is indexed-or-a-plugin-of-an-indexed-engine, each index entry resolves to a file, each .ai/scripts/ reference resolves, each plugin maps to an indexed parent, each non-plugin workflow has an orientation section, and no trigger phrase is claimed by two workflows. Exit 1 on any finding.
scripts/tests/workflow-integrity.bats covers the clean canonical state plus a fixture per breakage class. make test already globs scripts/tests/*.bats, so it's wired in. I calibrated against the 38 current workflows (clean). The orientation check accepts the real heading variety (Overview / Purpose / When to Use|Run / Status) and exempts plugins.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pre-commit caught a false-positive on its first real-world run. The
.ai/scripts/__pycache__ directory created by make test (pytest writes
.pyc and .pytest_cache, emacs writes .elc) was flagged as drift
against the canonical's clean tree.
Added matching --exclude patterns to the diff -rq check and the --fix
rsync calls. Patterns: __pycache__, *.pyc, *.pyo, .pytest_cache, *.elc.
The --fix excludes prevent rsync's --delete from wiping the mirror's
__pycache__ when the canonical has none.
Four new bats tests cover the exclusions. All 12 pass.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
scripts/status.sh prints a six-line summary composing existing checks:
- audit + doctor (one call, since audit.sh runs doctor internally)
- canonical/mirror sync state via sync-check.sh
- open todo count under * <Project> Open Work
- inbox count (excluding .gitkeep and PROCESSED- prefixes)
- git working-tree state with ahead/behind upstream
Sample output:
rulesets status — 2026-05-28 09:13 CDT
audit Summary: 41 ok, 0 warnings, 2 failures
sync canonical = mirror
todo 22 open
inbox 1 unprocessed
git main dirty — in sync with origin/main
The script adds no new logic beyond formatting. `make status` is the
entry point.
The scope here is limited per the triage disposition for codex item
#12. The rest of #12 was rejected. `make sync` duplicates the existing
sync flow, `make health` wraps existing checks without adding signal,
`make bootstrap-project` duplicates `install-ai` + `install-lang`.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
scripts/sync-check.sh diffs claude-templates/.ai/{protocols.org,
workflows,scripts} against the .ai/ mirror. Exits 0 when clean, 1 with
a diff report on drift, 2 outside a rulesets-shaped repo or git
checkout. --fix mode rsyncs canonical -> mirror and re-checks, then
prompts to re-stage.
githooks/pre-commit wraps the script. Commits abort on drift so the
issue surfaces at publish time, not at the next session's startup
rsync.
Two new Makefile targets:
- make sync-check [FIX=1] runs the script (FIX=1 passes --fix
through).
- make install-githooks sets core.hooksPath=githooks (idempotent).
scripts/tests/sync-check.bats holds 8 tests covering clean,
drift-per-path, --fix, extra-file removal, missing canonical, and
outside-git. All eight pass.
This catches the exact drift I had to fix manually during this
morning's audit pass. The mirror's open-tasks.org PROPERTIES drawer
sat below a sub-heading because the mirror commit was older than
canonical.
|
| |
|
|
|
|
| |
The bundle tracked .claude/rules, CLAUDE.md, and githooks/, ignoring only the personal overrides. For a code project, especially a third-party package checkout, the whole Claude footprint should stay local: install and sync deliver it, so it shouldn't land in the project's history. gitignore-add.txt now ignores .claude/, CLAUDE.md, and githooks/ next to the elisp build artifacts.
I also added install-lang.bats, which the bundle had no test for. It covers the landed footprint and the gitignore set.
|
| |
|
|
|
|
| |
make remove is the granular counterpart to make uninstall, which removes everything. remove.sh lists every rulesets-managed symlink under ~/.claude/ — only links whose target resolves into the repo, so foreign symlinks are left alone — pipes them through fzf --multi, and rm's the picked links. The repo's own files stay put, and make install re-creates anything removed.
It's split into --list and --remove-selected modes so the logic is testable without fzf. 5 bats cases cover the listing, the foreign-link exclusion, removal, report-and-continue on a missing target, and the empty no-op. The removal loop runs without set -e and without rm -f, so a vanished target reports visibly and the rest still process. shellcheck clean, make test green.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The global commits.md carried DeepSat-specific publishing steps — Linear ticket-state moves, the Slack notification protocol with its channel ID and engineer names, the deepsat.ghe.com host, the team merge norm. Those are symlinked into every project on the machine, so they sat as dead weight in personal repos and risked misfiring where there's no Linear ticket to move or Slack mpdm to ping.
I split them out. commits.md keeps the universal skeleton (identity, attribution, commit format, the review-and-publish gate, verification) and replaces the team steps with seams: "run the project's publishing overlay here if it defines one," the same pattern startup.org uses for startup-extras. A project with no overlay runs the complete flow, just without ticket and chat integration.
The DeepSat specifics move to teams/deepsat/claude/rules/publishing.md. That file is not a global rule — install-team.sh copies it into one project's .claude/rules/ (make install-team TEAM=deepsat PROJECT=...), keyed on the PROJECT argument, so only the named project gets it. Location decides distribution: claude-rules/ is the global-symlink set, teams/ is targeted-copy, so the overlay reaches DeepSat and nowhere else.
The startup freshness check (sync-language-bundle.sh) now covers team overlays alongside language bundles: a process_bundle function handles both, with a team syncing only its own rule (no generic rules, hooks, or settings — those belong to a language bundle). A drifted overlay rule auto-fixes from canonical at the project's next startup, the same mechanism language bundles already ride.
Tested: 3 new bats cases (team overlay clean / drifted-and-fixed / does-not-pull-generic-rules) on top of the 11 existing; install-team + sync verified end-to-end against a temp project. make test green, shellcheck clean.
|
| |
|
|
|
|
|
|
|
|
| |
Startup synced the .ai/ templates into the current project every session but never checked the language bundle (elisp, python) installed in .claude/. Bundle drift went unnoticed until someone re-ran make install-lang by hand: a generic rule added to claude-rules/ after the last install, or a changed validator hook.
scripts/sync-language-bundle.sh closes that gap. It fingerprints which bundle a project has by the presence of the language's own rule files (elisp.md, python-testing.md), then reconciles against the canonical source: auto-fix for rulesets-owned files (.claude/rules/*.md, .claude/hooks/*, githooks/*), surface-only for settings.json, which a project may have customized. CLAUDE.md is left alone. It's seed-only in install-lang and project-owned afterward, the same reason diff-lang skips it.
Startup Phase A step 12 calls it for the current project, guarded so older checkouts that lack the script still boot. It writes only under .claude/ and githooks/, disjoint from the .ai/ rsync paths, so the parallel batch stays safe. A script rather than a make target keeps the Makefile-parse layer off the boot path. The absolute rulesets path it depends on is the same one the rsyncs already carry.
Tested: 11 bats cases (no-bundle, clean, drifted rule/hook auto-fixed, surfaced settings.json asserted unmodified, absent CLAUDE.md not flagged, python detection, $PWD default, bad path). A smoke run against a copy of a real elisp project's .claude/ caught a perpetual "CLAUDE.md missing" alarm, which is what drove dropping CLAUDE.md from the surface set.
|
| |
|
|
|
|
| |
The `[ ] && echo` shortcut propagates the test's exit status out of the
while loop, which can muddy the pipeline's overall exit. The `if` form
keeps the loop body's status decoupled from the filter check.
|
| |
|
|
|
|
|
|
| |
Adds scripts/tests/audit.bats (6 tests) and scripts/tests/install-ai.bats (5 tests) covering the three destructive edge cases that the fold-epic test plan deferred yesterday: audit --apply --force clobbering a tracked dirty .ai/, audit's loop continuing past a missing-.ai/ project, and install-ai's interactive fzf-pick form. The first two go alongside happy-path sanity (clean sweep, drift detection, --apply convergence, dirty-skip); install-ai gets happy-path with explicit PROJECT, --track gitkeep stubs, refusal on existing .ai/, and notes.org placeholder substitution.
Strategy: redirect HOME to a per-test mktemp dir, scaffold synthetic project trees under HOME/code/, and run the real scripts against them. The canonical source stays the real one (resolved relative to each script's own location), so tests exercise the production rsync paths without copying canonical content. Use PATH stubs for fzf and find to cover the interactive and race-condition edges.
Makefile test: target extended with a bats stanza; description updated to "Run all test suites (pytest + ERT + bats)". make test now runs 352 green (296 pytest + 22 lint-org ERT + 23 todo-cleanup ERT + 6 audit bats + 5 install-ai bats), up from 341.
|
| |
|
|
|
|
|
|
| |
scripts/catchup-machine.sh runs the four steps that bring a machine in sync with rulesets canonical: git pull, make install (symlink refresh), make audit APPLY=1 (rsync .ai/ across all projects), and make doctor (verify). Idempotent, safe to re-run any time.
Built for the post-fold ratio migration but applies generally: after a fresh rulesets clone on a new machine, or whenever the canonical source has advanced since last sync.
Handles dirty working trees by skipping the pull and surfacing a warning; user commits or stashes before re-running.
|
| |
|
|
|
|
|
|
| |
scripts/install-ai.sh copies canonical .ai/ content from claude-templates/ into a fresh project. Rsyncs protocols.org, workflows/, scripts/, someday-maybe.org as-is; templates notes.org with project-name and date placeholders substituted; creates empty sessions/, references/, retrospectives/ dirs.
Two tracking modes: TRACK=1 adds .gitkeep files inside otherwise-empty dirs so they survive in git history; GITIGNORE=1 appends .ai/ to the project's .gitignore so session records stay local. Prompts interactively if neither flag is set.
Refuses if PROJECT/.ai/ already exists with a message pointing to `make audit APPLY=1` for sync of existing installs. Without a PROJECT argument, fzf-picks from ~/code/* + ~/projects/* git checkouts that don't already have .ai/.
|
| |
|
|
|
|
|
|
| |
scripts/audit.sh walks every .ai/-using project under ~/code/, ~/projects/, and ~/.emacs.d/, compares each .ai/ against the canonical source at claude-templates/.ai/, and reports drift per project. Default mode is report-only; APPLY=1 rsyncs detected drift into each project (no auto-commit). FORCE=1 also rsyncs into projects with uncommitted .ai/ changes (default: skip with a warning).
Uses diff -rq for content comparison rather than rsync --itemize-changes to avoid false positives on attribute-only drift (mtime, permissions). Skips the rulesets repo itself, the in-repo canonical source, and the legacy standalone ~/projects/claude-templates/ during the fold transition.
Output mirrors make doctor: per-project ok/drift/applied/skipped/FAIL lines, summary tally, exit 0 when all ok. Runs make doctor as the final check by default; NO_DOCTOR=1 skips.
|
| |
|
|
|
|
|
|
| |
Adds scripts/readability — a Python tool that prints standard readability metrics (Flesch Reading Ease, Flesch-Kincaid Grade, Gunning Fog, SMOG, Coleman-Liau, ARI, Dale-Chall, Linsear-Write) for one input file or as a side-by-side comparison of two.
Self-contained via PEP 723 inline metadata: textstat is declared as the script's only dependency, and the `#!/usr/bin/env -S uv run --quiet --script` shebang lets uv resolve it on each invocation.
The Makefile `deps` target now also pre-warms textstat in uv's cache so the first interactive run is fast.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
=make doctor= scans =~/.claude/= and reports drift against the repo + settings.json. Read-only diagnostic. Eight checks cover skills, rules, default hooks, claude config, settings.json hook references, enabledPlugins, MCP server registrations, and dangling symlinks. Each line prints =ok= / =WARN= / =FAIL= with a final summary. Exit 1 on any FAIL.
A sweep last night found =~/.claude/hooks/= didn't exist on this machine even though =settings.json= referenced a PreCompact hook there. Compaction would have silently failed to invoke it. doctor catches that kind of drift in one command instead of relying on a manual look.
The MCP drift check reads =~/.claude.json= directly rather than parsing =claude mcp list=. The CLI has no JSON output and runs a per-server health probe (~10s). The JSON file is the user-scope source of truth for registrations and parses in well under a second.
I verified by injecting four drift scenarios — removed hook symlink, removed skill symlink, moved-aside plugin data dir, unregistered MCP server. Each produced the expected =FAIL= line and exit 1. After restoring state, doctor came back clean (33 ok).
Bundling four other improvement TODOs from the same sweep — =mcp/README.org=, =make uninstall-mcp= and =mcp/install.py --check=, a README.org section for the MCP install pipeline, and a token-rotation helper for =@a-bonus/google-docs-mcp= OAuth refresh. Plus a stale-bullet note on the existing =make remove= TODO (the bridge symlink it references was removed earlier).
|
| |
|
|
|
|
|
|
|
|
| |
The refactor scan flagged three install/lint problems. I fixed all three.
- The Makefile SKILLS list was hand-maintained and had drifted: `respond-to-cj-comments` exists on disk but wasn't installed by `make install`. I replaced the list with `$(patsubst %/SKILL.md,%,$(wildcard */SKILL.md))` so every directory containing a SKILL.md is picked up automatically.
- Cross-references in installed skills point at `../claude-rules/foo.md`. The install layout puts rules at `~/.claude/rules/`, not `~/.claude/skills/claude-rules/`, so those links resolved in the source repo and silently broke at install. I added a bridge symlink to the install target. `~/.claude/skills/claude-rules` now points at the source `claude-rules/` directory, so the same relative path works in both layouts.
- I extended `scripts/lint.sh` with a `check_md_links` function that validates `claude-rules/` cross-references in `claude-rules/*.md` and `*/SKILL.md`. Scoped narrowly on purpose: skill bodies cite illustrative file names (ADR templates, arc42 sections) that aren't real source files and would generate noise.
Verified locally: `make install` is idempotent, the bridge resolves the previously-broken link, and `bash scripts/lint.sh` is clean.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Ports useful quality-of-life targets from DeepSat's coding-rulesets
Makefile, adapted to this repo's two-scope (global + per-project)
structure.
New targets:
make deps Install claude, jq, fzf, ripgrep,
emacs via brew/apt/pacman. Idempotent
(skips already-present tools). For
new machines and VMs.
make diff LANG=<lang> [PROJECT=<path>]
Show unified diff between repo source
and installed copies in a target
project. CLAUDE.md excluded (seed-
only, diverges by design).
make lint Validate ruleset structure: top-level
headings, 'Applies to:' headers on
rule files, shebangs and exec bits on
hook scripts.
Infrastructure:
- Help migrated to awk-parsed ##@/## pattern; new targets document
themselves via a single trailing `## ...` comment.
- fzf-picker fallback: if PROJECT= is unset, install-lang and diff
launch fzf over local .git dirs under $HOME. Keeps PROJECT=<path>
for scripts/automation; only interactive users hit fzf.
scripts/diff-lang.sh Walks the file list the installer would copy,
diffs each against the target.
scripts/lint.sh Standalone ruleset structure validator.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
claude-rules/testing.md is now language-agnostic (TDD principles, test
categories, coverage targets, anti-patterns). Scope header widened to
**/*. Python-specific content (pytest, fixtures, parametrize, anyio,
Django DB testing) moved to languages/python/claude/rules/python-testing.md.
Added languages/python/ bundle (rules only so far; no CLAUDE.md template
or hooks yet — Python validation tooling differs from Elisp). Added
install-python shortcut to the Makefile.
Updated scripts/install-lang.sh to copy claude-rules/*.md into each
target project's .claude/rules/. Bundles no longer need to carry their
own verification.md copy — deleted languages/elisp/claude/rules/verification.md.
Single source of truth in claude-rules/, fans out via install.
Elisp-testing.md now references testing.md as its base (matches the
python-testing.md pattern).
|
|
|
Introduces a second install mode alongside the existing global symlinks:
per-project language bundles that copy a language-specific Claude Code
setup (rules, hooks, settings, pre-commit) into a target project.
Layout additions:
languages/elisp/ - Emacs Lisp bundle (rules, hooks, settings, CLAUDE.md)
scripts/install-lang.sh - shared install logic
Makefile additions:
make help - unified help text
make install-lang LANG=<lang> PROJECT=<path> [FORCE=1]
make install-elisp PROJECT=<path> [FORCE=1] (shortcut)
make list-languages - show available bundles
Elisp bundle contents:
- CLAUDE.md template (seed on first install, preserved on update)
- .claude/rules/elisp.md, elisp-testing.md, verification.md
- .claude/hooks/validate-el.sh (check-parens, byte-compile, run matching tests)
- .claude/settings.json (permission allowlist, hook wiring)
- githooks/pre-commit (secret scan + staged-file paren check)
- gitignore-add.txt (append .claude/settings.local.json)
Hooks use \$CLAUDE_PROJECT_DIR with a script-relative fallback, so the
same bundle works on any machine or clone path. Install activates git
hooks via core.hooksPath=githooks automatically. Re-running install is
idempotent; CLAUDE.md is never overwritten without FORCE=1.
|