Repository navigation
Releases
v2.0.7 — guardian judges what runs, not words in data
Castra guardian: rules apply to what a command runs, not to words in its data.
The vendored guardian matched its patterns against the whole command line. Words in data set them off: a drop table in a migration file being written, "credentials" in a code comment, Format-Volume in a handover note, a grep for risky words, getent passwd. A pattern could also run on into the next command, so git push … ; pkill -f vite read as a force push.
Now:
- Here-document bodies written to files, comment lines, and
grep/echosegments are left out, unless they reach something that runs them:bash,psql,sshorpowershellbodies,subprocesscalls in a Python body, bodies inside$(…), a script written and then run, copied or registered in the same command, orecho … | psql. - Each rule is checked per command segment. Only pipe rules (
curl … | sh) look across segments. - Key and credential files are judged by the file a command opens, like
.envin v2.0.6 / v0.2.2. getent passwdand/etc/passwdare no longer read as a credential change.- Gap closed: git's global options no longer hide the subcommand.
git -C <dir> push --forceused to pass every git rule; on the author's history four real--force-with-leasepushes went through that way.
Replayed over 58,056 real commands: every downgrade on a hand-off or confirm rule was a word in data. git push --force-with-lease stays a hand-off.
Also: test_forge.py sent one fallback-path reply to the live Jev, which made it flaky; that call now runs without Jev.
v2.0.6 — fewer false .env denials
Castra guardian: .env reads are judged by the file a command opens.
The vendored guardian denied any command where a word like grep, open or type was followed anywhere by .env, in every permission mode. It blocked git grep "process.env…", file-name checks such as ls | grep -c '^\.env', handover scripts whose text mentions .env.local, and even node --input-type=… lines.
It now looks at the file arguments of reading commands (cat, head, tail, less, bat, grep, rg, PowerShell Get-Content/gc/type):
- Still handed off: reading a
.envfile directly, inside$(…)or backticks, insidebash <<'SH'bodies, or through Pythonopen('.env…'). - No longer denied:
process.env/import.meta.envsearches, file-name checks, and.envmentioned in here-document text.
On the author's real history, 95 of 115 denials now pass; the remaining 20 are direct reads of .env files. Counting lines or names in a .env file (grep -c) and reading .env.example stay handed off, as before.
v2.0.5 — a go-ahead no longer closes edits
SETTLE gate: judged with the conversation, not the message alone.
Up to v2.0.4 the gate asked whether each message literally fixed the result. A short go-ahead ("진행해줘", "go ahead") after a working turn never does, so edits closed in the middle of agreed work.
The gate now gives Jev the user's message and the agent's last message and asks one question: to carry this out, must the agent choose something the user will see or rely on that nothing has settled yet? Continuing, approving or correcting the plan, fixing a reported problem, running, testing, deploying and standing procedures keep edits open; open-ended new work asks first.
| Author's messages, fresh sample of 100 (threshold chosen on a separate 100) | Wrongly closed | Wrongly opened |
|---|---|---|
| v2.0.4 rule, after a working turn | 42 of 42 | 0 of 5 |
| v2.0.5 rule | 1 of 42 | 0 of 5 |
- Optional:
~/.config/lean-forge/profile.txt, a short private note on how you instruct agents, is sent to Jev with each judgment. Keep it out of any repository. - Without Jev, a message after an asking turn counts as its answer, and the one-line reason path stays for mechanical work.
- Threshold 0.8, set on one person's messages; tune it on your own traffic.
Same change as lean-forge-55 v0.2.0, without its shell layer.
v2.0.4
Fixes
- Jev was silently off. The TypeSafe API edge now rejects Python's default User-Agent (HTTP 403, error 1010), so every Jev call failed and the gate closed every request. Requests now send a User-Agent.
- A refused marker counted as a used hatch. When Jev had answered and the agent still wrote the marker file, the turn never moved to asked, and the user's answer was triaged as a new request.
- Background-task notices re-triaged the gate. A
<task-notification>reaches the prompt hook like a user message; the gate now keeps its state.
Statistics corrected
- Earlier READMEs pooled the 36 hidden tests as if independent and reported p < 0.001. Results are now clustered by task (paired differences, 95% CI, sign test), and
bench/significance.pyprints the same numbers. - The accuracy gain over Castra + Ponytail holds: task pass rate +0.46 (95% CI +0.17 to +0.76), at about a minute and $0.18 more per task. The personal-setup comparison is not conclusive at 7 tasks.
- Added: on Claude Opus 5.5, 0.75 with no harness vs 0.97 with lean-forge (+0.23, 95% CI −0.04 to +0.49).
v2.0.3
PROVE: the protocol now states that the Castra check runs as argv without a shell, and shows how to wrap checks that use &&, pipes or ! (sh -c '...'). A live session had passed a shell string as one argument; the check exited 127 and the edited file stayed unverified.
lean-forge 2.0.2
Fix: a turn that only explained something no longer opens the gate for the next request.
Before 2.0.2, any turn that ended without edits counted as "the agent asked", so the next message was treated as an answer and file edits opened without triage. After a plain question ("what does total do?"), a new, vague feature request could slip through.
Now the message that follows such a turn is judged by Jev: is it a reply to that turn, or a new request? A reply opens the gate as before; a new request is triaged from scratch. Calibrated on 10 exchanges (replies ≥ 0.79, new requests ≤ 0.24; threshold 0.5). If Jev is unavailable, the message counts as a reply (2.0.1 behavior).
hooks/test_forge.pygains the case; it fails on 2.0.1 and passes on 2.0.2.- README state diagrams show the new transition.
lean-forge 2.0.1
Ask what matters. Build the least. Prove it ran.
2.0.1 changes the documentation only: the README (English and Korean) now describes lean-forge on its own terms. Hooks, vendored Castra and Ponytail, and the benchmark are the same as in 2.0.0.
lean-forge 2.0 is one Claude Code plugin that combines three ideas which usually pull against each other:
- SETTLE — a hook keeps file edits closed until the decisions a result depends on are answered. Questions come once, with a recommendation and a worked boundary example each, ending with "any other rule you hold?". Jev opens the gate at once for requests that already fix their own result (typos, renames, stated output).
- BUILD — Ponytail 4.5.0, vendored. Confirmed rules become tests first; then the smallest change that passes.
- PROVE / REPORT — Castra 0.9.1, vendored unchanged: per-file evidence ledger, execution contract, guardian for risky commands, release gate, checkpoints.
Measured (8 tasks, 36 hidden tests, Claude Opus 5, no other harness loaded)
| Hidden tests | Time | Cost | |
|---|---|---|---|
| Castra + Ponytail | 15/36 | 10 min | $3.79 |
| lean-forge 2.0 (2 runs) | 31/36 · 36/36 | 18 min | $5.2 |
- vs Castra + Ponytail: 2.2× accuracy (p < 0.001), no significant time difference (p = 0.07)
- 6 of the 8 missed tests came from two runs where the scripted user answered against its own intent file.
Benchmark, hidden tests, runner and per-run numbers are in bench/.
Install
/plugin marketplace add beyondworks/lean-forge
/plugin install lean-forge@lean-forge
Requires python3, node, git. Optional: TYPESAFE_API_KEY (environment variable or macOS keychain item) for Jev. If you already run Castra hooks or the Ponytail plugin, turn them off; lean-forge contains both.
Known limits
One small benchmark repository; large codebases and on-device verification were not benchmarked. The SETTLE gate watches the edit tools only; shell writes pass it. The Jev threshold (0.5) was set on 16 requests.
한국어 소개: README.ko.md
lean-forge 2.0.0
Ask what matters. Build the least. Prove it ran.
lean-forge 2.0 is one Claude Code plugin that combines three ideas which usually pull against each other:
- SETTLE — a hook keeps file edits closed until the decisions a result depends on are answered. Questions come once, with a recommendation and a worked boundary example each, ending with "any other rule you hold?". Jev opens the gate at once for requests that already fix their own result (typos, renames, stated output).
- BUILD — Ponytail 4.5.0, vendored. Confirmed rules become tests first; then the smallest change that passes.
- PROVE / REPORT — Castra 0.9.1, vendored unchanged: per-file evidence ledger, execution contract, guardian for risky commands, release gate, checkpoints.
Measured (8 tasks, 36 hidden tests, Claude Opus 5, no other harness loaded)
| Hidden tests | Time | Cost | |
|---|---|---|---|
| Castra + Ponytail | 15/36 | 10 min | $3.79 |
| lean-forge 2.0 (2 runs) | 31/36 · 36/36 | 18 min | $5.2 |
- vs Castra + Ponytail: 2.2× accuracy (p < 0.001), no significant time difference (p = 0.07)
- 6 of the 8 missed tests came from two runs where the scripted user answered against its own intent file.
Benchmark, hidden tests, runner and per-run numbers are in bench/.
Install
/plugin marketplace add beyondworks/lean-forge
/plugin install lean-forge@lean-forge
Requires python3, node, git. Optional: TYPESAFE_API_KEY (environment variable or macOS keychain item) for Jev. If you already run Castra hooks or the Ponytail plugin, turn them off; lean-forge contains both.
Known limits
One small benchmark repository; large codebases and on-device verification were not benchmarked. The SETTLE gate watches the edit tools only; shell writes pass it. The Jev threshold (0.5) was set on 16 requests.
한국어 소개: README.ko.md