No description
  • Shell 89.9%
  • JavaScript 10.1%
Find a file
Lynx f6e5100988 feat(skills): vendor stop-slop prose-cleanup skill
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>
2026-06-30 12:22:06 +02:00
agents feat: vendor SocratiCode skills + agent, drop global plugin install 2026-06-09 16:23:17 +02:00
commands feat(handoff): add best-effort Phase 5 to refresh the SocratiCode index 2026-06-27 18:04:48 +02:00
plugins feat: add OpenCode research-enforcement plugin (Context7/SocratiCode tripwire) 2026-06-19 18:34:23 +02:00
rules feat: install global CLAUDE.md from a non-negotiables template 2026-06-19 18:05:13 +02:00
scripts feat: add reset-socraticode script to wipe Qdrant for a clean re-index 2026-06-13 10:52:02 +02:00
skills feat(skills): vendor stop-slop prose-cleanup skill 2026-06-30 12:22:06 +02:00
templates feat(bootstrap): install statusline (context/token usage, cost, git, rate limits) 2026-06-30 12:10:23 +02:00
.env.example docs: clarify SonarQube token placeholders in .env.example 2026-06-12 19:06:27 +02:00
.gitignore refactor: require service URLs via env, drop hardcoded LAN hosts 2026-06-12 18:38:32 +02:00
bootstrap-opencode.sh feat: add OpenCode research-enforcement plugin (Context7/SocratiCode tripwire) 2026-06-19 18:34:23 +02:00
bootstrap.sh feat(skills): vendor stop-slop prose-cleanup skill 2026-06-30 12:22:06 +02:00
README.md feat(skills): vendor stop-slop prose-cleanup skill 2026-06-30 12:22:06 +02:00
socraticode_todo.md fix: index SocratiCode per source root for polyglot repos 2026-06-13 10:41:58 +02:00

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.sh ships 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.md as a native global fallback when no AGENTS.md exists, so both agents share the same inline ruleset for free.
  • ~/.config/opencode/opencode.json with "instructions": [...] listing each ~/.claude/rules/*.md file — OpenCode loads them additively on top of the CLAUDE.md fallback. expo.md is excluded (it's path-scoped via paths: frontmatter, which OpenCode can't honor; the script drops any such rule generically).
  • MCP: context7 (docs) + playwright (browser) + sonarqube (code quality, reads SONARQUBE_URL/SONARQUBE_TOKEN), the same trio Claude Code gets. Context7 uses CONTEXT7_API_KEY if set, runs keyless otherwise.
  • Provider preset: a llama-cpp entry (@ai-sdk/openai-compatible) pointing at a LAN host, set as the default model. Edit provider.llama-cpp.options.baseURL + the model id in ~/.config/opencode/opencode.json, override at install time with LLAMACPP_URL=… LLAMACPP_MODEL=… ./bootstrap-opencode.sh, or swap the block for Anthropic / OpenRouter and run opencode auth login.
  • Permissions mirror Claude Code's pre-approved bash allow-list (git *, mise *, uv *, …) via OpenCode's permission.bash map; everything else defaults to ask.
  • opencode-yolo (~/.local/bin) — an opt-in launcher that runs OpenCode with all prompts auto-approved (sets the per-process OPENCODE_PERMISSION env var; OpenCode has no shipped --dangerously-skip-permissions flag). It's a separate command, so plain opencode keeps 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/*.md are copied into ~/.config/opencode/commands. Skills need no copy — OpenCode discovers SKILL.md from ~/.claude/skills (a Claude-compatible search path), so diagnose + the SocratiCode skills are visible to its skill tool automatically. The codebase-explorer agent is derived into ~/.config/opencode/agents/ in OpenCode's frontmatter format (mode: subagent + a least-privilege permission: 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 /sonar shells 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 meUSER token (SONARQUBE_URL + SONARQUBE_TOKEN).
  • The /sonar scanner 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. uv over pip. podman over docker. pnpm over npm. 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 vim and git, 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!)