@nxuss/lemma 1.5.0 → 1.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.
- package/README.md +182 -6
- package/bin/checkpoint.js +49 -0
- package/bin/init.js +21 -2
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +50 -6
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/index.js +23 -0
- package/dist/cjs/mcp/index.js.map +1 -1
- package/dist/cjs/mcp/prompts.d.ts +1 -2
- package/dist/cjs/mcp/prompts.d.ts.map +1 -1
- package/dist/cjs/mcp/prompts.js +34 -129
- package/dist/cjs/mcp/prompts.js.map +1 -1
- package/dist/cjs/mcp/resources.d.ts.map +1 -1
- package/dist/cjs/mcp/resources.js +4 -4
- package/dist/cjs/mcp/resources.js.map +1 -1
- package/dist/cjs/mcp/tool-profiles.d.ts +55 -0
- package/dist/cjs/mcp/tool-profiles.d.ts.map +1 -0
- package/dist/cjs/mcp/tool-profiles.js +204 -0
- package/dist/cjs/mcp/tool-profiles.js.map +1 -0
- package/dist/cjs/mcp/tools.d.ts +14 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +190 -27
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.d.ts +36 -2
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.js +104 -23
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -1
- package/dist/cjs/utils/AutoContextBundle.d.ts.map +1 -1
- package/dist/cjs/utils/AutoContextBundle.js +17 -20
- package/dist/cjs/utils/AutoContextBundle.js.map +1 -1
- package/dist/cjs/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/cjs/utils/ContextSqueezer.js +5 -3
- package/dist/cjs/utils/ContextSqueezer.js.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.d.ts +13 -0
- package/dist/cjs/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.js +58 -0
- package/dist/cjs/utils/ConversationCheckpoint.js.map +1 -1
- package/dist/cjs/utils/SavingsLedger.d.ts +38 -1
- package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -1
- package/dist/cjs/utils/SavingsLedger.js +89 -13
- package/dist/cjs/utils/SavingsLedger.js.map +1 -1
- package/dist/cjs/utils/TestAutofixInterceptor.d.ts.map +1 -1
- package/dist/cjs/utils/TestAutofixInterceptor.js +15 -26
- package/dist/cjs/utils/TestAutofixInterceptor.js.map +1 -1
- package/dist/cjs/utils/reportSavings.d.ts +15 -0
- package/dist/cjs/utils/reportSavings.d.ts.map +1 -1
- package/dist/cjs/utils/reportSavings.js +13 -1
- package/dist/cjs/utils/reportSavings.js.map +1 -1
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +50 -6
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/index.js +24 -1
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/mcp/prompts.d.ts +1 -2
- package/dist/esm/mcp/prompts.d.ts.map +1 -1
- package/dist/esm/mcp/prompts.js +33 -128
- package/dist/esm/mcp/prompts.js.map +1 -1
- package/dist/esm/mcp/resources.d.ts.map +1 -1
- package/dist/esm/mcp/resources.js +4 -4
- package/dist/esm/mcp/resources.js.map +1 -1
- package/dist/esm/mcp/tool-profiles.d.ts +55 -0
- package/dist/esm/mcp/tool-profiles.d.ts.map +1 -0
- package/dist/esm/mcp/tool-profiles.js +196 -0
- package/dist/esm/mcp/tool-profiles.js.map +1 -0
- package/dist/esm/mcp/tools.d.ts +14 -0
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +188 -29
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.d.ts +36 -2
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.js +103 -23
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -1
- package/dist/esm/utils/AutoContextBundle.d.ts.map +1 -1
- package/dist/esm/utils/AutoContextBundle.js +17 -20
- package/dist/esm/utils/AutoContextBundle.js.map +1 -1
- package/dist/esm/utils/ContextSqueezer.d.ts.map +1 -1
- package/dist/esm/utils/ContextSqueezer.js +5 -3
- package/dist/esm/utils/ContextSqueezer.js.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.d.ts +13 -0
- package/dist/esm/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.js +57 -0
- package/dist/esm/utils/ConversationCheckpoint.js.map +1 -1
- package/dist/esm/utils/SavingsLedger.d.ts +38 -1
- package/dist/esm/utils/SavingsLedger.d.ts.map +1 -1
- package/dist/esm/utils/SavingsLedger.js +89 -13
- package/dist/esm/utils/SavingsLedger.js.map +1 -1
- package/dist/esm/utils/TestAutofixInterceptor.d.ts.map +1 -1
- package/dist/esm/utils/TestAutofixInterceptor.js +15 -26
- package/dist/esm/utils/TestAutofixInterceptor.js.map +1 -1
- package/dist/esm/utils/reportSavings.d.ts +15 -0
- package/dist/esm/utils/reportSavings.d.ts.map +1 -1
- package/dist/esm/utils/reportSavings.js +12 -1
- package/dist/esm/utils/reportSavings.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# 🧠 Lemma v1.
|
|
1
|
+
# 🧠 Lemma v1.7.0
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@nxuss/lemma)
|
|
4
4
|
[](https://github.com/Nxusbets/lemma/blob/main/LICENSE)
|
|
5
|
-
[](https://github.com/Nxusbets/lemma)
|
|
6
6
|
[](https://github.com/Nxusbets/lemma)
|
|
7
7
|
[](https://github.com/Nxusbets/lemma)
|
|
8
|
-
[](https://github.com/Nxusbets/lemma)
|
|
9
9
|
|
|
10
10
|
```text
|
|
11
11
|
__
|
|
@@ -17,13 +17,125 @@
|
|
|
17
17
|
|
|
18
18
|
> **The Intelligent local-first AI Gateway — Privacy, Performance, Static Code Intelligence, and Infrastructure Command Center for the Agentic Era.**
|
|
19
19
|
|
|
20
|
-
Lemma is a high-performance orchestration layer that sits between your development environment and LLM providers. It transforms the way you build with AI by providing **Shared Semantic Memory**, **Autonomous Cost Optimization**, **Privacy Guardrails**, **22 Infrastructure Management Tools**, and a suite of **
|
|
20
|
+
Lemma is a high-performance orchestration layer that sits between your development environment and LLM providers. It transforms the way you build with AI by providing **Shared Semantic Memory**, **Autonomous Cost Optimization**, **Privacy Guardrails**, **22 Infrastructure Management Tools**, and a suite of **94 MCP tools** — including zero-token static code intelligence that runs entirely on your machine with no LLM calls required.
|
|
21
|
+
|
|
22
|
+
Since v1.7.0 those 94 tools are all registered, but only the ones your project can actually use
|
|
23
|
+
are advertised to the model each turn. The rest stay one `lemma_toolbox` call away. See
|
|
24
|
+
[Tool profiles](#-tool-profiles).
|
|
21
25
|
|
|
22
26
|
---
|
|
23
27
|
|
|
24
28
|
## 📋 Changelog
|
|
25
29
|
|
|
26
|
-
### v1.
|
|
30
|
+
### v1.7.0 — The cost of being connected *(NEW)*
|
|
31
|
+
|
|
32
|
+
Every tool schema Lemma advertises goes into the model's system prompt and is **re-sent on every
|
|
33
|
+
request of the session** — including schemas for tools the project can never use. On this repo that
|
|
34
|
+
was ~13,950 tokens per turn before Lemma saved anything.
|
|
35
|
+
|
|
36
|
+
| Change | What it does | Why it matters |
|
|
37
|
+
| :--- | :--- | :--- |
|
|
38
|
+
| **Project-scoped tool surface** | Core tools are always advertised; situational groups (infrastructure, review) only when the repo shows evidence they apply. `mcp.toolProfile` accepts `auto`, `core`, or `full`. | ~3,420 tokens of schemas per turn instead of ~11,600. |
|
|
39
|
+
| **`lemma_toolbox`** *(new)* | Lists tools whose schemas were withheld, returns any tool's schema, and invokes it. | A withheld schema is a deferred cost, never a lost capability. |
|
|
40
|
+
| **Leaner TURBOMODE prompt** | ~2,340 → ~1,700 tokens. Instructions for tools the session doesn't advertise are gone; every correctness rule stays, enforced by tests. | The prompt stops paying for advice the model can't act on directly. |
|
|
41
|
+
| **Overhead in `token_budget`** | Reports profile, advertised vs. hidden counts, active groups, and the real schema token cost. | The fixed cost is visible instead of assumed. |
|
|
42
|
+
|
|
43
|
+
**Total fixed cost per turn on this repo: ~5,120 tokens, down from ~13,950.**
|
|
44
|
+
|
|
45
|
+
**The Brain reported cache hits that never happened.** Duplicate detection runs an internal search
|
|
46
|
+
before storing a memory, and that search shared the counters behind the reported hit rate — while
|
|
47
|
+
searching with a minimum similarity of 0, so it matched nearly every time. Every store inflated the
|
|
48
|
+
session hit rate and bumped an unrelated entry's hit counter. Internal lookups no longer count.
|
|
49
|
+
**Reported hit rates will be lower after upgrading; the lower number is the correct one.**
|
|
50
|
+
|
|
51
|
+
**Three features silently did nothing.** `get_telepathic_hints`, `auto_context_bundle`, and
|
|
52
|
+
`test_autofix_interceptor` read prior knowledge from `<project>/.lemma/memory/` — a directory no
|
|
53
|
+
code path writes. They returned empty in every project while the Brain held the answers. All three
|
|
54
|
+
now query the Brain directly.
|
|
55
|
+
|
|
56
|
+
**Also in this release:**
|
|
57
|
+
|
|
58
|
+
- **Memories are scoped by project** via a `projectId` derived from the git remote. Entries stored
|
|
59
|
+
before this release stay visible everywhere rather than disappearing.
|
|
60
|
+
- **Brain entries are ~38% smaller** — `terms` duplicated `Object.keys(termFreq)` and is now
|
|
61
|
+
rebuilt on load instead of stored. Existing files are read unchanged.
|
|
62
|
+
- **`get_project_onboarding` works without the proxy**, falling back to a locally derived map
|
|
63
|
+
instead of returning an error on the first call of the session.
|
|
64
|
+
- **`lemma-checkpoint`** *(new CLI)* captures a checkpoint from repository state, so it no longer
|
|
65
|
+
depends on the assistant remembering to call the tool. See [Session checkpoints](#-session-checkpoints).
|
|
66
|
+
- **Removed the `lemma-autopilot` prompt** — it told the model to call tools unconditionally, the
|
|
67
|
+
opposite of the net-savings accounting added in v1.6.0. `lemma-turbomode` is the supported prompt.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### v1.6.0 — The savings ledger can now report a loss
|
|
72
|
+
|
|
73
|
+
Every release up to this one measured only what Lemma **saved**. That is single-entry bookkeeping:
|
|
74
|
+
a number that can only go up is not evidence of anything, because the ledger had no way to express
|
|
75
|
+
the outcome *"this session cost more than it returned."* Meanwhile Lemma's own overhead — the
|
|
76
|
+
TURBOMODE prompt and 93 tool schemas injected into the model's system prompt, plus every tool
|
|
77
|
+
result that lands in context — was real spend that appeared nowhere.
|
|
78
|
+
|
|
79
|
+
| Change | What it does | Why it matters |
|
|
80
|
+
| :--- | :--- | :--- |
|
|
81
|
+
| **Cost side of the ledger** | Records `mcpInstructions` (prompt + all tool schemas, charged **once at MCP connect** — ~13,700 tokens on this repo), `toolResult` (charged on **every** call, including ones that also report a saving), and `cacheMiss`. | A session now starts visibly underwater and has to earn its way out, instead of starting at zero and only ever climbing. |
|
|
82
|
+
| **Net position** | `getSnapshot()` returns `net.netTokens`, `net.netCost`, and `net.ratio` (saved ÷ spent). The terminal summary prints **`NET COST`** instead of `NET SAVED` when the ratio drops below 1.0. | The report is falsifiable. If Lemma isn't paying for itself, it says so. |
|
|
83
|
+
| **`POST /api/cost-record`** *(new)* | Counterpart to `/api/savings-record`, so out-of-process callers (MCP server, CLI) can charge the ledger. Unknown sources are rejected with a 400. | Costs incurred outside the proxy were previously unrecordable. |
|
|
84
|
+
| **Versioned ledger file** | `_version: 2`. Savings stay top-level so pre-v2 readers keep working; costs are additive under `_costs`. A v1 ledger loads with zeroed costs rather than failing. | No migration step, no data loss on upgrade. |
|
|
85
|
+
|
|
86
|
+
**Three ways the old numbers were wrong**, now fixed:
|
|
87
|
+
|
|
88
|
+
- **Cache hits were credited a hardcoded 2,000 tokens** — every semantic, synthesized, and cloud
|
|
89
|
+
hit reported the same invented figure regardless of what was actually served. Savings are now
|
|
90
|
+
measured from the cached payload that replaced the LLM call. The 2,000 placeholder survives only
|
|
91
|
+
in `stats.json` for dashboard continuity and is explicitly **barred from the ledger**: a guess
|
|
92
|
+
must not become part of a net-position claim.
|
|
93
|
+
- **`pruneHistoryMessages` counted chars as tokens**, inflating history-prune savings ~4x. It now
|
|
94
|
+
converts with the same `chars / 4` heuristic used everywhere else.
|
|
95
|
+
- **A cache *miss* is not charged.** The lookup is local, so a miss costs latency, not tokens.
|
|
96
|
+
Charging it an invented figure would repeat the exact error this release exists to correct.
|
|
97
|
+
|
|
98
|
+
**Also fixed**: `reportSessionOverhead()` ran *after* `await trySamplingContext()`, which blocks on
|
|
99
|
+
a `sampling/createMessage` round-trip that clients without sampling support never answer — so the
|
|
100
|
+
overhead charge was stalled until the SDK timeout, or dropped entirely. It now runs first.
|
|
101
|
+
|
|
102
|
+
**TURBOMODE prompt rewritten** at the top: it no longer promises to "multiply your LLM membership
|
|
103
|
+
50x" or tell the model to poll `token_budget` every 5 turns (a scheduled call that produces a
|
|
104
|
+
report instead of work, and costs tokens each time). It now states the real goal — reduce what
|
|
105
|
+
enters context without costing a correct answer — and warns that a tool call which saves less than
|
|
106
|
+
its own round-trip is a net loss.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### v1.5.0 — Command output stops being the biggest unoptimized token sink
|
|
111
|
+
|
|
112
|
+
`run_workspace_command` returned raw stdout/stderr in full — a failing `npm test` put thousands of
|
|
113
|
+
lines into context to communicate three broken assertions. Long output is now distilled by
|
|
114
|
+
deterministic per-format parsers (no LLM summarization: a summarizer that invents a test name is
|
|
115
|
+
worse than the dump it replaced).
|
|
116
|
+
|
|
117
|
+
| Format | What survives distillation |
|
|
118
|
+
| :--- | :--- |
|
|
119
|
+
| jest / vitest | Failing test titles, assertion reason and diff, project-local source location. Code frames and `node_modules` stack frames dropped. |
|
|
120
|
+
| tsc | Grouped by file; a repeated diagnostic printed once with its positions (`TS2322 ×40 … at 1:7, 2:7, …`) instead of forty identical lines. |
|
|
121
|
+
| eslint (stylish) | Grouped by file with a rule-frequency header, capped per file. |
|
|
122
|
+
| anything else | Head, tail, and the error/failure lines from the middle, each with its line number, plus an explicit count of what was withheld. |
|
|
123
|
+
|
|
124
|
+
Measured on this repo: a 30-failure jest run went from 32,324 → 7,183 chars (78% withheld); a
|
|
125
|
+
65-error `tsc --noEmit` from 5,597 → 527 (91%).
|
|
126
|
+
|
|
127
|
+
**Lossless, not lossy.** The complete output is written to disk *before* anything is trimmed and
|
|
128
|
+
the reply carries a handle; the footer always states how much was withheld. `output_region` *(new
|
|
129
|
+
tool)* retrieves stored output verbatim — the whole run, one named section (a failing test title or
|
|
130
|
+
file path), or a line range. Small outputs pass through untouched with no handle.
|
|
131
|
+
|
|
132
|
+
**Also fixed**: stderr was discarded on success. `execSync` leaves stderr attached to the parent
|
|
133
|
+
unless `stdio` is overridden, so a *passing* jest or tsc run — both report on stderr — returned an
|
|
134
|
+
empty "Output:". Switched to `spawnSync`, which also raises the output limit to 20MB.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### v1.4.0 — Correctness-first patching, search, and memory freshness
|
|
27
139
|
|
|
28
140
|
An audit of the everyday MCP path — read a file, search the workspace, patch it, remember the
|
|
29
141
|
answer — found the same failure mode repeating: a tool that looked done but silently degraded
|
|
@@ -173,6 +285,70 @@ Or use environment variables: `JENKINS_URL`, `JENKINS_USERNAME`, `JENKINS_API_TO
|
|
|
173
285
|
|
|
174
286
|
---
|
|
175
287
|
|
|
288
|
+
## 🎚️ Tool profiles
|
|
289
|
+
|
|
290
|
+
Every tool schema an MCP server advertises is injected into the model's system prompt and re-sent
|
|
291
|
+
on **every request** of the session. Advertising all 94 tools costs ~11,600 tokens per turn — much
|
|
292
|
+
of it schemas for tools a given project can never use.
|
|
293
|
+
|
|
294
|
+
Lemma advertises a scoped surface instead. Nothing is removed: every tool stays registered and
|
|
295
|
+
callable, and `lemma_toolbox` reaches the ones whose schemas were withheld.
|
|
296
|
+
|
|
297
|
+
| Profile | Advertises |
|
|
298
|
+
| :--- | :--- |
|
|
299
|
+
| `auto` *(default)* | Core tools, plus situational groups the repo shows evidence for |
|
|
300
|
+
| `core` | Core tools only |
|
|
301
|
+
| `full` | Everything, as before v1.7.0 |
|
|
302
|
+
|
|
303
|
+
```jsonc
|
|
304
|
+
// lemma.config.json
|
|
305
|
+
{ "mcp": { "toolProfile": "full" } } // or set LEMMA_TOOL_PROFILE=full
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Core** covers workspace reading and navigation, the patch/validate write path, AST context,
|
|
309
|
+
caching (`state_hash_cache`, `smarter_cache`, `search_memory`, `store_memory`), routing advice, and
|
|
310
|
+
the ledger. **Situational groups** are advertised on evidence — the infrastructure group when a
|
|
311
|
+
`Jenkinsfile`, `kustomization.yaml`, `.openshift/`, or a `.tf` file is present, or a provider is
|
|
312
|
+
already connected.
|
|
313
|
+
|
|
314
|
+
Reaching anything else:
|
|
315
|
+
|
|
316
|
+
```jsonc
|
|
317
|
+
lemma_toolbox { "action": "list", "query": "terraform drift" } // what exists
|
|
318
|
+
lemma_toolbox { "action": "schema", "tool": "terraform_drift" } // its inputs
|
|
319
|
+
lemma_toolbox { "action": "call", "tool": "terraform_drift", "args": {} }
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`token_budget` reports the profile in use, advertised vs. hidden counts, and what the advertised
|
|
323
|
+
schemas actually cost this session.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 💾 Session checkpoints
|
|
328
|
+
|
|
329
|
+
`conversation_checkpoint` saves what a session established so the next one doesn't re-derive it —
|
|
330
|
+
but it only runs if the assistant remembers to call it at the end, which is not something to build
|
|
331
|
+
on. `lemma-checkpoint` derives a checkpoint from repository state instead:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
lemma-checkpoint [projectRoot] [--summary "text"]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
It records the branch, changed files, and recent commits to `<project>/.lemma/session/`, exits 0
|
|
338
|
+
even outside a git repository, and is meant to be wired to your host's session-end event:
|
|
339
|
+
|
|
340
|
+
```jsonc
|
|
341
|
+
// ~/.claude/settings.json
|
|
342
|
+
{ "hooks": { "SessionEnd": [{ "hooks": [{ "type": "command",
|
|
343
|
+
"command": "lemma-checkpoint \"${CLAUDE_PROJECT_DIR:-$PWD}\"" }] }] } }
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Decisions made and tasks left pending exist only in the conversation, so an automatic capture
|
|
347
|
+
leaves those fields empty rather than inventing them — a checkpoint saved by the assistant stays
|
|
348
|
+
the richer record when one exists.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
176
352
|
## 💎 Free vs Pro
|
|
177
353
|
|
|
178
354
|
### 🆓 Free (33 Tools)
|
|
@@ -441,7 +617,7 @@ lemma start
|
|
|
441
617
|
| `lemma start` | Launches proxy, cache engine, and dashboard at `http://localhost:8081`. |
|
|
442
618
|
| `lemma stop` | Gracefully shuts down the background proxy. |
|
|
443
619
|
| `lemma status` | Health of all modules and active connections. |
|
|
444
|
-
| `lemma stats` | Real-time token and cost savings
|
|
620
|
+
| `lemma stats` | Real-time token and cost metrics — savings, Lemma's own overhead, and the net position between them. Reports `NET COST` when the session is underwater. |
|
|
445
621
|
| `lemma mcp` | Starts the stdin/stdout MCP server for IDE integration. |
|
|
446
622
|
| `lemma heal` | Auto-diagnose and repair the latest server crash from live context logs. |
|
|
447
623
|
| `lemma autopilot` | Toggle autonomous background watcher mode (auto-applies fixes). |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Session-end checkpoint capture.
|
|
6
|
+
*
|
|
7
|
+
* Meant to be wired to a host's session-end event (e.g. a Claude Code Stop/SessionEnd hook)
|
|
8
|
+
* so a checkpoint gets written even when the assistant never calls the MCP tool. Writes to
|
|
9
|
+
* <project>/.lemma/session/ and exits 0 no matter what — a hook that fails must never be the
|
|
10
|
+
* reason a session ends badly.
|
|
11
|
+
*
|
|
12
|
+
* Usage: lemma-checkpoint [projectRoot] [--summary "text"]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
function main() {
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const summaryIndex = args.indexOf('--summary');
|
|
20
|
+
const summary = summaryIndex >= 0 ? args[summaryIndex + 1] : undefined;
|
|
21
|
+
const positional = args.filter((a, i) => !a.startsWith('--') && i !== summaryIndex + 1);
|
|
22
|
+
const projectRoot = path.resolve(positional[0] || process.cwd());
|
|
23
|
+
|
|
24
|
+
const distPath = path.join(__dirname, '..', 'dist', 'cjs', 'utils', 'ConversationCheckpoint.js');
|
|
25
|
+
let captureAutoCheckpoint;
|
|
26
|
+
try {
|
|
27
|
+
({ captureAutoCheckpoint } = require(distPath));
|
|
28
|
+
} catch {
|
|
29
|
+
console.error('[lemma-checkpoint] Build not found. Run "npm run build" first.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const result = captureAutoCheckpoint(projectRoot, summary);
|
|
34
|
+
if (!result) {
|
|
35
|
+
console.error('[lemma-checkpoint] Not a git repository — nothing to capture.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.error(
|
|
39
|
+
`[lemma-checkpoint] Saved: ${result.filesModified.length} file(s) changed → ${path.join(projectRoot, '.lemma/session')}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
main();
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// A checkpoint is best-effort context, never a reason to fail the host's shutdown.
|
|
47
|
+
console.error('[lemma-checkpoint] Skipped:', err && err.message);
|
|
48
|
+
}
|
|
49
|
+
process.exit(0);
|
package/bin/init.js
CHANGED
|
@@ -278,6 +278,25 @@ tokens sale más cara que los tokens que ahorra.
|
|
|
278
278
|
| "¿Cuánto ahorramos de verdad?" | \`mcp__lemma__token_receipt\` | Bitácora auditable real, no una cifra inventada |
|
|
279
279
|
| Guardar una solución para el futuro | \`mcp__lemma__store_memory\` | Solo después de verificarla (tests pasaron, el usuario confirmó) — una solución mala guardada se vuelve un cache hit falso más adelante |
|
|
280
280
|
|
|
281
|
+
## No todas las tools de Lemma están en tu lista
|
|
282
|
+
|
|
283
|
+
Cada schema que Lemma anuncia se re-envía en **cada** request de la sesión, así que solo se
|
|
284
|
+
anuncian las que este proyecto puede usar de verdad. El resto sigue registrado y disponible:
|
|
285
|
+
|
|
286
|
+
- \`mcp__lemma__lemma_toolbox\` con \`action="list"\` (y un \`query\`) para ver qué existe.
|
|
287
|
+
- \`action="schema"\` para leer los inputs de una tool antes de llamarla.
|
|
288
|
+
- \`action="call"\` para ejecutarla.
|
|
289
|
+
|
|
290
|
+
Antes de decirle al usuario que Lemma no puede hacer algo, revisa el toolbox. Si el proyecto
|
|
291
|
+
necesita todas las tools anunciadas de forma permanente, pon \`mcp.toolProfile: "full"\` en
|
|
292
|
+
\`lemma.config.json\`.
|
|
293
|
+
|
|
294
|
+
## Antes de pedirle código al usuario
|
|
295
|
+
|
|
296
|
+
No le pidas que pegue archivos, logs o documentación que puedes obtener tú: agota primero
|
|
297
|
+
\`search_workspace\`, \`get_ast_hologram\` (estructura: da nombres y líneas, no firmas) y
|
|
298
|
+
\`read_workspace_file\`. Cada ida y vuelta con el usuario cuesta más que la lectura.
|
|
299
|
+
|
|
281
300
|
## Al iniciar en este proyecto
|
|
282
301
|
|
|
283
302
|
1. Llama \`mcp__lemma__get_project_onboarding\` para orientarte con el contexto ya indexado.
|
|
@@ -286,8 +305,8 @@ tokens sale más cara que los tokens que ahorra.
|
|
|
286
305
|
|
|
287
306
|
## Al reportar o investigar un bug
|
|
288
307
|
|
|
289
|
-
1. \`
|
|
290
|
-
2. \`
|
|
308
|
+
1. \`mcp__lemma__search_memory\` con el mensaje de error, por si ya se resolvió algo parecido.
|
|
309
|
+
2. \`lemma_toolbox\` → \`get_telepathic_hints\` con el archivo relevante para contexto de runtime.
|
|
291
310
|
|
|
292
311
|
## Una sola regla dura
|
|
293
312
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AA4cA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA4DvF;
|
|
1
|
+
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AA4cA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA4DvF;AA8lED,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6KhL"}
|
|
@@ -587,6 +587,31 @@ async function loadUsage() {
|
|
|
587
587
|
async function loadStats() {
|
|
588
588
|
return readJson(ProjectStore_1.STATS_FILE, {});
|
|
589
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Tokens a cache hit actually avoided regenerating, measured from the cached payload
|
|
592
|
+
* that was served instead of an LLM call. Replaces the old hardcoded 2000 guess.
|
|
593
|
+
*/
|
|
594
|
+
function measureCachedTokens(data) {
|
|
595
|
+
try {
|
|
596
|
+
const chars = JSON.stringify(data)?.length;
|
|
597
|
+
if (!chars)
|
|
598
|
+
return undefined;
|
|
599
|
+
return Math.floor(chars / 4);
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* @param tokensSaved Measured tokens saved. When omitted for a cache hit, stats.json
|
|
607
|
+
* still falls back to a 2000-token placeholder for dashboard continuity, but that
|
|
608
|
+
* guess is deliberately NOT written to the savings ledger — the ledger only accepts
|
|
609
|
+
* measured values, otherwise its net position is built on invented numbers.
|
|
610
|
+
*
|
|
611
|
+
* Note: a cache *miss* is not charged to the cost ledger. The lookup is local (no LLM
|
|
612
|
+
* round-trip), so a miss costs latency, not tokens. Charging it a fabricated token
|
|
613
|
+
* figure would repeat exactly the error the cost ledger exists to correct.
|
|
614
|
+
*/
|
|
590
615
|
async function recordStat(stats, project, fromCache, latencyMs, provider, tokensSaved) {
|
|
591
616
|
if (!stats[project]) {
|
|
592
617
|
stats[project] = { total: 0, hits: 0, misses: 0, totalLatency: 0, totalTokensSaved: 0, providers: {} };
|
|
@@ -600,9 +625,11 @@ async function recordStat(stats, project, fromCache, latencyMs, provider, tokens
|
|
|
600
625
|
if (!s.providers[provider])
|
|
601
626
|
s.providers[provider] = { hits: 0, misses: 0 };
|
|
602
627
|
fromCache ? s.providers[provider].hits++ : s.providers[provider].misses++;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
628
|
+
// Only measured savings reach the ledger. `tokensSaved === undefined` means the
|
|
629
|
+
// caller never counted, so tkSaved is the 2000 placeholder — usable for the
|
|
630
|
+
// dashboard, not for a net-position claim.
|
|
631
|
+
if (fromCache && typeof tokensSaved === 'number' && tokensSaved > 0) {
|
|
632
|
+
SavingsLedger_1.savingsLedger.recordTokens('cache', tokensSaved);
|
|
606
633
|
}
|
|
607
634
|
logEvent({
|
|
608
635
|
type: fromCache ? 'cache:hit' : 'cache:miss',
|
|
@@ -1508,6 +1535,23 @@ class LemmaServer {
|
|
|
1508
1535
|
}
|
|
1509
1536
|
res.json({ ok: true, snapshot: SavingsLedger_1.savingsLedger.getSnapshot().total });
|
|
1510
1537
|
});
|
|
1538
|
+
// Cost Record Endpoint — the counterpart to /api/savings-record. Without it the
|
|
1539
|
+
// ledger only ever counts wins, which makes its totals unfalsifiable.
|
|
1540
|
+
this.app.post('/api/cost-record', (req, res) => {
|
|
1541
|
+
const { source, tokens, chars } = req.body || {};
|
|
1542
|
+
const validSources = ['mcpInstructions', 'toolResult', 'cacheMiss'];
|
|
1543
|
+
if (!source || !validSources.includes(source)) {
|
|
1544
|
+
return res.status(400).json({ error: 'Invalid cost source' });
|
|
1545
|
+
}
|
|
1546
|
+
if (typeof tokens === 'number' && tokens > 0) {
|
|
1547
|
+
SavingsLedger_1.savingsLedger.recordCost(source, tokens);
|
|
1548
|
+
}
|
|
1549
|
+
else if (typeof chars === 'number' && chars > 0) {
|
|
1550
|
+
SavingsLedger_1.savingsLedger.recordCostChars(source, chars);
|
|
1551
|
+
}
|
|
1552
|
+
const snap = SavingsLedger_1.savingsLedger.getSnapshot();
|
|
1553
|
+
res.json({ ok: true, totalCost: snap.totalCost, net: snap.net });
|
|
1554
|
+
});
|
|
1511
1555
|
// MCP Tool Event Endpoint — called by MCP server when tools execute
|
|
1512
1556
|
this.app.post('/api/mcp-event', (req, res) => {
|
|
1513
1557
|
try {
|
|
@@ -1733,7 +1777,7 @@ class LemmaServer {
|
|
|
1733
1777
|
const semHit = await semanticGet(provider, prompt, this.projectName);
|
|
1734
1778
|
if (semHit) {
|
|
1735
1779
|
if (semHit.similarity >= 0.90) {
|
|
1736
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider,
|
|
1780
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, measureCachedTokens(semHit.data));
|
|
1737
1781
|
const unmaskedData = semanticScrubber.unmask(semHit.data, tokenMap);
|
|
1738
1782
|
const cacheHeader = semHit.hiveMind ? 'HIT-HIVE-MIND' : 'HIT';
|
|
1739
1783
|
if (isStream)
|
|
@@ -1784,7 +1828,7 @@ Adjusted Answer:`;
|
|
|
1784
1828
|
else if (provider === 'anthropic') {
|
|
1785
1829
|
synthesizedData.content[0].text = adjustedText;
|
|
1786
1830
|
}
|
|
1787
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider,
|
|
1831
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, measureCachedTokens(synthesizedData));
|
|
1788
1832
|
const unmaskedData = semanticScrubber.unmask(synthesizedData, tokenMap);
|
|
1789
1833
|
if (isStream) {
|
|
1790
1834
|
return SseRelay_1.SseRelay.simulateHit(res, unmaskedData, { provider: provider, tier, similarity: semHit.similarity });
|
|
@@ -1804,7 +1848,7 @@ Adjusted Answer:`;
|
|
|
1804
1848
|
const cloudHit = await cloudSync.get(prompt);
|
|
1805
1849
|
if (cloudHit) {
|
|
1806
1850
|
cacheSet(provider, prompt, cloudHit.data);
|
|
1807
|
-
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider,
|
|
1851
|
+
await recordStat(this.stats, this.projectName, true, Date.now() - t0, provider, measureCachedTokens(cloudHit.data));
|
|
1808
1852
|
const unmaskedData = semanticScrubber.unmask(cloudHit.data, tokenMap);
|
|
1809
1853
|
if (isStream)
|
|
1810
1854
|
return SseRelay_1.SseRelay.simulateHit(res, unmaskedData, { provider: provider, tier, similarity: cloudHit.similarity || 0.95 });
|