@nxuss/lemma 1.5.0 โ†’ 1.6.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 (50) hide show
  1. package/README.md +73 -6
  2. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  3. package/dist/cjs/cli/lemma-proxy.js +50 -6
  4. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  5. package/dist/cjs/mcp/index.js +23 -0
  6. package/dist/cjs/mcp/index.js.map +1 -1
  7. package/dist/cjs/mcp/prompts.d.ts +1 -1
  8. package/dist/cjs/mcp/prompts.d.ts.map +1 -1
  9. package/dist/cjs/mcp/prompts.js +6 -4
  10. package/dist/cjs/mcp/prompts.js.map +1 -1
  11. package/dist/cjs/mcp/tools.d.ts +2 -0
  12. package/dist/cjs/mcp/tools.d.ts.map +1 -1
  13. package/dist/cjs/mcp/tools.js +15 -1
  14. package/dist/cjs/mcp/tools.js.map +1 -1
  15. package/dist/cjs/utils/ContextSqueezer.d.ts.map +1 -1
  16. package/dist/cjs/utils/ContextSqueezer.js +5 -3
  17. package/dist/cjs/utils/ContextSqueezer.js.map +1 -1
  18. package/dist/cjs/utils/SavingsLedger.d.ts +38 -1
  19. package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -1
  20. package/dist/cjs/utils/SavingsLedger.js +89 -13
  21. package/dist/cjs/utils/SavingsLedger.js.map +1 -1
  22. package/dist/cjs/utils/reportSavings.d.ts +15 -0
  23. package/dist/cjs/utils/reportSavings.d.ts.map +1 -1
  24. package/dist/cjs/utils/reportSavings.js +13 -1
  25. package/dist/cjs/utils/reportSavings.js.map +1 -1
  26. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  27. package/dist/esm/cli/lemma-proxy.js +50 -6
  28. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  29. package/dist/esm/mcp/index.js +24 -1
  30. package/dist/esm/mcp/index.js.map +1 -1
  31. package/dist/esm/mcp/prompts.d.ts +1 -1
  32. package/dist/esm/mcp/prompts.d.ts.map +1 -1
  33. package/dist/esm/mcp/prompts.js +6 -4
  34. package/dist/esm/mcp/prompts.js.map +1 -1
  35. package/dist/esm/mcp/tools.d.ts +2 -0
  36. package/dist/esm/mcp/tools.d.ts.map +1 -1
  37. package/dist/esm/mcp/tools.js +15 -2
  38. package/dist/esm/mcp/tools.js.map +1 -1
  39. package/dist/esm/utils/ContextSqueezer.d.ts.map +1 -1
  40. package/dist/esm/utils/ContextSqueezer.js +5 -3
  41. package/dist/esm/utils/ContextSqueezer.js.map +1 -1
  42. package/dist/esm/utils/SavingsLedger.d.ts +38 -1
  43. package/dist/esm/utils/SavingsLedger.d.ts.map +1 -1
  44. package/dist/esm/utils/SavingsLedger.js +89 -13
  45. package/dist/esm/utils/SavingsLedger.js.map +1 -1
  46. package/dist/esm/utils/reportSavings.d.ts +15 -0
  47. package/dist/esm/utils/reportSavings.d.ts.map +1 -1
  48. package/dist/esm/utils/reportSavings.js +12 -1
  49. package/dist/esm/utils/reportSavings.js.map +1 -1
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # ๐Ÿง  Lemma v1.4.0
1
+ # ๐Ÿง  Lemma v1.6.0
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@nxuss/lemma.svg?color=orange&style=flat-square)](https://www.npmjs.com/package/@nxuss/lemma)
4
4
  [![License](https://img.shields.io/npm/l/@nxuss/lemma.svg?color=blue&style=flat-square)](https://github.com/Nxusbets/lemma/blob/main/LICENSE)
5
- [![MCP Tools](https://img.shields.io/badge/MCP%20tools-92-ff69b4?style=flat-square)](https://github.com/Nxusbets/lemma)
5
+ [![MCP Tools](https://img.shields.io/badge/MCP%20tools-93-ff69b4?style=flat-square)](https://github.com/Nxusbets/lemma)
6
6
  [![Security](https://img.shields.io/badge/privacy-firewall%20active-blueviolet?style=flat-square)](https://github.com/Nxusbets/lemma)
7
7
  [![Caching](https://img.shields.io/badge/semantic%20memory-enabled-success?style=flat-square)](https://github.com/Nxusbets/lemma)
8
- [![Tests](https://img.shields.io/badge/tests-199%20passing-00c853?style=flat-square)](https://github.com/Nxusbets/lemma)
8
+ [![Tests](https://img.shields.io/badge/tests-236%20passing-00c853?style=flat-square)](https://github.com/Nxusbets/lemma)
9
9
 
10
10
  ```text
11
11
  __
@@ -17,13 +17,80 @@
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 **92 MCP tools** โ€” including zero-token static code intelligence that runs entirely on your machine with no LLM calls required.
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 **93 MCP tools** โ€” including zero-token static code intelligence that runs entirely on your machine with no LLM calls required.
21
21
 
22
22
  ---
23
23
 
24
24
  ## ๐Ÿ“‹ Changelog
25
25
 
26
- ### v1.4.0 โ€” Correctness-first patching, search, and memory freshness *(NEW)*
26
+ ### v1.6.0 โ€” The savings ledger can now report a loss *(NEW)*
27
+
28
+ Every release up to this one measured only what Lemma **saved**. That is single-entry bookkeeping:
29
+ a number that can only go up is not evidence of anything, because the ledger had no way to express
30
+ the outcome *"this session cost more than it returned."* Meanwhile Lemma's own overhead โ€” the
31
+ TURBOMODE prompt and 93 tool schemas injected into the model's system prompt, plus every tool
32
+ result that lands in context โ€” was real spend that appeared nowhere.
33
+
34
+ | Change | What it does | Why it matters |
35
+ | :--- | :--- | :--- |
36
+ | **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. |
37
+ | **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. |
38
+ | **`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. |
39
+ | **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. |
40
+
41
+ **Three ways the old numbers were wrong**, now fixed:
42
+
43
+ - **Cache hits were credited a hardcoded 2,000 tokens** โ€” every semantic, synthesized, and cloud
44
+ hit reported the same invented figure regardless of what was actually served. Savings are now
45
+ measured from the cached payload that replaced the LLM call. The 2,000 placeholder survives only
46
+ in `stats.json` for dashboard continuity and is explicitly **barred from the ledger**: a guess
47
+ must not become part of a net-position claim.
48
+ - **`pruneHistoryMessages` counted chars as tokens**, inflating history-prune savings ~4x. It now
49
+ converts with the same `chars / 4` heuristic used everywhere else.
50
+ - **A cache *miss* is not charged.** The lookup is local, so a miss costs latency, not tokens.
51
+ Charging it an invented figure would repeat the exact error this release exists to correct.
52
+
53
+ **Also fixed**: `reportSessionOverhead()` ran *after* `await trySamplingContext()`, which blocks on
54
+ a `sampling/createMessage` round-trip that clients without sampling support never answer โ€” so the
55
+ overhead charge was stalled until the SDK timeout, or dropped entirely. It now runs first.
56
+
57
+ **TURBOMODE prompt rewritten** at the top: it no longer promises to "multiply your LLM membership
58
+ 50x" or tell the model to poll `token_budget` every 5 turns (a scheduled call that produces a
59
+ report instead of work, and costs tokens each time). It now states the real goal โ€” reduce what
60
+ enters context without costing a correct answer โ€” and warns that a tool call which saves less than
61
+ its own round-trip is a net loss.
62
+
63
+ ---
64
+
65
+ ### v1.5.0 โ€” Command output stops being the biggest unoptimized token sink
66
+
67
+ `run_workspace_command` returned raw stdout/stderr in full โ€” a failing `npm test` put thousands of
68
+ lines into context to communicate three broken assertions. Long output is now distilled by
69
+ deterministic per-format parsers (no LLM summarization: a summarizer that invents a test name is
70
+ worse than the dump it replaced).
71
+
72
+ | Format | What survives distillation |
73
+ | :--- | :--- |
74
+ | jest / vitest | Failing test titles, assertion reason and diff, project-local source location. Code frames and `node_modules` stack frames dropped. |
75
+ | tsc | Grouped by file; a repeated diagnostic printed once with its positions (`TS2322 ร—40 โ€ฆ at 1:7, 2:7, โ€ฆ`) instead of forty identical lines. |
76
+ | eslint (stylish) | Grouped by file with a rule-frequency header, capped per file. |
77
+ | 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. |
78
+
79
+ Measured on this repo: a 30-failure jest run went from 32,324 โ†’ 7,183 chars (78% withheld); a
80
+ 65-error `tsc --noEmit` from 5,597 โ†’ 527 (91%).
81
+
82
+ **Lossless, not lossy.** The complete output is written to disk *before* anything is trimmed and
83
+ the reply carries a handle; the footer always states how much was withheld. `output_region` *(new
84
+ tool)* retrieves stored output verbatim โ€” the whole run, one named section (a failing test title or
85
+ file path), or a line range. Small outputs pass through untouched with no handle.
86
+
87
+ **Also fixed**: stderr was discarded on success. `execSync` leaves stderr attached to the parent
88
+ unless `stdio` is overridden, so a *passing* jest or tsc run โ€” both report on stderr โ€” returned an
89
+ empty "Output:". Switched to `spawnSync`, which also raises the output limit to 20MB.
90
+
91
+ ---
92
+
93
+ ### v1.4.0 โ€” Correctness-first patching, search, and memory freshness
27
94
 
28
95
  An audit of the everyday MCP path โ€” read a file, search the workspace, patch it, remember the
29
96
  answer โ€” found the same failure mode repeating: a tool that looked done but silently degraded
@@ -441,7 +508,7 @@ lemma start
441
508
  | `lemma start` | Launches proxy, cache engine, and dashboard at `http://localhost:8081`. |
442
509
  | `lemma stop` | Gracefully shuts down the background proxy. |
443
510
  | `lemma status` | Health of all modules and active connections. |
444
- | `lemma stats` | Real-time token and cost savings metrics. |
511
+ | `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
512
  | `lemma mcp` | Starts the stdin/stdout MCP server for IDE integration. |
446
513
  | `lemma heal` | Auto-diagnose and repair the latest server crash from live context logs. |
447
514
  | `lemma autopilot` | Toggle autonomous background watcher mode (auto-applies fixes). |
@@ -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;AAijED,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"}
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
- if (fromCache && tkSaved > 0) {
604
- // Automatically report cache hits directly to the server session ledger for absolute sync consistency
605
- SavingsLedger_1.savingsLedger.recordTokens('cache', tkSaved);
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, 2000);
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, 2000);
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, 2000);
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 });