Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions THEORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# A theory of `claude-code-setup`

## What this system is

This repository is a single person's working agreement with a coding agent. The world it models is not "software" in the generic sense — it models the interaction between one engineer (Mark Ayers) and Claude Code as his programming collaborator, and it captures, in files, everything the engineer has learned about how he wants that collaboration to go. The repo directory literally _is_ `~/.claude`: the checked-in source and the live configuration the CLI reads are the same tree. So unlike a library or product, there are exactly two "users" — the human and the LLM running on his behalf — and the artifact under version control is the contract between them.

The core entities of that contract are four things Claude Code natively understands, plus one the maintainer has layered on top. Natively: **Skills** (packaged pieces of expertise the agent can auto-select), **Agents** (specialized sub-personas with their own tool grants), **Hooks** (shell or Python scripts the harness runs on lifecycle events), and **Rules** — the maintainer's coinage for auto-loaded markdown files in `rules/` that get concatenated into every session's system prompt. On top of these sits a fifth, emergent entity: **Quality**, formalized in the `cc-review` skill as a 6-dimension weighted rubric. Quality is reified because this repository has enough components that their drift is an observable problem, and the maintainer has responded by writing a lint/score/improve pipeline for the components themselves. A sibling setup might simply let things accumulate; this one treats its own contents as code that needs review.

## The organizing ideas

**The harness is reflective all the way down.** It contains skills for reviewing skills (`cc-review`), improving instructions (`md-improve`, `md-audit`), auditing itself when a new Claude Code version ships (`cc-release-review`), and recommending automations to add (`cc-automation-recommender`). The same `vc-ship` skill that commits work in a target codebase is also how changes to this very repo get shipped. Self-modification is not an occasional housekeeping task — it is a first-class workflow with dedicated tooling. A maintainer who doesn't recognize this will keep tripping over the recursion: editing `rules/git.md` in session N doesn't affect session N's own behavior because rules are frozen at session start, so testing a rule change always means opening a new session.

**Convention replaces registration.** Adding a skill means dropping a directory under `skills/<name>/`; adding a global rule means dropping a markdown file under `rules/`; adding a hook script means putting it in `hooks/`. There is no manifest, no import list, no registry. The only place wiring happens explicitly is `settings.json`, and its size (505 lines) compared to the rest of the repo is disproportionate precisely because it is the one seam where explicitness was unavoidable. The open issue #348 — "hooks section is ~400 lines of mostly-identical log-event wiring" — is the maintainer's own recognition that this seam has gotten ugly, but he has declined to paper over it with a shell abstraction because the harness's own schema is what he has to work with, and the fix properly belongs upstream in Claude Code. That restraint is itself part of the theory: do not invent a cleverness that replaces a vendor-defined interface.

**Hooks are advisory, not authoritative.** Nearly every hook script — `config-protection.sh`, `prompt-injection-guard.py`, `validate-bash-commands.py`, `stale-branch-guard.sh`, `context-monitor.sh` — can detect a problem, but almost none of them block. They write advisory text to stderr or emit a `hookSpecificOutput.additionalContext` JSON payload and exit 0. `config-protection.sh` does use exit 1 (warn), but it is explicit in its own comments that this warns the user rather than blocking Claude. The invariant is: _the harness must never leave the agent wedged_. Several hook files carry a load-bearing comment — "Intentionally no `set -euo pipefail` — hooks must always exit 0" — which is a defensive note to prevent a future maintainer (human or AI) from "fixing" the lack of strict mode and thereby breaking the system. Read this comment as a scar tissue marker for a lesson already learned.

**Separation of concerns follows audience, not topic.** There are three CLAUDE.md files in view, and understanding which is which is essential. `rules/*.md` holds language- and tool-specific guidance (`go.md`, `typescript.md`, `markdown.md`, `web.md`…) that applies to _every project on this machine_ and is auto-injected at session start. `.claude/CLAUDE.md` (inside the repo) holds harness-maintenance guidance that applies _only when editing this repo_. `~/.claude/CLAUDE.md` holds cross-project collaboration style. The root `CLAUDE.md` at the repo top is currently a verbatim duplicate of `~/.claude/CLAUDE.md`; I read this as a workaround for Claude Code's project-directory resolution, which looks at the workspace root rather than `.claude/` — without the duplicate, working _on_ the repo as a project would lose the global collaboration style. It is the most suspicious file in the tree, and the recent commit "docs: tighten global CLAUDE.md" suggests the two drift apart and have to be resynchronized by hand.

## The seams

The sharpest internal seam runs between `statusline-command.sh` and `context-monitor.sh`. The status line runs on a different cadence than tool-use hooks and is the only place the remaining-context percentage is exposed, so it writes a bridge file `/tmp/claude-ctx-${session_id}.json` that the PostToolUse hook then reads, applying thresholds (35% warning, 25% critical), debouncing (8 calls), and staleness (120 seconds) before injecting an `additionalContext` warning. This is a filesystem-mediated side-channel between two hooks that share no process and no memory, keyed on session ID so multiple concurrent Claude sessions don't clobber each other. It is fragile — a renamed session ID field, a cleanup that deletes `/tmp/claude-ctx-*` at the wrong moment, or a harness change that drops the percentage from the status line input would silently disable the warnings — but nothing else could work given how Claude Code exposes context metrics. It is also the only place in the whole repo where two components genuinely share mutable state, which tells you the maintainer has kept cross-component coupling near zero on purpose.

The external seams are: the Claude Code harness itself (unstable — hence `cc-release-review`, a skill whose entire purpose is to re-audit local config after an upstream release); the installed CLI tools that the task runner and skills assume exist (`bunx`, `uvx`, `shellcheck`, `shfmt`, `gofumpt`, `biome`, `prettier`, `ruff`); and the plugin marketplaces under `plugins/marketplaces/`, which the maintainer explicitly treats as read-only ("overwritten on update — never treat them as editable source"). Local skills in `skills/` and plugin-provided skills with overlapping concerns — e.g., this repo has `tdd-cycle` and `refactor-clean`, and the enabled `superpowers` plugin brings its own `test-driven-development` and others — coexist without any documented precedence rule. I could not find a conflict-resolution policy in code, and I suspect the maintainer relies on the description-matching heuristic and on his own choice of which slash command to type. That is a thin theory; it will hold until two skills with near-identical descriptions are auto-triggered and fight.

The human-to-harness seam is the permissions block in `settings.json`. The `allow` and `deny` lists encode a specific posture: anything under `~/.aws/`, `~/.kube/`, `~/.gnupg/`, `package-lock.json`, and the classic dotenv set is off limits; all of `npm`/`yarn`/`pnpm`/`pip`/`python` are denied in favor of `bun`/`bunx`/`uv`/`uvx`; and `Write` against `settings.json` and `.mcp.json` is explicitly disallowed even though `*.json` is allowed. This last rule, combined with `config-protection.sh`'s warning on linter configs, expresses a principle: **the agent should fix code, not configuration**. A new maintainer who casually adds `biome.json` to the allow list is missing that.

## The vocabulary

A handful of names carry more meaning than they look like they do. The `cc-` prefix is for Claude Code _meta_-tools — things that operate on the harness. `vc-` is "version control," a deliberate abstraction over `git-` even though every implementation is git today. `md-` specifically means CLAUDE.md operations, not markdown generally. "Ship" and "sync" are the two verbs for git work: ship means commit-organize-review-push-PR (finishing), sync means pull-prune (catching up), and they are split because conflating them in one skill blurs the user's intent. "Quality gate" is reserved for fast, single-language check runners (`go-quality-gate`, `python-quality-gate`, `bash-quality-gate`, `typescript-quality-gate`), distinguishing them from the deeper, multi-phase `refactor-clean` and `tech-debt` skills. The Latin header `COGITA·DISCE·NECTE·ENUNTIA` (think, learn, connect, express) is personal — it is the maintainer's motto, and its presence in every CLAUDE.md copy is the fingerprint to look for when spotting files that should share the collaboration style.

## What the system bends to accommodate, and what it does not

Adding a new skill, rule, hook script, or agent is cheap and well-shaped: drop a file, test in a fresh session, ship with `vc-ship`, re-audit with `cc-review`. The rename protocol in `.claude/CLAUDE.md` exists because `settings.json`, `settings.local.json`, and cross-skill references are all kept in sync by grep rather than by symbolic reference, so renames are mechanical but exhaustive — and the protocol calls out that `settings.local.json` is gitignored and easy to miss. Adding a new language's quality gate is a clean copy-and-adapt of an existing one.

Things that would require rethinking: any automation that needs to _block_ rather than advise would collide with the "always exit 0" invariant. Cross-session state beyond the /tmp bridge files has no home — the `state/` directory has exactly one file in it (`cc-release-review-version.txt`), and there is no convention for what belongs there. Multi-user collaboration is not modeled at all; the permission lists, the attribution block, and the `memory/` directory under `~/.claude/projects/…` are all singular. Moving to a different agent harness would require rewriting every skill, because the frontmatter (`allowed-tools`, `disable-model-invocation`) and lifecycle event names (`PreToolUse`, `SessionStart`, `PostToolUseFailure`, `ElicitationResult`) are Claude-Code-specific and their semantics are nowhere defined in this repo — they are imported knowledge.

A new requirement arriving tomorrow: if it's "warn me when X happens in my code," it belongs as a new PostToolUse hook modeled on `context-monitor.sh`. If it's "help me do Y," it belongs as a skill with progressive-disclosure references. If it's "never let me do Z," resist the reflex to make it a blocking hook — check first whether it belongs in the `deny` list in `settings.json` or as an advisory with strong wording. A maintainer who doesn't understand the advisory invariant will write a blocking hook, accidentally wedge a session, and then get blamed by the agent in the log.

## Uncertainties and tensions

I am inferring the following and could be wrong:

- The duplication between repo-root `CLAUDE.md` and `~/.claude/CLAUDE.md` looks like a workaround rather than a deliberate design. If it is deliberate, the reason is undocumented; if it is drift, the recent tighten-global commit is evidence the maintainer is aware and has simply chosen manual sync over a symlink or include mechanism.
- I could not find a conflict policy between local skills and plugin-provided skills of similar purpose. My best reading is that the maintainer curates which plugins stay enabled and trusts Claude Code's description-matching to route correctly, but this is an assumption.
- The `state/` directory contains one file and no README. Whether this is the beginning of a convention or an accidental dumping ground is not clear from the code.
- Issue #348's existence tells me the maintainer sees the hooks-block repetition as debt, but he has not acted, and my reading is that the right fix lives upstream in Claude Code's schema (wildcards or a default-for-all handler) rather than in this repo. A new maintainer who tries to collapse the hooks block with generated JSON or a shell-templating step would probably fight the `settings.json` schema validator and end up worse off.
- The `superpowers` plugin is enabled and aggressively prescriptive — its `using-superpowers` skill imposes a "invoke a skill before responding" discipline that is in clear tension with the local `rules/` philosophy of terse, situational guidance. The local `.claude/CLAUDE.md` does not acknowledge or reconcile this tension. I read the arrangement as the maintainer experimenting with superpowers without yet deciding whether to keep it, and the evidence is that nothing in the local skill set has been rewritten to integrate with it.

The most honest statement of where the theory is thinnest: this is a one-person instrument, and the parts that hold it together are a set of small habits the maintainer has — running `cc-review` after structural changes, keeping global CLAUDE.md and root CLAUDE.md in sync by hand, manually curating which plugin skills stay enabled, remembering that rule edits only land on the next session. None of these habits are enforced by the code. Inheriting the repo without inheriting the habits would be a slow failure: nothing would break immediately, but the quality rubric would stop being applied, the CLAUDE.md files would diverge, rules would be edited and tested in the same session and appear not to work, and plugin and local skills would begin to contradict each other unnoticed. The theory lives as much in the maintainer's head as in the files.
Loading