lema-mcp 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -0
  3. package/package.json +8 -5
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lema
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # lema-mcp
2
+
3
+ Your coding agent reasons well — it just doesn't know *your* system, and it keeps
4
+ re-proposing the options your team already ruled out. `lema-mcp` is a local
5
+ [MCP](https://modelcontextprotocol.io) server that fixes the second problem the
6
+ way no read-only context tool can: it **captures the decisions you make** — the
7
+ chosen option *and* the alternatives you killed — and then **enforces them**, so a
8
+ settled decision stops getting reopened. No account, no database, no network.
9
+
10
+ Most "context" tools are read-only: a nicer way to grep your docs. `lema-mcp`
11
+ reads too, but its job is **never-reopen**:
12
+
13
+ - Your agent settles a choice → it calls **`record_decision`** with what it chose
14
+ **and the alternatives it rejected, with why each was killed** (the part that
15
+ never survives into the code).
16
+ - Before anyone's agent proposes a direction → **`check_decided`** returns the
17
+ prior decision if that option is **CLOSED**.
18
+ - And on every edit, a **PreToolUse guard hook** (installed for you by `init`)
19
+ reads the draft change and surfaces a CLOSED decision *before* the dead option
20
+ gets re-proposed — enforced off both your captured decisions **and the repo's
21
+ own ADRs**.
22
+
23
+ ```bash
24
+ npx lema-mcp demo # 30-second never-reopen walkthrough on a throwaway temp dir
25
+ npx lema-mcp init # wire this repo for capture + enforcement (idempotent)
26
+ ```
27
+
28
+ Then open the repo in your agent and ask *"why did we choose X?"* — or just let it
29
+ work, and watch it record decisions and get nudged off the ones you settled.
30
+
31
+ ## What never-reopen looks like
32
+
33
+ Your agent settles a choice and calls `record_decision` with the chosen option
34
+ and the rejected alternatives. Later, anyone's agent reaches for a dead option —
35
+ in a `check_decided` call, in a `search_decisions` result, or while drafting an
36
+ edit the guard inspects — and the killed option comes back **CLOSED**, with the
37
+ original reason attached:
38
+
39
+ > ⛔ **CLOSED — do not propose "SWR":** no first-class mutation / cache
40
+ > invalidation — we'd hand-roll it *(decided 2026-06-04 · "Data fetching for the
41
+ > web app" · chose TanStack Query)*
42
+
43
+ So the agent surfaces the prior decision instead of re-litigating it. Supersede a
44
+ decision and the *previously chosen* option goes CLOSED too — never-reopen,
45
+ enforced both ways. (That's the real output of `npx lema-mcp demo`, run against a
46
+ throwaway temp dir.)
47
+
48
+ The guard is **advisory and fail-open today**: in its default `context` mode it
49
+ injects that CLOSED note as a non-blocking nudge for the agent — it never emits an
50
+ `allow` (which would skip your normal Edit/Write confirmation on the very edit
51
+ it's flagging) and it never hard-blocks (`deny`) in v1. Opt into
52
+ `LEMA_GUARD_MODE=ask` and a strong match prompts *you* before the edit; `off` is a
53
+ kill switch. Any error → it emits nothing and gets out of the way.
54
+
55
+ Decisions are captured to `.lema/decisions.jsonl` — a plain append-only file you
56
+ can commit, so your whole team's agents share the same memory through git. No key,
57
+ no LLM call on our side: your agent forms the decision; `lema-mcp` stores it and
58
+ serves it back.
59
+
60
+ ## Does enforcement actually change what the agent does?
61
+
62
+ We measured it on **two real public repos we didn't write** (Backstage, vite),
63
+ transcribing six of their own documented decisions into the `record_decision`
64
+ format and running the **real `lema-mcp guard` binary** on the agent's draft edits.
65
+ Agent and an arm-blind judge were Claude Sonnet 4.6; 168 trials, 0 errors; a
66
+ deterministic code check agreed with the judge on 163/168 (the 5 diffs
67
+ hand-verified in lema's favor). Three arms: **blind** (no doc), **docs** (the
68
+ decision pre-loaded in context), **lema** (guard only, no doc in context).
69
+
70
+ The honest result is an **existence proof**, not "agents are wrong 58% of the
71
+ time":
72
+
73
+ - Of the six well-documented public decisions, only **one** cut against the 2026
74
+ frontier model (`node-fetch` → native `fetch`). On that one contrarian decision,
75
+ a blind agent re-proposed the killed library **58.3% of the time (14/24, N=24)**;
76
+ lema drove it to **0%** — matching the docs-preloaded arm *without carrying the
77
+ doc in context*.
78
+ - On the **five decisions the model already gets right**, lema stayed silent: **0%
79
+ re-proposal and 0% false-abstain** across 48 trials. The nagging / false-positive
80
+ failure people fear about enforcement didn't happen.
81
+
82
+ The honest caveats, stated plainly: it's a **single contrarian decision-type**
83
+ (node-fetch, N=24) — a solid existence proof, not breadth. And a public-repo
84
+ benchmark *structurally understates* enforcement's value: public decisions are
85
+ disproportionately the ones the model already absorbed in training (that's why
86
+ they're widespread enough to be documented). The decisions where enforcement
87
+ actually moves the needle are **proprietary, contrarian, recent, team-specific** —
88
+ which by construction aren't in any public repo. That's the capture-forward thesis:
89
+ enforcement catches the decisions where your team disagrees with the AI's defaults,
90
+ and gets out of the way everywhere else.
91
+
92
+ Full method, the result table, and every raw trial: [`./docs/enforcement-lift`](./docs/enforcement-lift).
93
+
94
+ ## Install
95
+
96
+ `npx` needs only Node — no Go toolchain, no account:
97
+
98
+ ```bash
99
+ npx lema-mcp init # one-time: wire this repo for capture + enforcement
100
+ ```
101
+
102
+ `init` is non-destructive and idempotent — existing config is merged, not
103
+ clobbered, and re-running changes nothing. It writes **three files** via five
104
+ idempotent steps:
105
+
106
+ 1. **`.mcp.json`** — registers the `lema` MCP server (preserving any servers
107
+ already there).
108
+ 2. **`AGENTS.md`** — appends a short, managed capture-protocol block: *when you
109
+ settle a non-trivial decision call `record_decision` with what you chose and
110
+ rejected; before proposing a direction call `check_decided`; treat a CLOSED
111
+ result as binding.* This protocol is what actually drives capture — a hook
112
+ can't form the decision itself.
113
+ 3. **`.claude/settings.json`** — installs **three hooks**: a PostToolUse commit
114
+ reminder (fires only on `git commit`), the PostToolUse **capture-nudge**
115
+ (`lema-mcp nudge`, prompts `record_decision` when you edit a dependency
116
+ manifest), and the PreToolUse **never-reopen guard** (`lema-mcp guard`, the
117
+ enforcement above).
118
+
119
+ `init` does **not** create `.lema/decisions.jsonl` — the capture store is created
120
+ lazily on the first `record_decision`.
121
+
122
+ Prefer Go, or want a pinned binary?
123
+
124
+ ```bash
125
+ go install github.com/lemahq/lema-mcp/cmd/lema-mcp@latest
126
+ ```
127
+
128
+ Or wire it by hand — add to your agent's MCP config (Claude Code `.mcp.json`).
129
+ Note this gets you the read + capture tools, but not the guard/nudge hooks that
130
+ `init` installs:
131
+
132
+ ```json
133
+ {
134
+ "mcpServers": {
135
+ "lema": { "command": "npx", "args": ["-y", "lema-mcp@latest"] }
136
+ }
137
+ }
138
+ ```
139
+
140
+ With no flags, `lema-mcp` auto-discovers a decisions directory in the working
141
+ directory (`docs/adr`, `doc/adr`, `docs/adrs`, `docs/decisions`,
142
+ `docs/architecture/decisions`, `architecture/decisions`, `adr`, `.adr`) and an
143
+ `openspec/` tree if present. You can also point it explicitly:
144
+
145
+ ```bash
146
+ lema-mcp --adr-dir docs/adr # a local directory (no account, no network)
147
+ lema-mcp --repo github.com/org/name # a public repo (GITHUB_TOKEN for private)
148
+ ```
149
+
150
+ Other flags: `--ref` (branch/ref for a remote `--repo`), `--pattern` (ADR filename
151
+ regex), `--openspec-dir`, `--capture-file` (override the JSONL path), and
152
+ `--http`/`--port` (see *serve mode* below).
153
+
154
+ ## The tools
155
+
156
+ Your agent calls these over MCP. Six are always on; two more
157
+ (`search_docs` / `get_doc`) register automatically in local mode once a markdown
158
+ tree is indexed.
159
+
160
+ **Enforce + capture (the part read-only tools don't have):**
161
+
162
+ - **`record_decision`** — capture a decision you just settled: the chosen option,
163
+ the **rejected** alternatives (with why each was killed), optional rationale /
164
+ refs / constraint / consequence, and `supersedes` to retire an earlier one.
165
+ Rejected and superseded options come back CLOSED. Appends to
166
+ `.lema/decisions.jsonl`.
167
+ - **`check_decided`** — before proposing a direction, check whether it's already
168
+ decided and CLOSED. Matched off **both** the capture store **and the repo's own
169
+ ADRs**, so a documented decision stops a fresh agent even if it was never
170
+ captured live.
171
+
172
+ **Read (the entry point — query before you write code):**
173
+
174
+ - **`search_decisions`** — natural-language query → the most relevant atomic
175
+ claims (chosen / rejected / constraint / consequence) with their source ADR,
176
+ under a token budget. CLOSED flags surface here too.
177
+ - **`get_decision`** — one decision's full body, status, and typed edges.
178
+ - **`list_decisions`** — the decisions recorded in the repo, optionally by status.
179
+ - **`get_decision_graph`** — traverse typed edges (`supersedes`, `superseded_by`,
180
+ `depends_on`, `related_to`) to connected decisions.
181
+ - **`search_docs`** / **`get_doc`** *(local mode, when a doc tree is indexed)* —
182
+ sectioned, budgeted retrieval over the repo's project markdown (specs, READMEs,
183
+ agent instructions, ADR/openspec full text) so the agent reads the sections that
184
+ matter instead of whole files.
185
+
186
+ ```
187
+ > search_decisions "why did we choose an MCP-first architecture?"
188
+ ```
189
+
190
+ ```jsonc
191
+ // illustrative shape — atom text/ids vary by repo
192
+ {
193
+ "repo": "docs/adr",
194
+ "claims": [
195
+ { "id": "16-2", "type": "chosen", "text": "One MCP server is the single surface agents call — all writes route through it.", "ref": "ADR-0016" },
196
+ { "id": "27-4", "type": "rejected", "text": "Folding inference into lema was rejected on the serve path: the agent reasoning over the atoms is the customer's own.", "ref": "ADR-0027" }
197
+ ],
198
+ "tokens_used": 211,
199
+ "usage": { "atoms_tokens": 211, "source_decisions": 2, "source_tokens": 1840, "tokens_saved": 1629, "compression_ratio": 8.7 },
200
+ "truncated": false
201
+ }
202
+ ```
203
+
204
+ A note on token cost: the read tools return tight, sourced atoms (default
205
+ ~1500-token budget, tunable with `max_tokens`) instead of whole documents, and
206
+ every `search_decisions` response carries a self-reported `usage` block estimating
207
+ the tokens returned versus the source-document tokens for that call (the numbers
208
+ above are illustrative). On a large decision record that's a meaningful per-call
209
+ saving — but it's a local, self-measured estimate, not an audited benchmark, and
210
+ it's a side benefit. The reason to run lema is never-reopen, above.
211
+
212
+ ## The subcommands
213
+
214
+ - **`init [dir]`** — wire a repo for capture (the three files / three hooks above);
215
+ idempotent. The same code path backs the lema Workbench GUI's "enable capture"
216
+ button.
217
+ - **`demo`** — a ~30-second never-reopen walkthrough using the real capture +
218
+ enforce path against a throwaway temp dir that's deleted afterward. Nothing is
219
+ written to your repo. This is the fastest way to see the CLOSED behavior.
220
+ - **`guard`** — the PreToolUse hook body: reads the tool-call payload on stdin,
221
+ emits a never-reopen permission decision on stdout. Advisory, fail-open, always
222
+ exits 0. `init` installs it; you don't call it directly.
223
+ - **`nudge`** — the PostToolUse hook body: on an Edit/Write/MultiEdit to a
224
+ dependency manifest (`go.mod`, `package.json`, `cargo.toml`, `pyproject.toml`,
225
+ `requirements.txt`, `gemfile`, `build.gradle`, `pom.xml`), emits a non-blocking
226
+ reminder to `record_decision`. Fail-open. Installed by `init`.
227
+ - **`serve`** (≡ the `--http` flag) — serve the same engine over localhost HTTP
228
+ (default `:4321`, `--port`) for the lema Workbench desktop GUI instead of stdio
229
+ MCP.
230
+
231
+ ## Configuration & privacy
232
+
233
+ - **`LEMA_GUARD_MODE`** — `context` (default, non-blocking nudge), `ask` (prompt
234
+ the human on a strong match), or `off` (kill switch).
235
+ - **`LEMA_DISABLE_QUERY_LOGGING=1`** — drop query text from the usage log entirely.
236
+ Otherwise queries are scrubbed for credential-shaped substrings before logging.
237
+ - **`LEMA_USAGE_LOG` / `LEMA_QUESTION_LOG` / `LEMA_GUARD_LOG`** — opt-in local log
238
+ files (tool usage, unanswered questions, guard fires) for measuring and
239
+ calibrating; all off unless set.
240
+
241
+ ## Hosted retrieval (optional)
242
+
243
+ By default everything is local and lexical. To point `search_decisions` at hosted
244
+ hybrid retrieval over your full decision layer, set two env vars — no other change:
245
+
246
+ ```bash
247
+ LEMA_API_URL=https://<your-lema-api> LEMA_API_TOKEN=<bearer> lema-mcp
248
+ ```
249
+
250
+ In hosted mode `search_decisions` runs against the atom layer over `POST /retrieve`;
251
+ this is **search-only** in the MVP, so `get_decision` / `list_decisions` /
252
+ `get_decision_graph` return a search-only error and the doc tools aren't
253
+ registered. Capture and enforcement (`record_decision` / `check_decided` / the
254
+ guard) are **always local**.
255
+
256
+ ## License
257
+
258
+ MIT. `lema-mcp` is the free, local wedge of [**lema**](https://lema.sh) — the
259
+ system of record for *why*. The hosted decision graph, the team why-surface, and
260
+ the manager-facing Intelligence layer are coming at [lema.sh](https://lema.sh).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lema-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Local, database-less MCP server that makes your repo's decisions — and what it ruled out — queryable by your coding agent, and lets the agent record new ones with enforced never-reopen.",
5
5
  "bin": {
6
6
  "lema-mcp": "bin/lema-mcp.js"
@@ -8,11 +8,14 @@
8
8
  "files": [
9
9
  "bin"
10
10
  ],
11
+ "scripts": {
12
+ "prepack": "cp ../../README.md ./README.md && cp ../../LICENSE ./LICENSE"
13
+ },
11
14
  "optionalDependencies": {
12
- "lema-mcp-darwin-arm64": "0.5.0",
13
- "lema-mcp-darwin-x64": "0.5.0",
14
- "lema-mcp-linux-x64": "0.5.0",
15
- "lema-mcp-linux-arm64": "0.5.0"
15
+ "lema-mcp-darwin-arm64": "0.7.0",
16
+ "lema-mcp-darwin-x64": "0.7.0",
17
+ "lema-mcp-linux-x64": "0.7.0",
18
+ "lema-mcp-linux-arm64": "0.7.0"
16
19
  },
17
20
  "engines": {
18
21
  "node": ">=18"