- Shell 89.9%
- JavaScript 10.1%
Adds Hardik Pandya's stop-slop skill (MIT) under skills/. The agent reaches for it when drafting/editing prose to strip AI tells. Picked up automatically by bootstrap.sh's skills/*/ install glob; banner count and README skills tree updated to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| agents | ||
| commands | ||
| plugins | ||
| rules | ||
| scripts | ||
| skills | ||
| templates | ||
| .env.example | ||
| .gitignore | ||
| bootstrap-opencode.sh | ||
| bootstrap.sh | ||
| README.md | ||
| socraticode_todo.md | ||
lean-code
A minimalist Claude Code config that does three things: install, configure, get out of the way.
Heads up — this is not a copy-paste magic script. It's calibrated to my setup, my projects, my mood. There's a Qdrant on my homelab, a llama-cpp endpoint, Sonar, opinions about coffee, and a handful of rules that exist because of incidents (...slops!) you weren't around for. Plan to read every file and swap out whatever doesn't match your reality before you trust any of it. You'll inherit my bugs otherwise!
Why this exists
ECC has skills, agents, hooks, MCP servers, a project memory system, slash commands for every imaginable workflow, and an opinion about which way the toilet paper goes. It's a lot. Most of it is good, but it's a BEAST. I wanted to understand what's actually happening when Claude reads my repo and decides what to do next, so... so I did the only thing I could do, I asked Claude to tell me about itself.
This is the rebuild from first principles and some other blogs and materials. Strip everything to the bone, then add back only the pieces I can explain to the sharpest knife in the drawer after they've had three beers. If I can't justify a file, the file dies. The result is small enough to read end-to-end in one sitting, opinionated enough to actually matter, and dumb enough that I can debug it with cat and grep while petting the dog.
If you want a fully-featured agentic workshop, install ECC. If you want to understand how the moving parts fit together and you like tears and suffer, you can build your own. (You will thank yourself later!)
The three-layer model
The whole thing is three layers.
System layer — bash tools, Claude Code itself, the MCPs I actually use. Installed once per machine by bootstrap.sh. After this runs I have a working AI coding setup. This layer doesn't know anything about my projects.
Global config layer — the rules, slash commands, and skills that live in ~/.claude/. Loaded by Claude Code every time it starts, in every project. This is where opinions live: "use uv, not pip", "read before you edit", "tests have tiers, pick one." Three artifact types, three invocation models: a rule is an always-on belief, a command is a tool I pick up (/plan, /handoff, /sonar), a skill is a reflex the agent reaches for on its own (diagnose, when a bug resists the first fix). Also installed by bootstrap.sh.
Project layer — per-project state. Three files: CLAUDE.md (durable status the agent reads at session start), NOTES.md (in-session scratch that gets drained by /handoff at session end), and CONTEXT.md (the domain glossary — one word per concept, so naming stays consistent and the agent burns fewer tokens decoding jargon). For full-stack scaffolds, bootstrap-project.sh generates the whole project including these three files in one go. (well... it's not perfect yet)
System assumes nothing (EXCEPT! BTW... I use Arch. And the scripts too!). Global assumes system. Project assumes global. Layers don't reach across.
What's in the box
lean-code/
├── bootstrap.sh # one-shot install: tools + Claude Code + global config
├── bootstrap-opencode.sh # agent-layer add-on: OpenCode CLI + ~/.config/opencode config
│
├── rules/ # auto-loaded by Claude Code from ~/.claude/rules/
│ ├── core.md # universal principles, security, YAGNI
│ ├── discipline.md # read-before-edit, one-change-one-verify, stop after 3 fails
│ ├── toolchain.md # mise/uv/podman/pnpm — the modern stack lock-in
│ ├── context7.md # consult Context7 for library docs vs. trusting stale memory
│ ├── testing.md # tiered testing (script / library / app), anti-patterns
│ ├── workflow.md # session-start ritual, conventional commits, handoff cues
│ ├── quality-gate.md # native floor (types/lint/security/tests) the agent keeps green
│ ├── expo.md # SDK 55 gotchas (path-scoped, only loads in Expo projects)
│ └── understand-anything.md # Understand-Anything visualizer — opt-in only
│
├── commands/ # slash commands — human-typed, never auto-invoked
│ ├── plan.md # /plan — grill, draft, WAIT for confirmation, then code
│ ├── sonar.md # /sonar — deep SonarQube scan + dashboard, you decide (opt-in)
│ └── handoff.md # /handoff — drain NOTES, update CLAUDE.md, commit, done
│
├── skills/ # model-invoked — the agent reaches for these on its own
│ ├── diagnose/SKILL.md # disciplined debugging loop for hard bugs (auto-fires when stuck)
│ ├── stop-slop/ # vendored — strip AI tells from prose (fires when writing/editing)
│ ├── codebase-exploration/ # vendored SocratiCode — search/graph/read a codebase
│ └── codebase-management/ # vendored SocratiCode — index, watch, context artifacts
│
├── agents/ # subagents the model can delegate to
│ └── codebase-explorer.md # vendored SocratiCode — deep multi-file exploration
│
└── scripts/
├── bootstrap-project.sh # scaffold a full FastAPI+Vite project, including the two .md files
├── setup-socraticode.sh # wire a project into the Socraticode MCP (per-project .mcp.json)
└── reset-socraticode.sh # drop Qdrant collections for a clean SocratiCode re-index
Install
Two scripts, run in order. Step 1 is required for everyone. Step 2 is optional — add it only if you want OpenCode too.
Configure your endpoints first (.env)
The scripts bake in no host defaults — they stay portable by reading every service URL from the environment, so set these up before you run either script. Copy the example, point it at wherever your services actually live, and source it into the shell you'll run the scripts from:
cp .env.example .env
$EDITOR .env
set -a; . ./.env; set +a # export everything into this shell
| Var | Used by | Notes |
|---|---|---|
QDRANT_URL, OLLAMA_URL |
SocratiCode indexing | required, no default |
EMBEDDING_MODEL |
SocratiCode | optional (defaults to nomic-embed-text) |
LLAMACPP_URL, LLAMACPP_MODEL |
OpenCode local model | URL required; model optional |
SONARQUBE_URL, SONARQUBE_TOKEN |
SonarQube MCP server | USER token |
SONAR_HOST_URL, SONAR_TOKEN |
/sonar scanner |
Global Analysis token |
.env is gitignored — secrets never get committed. Anything you'd rather keep
permanent goes in your shell rc instead. Tip: prefer 127.0.0.1 over localhost
— rootless podman can resolve localhost to IPv6 and miss an IPv4-only service.
Step 1 — bootstrap.sh
On a fresh, minimal Arch box (no GUI needed):
git clone <this-repo> ~/lean-code
cd ~/lean-code
./bootstrap.sh
This installs the system layer (git, mise, uv, podman, pnpm, Claude Code, the MCPs) and the global config layer — it drops rules/, commands/, skills/, agents/, and the templates into ~/.claude/, and copies bootstrap-project, setup-socraticode + reset-socraticode into ~/.local/bin (add that to your PATH). Run it once per machine.
Think of this script as the single source of truth. Everything downstream — including OpenCode — reuses what it produces, above all the rule files in ~/.claude/rules/.
Step 2 — bootstrap-opencode.sh (optional, adds OpenCode)
Want OpenCode, the open-source agent — alongside Claude Code, or instead of it? Run:
./bootstrap-opencode.sh
⚠️ Run Step 1 first — even if you never plan to use Claude Code.
bootstrap-opencode.shships no rules of its own. It just points OpenCode at the same~/.claude/rules/files Step 1 created. That's the entire point: one set of rules, two agents, maintained in one place — never the same rule copied into two configs. Skip Step 1 and this script preflight-checks, finds no rules, and stops.
The two scripts complement each other: Step 1 owns the system and the rules; Step 2 is a thin adapter that teaches OpenCode to read them (plus an OpenCode-shaped MCP + provider config). After both, claude and opencode behave the same way in every project — same rules, same conventions, same blind spots ignored.
What Step 2 wires up
- No symlink, no
AGENTS.md. OpenCode reads~/.claude/CLAUDE.mdas a native global fallback when noAGENTS.mdexists, so both agents share the same inline ruleset for free. ~/.config/opencode/opencode.jsonwith"instructions": [...]listing each~/.claude/rules/*.mdfile — OpenCode loads them additively on top of the CLAUDE.md fallback.expo.mdis excluded (it's path-scoped viapaths:frontmatter, which OpenCode can't honor; the script drops any such rule generically).- MCP:
context7(docs) +playwright(browser) +sonarqube(code quality, readsSONARQUBE_URL/SONARQUBE_TOKEN), the same trio Claude Code gets. Context7 usesCONTEXT7_API_KEYif set, runs keyless otherwise. - Provider preset: a
llama-cppentry (@ai-sdk/openai-compatible) pointing at a LAN host, set as the default model. Editprovider.llama-cpp.options.baseURL+ the model id in~/.config/opencode/opencode.json, override at install time withLLAMACPP_URL=… LLAMACPP_MODEL=… ./bootstrap-opencode.sh, or swap the block for Anthropic / OpenRouter and runopencode auth login. - Permissions mirror Claude Code's pre-approved bash allow-list (
git *,mise *,uv *, …) via OpenCode'spermission.bashmap; everything else defaults toask. opencode-yolo(~/.local/bin) — an opt-in launcher that runs OpenCode with all prompts auto-approved (sets the per-processOPENCODE_PERMISSIONenv var; OpenCode has no shipped--dangerously-skip-permissionsflag). It's a separate command, so plainopencodekeeps the scoped allow-list above. ⚠️ Edits + bash run unconfirmed — use only in a throwaway VM / container / git worktree.- Commands, skills, and the codebase agent. The same
commands/*.mdare copied into~/.config/opencode/commands. Skills need no copy — OpenCode discoversSKILL.mdfrom~/.claude/skills(a Claude-compatible search path), sodiagnose+ the SocratiCode skills are visible to itsskilltool automatically. Thecodebase-exploreragent is derived into~/.config/opencode/agents/in OpenCode's frontmatter format (mode: subagent+ a least-privilegepermission:block — read/grep/glob/list/skill allowed, edit/bash/webfetch denied) from the same agent body Claude Code uses. - SocratiCode + Understand-Anything work with both agents — see Codebase analysis below.
Use it
Starting a new project (full-stack, MY stack! you may skip the bootstrap-project):
bootstrap-project.sh my-thing
cd my-thing
claude
You get a scaffolded FastAPI backend, Vite frontend, working test setup at three tiers, an initial commit, and CLAUDE.md + NOTES.md + CONTEXT.md pre-filled with the deterministic facts the script knows (versions, run commands, project structure) plus an empty glossary ready to populate. Then run /plan and let the agent "grill and cook", populate the rest based on what you actually want to build.
Day-to-day:
/plan <what you want> — the agent reads the rules, grills(thx Matt) you one question at a time (proposing its own answer each time, exploring the code instead of asking when it can), drafts a plan, and waits for you to say go. Domain terms settled during the grilling land in CONTEXT.md as they're resolved. Good for anything bigger than "fix this typo."
/handoff at the end of a session — drains NOTES.md (every entry gets classified: promote to a task, promote to a comment, promote to a decision doc, or delete), updates CLAUDE.md's Status block, and commits the bookkeeping as a clean chore commit. Future-you opens the next session and the state is right there.
In between, you (I mean, the AI...) just code. The rules quietly steer the agent away from the usual failure modes. If something surprising comes up mid-task — a TODO surfaced by the code, a workaround you applied, an open question — append a timestamped entry to NOTES.md and keep going. /handoff will deal with it.
That's the whole model. Read the files, follow the conventions, commit the diffs.
Codebase analysis: one for me, one for the "dude" who does the heavy lifting
Two tools live in the box doing similar-sounding work but pointed at different audiences.
SocratiCode MCP is the agent's eyes. The agent queries it on demand during a session — semantic search, blast-radius impact, symbol-level call graphs. It reaches for it the way a human reaches for grep, except it understands meaning. It is not installed as a global Claude Code plugin — the plugin's bundled MCP server is static (Docker-managed Qdrant/Ollama, auto-generated project ID), and my Qdrant/Ollama addresses differ per project and machine. Instead: scripts/setup-socraticode.sh writes a per-project .mcp.json that runs npx -y socraticode against my own Qdrant and Ollama with no pinned project ID — so each source root gets its own per-path collection and the agent indexes polyglot repos one source root at a time (reset-socraticode wipes the collections for a clean re-index). The plugin's workflow skills (codebase-exploration, codebase-management) + the codebase-explorer agent are vendored into this repo (skills/, agents/) and deployed with the rest of the rules pack. Used silently in every project. Cheap to have around because nothing fires unless the agent asks.
Understand-Anything is the human's eyes. Optional, prompt-gated in both bootstraps: bootstrap.sh installs it as a Claude Code plugin, bootstrap-opencode.sh installs it via the upstream installer's first-class opencode backend (install.sh opencode — clones to ~/.understand-anything/repo, symlinks the skills into ~/.agents/skills). Either way its restraint rule rides along: rules/understand-anything.md is one of the files OpenCode's instructions glob pulls in. It builds a knowledge graph and ships a Vite dashboard that visualises layers, dependencies, and a guided tour — perfect for onboarding a teammate, prepping a code review, or amusing the moneybag who needs to see something fancy before signing the next invoice. The agent will not invoke it on its own — rules/understand-anything.md makes that explicit.
Roughly: SocratiCode answers questions for the agent; Understand-Anything answers questions for the rest of us.
Both work with either agent, with full parity. SocratiCode ships only as a project-scoped MCP server (no global plugin) — setup-socraticode autodetects which agents you have installed and writes the right config for each (.mcp.json for Claude Code, the mcp block in opencode.json for OpenCode). The skills are shared verbatim: both read the same SKILL.md files from ~/.claude/skills (OpenCode lists it as a Claude-compatible search path), so bootstrap.sh deploys them once and OpenCode picks them up for free. The agent is the one artifact whose format genuinely differs — Claude reads ~/.claude/agents/codebase-explorer.md (vendored as-is), while OpenCode reads ~/.config/opencode/agents/ with its own mode: subagent + permission: frontmatter, so bootstrap-opencode.sh derives that copy from the same agent body (one source, two frontmatters — the same trick setup-socraticode uses for the MCP block). Understand-Anything is a first-class OpenCode backend of its own installer, so bootstrap-opencode.sh offers it as a prompt-gated step (install.sh opencode), the same way bootstrap.sh prompts for the Claude Code plugin.
Run claude or opencode from any project — both read the same rules, follow the same conventions, ignore the same noise.
Code quality: the floor and the deep scan
Two layers, both opinionated.
The native floor runs every loop, for free: rules/quality-gate.md makes the
agent clear types, lint, security, and tests before it says "done." No server, no
tokens — just the tools already on the box. Green here means "not obviously
broken," nothing more.
The deep scan is /sonar — a lever I pull, never auto-fired. It runs the
SonarQube scanner against a SonarQube server I host, then reads the findings
back inline. bootstrap.sh pre-pulls both Sonar images so the first scan doesn't
stall on a download:
sonar-scanner-cli— the scanner/sonarshells out to.mcp/sonarqube— the MCP server both Claude Code and OpenCode use to read issues back as me.
Two tools, two token types, one server. The split trips people up:
- The MCP server reads issues as me → USER token (
SONARQUBE_URL+SONARQUBE_TOKEN). - The
/sonarscanner pushes analysis results → Global Analysis token (SONAR_HOST_URL+SONAR_TOKEN).
Both URLs point at the same running SonarQube. Generate both tokens from the web UI
(avatar → My Account → Security → Generate Tokens, pick the type per above); the
per-project analysis key lives in sonar-project.properties. You bring your own
SonarQube — lean-code wires the plumbing, not the server.
Design rules
- YAGNI, ruthlessly. Every file justifies itself or it dies. Three near-duplicate rule sets become one. Templates with placeholder values get the values filled in deterministically by the script, not left for a human to fix.
- Opinionated by default. Modern tools, not legacy ones.
uvoverpip.podmanoverdocker.pnpmovernpm. Expo Router over React Navigation. The rules don't say "consider", they say "always" — and where there's a real reason to break a rule, the rule says so. - Layers don't reach across. A rule doesn't depend on a specific project structure. A project doesn't depend on a specific rule being present. Each layer works alone.
- Plain text, no clever encoding. Markdown for everything humans read. Bash for everything machines run. If you can't fix it with
vimandgit, it doesn't belong here. - No metrics. Coverage thresholds, lint scores, complexity numbers — all gameable by an LLM, all noise. Quality is enforced by criteria (happy path / error path / boundary), not by a number to chase.
Not for
- Teams who need uniform tooling enforced via CI hooks
- Polyglot monorepos (the rules assume Python or TypeScript or both)
- Anyone who thinks "1% test coverage gain" is a meaningful sentence
Credits
The skeleton owes its existence to Everything Claude Code. This repo is what's left after asking "but do I actually need that?" eight hundred times. Go look at ECC if you want the deluxe version; come back here when you want to know which parts I need. (It's an opinion, not the only right way. But at least, I understand it better now!)