| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
| |
A gitignore-mode project only ignored .ai/. CLAUDE.md was left untracked but not ignored, so an accidental git add or a codify run could still commit a personal CLAUDE.md, the private rule copies under .claude/, or an AGENTS.md. install-ai now ignores the whole set (.ai/, .claude/, CLAUDE.md, AGENTS.md) at bootstrap, line-idempotent so an existing .gitignore isn't duplicated.
.claude/ goes in the set because it's rulesets-owned (copies of claude-rules/*.md plus the language bundle's rules, hooks, and settings), re-synced from rulesets every startup, so git isn't how it travels. Ignoring it also keeps those private rule copies out of the repo, which ignoring CLAUDE.md alone would miss. The gate is unchanged: track-mode projects (personal/doc repos, team repos sharing config) keep tracking the set.
sweep-gitignore-tooling.sh backfills the set across existing gitignore-mode projects, idempotent and skipping track-mode by design. It warns when a now-ignored path is already tracked, since the ignore won't untrack it. protocols.org states the policy once.
|
| |
|
|
|
|
| |
The startup bundle sync re-dropped from-rulesets-coverage-makefile.txt into a project's inbox on every run, even after the project had adopted the targets. inbox_drop only treated the fragment as adopted if coverage-makefile.txt still sat at the project root or waited in the inbox. But install-lang tells users the opposite: copy the targets into your Makefile, then delete the fragment. So a project that followed the documented path got the drop re-suggested forever (deleted three sessions running in one case).
I guarded the drop so a project Makefile that already defines the distinctive coverage-summary target counts as adopted. The check lives at the call site, keeping inbox_drop generic. Added two bats cases: targets-in-Makefile suppresses the drop, an unrelated Makefile still gets it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The Go bundle was coverage-slice-only. Because it shipped no rule files, sync-language-bundle.sh (which fingerprints a project's bundle by spotting one of its rule files in .claude/rules/) couldn't detect it, so the coverage slice it did ship never stayed in sync. Adding the rules is what makes the bundle sync-maintainable, which was the point.
Brought Go to the full tier, matching elisp:
- claude/rules/go.md and go-testing.md, the style and testing rules (table-driven tests, go test -race, errors.Is over message matching, how the coverage slice fits). These two are also the sync fingerprint.
- claude/hooks/validate-go.sh, a PostToolUse hook that runs gofmt and go vet on each edited .go file. go vet type-checks, so compile and syntax errors surface at edit time. It deliberately doesn't auto-run tests, since a package's tests can be slow or integration-tagged and shouldn't fire on every keystroke.
- claude/settings.json, Go permissions plus the hook wiring.
- githooks/pre-commit, a secret scan and a gofmt check on staged .go.
- CLAUDE.md, the seed.
validate-go.sh is TDD'd by scripts/tests/validate-go.bats: a clean file passes, gofmt and vet failures both block with the JSON payload, and non-go, missing, or empty paths are ignored. I updated install-lang.bats test 7, which asserted Go installs no CLAUDE.md, to check the full bundle instead. Verified with a real install into a throwaway project and a green make test.
|
| |
|
|
|
|
| |
install-ai now creates a top-level inbox/ with a .gitkeep in every project it bootstraps. inbox-send treats a project as a messaging target only when it has both a .ai/ marker and a top-level inbox/, so before this a freshly bootstrapped project couldn't receive cross-project handoffs until the inbox was made by hand.
The directory is created in both track and gitignore modes, since inbox/ is a project-root convention independent of whether .ai/ is tracked. The step is idempotent, so a project that already has an inbox keeps its contents.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|