@nxuss/lemma 1.7.2 → 1.8.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 +69 -8
- package/bin/brain-ingest.js +56 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +132 -33
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/pr-review/bridge/BrainBridge.d.ts +11 -1
- package/dist/cjs/pr-review/bridge/BrainBridge.d.ts.map +1 -1
- package/dist/cjs/pr-review/bridge/BrainBridge.js +18 -23
- package/dist/cjs/pr-review/bridge/BrainBridge.js.map +1 -1
- package/dist/cjs/protocol/utils.d.ts.map +1 -1
- package/dist/cjs/protocol/utils.js +6 -5
- package/dist/cjs/protocol/utils.js.map +1 -1
- package/dist/cjs/subconscious/GitIngest.d.ts +48 -0
- package/dist/cjs/subconscious/GitIngest.d.ts.map +1 -0
- package/dist/cjs/subconscious/GitIngest.js +175 -0
- package/dist/cjs/subconscious/GitIngest.js.map +1 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts +23 -1
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.js +124 -17
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.d.ts +2 -0
- package/dist/cjs/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/cjs/utils/ConversationCheckpoint.js +38 -0
- package/dist/cjs/utils/ConversationCheckpoint.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +134 -35
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/pr-review/bridge/BrainBridge.d.ts +11 -1
- package/dist/esm/pr-review/bridge/BrainBridge.d.ts.map +1 -1
- package/dist/esm/pr-review/bridge/BrainBridge.js +18 -20
- package/dist/esm/pr-review/bridge/BrainBridge.js.map +1 -1
- package/dist/esm/protocol/utils.d.ts.map +1 -1
- package/dist/esm/protocol/utils.js +6 -5
- package/dist/esm/protocol/utils.js.map +1 -1
- package/dist/esm/subconscious/GitIngest.d.ts +48 -0
- package/dist/esm/subconscious/GitIngest.d.ts.map +1 -0
- package/dist/esm/subconscious/GitIngest.js +166 -0
- package/dist/esm/subconscious/GitIngest.js.map +1 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts +23 -1
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.js +124 -17
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.d.ts +2 -0
- package/dist/esm/utils/ConversationCheckpoint.d.ts.map +1 -1
- package/dist/esm/utils/ConversationCheckpoint.js +37 -0
- package/dist/esm/utils/ConversationCheckpoint.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# 🧠 Lemma v1.
|
|
1
|
+
# 🧠 Lemma v1.8.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,9 +17,9 @@
|
|
|
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 **95 MCP tools** — including zero-token static code intelligence that runs entirely on your machine with no LLM calls required.
|
|
21
21
|
|
|
22
|
-
Since v1.7.0 those
|
|
22
|
+
Since v1.7.0 those 95 tools are all registered, but only the ones your project can actually use
|
|
23
23
|
are advertised to the model each turn. The rest stay one `lemma_toolbox` call away. See
|
|
24
24
|
[Tool profiles](#-tool-profiles).
|
|
25
25
|
|
|
@@ -27,7 +27,41 @@ are advertised to the model each turn. The rest stay one `lemma_toolbox` call aw
|
|
|
27
27
|
|
|
28
28
|
## 📋 Changelog
|
|
29
29
|
|
|
30
|
-
### v1.
|
|
30
|
+
### v1.8.0 — The Brain learns from project history, not just what it's told *(NEW)*
|
|
31
|
+
|
|
32
|
+
Every memory in The Brain used to depend on the assistant remembering to call `store_memory`. If
|
|
33
|
+
nobody called it, the Brain knew nothing about work that already happened.
|
|
34
|
+
|
|
35
|
+
| Change | What it does | Why it matters |
|
|
36
|
+
| :--- | :--- | :--- |
|
|
37
|
+
| **`lemma-brain-ingest`** *(new CLI)* | Feeds The Brain from git commits and `CHANGELOG.md` — no assistant call needed. Incremental via a marker file, safe to wire to `post-commit`. | The Brain stops depending on the model remembering to save anything. |
|
|
38
|
+
| **Checkpoints now feed The Brain** | `saveCheckpoint()` indexes any checkpoint with real content into the same Brain `search_memory` reads. | Decisions and bugs recorded in a checkpoint used to be invisible to search. |
|
|
39
|
+
| **`get_project_history`** *(new tool)* | Merges recent commits, checkpoints, and Brain memories for the current project into one call. | Answers "what have we done here" without three separate lookups. |
|
|
40
|
+
| **Outcome tagging** | `store_memory` accepts `outcome: 'failed'`; search surfaces it as a warning, not a suggestion. | A dead end is now as memorable as a fix. |
|
|
41
|
+
| **Symbol-level freshness** | `store_memory` accepts `symbols: [{filePath, symbolName}]` — freshness is judged by that function's own source, not the whole file. | An unrelated edit elsewhere in the file no longer stales the memory out. |
|
|
42
|
+
|
|
43
|
+
**PR review memory was silently going nowhere.** `BrainBridge` talked to the proxy's
|
|
44
|
+
`/api/search`/`/api/memory/store`, both backed by ChromaDB + a local Ollama server on `:11434` —
|
|
45
|
+
the exact stack TheBrainV2 replaced in 1.0.0. Whenever Ollama wasn't running (the common case
|
|
46
|
+
now), every call failed inside a `try/catch` and returned empty. It now reads/writes `TheBrainV2`
|
|
47
|
+
directly, so PR review and interactive coding share one memory instead of two disconnected ones.
|
|
48
|
+
|
|
49
|
+
**Two projects could quietly share — or split — memories.** `deriveProjectId` hashed the
|
|
50
|
+
project path without resolving symlinks; on macOS `process.cwd()` and an `os.tmpdir()`-built path
|
|
51
|
+
can report different strings for the same directory, hashing to two different project scopes. It
|
|
52
|
+
now resolves via `fs.realpathSync` first.
|
|
53
|
+
|
|
54
|
+
**Spanish text was mangled before it reached the index.** `tokenize()` used `\w` to strip
|
|
55
|
+
punctuation, which doesn't match accented letters — "Cómo" became "mo", "diseño" became "dise",
|
|
56
|
+
and the same word typed without its accent tokenized completely differently. `tokenize()` now
|
|
57
|
+
normalizes diacritics first, and gained an additive English/Spanish stemmer and Spanish stopwords.
|
|
58
|
+
|
|
59
|
+
**`token_budget` reported a multiplier no ledger backed.** It fetched a proxy route that doesn't
|
|
60
|
+
exist, silently fell back to an uncosted estimate, and reported a bigger number than the
|
|
61
|
+
session's own auditable ledger would support. It now reads the real `SavingsLedger` net position
|
|
62
|
+
(`/api/savings-breakdown`) or says plainly there's nothing to report if the proxy isn't running.
|
|
63
|
+
|
|
64
|
+
### v1.7.0 — The cost of being connected
|
|
31
65
|
|
|
32
66
|
Every tool schema Lemma advertises goes into the model's system prompt and is **re-sent on every
|
|
33
67
|
request of the session** — including schemas for tools the project can never use. On this repo that
|
|
@@ -288,7 +322,7 @@ Or use environment variables: `JENKINS_URL`, `JENKINS_USERNAME`, `JENKINS_API_TO
|
|
|
288
322
|
## 🎚️ Tool profiles
|
|
289
323
|
|
|
290
324
|
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
|
|
325
|
+
on **every request** of the session. Advertising all 95 tools costs ~11,600 tokens per turn — much
|
|
292
326
|
of it schemas for tools a given project can never use.
|
|
293
327
|
|
|
294
328
|
Lemma advertises a scoped surface instead. Nothing is removed: every tool stays registered and
|
|
@@ -349,9 +383,35 @@ the richer record when one exists.
|
|
|
349
383
|
|
|
350
384
|
---
|
|
351
385
|
|
|
386
|
+
## 🧠 Feeding The Brain from project history
|
|
387
|
+
|
|
388
|
+
`store_memory` also depends on the assistant remembering to call it. `lemma-brain-ingest` pulls
|
|
389
|
+
memories from sources that already exist and don't need an assistant in the loop:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
lemma-brain-ingest [projectRoot] [--max N] [--changelog] [--no-commits]
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
- Every git commit (message + files touched) becomes one Brain entry, so `search_memory` and
|
|
396
|
+
`get_telepathic_hints` can recall *why* a change happened, not just what the code looks like now.
|
|
397
|
+
- `--changelog` also ingests `CHANGELOG.md` (falls back to `docs/CHANGELOG.md`) — one entry per release, parsed
|
|
398
|
+
directly, no LLM involved.
|
|
399
|
+
|
|
400
|
+
A marker file under `<project>/.lemma/session/` tracks the last commit and changelog version
|
|
401
|
+
ingested, so this is safe to run after every commit and safe to run by hand for a one-time
|
|
402
|
+
backfill of existing history — both take the same incremental path and never double-store.
|
|
403
|
+
Wire it to a post-commit hook to keep it current automatically:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
echo 'lemma-brain-ingest "$(git rev-parse --show-toplevel)" &' >> .git/hooks/post-commit
|
|
407
|
+
chmod +x .git/hooks/post-commit
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
352
412
|
## 💎 Free vs Pro
|
|
353
413
|
|
|
354
|
-
### 🆓 Free (
|
|
414
|
+
### 🆓 Free (34 Tools)
|
|
355
415
|
|
|
356
416
|
These tools work **without a Pro license**. Activate them with just `lemma init`:
|
|
357
417
|
|
|
@@ -366,6 +426,7 @@ These tools work **without a Pro license**. Activate them with just `lemma init`
|
|
|
366
426
|
| `auto_context_bundle` | Full context bundle per file |
|
|
367
427
|
| `repeat_guard` | Duplicate output detector |
|
|
368
428
|
| `conversation_checkpoint` | Session state save/load |
|
|
429
|
+
| `get_project_history` | Git log + checkpoints + Brain memories in one call |
|
|
369
430
|
| `test_autofix_interceptor` | Test failure → Brain auto-fix |
|
|
370
431
|
| `prompt_pattern_cache` | Reusable prompt templates |
|
|
371
432
|
| `infra_discover` | Auto-detect Jenkins, OKD, Terraform |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Feeds The Brain from sources the model never has to remember to store manually.
|
|
6
|
+
*
|
|
7
|
+
* Meant to be wired to a git post-commit hook so every commit becomes a searchable memory
|
|
8
|
+
* without depending on the assistant calling `store_memory`:
|
|
9
|
+
*
|
|
10
|
+
* echo 'lemma-brain-ingest "$(git rev-parse --show-toplevel)" &' >> .git/hooks/post-commit
|
|
11
|
+
*
|
|
12
|
+
* With no flags it's an incremental catch-up: a marker file under .lemma/session/ tracks the
|
|
13
|
+
* last commit ingested, so running this after every commit or by hand both take the same path
|
|
14
|
+
* and never double-store. `--max N` only matters the first time there is no marker yet.
|
|
15
|
+
*
|
|
16
|
+
* Usage: lemma-brain-ingest [projectRoot] [--max N] [--changelog] [--no-commits]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
function main() {
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const maxIndex = args.indexOf('--max');
|
|
24
|
+
const maxCommits = maxIndex >= 0 ? parseInt(args[maxIndex + 1], 10) : undefined;
|
|
25
|
+
const includeChangelog = args.includes('--changelog');
|
|
26
|
+
const includeCommits = !args.includes('--no-commits');
|
|
27
|
+
const positional = args.filter((a, i) => !a.startsWith('--') && i !== maxIndex + 1);
|
|
28
|
+
const projectRoot = path.resolve(positional[0] || process.cwd());
|
|
29
|
+
|
|
30
|
+
const distPath = path.join(__dirname, '..', 'dist', 'cjs', 'subconscious', 'GitIngest.js');
|
|
31
|
+
let ingestRecentCommits, ingestChangelog;
|
|
32
|
+
try {
|
|
33
|
+
({ ingestRecentCommits, ingestChangelog } = require(distPath));
|
|
34
|
+
} catch {
|
|
35
|
+
console.error('[lemma-brain-ingest] Build not found. Run "npm run build" first.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (includeCommits) {
|
|
40
|
+
const res = ingestRecentCommits(projectRoot, { maxCommits });
|
|
41
|
+
console.error(`[lemma-brain-ingest] commits: ${res.ingested} stored, ${res.skipped} skipped (up to ${res.lastSha ? res.lastSha.substring(0, 12) : 'none'})`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (includeChangelog) {
|
|
45
|
+
const res = ingestChangelog(projectRoot);
|
|
46
|
+
console.error(`[lemma-brain-ingest] changelog: ${res.ingested} stored, ${res.skipped} skipped (${res.versionsSeen} version(s) seen)`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
main();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
// A hook that fails must never be the reason a commit or session goes bad.
|
|
54
|
+
console.error('[lemma-brain-ingest] Skipped:', err && err.message);
|
|
55
|
+
}
|
|
56
|
+
process.exit(0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAwjCnE,oFAAoF;AACpF,wBAAgB,kBAAkB,IAAI,MAAM,CAM3C;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,IAAI;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAQA;AAqHD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,MAAM,EAAE,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqCD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAC1C,IAAI,CAwEN"}
|
package/dist/cjs/mcp/tools.js
CHANGED
|
@@ -101,7 +101,7 @@ function isProUser() {
|
|
|
101
101
|
}
|
|
102
102
|
const FREE_TOOLS = new Set([
|
|
103
103
|
// Cache & Memory — the hook that shows instant value
|
|
104
|
-
"smarter_cache", "state_hash_cache", "token_receipt", "search_memory", "store_memory",
|
|
104
|
+
"smarter_cache", "state_hash_cache", "token_receipt", "search_memory", "store_memory", "get_project_history",
|
|
105
105
|
// Token optimization — shows what they're saving
|
|
106
106
|
"token_budget", "squeeze_prompt", "turbosqueeze",
|
|
107
107
|
// Utility — just enough to function
|
|
@@ -157,7 +157,7 @@ const toolDefinitions = [
|
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
name: "store_memory",
|
|
160
|
-
description: "Persist a technical solution, bug fix, architecture decision, or key fact into Lemma's Brain — so future questions on the same topic (even phrased differently) don't require re-investigating the repo. Pass filePaths for anything derived from specific files (e.g. 'where is X implemented', 'how does Y work') so the memory auto-invalidates the moment those files change, instead of risking a stale answer being reused.",
|
|
160
|
+
description: "Persist a technical solution, bug fix, architecture decision, or key fact into Lemma's Brain — so future questions on the same topic (even phrased differently) don't require re-investigating the repo. Pass filePaths for anything derived from specific files (e.g. 'where is X implemented', 'how does Y work') so the memory auto-invalidates the moment those files change, instead of risking a stale answer being reused. If the answer is really about one function/class rather than the whole file, pass `symbols` instead (or in addition) so an unrelated edit elsewhere in that file doesn't stale it out. Pass outcome='failed' for an approach that was tried and did NOT work — that's just as worth remembering as a fix, so the Brain can warn 'already tried, didn't work' instead of only ever suggesting reuse.",
|
|
161
161
|
inputSchema: {
|
|
162
162
|
type: "object",
|
|
163
163
|
properties: {
|
|
@@ -165,6 +165,19 @@ const toolDefinitions = [
|
|
|
165
165
|
response: { type: "string", description: "The complete technical content to memorize" },
|
|
166
166
|
provider: { type: "string", description: "Optional model provider name", default: "generic" },
|
|
167
167
|
filePaths: { type: "array", items: { type: "string" }, description: "Paths (relative to project root) this answer depends on. If any changes later, this memory is marked stale instead of being silently reused." },
|
|
168
|
+
symbols: {
|
|
169
|
+
type: "array",
|
|
170
|
+
items: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
filePath: { type: "string", description: "Path (relative to project root) containing the symbol" },
|
|
174
|
+
symbolName: { type: "string", description: "Function/class/interface/type/enum/const name" },
|
|
175
|
+
},
|
|
176
|
+
required: ["filePath", "symbolName"],
|
|
177
|
+
},
|
|
178
|
+
description: "Symbols (not whole files) this answer depends on. Freshness is then judged by that symbol's own source, so edits elsewhere in the same file don't stale this memory out.",
|
|
179
|
+
},
|
|
180
|
+
outcome: { type: "string", enum: ["confirmed", "failed"], description: "Default 'confirmed' (a verified working solution). Set 'failed' to record a dead end — an approach ruled out, not one to recommend." },
|
|
168
181
|
},
|
|
169
182
|
required: ["query", "response"],
|
|
170
183
|
},
|
|
@@ -316,6 +329,16 @@ const toolDefinitions = [
|
|
|
316
329
|
properties: {},
|
|
317
330
|
},
|
|
318
331
|
},
|
|
332
|
+
{
|
|
333
|
+
name: "get_project_history",
|
|
334
|
+
description: "Answers 'what have we done in this project': merges recent git commits, session checkpoints, and The Brain's memories (decisions, fixes, prior PR reviews) for the current project into one narrative. Use this instead of piecing the same picture together from search_memory + git log + reading checkpoint files separately.",
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: "object",
|
|
337
|
+
properties: {
|
|
338
|
+
limit: { type: "number", description: "Max items per section (commits, checkpoints, Brain memories)", default: 10 },
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
319
342
|
{
|
|
320
343
|
name: "get_ast_hologram",
|
|
321
344
|
description: "Generate a dense, token-efficient Holographic AST Map of the workspace using the TypeScript compiler. Returns structured JSON of all exported symbols with file paths and line numbers.",
|
|
@@ -1164,6 +1187,7 @@ const toolHandlers = {
|
|
|
1164
1187
|
auto_context_bundle: handleAutoContextBundle,
|
|
1165
1188
|
repeat_guard: handleRepeatGuard,
|
|
1166
1189
|
conversation_checkpoint: handleConversationCheckpoint,
|
|
1190
|
+
get_project_history: handleGetProjectHistory,
|
|
1167
1191
|
test_autofix_interceptor: handleTestAutofixInterceptor,
|
|
1168
1192
|
prompt_pattern_cache: handlePromptPatternCache,
|
|
1169
1193
|
lemma_toolbox: handleToolbox,
|
|
@@ -1349,7 +1373,7 @@ async function handleSearchMemory(args) {
|
|
|
1349
1373
|
}
|
|
1350
1374
|
const fresh = results.filter((r) => r.fresh);
|
|
1351
1375
|
const stale = results.filter((r) => !r.fresh);
|
|
1352
|
-
const formatResult = (r, i) => `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)\nPrompt: ${r.query.substring(0, 300)}...\nResponse: ${typeof r.response === "string" ? r.response : JSON.stringify(r.response, null, 2)}`;
|
|
1376
|
+
const formatResult = (r, i) => `Result ${i + 1} (Similarity: ${(r.similarity * 100).toFixed(1)}%)${r.outcome === "failed" ? "\n⚠️ TAGGED AS A FAILED ATTEMPT — this was tried before and did NOT work. Do not repeat it; treat this as a warning, not a suggestion." : ""}\nPrompt: ${r.query.substring(0, 300)}...\nResponse: ${typeof r.response === "string" ? r.response : JSON.stringify(r.response, null, 2)}`;
|
|
1353
1377
|
if (fresh.length > 0) {
|
|
1354
1378
|
// Only a fresh hit (or an untracked, purely conceptual entry) counts as a real
|
|
1355
1379
|
// avoided re-investigation — this is the only case worth crediting as savings.
|
|
@@ -1390,11 +1414,15 @@ async function handleStoreMemory(args) {
|
|
|
1390
1414
|
const responseText = args?.response;
|
|
1391
1415
|
const provider = args?.provider || "generic";
|
|
1392
1416
|
const filePaths = Array.isArray(args?.filePaths) ? args.filePaths : undefined;
|
|
1417
|
+
const outcome = args?.outcome === "failed" ? "failed" : undefined;
|
|
1418
|
+
const symbols = Array.isArray(args?.symbols)
|
|
1419
|
+
? args.symbols.filter((s) => s?.filePath && s?.symbolName)
|
|
1420
|
+
: undefined;
|
|
1393
1421
|
if (!query || !responseText)
|
|
1394
1422
|
throw new Error("Query and response are required");
|
|
1395
1423
|
try {
|
|
1396
1424
|
const brain = (0, TheBrainV2_1.getBrain)();
|
|
1397
|
-
const storeRes = brain.store(query, responseText, provider, 0.92, filePaths);
|
|
1425
|
+
const storeRes = brain.store(query, responseText, provider, 0.92, filePaths, undefined, outcome, symbols);
|
|
1398
1426
|
// Storing a memory saves nothing — it only creates the chance of a saving later.
|
|
1399
1427
|
// Crediting tokens here inflated the ledger on write and then credited the same
|
|
1400
1428
|
// answer again on every read. The saving is booked by search_memory on a fresh hit.
|
|
@@ -1404,7 +1432,9 @@ async function handleStoreMemory(args) {
|
|
|
1404
1432
|
note: "memory written — no tokens saved yet",
|
|
1405
1433
|
});
|
|
1406
1434
|
const trackingNote = filePaths && filePaths.length > 0 ? ` Tracking freshness against ${filePaths.length} file(s) — this memory auto-invalidates if they change.` : "";
|
|
1407
|
-
|
|
1435
|
+
const symbolNote = symbols && symbols.length > 0 ? ` Tracking freshness against ${symbols.length} symbol(s) specifically — unrelated edits elsewhere in those files won't stale this out.` : "";
|
|
1436
|
+
const outcomeNote = outcome === "failed" ? " Tagged as a FAILED attempt — future searches will surface it as a warning, not a suggestion." : "";
|
|
1437
|
+
return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}${trackingNote}${symbolNote}${outcomeNote}` }] };
|
|
1408
1438
|
}
|
|
1409
1439
|
catch (e) {
|
|
1410
1440
|
(0, utils_1.logError)("store_memory", e);
|
|
@@ -1923,6 +1953,59 @@ async function handleGetProjectOnboarding(_args) {
|
|
|
1923
1953
|
],
|
|
1924
1954
|
};
|
|
1925
1955
|
}
|
|
1956
|
+
// ── Project History (aggregate: git log + checkpoints + Brain) ─────
|
|
1957
|
+
/**
|
|
1958
|
+
* "What have we done in this project" today means piecing together search_memory, git log,
|
|
1959
|
+
* and manually reading .lemma/session/ checkpoint files by hand — three separate calls whose
|
|
1960
|
+
* results the model has to merge itself. This does that merge in one call.
|
|
1961
|
+
*/
|
|
1962
|
+
async function handleGetProjectHistory(args) {
|
|
1963
|
+
const cwd = process.cwd();
|
|
1964
|
+
const limit = typeof args?.limit === "number" ? args.limit : 10;
|
|
1965
|
+
try {
|
|
1966
|
+
const projectId = (0, TheBrainV2_1.deriveProjectId)(cwd);
|
|
1967
|
+
const brainEntries = (0, TheBrainV2_1.getBrain)()
|
|
1968
|
+
.getEntriesForProject(projectId)
|
|
1969
|
+
.sort((a, b) => (b.hits - a.hits) || (new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()))
|
|
1970
|
+
.slice(0, limit);
|
|
1971
|
+
const checkpoints = (0, ConversationCheckpoint_1.loadRecentCheckpoints)(cwd, Math.min(limit, 5));
|
|
1972
|
+
let recentCommits = [];
|
|
1973
|
+
try {
|
|
1974
|
+
const log = (0, child_process_1.execFileSync)("git", ["-C", cwd, "log", `-${limit}`, "--pretty=format:%h %s (%ar)"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
1975
|
+
recentCommits = log.split("\n").filter(Boolean);
|
|
1976
|
+
}
|
|
1977
|
+
catch {
|
|
1978
|
+
// Not a git repo, or no commits yet — sections below just won't include this one.
|
|
1979
|
+
}
|
|
1980
|
+
const parts = [`# Project history — ${path_1.default.basename(cwd)}`];
|
|
1981
|
+
if (recentCommits.length > 0) {
|
|
1982
|
+
parts.push(`## Recent commits\n${recentCommits.map((c) => `- ${c}`).join("\n")}`);
|
|
1983
|
+
}
|
|
1984
|
+
if (checkpoints.length > 0) {
|
|
1985
|
+
const checkpointLines = checkpoints.map((cp) => {
|
|
1986
|
+
const date = new Date(cp.timestamp).toISOString().replace("T", " ").slice(0, 16);
|
|
1987
|
+
return `- **${date}** — ${cp.summary.replace(/\n+/g, " ")}`;
|
|
1988
|
+
});
|
|
1989
|
+
parts.push(`## Session checkpoints\n${checkpointLines.join("\n")}`);
|
|
1990
|
+
}
|
|
1991
|
+
if (brainEntries.length > 0) {
|
|
1992
|
+
const brainLines = brainEntries.map((e) => {
|
|
1993
|
+
const tag = e.outcome === "failed" ? " ⚠️ FAILED ATTEMPT" : "";
|
|
1994
|
+
const snippet = e.response.length > 300 ? `${e.response.substring(0, 300)}...` : e.response;
|
|
1995
|
+
return `- **${e.query}**${tag} _(${e.provider}, ${e.hits} hit(s))_\n ${snippet}`;
|
|
1996
|
+
});
|
|
1997
|
+
parts.push(`## Brain memories (decisions, fixes, prior reviews)\n${brainLines.join("\n")}`);
|
|
1998
|
+
}
|
|
1999
|
+
if (parts.length === 1) {
|
|
2000
|
+
parts.push("Nothing recorded yet — no git history, checkpoints, or Brain memories found for this project.");
|
|
2001
|
+
}
|
|
2002
|
+
return { content: [{ type: "text", text: parts.join("\n\n") }] };
|
|
2003
|
+
}
|
|
2004
|
+
catch (e) {
|
|
2005
|
+
(0, utils_1.logError)("get_project_history", e);
|
|
2006
|
+
return { content: [{ type: "text", text: `get_project_history failed: ${e.message}` }] };
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
1926
2009
|
function extractSymbolsWithTsCompiler(filePath, relPath, parseErrors) {
|
|
1927
2010
|
try {
|
|
1928
2011
|
const src = fs_1.default.readFileSync(filePath, "utf8");
|
|
@@ -2243,7 +2326,7 @@ async function handleGetTelepathicHints(args) {
|
|
|
2243
2326
|
const similarity = (r.similarity * 100).toFixed(1);
|
|
2244
2327
|
const prompt = r.query || "Unknown";
|
|
2245
2328
|
const responseContent = r.response || "";
|
|
2246
|
-
hintsText += `### Hint ${i + 1} (${similarity}% match)\n`;
|
|
2329
|
+
hintsText += `### Hint ${i + 1} (${similarity}% match)${r.outcome === "failed" ? " — ⚠️ FAILED ATTEMPT, do not repeat" : ""}\n`;
|
|
2247
2330
|
hintsText += `**Memory:** ${prompt}\n\n`;
|
|
2248
2331
|
hintsText += `${responseContent.substring(0, 400)}${responseContent.length > 400 ? "..." : ""}\n\n---\n\n`;
|
|
2249
2332
|
});
|
|
@@ -2592,48 +2675,64 @@ async function handleTokenReceipt(args) {
|
|
|
2592
2675
|
}
|
|
2593
2676
|
// ── Token Budget ─────────────────────────────────────────────────
|
|
2594
2677
|
async function handleTokenBudget(_args) {
|
|
2595
|
-
const statsFile = path_1.default.join(os_1.default.homedir(), '.lemma-cache/stats.json');
|
|
2596
2678
|
const port = (0, utils_1.getProxyPort)();
|
|
2597
|
-
|
|
2679
|
+
const schemaTokens = Math.round(getToolSchemaChars() / 4);
|
|
2680
|
+
// /api/savings-breakdown serves SavingsLedger.getSnapshot() — the same ledger that
|
|
2681
|
+
// already nets tokensSaved against what Lemma itself spent (MCP schema injection,
|
|
2682
|
+
// tool-result bytes, cache misses). There is no separate "membership multiplier" to
|
|
2683
|
+
// compute: reusing that one number keeps this tool from reporting a bigger win than
|
|
2684
|
+
// the ledger the user can audit via `token_receipt` actually shows.
|
|
2685
|
+
let snapshot = null;
|
|
2598
2686
|
try {
|
|
2599
|
-
|
|
2687
|
+
const resp = await axios_1.default.get(`http://localhost:${port}/api/savings-breakdown`);
|
|
2688
|
+
snapshot = resp.data;
|
|
2600
2689
|
}
|
|
2601
2690
|
catch { }
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2691
|
+
if (!snapshot) {
|
|
2692
|
+
return {
|
|
2693
|
+
content: [{
|
|
2694
|
+
type: "text",
|
|
2695
|
+
text: JSON.stringify({
|
|
2696
|
+
note: "El proxy de Lemma no está corriendo, así que no hay bitácora de ahorro/costo que leer — no se inventa un número aquí. Arranca el proxy para que este reporte sea real.",
|
|
2697
|
+
perTurnOverhead: { ...getToolSurfaceSummary(), schemaTokens },
|
|
2698
|
+
}, null, 2),
|
|
2699
|
+
}],
|
|
2700
|
+
};
|
|
2606
2701
|
}
|
|
2607
|
-
|
|
2608
|
-
const
|
|
2609
|
-
const monthlyTokens = stats.monthlyTokens || stats.totalTokens || 100000;
|
|
2610
|
-
const multiplier = monthlyTokens > 0 ? ((monthlyTokens + totalTokensSaved) / monthlyTokens).toFixed(1) : "N/A";
|
|
2611
|
-
const toolCallCount = ledger.length || stats.toolCalls || 0;
|
|
2702
|
+
const { total, totalCost, net } = snapshot;
|
|
2703
|
+
const ratioStr = net.ratio === Infinity ? "n/a (sin costo registrado todavía)" : `${net.ratio.toFixed(2)}x`;
|
|
2612
2704
|
return {
|
|
2613
2705
|
content: [{
|
|
2614
2706
|
type: "text",
|
|
2615
2707
|
text: JSON.stringify({
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2708
|
+
netRatio: ratioStr,
|
|
2709
|
+
note: "netRatio = tokensSaved / tokensSpent, ambos de la misma bitácora auditable que expone token_receipt. Por debajo de 1.0x, Lemma está costando más de lo que ahorra en esta sesión.",
|
|
2710
|
+
tokensSaved: total.tokensSaved.toLocaleString(),
|
|
2711
|
+
tokensSpent: totalCost.tokensSpent.toLocaleString(),
|
|
2712
|
+
netTokens: net.netTokens.toLocaleString(),
|
|
2713
|
+
netCostUSD: net.netCost.toFixed(4),
|
|
2714
|
+
breakdown: {
|
|
2715
|
+
saved: {
|
|
2716
|
+
cache: snapshot.cache?.tokensSaved ?? 0,
|
|
2717
|
+
contextSqueeze: snapshot.contextSqueeze?.tokensSaved ?? 0,
|
|
2718
|
+
historyPrune: snapshot.historyPrune?.tokensSaved ?? 0,
|
|
2719
|
+
complexityRouting: snapshot.complexityRouting?.tokensSaved ?? 0,
|
|
2720
|
+
clipboard: snapshot.clipboard?.tokensSaved ?? 0,
|
|
2721
|
+
},
|
|
2722
|
+
spent: {
|
|
2723
|
+
mcpInstructions: snapshot.costs?.mcpInstructions?.tokensSpent ?? 0,
|
|
2724
|
+
toolResult: snapshot.costs?.toolResult?.tokensSpent ?? 0,
|
|
2725
|
+
cacheMiss: snapshot.costs?.cacheMiss?.tokensSpent ?? 0,
|
|
2726
|
+
},
|
|
2628
2727
|
},
|
|
2629
2728
|
// The fixed cost of being connected, re-sent on every request of the session.
|
|
2630
2729
|
perTurnOverhead: {
|
|
2631
2730
|
...getToolSurfaceSummary(),
|
|
2632
|
-
schemaTokens
|
|
2731
|
+
schemaTokens,
|
|
2633
2732
|
note: "Hidden tools stay callable via lemma_toolbox. Set mcp.toolProfile='full' in lemma.config.json to advertise all of them.",
|
|
2634
2733
|
},
|
|
2635
|
-
recommendations:
|
|
2636
|
-
? ["
|
|
2734
|
+
recommendations: net.ratio !== Infinity && net.ratio < 1
|
|
2735
|
+
? ["netRatio < 1.0x: revisa si search_memory/smarter_cache se están llamando antes de razonar, o si el toolProfile anuncia más tools de las que este proyecto necesita — cada schema extra es costo fijo por turno."]
|
|
2637
2736
|
: ["Sigue usando turbosqueeze antes de cada code block grande.", "Usa smarter_cache antes de razonar."],
|
|
2638
2737
|
}, null, 2),
|
|
2639
2738
|
}],
|