CLAUDE.md · diff

git:20260902.0f820d6 to git:20260903.fcfeca4

32 added, 51 removed. Audit A to A.

# claude-code-hermit (monorepo)
- This repo is a multi-plugin Claude Code marketplace. Seven plugins ship from `plugins/<slug>/`:
- `claude-code-hermit` (core), `claude-code-dev-hermit`, `claude-code-homeassistant-hermit`, `claude-code-fitness-hermit`, `hermit-scribe`, `laravel-forge-hermit`, `feed-hermit`.
- Each plugin has its own `CLAUDE.md`, `CHANGELOG.md`, and `tests/` — read those for plugin-specific context.
-
- The top-level `.claude-plugin/marketplace.json` is the only marketplace. The README at the repo root is the canonical hermit pitch.
+ This repo is a multi-plugin Claude Code marketplace. Seven plugins ship from `plugins/<slug>/`, each with its own `CLAUDE.md`, `CHANGELOG.md`, and `tests/`. `.claude-plugin/marketplace.json` is the only marketplace; the root README is the canonical hermit pitch.
- Always launch Claude Code from this repo's root, not from inside a plugin dir. Auto-memory is keyed by CWD, and plugin dirs contain their own `.claude-plugin/` (launching there can load the plugin under test as the project plugin). Per-plugin `CLAUDE.md` files load on demand when you touch their files.
+ Always launch Claude Code from this repo's root. Auto-memory is keyed by CWD, and a plugin dir's own `.claude-plugin/` would load the plugin under test as the project plugin.
## Conventions
- - **Per-plugin paths**: every plugin lives at `plugins/<slug>/`. Tests, scripts, skills, agents, hooks, state-templates, docs, CHANGELOG, CLAUDE.md all live inside that dir.
- - **Tests run from inside the plugin dir**: core and HA are pure `bun test` (auto-discovers `tests/*.test.ts`; HA keeps Python only as a test fixture, nothing shipped runs it); dev/fitness/scribe/forge use `bash tests/run-all.sh` (forge also needs a composer/PHP step). Helpers use CWD-relative paths and break if invoked from repo root.
- - **Tag format**: `<slug>--v<X.Y.Z>` (double-dash, e.g. `claude-code-hermit--v1.0.20`).
- - **Independent versioning**: each plugin's `plugin.json` bumps on its own cadence. Domain plugins declare core compat via `required_core_version: ">=X.Y.Z"` (semver range, not pin).
- - **Dependency fields**: `required_core_version` and `requires` live in `.claude-plugin/hermit-meta.json` (hermit-internal, validator-invisible). `dependencies` is the native Claude Code resolver field in `plugin.json`. `required_core_version` is authoritative — read by `plugins/claude-code-hermit/scripts/doctor-check.ts` from `hermit-meta.json`. `requires` mirrors it for documentation. Update all three when the core version requirement changes. All hermit-internal manifest extensions (`hermit.*`, etc.) belong in hermit-meta.json. When a domain hatch depends on new core runtime behavior, bump `required_core_version` to the first compatible core version.
- - **CC-version-gated work bumps the floor, never shims around it.** When a fix or feature depends on Claude Code CLI behavior introduced at a specific version (a new tool, hook payload shape, flag, or harness fix), bump that plugin's `min_claude_code_version` in `.claude-plugin/hermit-meta.json` to the version that introduces it — don't write feature-detection, version branches, or fallback code paths to keep the change working on older CC. The floor is the contract (enforced at `hermit-evolve` Step 0); raise it instead of carrying legacy compatibility code.
- - **Marketplace.json bumps**: only the matching plugin's entry. The release skill takes a slug arg: `/release <plugin-slug>`.
- - **Dependency direction is one-way**: domain plugins depend on core; core never depends on them. `plugins/claude-code-hermit/` must not import a sibling, hardcode a sibling slug in logic, or branch on one being installed — it discovers siblings generically (sibling-scan + name-contains-`hermit`) and consumes only what they *declare* (`hermit-meta.json` `hermit.*`). Naming a plugin as an example in prose, docs, or a recommendation list is fine; conditioning behavior on it is not. Reverse direction too: siblings can't import core — shared logic ships as a `hermit-run` verb behind a `required_core_version` floor.
- - **Ship mechanism, not policy (all plugins)**: contracts stay strict, content stays loose — an installed hermit belongs to its operator, give skills data + goal + voice and let the model compose (don't hardcode content or operator-workflow opinions), and anything operator-editable must survive `hermit-evolve`. Contract list and extension points: `plugins/claude-code-hermit/CLAUDE.md` § Authorship layers.
- - **Ship standard: default-on, research preview.** A new feature ships enabled by default, not behind an opt-in toggle defaulted off — treat it as a research preview to iterate on from real usage, not something held back by a conservative default. Default off only when the feature needs an operator-supplied credential/config it can't have yet, costs real spend per invocation, or is destructive/irreversible.
- - **Token discipline (all plugins)**: hook stdout, skill-driven `Read`s, and helper-script output are injected into the operator's context — print verdict-sized digests, and never have a skill `Read` an unbounded surface (JSONL event logs, DBs) directly; front it with a script that returns a bounded summary. The atom of cost is the API call, not the prompt: every tool call re-reads the full accumulated context from cache, so cache traffic is ≈85-90% of an always-on hermit's spend, measured live. Full pattern and examples: `plugins/claude-code-hermit/CLAUDE.md` § Development constraints. (This file is itself the costliest token surface in the repo — loaded every session and re-seeded into every subagent — so keep additions here terse.)
+ - **Tests run from inside the plugin dir** (`bun test` for core and HA, `bash tests/run-all.sh` for the rest). Helpers use CWD-relative paths and break from repo root.
+ - **Independent versioning, tag `<slug>--v<X.Y.Z>`.** Domain plugins declare core compat as `required_core_version: ">=X.Y.Z"` in `.claude-plugin/hermit-meta.json`, mirrored by `requires` there and `dependencies` in `plugin.json`; `required_core_version` is what `doctor-check.ts` reads. Update all three together. All hermit-internal manifest extensions (`hermit.*`) live in hermit-meta.json.
+ - **CC-version-gated work bumps the floor, never shims around it.** When a change depends on Claude Code behavior introduced at a version, raise that plugin's `min_claude_code_version` in hermit-meta.json; no feature detection or fallback paths for older CC.
+ - **Dependency direction is one-way**: domain plugins depend on core; core never imports a sibling, hardcodes a sibling slug in logic, or branches on one being installed. It discovers siblings generically (name-contains-`hermit`) and consumes only what they declare in `hermit-meta.json`. Siblings can't import core either: shared logic ships as a `hermit-run` verb behind a `required_core_version` floor.
+ - **Ship mechanism, not policy (all plugins)**: contracts stay strict, content stays loose. An installed hermit belongs to its operator, so give skills data + goal + voice and let the model compose, and anything operator-editable must survive `hermit-evolve`. Contract list, extension points, and how skill text is written: `plugins/claude-code-hermit/CLAUDE.md` § Authorship layers.
+ - **Ship standard: default-on, research preview.** Default off only for features needing an operator credential/config, real per-invocation spend, or that are destructive.
+ - **Token discipline (all plugins)**: hook stdout, skill-driven `Read`s, and helper-script output land in the operator's context. Print verdict-sized digests; front unbounded surfaces (JSONL logs, DBs) with a script that returns a bounded summary. Every tool call re-reads the full context from cache, so cache traffic is ≈85-90% of an always-on hermit's spend. Pattern and examples: `plugins/claude-code-hermit/CLAUDE.md` § Development constraints. This file is loaded every session and re-seeded into every subagent; keep it terse.
## Commits
- - Root-scope edits (CI, root README, `.claude/`, `.claude-plugin/marketplace.json`) skip the CHANGELOG step entirely — they don't ship to operators. `/commit` handles that automatically.
- - **Docs-only changes never get a CHANGELOG bullet.** README, `docs/`, `CLAUDE.md`, and code-comment edits (even inside a plugin scope) are not something an operator experiences as a shipped change. Only real code/behavior changes earn an entry. When a task bundles a code fix with an incidental docs correction, only the code fix gets a bullet; the docs correction goes in the commit message and PR description. `/commit` and `/release` enforce this.
- - Releases still go through `/release <slug>`, which promotes a plugin's `[Unreleased]` section to a real version. `/commit` accumulates those entries during day-to-day work.
- - **Where these skills live**: `/commit`, `/release`, `/release-status`, `/fleet-release`, `/bump-core-req`, `/test-run`, `/tackle-issue`, `/pre-release-review`, `/docs-drift`, `/pipeline-digest` are repo-internal skills under `.claude/skills/` — they're not shipped to operators, only used during monorepo dev. Use `/release-status` for a read-only pipeline snapshot before any release session; use `/fleet-release` when multiple plugins change together on one branch (handles dep ordering and `required_core_version` sync automatically).
- - **Changelog style: terse like [Claude Code's CHANGELOG](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md).** Keep the `### Added / Changed / Fixed` headers, but write each bullet as a plain sentence-case line describing the change — the header supplies the category, so no leading Fixed/Added verb and no `- **component:**` prefix. Backticks for commands, paths, and flags; 1–2 lines each. No `### Files affected` tables. Rationale, debugging context, and design tradeoffs go in the commit message and PR description, not the bullet. The verbose form is reserved for `### Upgrade Instructions`, which `hermit-evolve` reads imperative-step-by-step. The `/commit` and `/release` skills enforce this; the convention lives here for any agent that bypasses them.
- - **`/release` is operator-initiated — don't auto-suggest it.** Don't propose `/release <slug>` or `/fleet-release` in plans, summaries, or "next steps." Wait for an explicit ship/release/version request. `/release-status` is read-only and fine to suggest when checking pipeline state.
- - **Proposals are local-only — never reference them externally.** Proposals (`PROP-NNN`, the local proposal queue) live in this project's hermit state and are internal dev bookkeeping. Never mention a proposal ID or the fact that work came from a proposal in a CHANGELOG entry, commit message, PR title/description, or branch name. Describe the change on its own terms. Branch names use `feat/<N>-<slug>`/`fix/<N>-<slug>` off the GH issue number, not a proposal ID.
- - **Never publish Claude Code session URLs.** This is a public repo. Session/transcript links (`claude.ai/...` session URLs, share links, artifact URLs tied to a session) must never appear in a PR title/description, PR comment, issue, CHANGELOG entry, commit message, proposal, or any other externally-visible surface. They can leak internal context and aren't stable references. Describe the work directly instead.
+ - `/commit` accumulates `[Unreleased]` CHANGELOG entries; `/release <slug>` promotes them and is the ship event. Root-scope edits and docs-only changes (README, `docs/`, `CLAUDE.md`, comments) get no CHANGELOG bullet; a bundled docs correction goes in the commit message instead.
+ - **Changelog style: terse like [Claude Code's CHANGELOG](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md).** `### Added / Changed / Fixed` headers, plain sentence-case bullets with no leading verb or component prefix, backticks for commands and paths, no `### Files affected` tables. Rationale goes in the commit and PR. Only `### Upgrade Instructions` is verbose: `hermit-evolve` executes it step by step.
+ - **`/release` and `/fleet-release` are operator-initiated.** Never suggest them in plans, summaries, or next steps. `/release-status` is read-only and fine to suggest.
+ - **Proposals (`PROP-NNN`) are local bookkeeping.** Never mention one in a CHANGELOG entry, commit, PR, or branch name; describe the change on its own terms.
+ - **Never publish Claude Code session URLs** (session, share, or session-tied artifact links) in any externally visible surface. This is a public repo.
## Branching
- - **Default: PR `fix/<N>-<slug>` / `feat/<N>-<slug>` / `chore/<slug>` branches to `main`.** Same flow for contributors and maintainer. `<N>` is the GH issue number (omit if no issue), `<slug>` is a short kebab-case descriptor. Examples: `fix/44-self-update-race`, `feat/57-cortex-tagging`, `chore/upgrade-node-24`. Regular merge to `main`.
- - **Why main is safe as staging.** Claude Code's `/plugin update` only fires when `version` in `plugin.json` changes ([docs](https://code.claude.com/docs/en/plugins-reference#version-management)). Commits on `main` between releases are invisible to operators on the standard install path — `/release <slug>` is the actual ship event because it bumps `version` and promotes `[Unreleased]` → `[X.Y.Z]`. Caveats: brand-new installers and `--plugin-dir` testers get whatever is on `main` HEAD, so don't leave `main` knowingly broken for long.
- - **Tags ship to operators on next `/plugin update`.** The `version` bump inside `/release` is the gate — pre-release commits on `main` don't reach `/plugin update` users until that bump happens.
- - **Worktree discipline (`claude --worktree`).** When the session runs inside a git worktree, the repo root is the worktree (`git rev-parse --show-toplevel`), not the main checkout. Never `cd` into, edit, or `git -C` files under the main checkout or any sibling worktree — that's another session's territory. The only shared, intentionally main-rooted path is `.claude-code-hermit/` (gitignored hermit state). The `claude-code-dev-hermit` `worktree-boundary-guard` hook hard-blocks edits that escape the worktree.
+ - **PR `fix/<N>-<slug>` / `feat/<N>-<slug>` / `chore/<slug>` branches to `main`**, `<N>` the GH issue number (omit if none). Regular merge.
+ - **Main is safe as staging.** `/plugin update` only fires when `version` in `plugin.json` changes ([docs](https://code.claude.com/docs/en/plugins-reference#version-management)), so commits on `main` between releases are invisible to installed operators. Brand-new installers and `--plugin-dir` testers get `main` HEAD, so don't leave it knowingly broken for long.
+ - **Worktree discipline (`claude --worktree`).** The repo root is the worktree (`git rev-parse --show-toplevel`). Never `cd` into, edit, or `git -C` the main checkout or a sibling worktree. The one shared main-rooted path is `.claude-code-hermit/` (gitignored hermit state). The dev-hermit `worktree-boundary-guard` hook hard-blocks escapes.
## Layout gotchas
- - **Sibling-scan pattern**: `${CLAUDE_PLUGIN_ROOT}/../*/.claude-plugin/plugin.json` resolves to `plugins/*/...` and finds all fleet plugins as guaranteed siblings.
- - **Old standalone repos are redirect-only zombies**: `gtapps/claude-code-dev-hermit` and `gtapps/claude-code-homeassistant-hermit` exist but their `marketplace.json` redirects to this monorepo via `git-subdir`. **Do not push code there.** All work happens here.
+ - **Sibling-scan pattern**: `${CLAUDE_PLUGIN_ROOT}/../*/.claude-plugin/plugin.json` resolves to `plugins/*/...` and finds every fleet plugin.
+ - **Old standalone repos are redirect-only zombies**: `gtapps/claude-code-dev-hermit` and `gtapps/claude-code-homeassistant-hermit` redirect here via `git-subdir`. Do not push code there.
## graphify
- Two graph levels. Pick by scope **before** answering a codebase question.
-
- - **Inside one plugin** (most questions): `graphify query "<q>" --graph plugins/<name>/graphify-out/graph.json`
- - **Spanning plugins**, or about `scripts/`, `tests/cross-plugin/`, `marketplace.json`: `graphify query "<q>"` (root graph)
-
- - **The root graph is dominated by `claude-code-hermit`**, so a single-plugin question asked against it spends its budget on core's test harness and never reaches the answer. Always pass `--graph` for single-plugin questions.
- - `graphify path "<A>" "<B>"` for relationships between two nodes, `graphify explain "<concept>"` for one node and its neighbours.
- - **Refresh every level with `bun run graph`** (AST-only, no API cost). Run it after a pull, or when the graph is older than the last commit: a stale graph answers confidently about deleted code.
- - **Worktrees start with no graph on purpose.** A copied graph would be frozen at branch-creation time, containing none of the branch's own work. `hook-guard` silently no-ops when `graphify-out/` is absent and the session falls back to grep, which is always correct. If graph orientation is worth it for a long-lived worktree, run `graphify update .` inside it once: that builds a graph that includes the branch work. `bun run graph` refuses to run in a worktree.
+ - **Single-plugin questions (most) pass `--graph plugins/<name>/graphify-out/graph.json`.** The root graph is dominated by core's test harness and never reaches a single-plugin answer. Use the root graph only for `scripts/`, `tests/cross-plugin/`, `marketplace.json`, or cross-plugin questions.
+ - **Refresh with `bun run graph`** after a pull or when the graph predates the last commit: a stale graph answers confidently about deleted code.
+ - **Worktrees start with no graph on purpose** (a copied graph would predate the branch work); `hook-guard` no-ops and grep is always correct. `graphify update .` inside a long-lived worktree builds one that includes the branch. `bun run graph` refuses to run in a worktree.
## Environment quirks
- - **Secrets:** env vars → `.env` (gitignored); secret files (`.pem` etc.) → `.claude.local/` (gitignored). Never `.claude/` — it's checked in.
- - **Docker paths mirror the host** (`${PWD}:${PWD}` mount) — absolute paths are identical inside the container despite the container user being `claude`.
- - **`rm -rf` is a native deny** (`Bash(rm -rf *)` and the flag-order / path-prefixed siblings in `permissions.deny`). Use `rm -r` (no `-f`) for scratch cleanup. The native engine does not fold whitespace/`$IFS`/backslash obfuscation; those are classifier-watched, not a second permission engine.
- - **Subtree imports are unsquashed**: `git log --first-parent` for the monorepo-only view; full upstream commits live under each subtree merge.
- - **CI is path-filtered per plugin**: every plugin has a paths-filtered workflow under `.github/workflows/` (`test-hooks` core, `test-ha`, `test-dev`, `test-fitness`, `test-scribe`, `test-forge`, `test-feed`), so a PR touching one plugin runs only that plugin's suite. Root-file changes (`package.json`, `bun.lock`, `tsconfig.json`) trigger all of them.
- - **Shell `cd` persists across Bash calls.** Any `cd` in a Bash call leaves CWD pinned for subsequent Bash calls in the session — affects CWD-relative scripts like `heartbeat-precheck.ts .claude-code-hermit` (silently `SKIP|HEARTBEAT.md missing`) and commands like `git add`. Plugin test runners (`bun test` or `bash plugins/<slug>/tests/run-all.sh` run from inside the plugin dir) end inside `plugins/<slug>/`. Use absolute paths or prefix `cd "$(git rev-parse --show-toplevel)" && …` (resolves to the current tree root — the worktree under `claude --worktree`, the main checkout otherwise — never a hardcoded path).
+ - **Secrets:** env vars → `.env`; secret files (`.pem` etc.) → `.claude.local/`. Both gitignored. Never `.claude/`, which is checked in.
+ - **Docker paths mirror the host** (`${PWD}:${PWD}` mount): absolute paths are identical inside the container.
+ - **`rm -rf` is a native deny.** Use `rm -r` for scratch cleanup. Obfuscated forms are classifier-watched, not a second permission engine.
+ - **Subtree imports are unsquashed**: `git log --first-parent` for the monorepo-only view.
+ - **CI is path-filtered per plugin** under `.github/workflows/`; root-file changes (`package.json`, `bun.lock`, `tsconfig.json`) trigger every suite.
+ - **Shell `cd` persists across Bash calls.** Plugin test runners end inside `plugins/<slug>/`, and CWD-relative scripts like `heartbeat-precheck.ts .claude-code-hermit` then fail silently. Use absolute paths or prefix `cd "$(git rev-parse --show-toplevel)" && …` (never a hardcoded path).
## Verification
- - **Confirm Claude Code's own behavior empirically before building on it.** When a feature or bug depends on how Claude Code itself behaves (what a tool actually returns, the exact shape a hook receives on stdin, how the harness namespaces or loads things, what a setting or slash command really does, how a native feature fires), don't conclude from docs, memory, or assumption. Spin up a tmux session running real Claude Code, exercise the actual tool, feature, or behavior, and read what it does. The empirical verdict from that run is what you build on; if it contradicts the docs or your expectation, the live behavior wins and that gap is the finding.
- - **Run the probe with `/probe`.** It owns the tmux session, the sentinel-verdict protocol, and the model choice (haiku by default to keep cost down; classifier and skill-recall questions need sonnet, since haiku has no auto mode and silently falls back to `acceptEdits`). Under `--plugin-dir`, skills load at session start, so edits to a skill mid-probe are not picked up (relaunch to test changed skill text), and `${CLAUDE_PLUGIN_ROOT}` is not substituted (derive the plugin root from the skill's Base directory instead).
- - **Auto mode suspends wildcarded-interpreter `permissions.allow` rules** (e.g. `Bash(bun */scripts/*.ts*)`), empirically confirmed, so a hermit's own sealed script allow-list buys nothing there: every such call re-enters the classifier. Details: `plugins/claude-code-hermit/docs/security.md` § Auto-mode Classifier.
+ - **Confirm Claude Code's own behavior empirically before building on it**, with `/probe`: it owns the tmux session, the sentinel-verdict protocol, and the model choice. Live behavior wins over docs, memory, or assumption; a contradiction is the finding. Under `--plugin-dir`, skills load at session start (relaunch to test edited skill text) and `${CLAUDE_PLUGIN_ROOT}` is not substituted (derive the root from the skill's Base directory).
+ - **Auto mode suspends wildcarded-interpreter `permissions.allow` rules** (e.g. `Bash(bun */scripts/*.ts*)`), so a hermit's sealed script allow-list buys nothing there. Details: `plugins/claude-code-hermit/docs/security.md` § Auto-mode Classifier.
## Rules
- - Always use Context7 for library/API documentation, code generation, and setup/configuration steps — don't wait for an explicit request.
+ - Always use Context7 for library/API documentation, code generation, and setup/configuration steps, without waiting for an explicit request.
- Don't overengineer.
- - **This hermit is the plugin-dev special case.** When reasoning about utility of features in `plugins/claude-code-hermit/` (the shipped hermit), don't use this hermit's session history as evidence — the operator here maintains the plugin source. Target users are downstream operators who interact via Discord/Telegram and don't open feature branches.
+ - **This hermit is the plugin-dev special case.** When judging the utility of a feature in `plugins/claude-code-hermit/`, don't use this hermit's session history as evidence; target users are downstream operators on Discord/Telegram who don't open feature branches.