Skip to content

Development

Arnel Robles edited this page Oct 20, 2026 · 2 revisions

Development

Layout

Path Holds
.claude-plugin/ plugin.json (name, version and the brain MCP server) and marketplace.json
skills/ method, shape-ticket, adversarial-review, lean-retro
hooks/ hooks.json and one Python file per hook
lib/ hookkit.py (shared hook plumbing), house_style.py (the slop and attribution lists), leanstats.py and heavy_lock.py (the lane lock)
bin/ The scripts. Linked at the repository root.
examples/, templates/ A starting rules file and the stale pull request workflow
vscode/ The Lean Brain Extension, with its own vitest tests
docs/ remote-lane.md and the specs and plans
tests/run-all.sh Runs every self-test

Tests

bash tests/run-all.sh

It does four things:

  1. Fails on a duplicate key in the plugin JSON files. A text merge can leave two PostToolUse keys, which still parses, and the parser keeps only the last.
  2. Fails when skills, hooks, bin, lib or .claude-plugin changed since the current version's tag.
  3. Lists scripts that have no --self-test.
  4. Runs every --self-test it finds, each capped at 300 seconds where timeout or gtimeout exists.

A script opts in by handling --self-test. The runner finds scripts by that string, so a new hook or script is covered without editing the runner. Exit code 77 from a self-test means this machine cannot run it, and counts as skipped.

CI runs tests/run-all.sh on Ubuntu for every pull request and every push to main. It fetches the tags, since step 2 compares against the version's tag.

run-all.sh does not run the extension's tests. In vscode/:

npm ci
npm test

npm test runs vitest. npm run typecheck runs tsc --noEmit. The extension has its own version in vscode/package.json, separate from the plugin's, and the version check in run-all.sh does not cover vscode/.

Versioning

Claude Code updates an installed plugin only when its version changes. So:

  1. A change to skills/, hooks/, bin/ or lib/ moves the version in .claude-plugin/plugin.json.
  2. The merge is tagged v<version>.
  3. claude plugin update lean-agent@lean-agent picks it up.

Step 2 of the tests is what enforces step 1.

Platforms

Hooks and scripts run on Linux and on macOS's stock bash 3.2. heavy.sh uses util-linux flock on Linux and a Python lock helper (lib/heavy_lock.py) elsewhere, and its self-test runs the Python path everywhere and flock on Linux. It reports skipped (77) only when a machine has neither python3 nor flock.

Hooks need Python 3.9 or newer and use only the standard library.

Writing a hook

  • Read one JSON event on stdin with hookkit.read_event().
  • Run main through hookkit.run, which turns any internal error into exit 0.
  • Give it an off switch and a --self-test.
  • Register it in hooks/hooks.json.

A hook that notes should never block. A hook that blocks should name what to change.

Changes to the method

Every change arrives as a pull request and a person merges it. Changes to the rules themselves come from the Retro, each with the signal that triggered it and the number that should move.

No agent attribution and no slop in commits, pull requests, issues or releases. The public-text.py hook blocks both. Rewrite the text, do not turn the hook off.

Clone this wiki locally