@nxuss/lemma 1.7.1 → 1.7.2
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/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +115 -15
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/proxy/ComplexityRouter.d.ts +7 -0
- package/dist/cjs/proxy/ComplexityRouter.d.ts.map +1 -1
- package/dist/cjs/proxy/ComplexityRouter.js +18 -5
- package/dist/cjs/proxy/ComplexityRouter.js.map +1 -1
- package/dist/cjs/utils/TokenReceipt.d.ts +6 -0
- package/dist/cjs/utils/TokenReceipt.d.ts.map +1 -1
- package/dist/cjs/utils/TokenReceipt.js +9 -0
- package/dist/cjs/utils/TokenReceipt.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +116 -16
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/proxy/ComplexityRouter.d.ts +7 -0
- package/dist/esm/proxy/ComplexityRouter.d.ts.map +1 -1
- package/dist/esm/proxy/ComplexityRouter.js +18 -5
- package/dist/esm/proxy/ComplexityRouter.js.map +1 -1
- package/dist/esm/utils/TokenReceipt.d.ts +6 -0
- package/dist/esm/utils/TokenReceipt.d.ts.map +1 -1
- package/dist/esm/utils/TokenReceipt.js +8 -0
- package/dist/esm/utils/TokenReceipt.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
export interface ComplexityRouterConfig {
|
|
2
2
|
cheapModel?: string;
|
|
3
|
+
/**
|
|
4
|
+
* Cheap model to fall back to per provider family, keyed by a substring of the
|
|
5
|
+
* requested model id ('claude', 'gemini', ...). Overridable from lemma.config.json
|
|
6
|
+
* so a retired model id can be corrected without shipping a new build.
|
|
7
|
+
*/
|
|
8
|
+
cheapModelsByFamily?: Record<string, string>;
|
|
3
9
|
complexKeywords?: string[];
|
|
4
10
|
complexityThreshold?: number;
|
|
5
11
|
disabled?: boolean;
|
|
6
12
|
}
|
|
7
13
|
export declare class ComplexityRouter {
|
|
8
14
|
private cheapModel;
|
|
15
|
+
private cheapModelsByFamily;
|
|
9
16
|
private complexKeywords;
|
|
10
17
|
private syntaxDensityThreshold;
|
|
11
18
|
constructor(config?: ComplexityRouterConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComplexityRouter.d.ts","sourceRoot":"","sources":["../../../src/proxy/ComplexityRouter.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"ComplexityRouter.d.ts","sourceRoot":"","sources":["../../../src/proxy/ComplexityRouter.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAYD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,mBAAmB,CAAyB;IACpD,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,sBAAsB,CAAS;gBAE3B,MAAM,GAAE,sBAA2B;IAYxC,YAAY,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IASlD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE;CAsEvG;AAED,eAAe,gBAAgB,CAAC"}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ComplexityRouter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Model ids go stale as providers retire them; these are defaults, not truth. If the
|
|
6
|
+
* router starts recommending a model your provider rejects, override
|
|
7
|
+
* routing.complexity.cheapModelsByFamily in lemma.config.json.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CHEAP_MODELS_BY_FAMILY = {
|
|
10
|
+
claude: 'claude-haiku-4-5-20251001',
|
|
11
|
+
gemini: 'gemini-2.0-flash',
|
|
12
|
+
};
|
|
4
13
|
class ComplexityRouter {
|
|
5
14
|
constructor(config = {}) {
|
|
6
15
|
this.cheapModel = config.cheapModel || 'gpt-4o-mini';
|
|
16
|
+
this.cheapModelsByFamily = { ...DEFAULT_CHEAP_MODELS_BY_FAMILY, ...(config.cheapModelsByFamily || {}) };
|
|
7
17
|
this.complexKeywords = config.complexKeywords || [
|
|
8
18
|
'architecture', 'design', 'debug', 'trace', 'system', 'microservices',
|
|
9
19
|
'refactor', 'optimize', 'implement', 'algorithm', 'distributed',
|
|
@@ -15,6 +25,9 @@ class ComplexityRouter {
|
|
|
15
25
|
updateConfig(config) {
|
|
16
26
|
if (config.cheapModel)
|
|
17
27
|
this.cheapModel = config.cheapModel;
|
|
28
|
+
if (config.cheapModelsByFamily) {
|
|
29
|
+
this.cheapModelsByFamily = { ...this.cheapModelsByFamily, ...config.cheapModelsByFamily };
|
|
30
|
+
}
|
|
18
31
|
if (config.complexKeywords)
|
|
19
32
|
this.complexKeywords = config.complexKeywords;
|
|
20
33
|
if (config.complexityThreshold)
|
|
@@ -72,11 +85,11 @@ class ComplexityRouter {
|
|
|
72
85
|
let cheapModel = this.cheapModel;
|
|
73
86
|
if (originalModel) {
|
|
74
87
|
const lowerModel = originalModel.toLowerCase();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
for (const [family, model] of Object.entries(this.cheapModelsByFamily)) {
|
|
89
|
+
if (lowerModel.includes(family)) {
|
|
90
|
+
cheapModel = model;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
80
93
|
}
|
|
81
94
|
}
|
|
82
95
|
console.log(`[ComplexityRouter] Low complexity detected. Routing to ${cheapModel}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComplexityRouter.js","sourceRoot":"","sources":["../../../src/proxy/ComplexityRouter.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"ComplexityRouter.js","sourceRoot":"","sources":["../../../src/proxy/ComplexityRouter.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,12 @@ export interface ReceiptEvent {
|
|
|
14
14
|
meta?: Record<string, unknown>;
|
|
15
15
|
}
|
|
16
16
|
export declare function recordReceiptEvent(type: ReceiptEventType, label: string, meta?: Record<string, unknown>): void;
|
|
17
|
+
/**
|
|
18
|
+
* Number of events recorded so far. The dispatcher samples this around a handler to
|
|
19
|
+
* tell whether the handler already logged something specific (a cache hit, a miss)
|
|
20
|
+
* before falling back to a generic tool_call entry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getLedgerLength(): number;
|
|
17
23
|
export interface ReceiptSummary {
|
|
18
24
|
totalEvents: number;
|
|
19
25
|
byType: Record<ReceiptEventType, number>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenReceipt.d.ts","sourceRoot":"","sources":["../../../src/utils/TokenReceipt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,oBAAoB,GACpB,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAK9G;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,SAAK,GAAG,cAAc,CAc5D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
1
|
+
{"version":3,"file":"TokenReceipt.d.ts","sourceRoot":"","sources":["../../../src/utils/TokenReceipt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,oBAAoB,GACpB,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAK9G;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,SAAK,GAAG,cAAc,CAc5D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.recordReceiptEvent = recordReceiptEvent;
|
|
12
|
+
exports.getLedgerLength = getLedgerLength;
|
|
12
13
|
exports.getReceiptSummary = getReceiptSummary;
|
|
13
14
|
exports.clearReceiptLedger = clearReceiptLedger;
|
|
14
15
|
const MAX_EVENTS = 2000;
|
|
@@ -19,6 +20,14 @@ function recordReceiptEvent(type, label, meta) {
|
|
|
19
20
|
sessionLedger.splice(0, sessionLedger.length - MAX_EVENTS);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Number of events recorded so far. The dispatcher samples this around a handler to
|
|
25
|
+
* tell whether the handler already logged something specific (a cache hit, a miss)
|
|
26
|
+
* before falling back to a generic tool_call entry.
|
|
27
|
+
*/
|
|
28
|
+
function getLedgerLength() {
|
|
29
|
+
return sessionLedger.length;
|
|
30
|
+
}
|
|
22
31
|
function getReceiptSummary(limit = 20) {
|
|
23
32
|
const byType = {
|
|
24
33
|
exact_cache_hit: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenReceipt.js","sourceRoot":"","sources":["../../../src/utils/TokenReceipt.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAoBH,gDAKC;AAQD,8CAcC;AAED,gDAEC;
|
|
1
|
+
{"version":3,"file":"TokenReceipt.js","sourceRoot":"","sources":["../../../src/utils/TokenReceipt.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAoBH,gDAKC;AAOD,0CAEC;AAQD,8CAcC;AAED,gDAEC;AA1DD,MAAM,UAAU,GAAG,IAAI,CAAC;AAgBxB,MAAM,aAAa,GAAmB,EAAE,CAAC;AAEzC,SAAgB,kBAAkB,CAAC,IAAsB,EAAE,KAAa,EAAE,IAA8B;IACtG,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,IAAI,aAAa,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QACtC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,eAAe;IAC7B,OAAO,aAAa,CAAC,MAAM,CAAC;AAC9B,CAAC;AAQD,SAAgB,iBAAiB,CAAC,KAAK,GAAG,EAAE;IAC1C,MAAM,MAAM,GAAqC;QAC/C,eAAe,EAAE,CAAC;QAClB,kBAAkB,EAAE,CAAC;QACrB,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,CAAC;KACb,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,aAAa;QAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;IAChD,OAAO;QACL,WAAW,EAAE,aAAa,CAAC,MAAM;QACjC,MAAM;QACN,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;KACpC,CAAC;AACJ,CAAC;AAED,SAAgB,kBAAkB;IAChC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -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;AAiiCnE,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;AAoHD,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/esm/mcp/tools.js
CHANGED
|
@@ -33,7 +33,7 @@ import { surgicalASTInsert } from "../utils/SurgicalASTInsert.js";
|
|
|
33
33
|
import { getInfraToolDefinitions, getInfraToolHandlers, INFRA_TOOL_NAMES } from "../infra/mcp-tools.js";
|
|
34
34
|
import { resolveToolSurface, buildToolboxCatalog } from "./tool-profiles.js";
|
|
35
35
|
import { lookupStateHash, storeStateHash } from "../utils/StateHashCache.js";
|
|
36
|
-
import { recordReceiptEvent, getReceiptSummary } from "../utils/TokenReceipt.js";
|
|
36
|
+
import { recordReceiptEvent, getReceiptSummary, getLedgerLength } from "../utils/TokenReceipt.js";
|
|
37
37
|
import { distillCommandOutput, buildDistillFooter, readRegion } from "../utils/CommandOutputDistiller.js";
|
|
38
38
|
import { findMatch, reindentReplacement } from "../utils/PatchMatcher.js";
|
|
39
39
|
import { searchWorkspace, groupSearchResults, parseExtensionFilter } from "../utils/WorkspaceSearch.js";
|
|
@@ -103,6 +103,11 @@ const toolDefinitions = [
|
|
|
103
103
|
properties: {
|
|
104
104
|
query: { type: "string", description: "The natural language query" },
|
|
105
105
|
limit: { type: "number", description: "Maximum results to return", default: 5 },
|
|
106
|
+
minSimilarity: {
|
|
107
|
+
type: "number",
|
|
108
|
+
description: "Similarity floor (0.0-1.0) a memory must clear to be returned. Lowering this surfaces loosely related memories that are usually noise — leave it alone unless a known-relevant memory is being filtered out.",
|
|
109
|
+
default: 0.75,
|
|
110
|
+
},
|
|
106
111
|
},
|
|
107
112
|
required: ["query"],
|
|
108
113
|
},
|
|
@@ -1156,6 +1161,41 @@ async function handleToolbox(args) {
|
|
|
1156
1161
|
}
|
|
1157
1162
|
throw new Error(`Unknown action: ${action}. Use 'list', 'schema', or 'call'.`);
|
|
1158
1163
|
}
|
|
1164
|
+
/** Tools whose whole job is to put file contents into the model's context. */
|
|
1165
|
+
const FILE_READ_TOOLS = new Set([
|
|
1166
|
+
"read_workspace_file",
|
|
1167
|
+
"smart_file_slice",
|
|
1168
|
+
"get_symbol_surgical_context",
|
|
1169
|
+
"get_ast_hologram",
|
|
1170
|
+
"list_workspace_dir",
|
|
1171
|
+
"search_workspace",
|
|
1172
|
+
"read_token_budgeted",
|
|
1173
|
+
"bulk_file_digest",
|
|
1174
|
+
"import_tree_context",
|
|
1175
|
+
]);
|
|
1176
|
+
function receiptTypeForTool(name) {
|
|
1177
|
+
return FILE_READ_TOOLS.has(name) ? "file_read" : "tool_call";
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* A receipt entry is only useful if it says what the call was about. Pull the most
|
|
1181
|
+
* identifying argument without dragging whole file contents into the ledger.
|
|
1182
|
+
*/
|
|
1183
|
+
function receiptLabelMeta(name, args) {
|
|
1184
|
+
if (!args)
|
|
1185
|
+
return {};
|
|
1186
|
+
const meta = {};
|
|
1187
|
+
if (typeof args.filePath === "string")
|
|
1188
|
+
meta.filePath = args.filePath;
|
|
1189
|
+
if (typeof args.dirPath === "string")
|
|
1190
|
+
meta.dirPath = args.dirPath;
|
|
1191
|
+
if (typeof args.query === "string")
|
|
1192
|
+
meta.query = args.query.substring(0, 100);
|
|
1193
|
+
if (typeof args.command === "string")
|
|
1194
|
+
meta.command = args.command.substring(0, 100);
|
|
1195
|
+
if (typeof args.symbolName === "string")
|
|
1196
|
+
meta.symbolName = args.symbolName;
|
|
1197
|
+
return meta;
|
|
1198
|
+
}
|
|
1159
1199
|
export function setupToolsHandlers(server, onToolCall) {
|
|
1160
1200
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1161
1201
|
tools: toolDefinitionsArray,
|
|
@@ -1190,7 +1230,16 @@ export function setupToolsHandlers(server, onToolCall) {
|
|
|
1190
1230
|
throw new Error(`Unknown tool: ${name}`);
|
|
1191
1231
|
}
|
|
1192
1232
|
try {
|
|
1233
|
+
// Sampled around the handler so a tool that logs its own, more specific event
|
|
1234
|
+
// (a cache hit, a miss) isn't double-counted by the generic entry below.
|
|
1235
|
+
const ledgerBefore = getLedgerLength();
|
|
1193
1236
|
const result = await handler((args || {}));
|
|
1237
|
+
if (getLedgerLength() === ledgerBefore) {
|
|
1238
|
+
recordReceiptEvent(receiptTypeForTool(name), name, {
|
|
1239
|
+
tool: name,
|
|
1240
|
+
...receiptLabelMeta(name, args),
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1194
1243
|
const tokensImpact = estimateTokensFromResult(result);
|
|
1195
1244
|
onToolCall?.({
|
|
1196
1245
|
tool: name,
|
|
@@ -1225,16 +1274,35 @@ async function handleScrubPrivacy(args) {
|
|
|
1225
1274
|
const { maskedPrompt } = scrubber.mask(text);
|
|
1226
1275
|
return { content: [{ type: "text", text: maskedPrompt }] };
|
|
1227
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Minimum similarity for a stored memory to be offered for reuse. Shared by
|
|
1279
|
+
* search_memory and smarter_cache so the two never disagree about whether the same
|
|
1280
|
+
* query is a hit.
|
|
1281
|
+
*/
|
|
1282
|
+
const DEFAULT_MEMORY_SIMILARITY_FLOOR = 0.75;
|
|
1228
1283
|
async function handleSearchMemory(args) {
|
|
1229
1284
|
const query = args?.query;
|
|
1230
1285
|
const limit = args?.limit || 5;
|
|
1286
|
+
// A floor of 0 returns the nearest neighbour no matter how unrelated it is, and the
|
|
1287
|
+
// formatting below then presents it as a reusable memory. Match smarter_cache's
|
|
1288
|
+
// threshold so both paths agree on what counts as a hit.
|
|
1289
|
+
const minSimilarity = typeof args?.minSimilarity === "number" ? args.minSimilarity : DEFAULT_MEMORY_SIMILARITY_FLOOR;
|
|
1231
1290
|
if (!query)
|
|
1232
1291
|
throw new Error("Query is required");
|
|
1233
1292
|
try {
|
|
1234
1293
|
const brain = getBrain();
|
|
1235
|
-
const results = brain.search(query, limit,
|
|
1294
|
+
const results = brain.search(query, limit, minSimilarity, { projectId: deriveProjectId() });
|
|
1236
1295
|
if (results.length === 0) {
|
|
1237
|
-
|
|
1296
|
+
recordReceiptEvent("reasoning", query.substring(0, 100), {
|
|
1297
|
+
tool: "search_memory",
|
|
1298
|
+
reason: `no memory above the ${minSimilarity} similarity floor`,
|
|
1299
|
+
});
|
|
1300
|
+
return {
|
|
1301
|
+
content: [{
|
|
1302
|
+
type: "text",
|
|
1303
|
+
text: `No memory in Lemma's Brain scored at or above the ${(minSimilarity * 100).toFixed(0)}% similarity floor for this query. Investigate from scratch — there is nothing safe to reuse.`,
|
|
1304
|
+
}],
|
|
1305
|
+
};
|
|
1238
1306
|
}
|
|
1239
1307
|
const fresh = results.filter((r) => r.fresh);
|
|
1240
1308
|
const stale = results.filter((r) => !r.fresh);
|
|
@@ -1245,6 +1313,17 @@ async function handleSearchMemory(args) {
|
|
|
1245
1313
|
const bestFresh = fresh[0];
|
|
1246
1314
|
const tokensSaved = Math.max(100, Math.floor(String(bestFresh.response).length / 4));
|
|
1247
1315
|
reportSavings({ source: "cache", tokens: tokensSaved, toolName: "search_memory", query: query.substring(0, 100) });
|
|
1316
|
+
recordReceiptEvent("semantic_cache_hit", query.substring(0, 100), {
|
|
1317
|
+
tool: "search_memory",
|
|
1318
|
+
similarity: bestFresh.similarity,
|
|
1319
|
+
tokensSaved,
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
recordReceiptEvent("reasoning", query.substring(0, 100), {
|
|
1324
|
+
tool: "search_memory",
|
|
1325
|
+
reason: `${stale.length} similar memory/memories found but all stale — must re-verify`,
|
|
1326
|
+
});
|
|
1248
1327
|
}
|
|
1249
1328
|
const parts = [];
|
|
1250
1329
|
if (fresh.length > 0) {
|
|
@@ -1273,12 +1352,13 @@ async function handleStoreMemory(args) {
|
|
|
1273
1352
|
try {
|
|
1274
1353
|
const brain = getBrain();
|
|
1275
1354
|
const storeRes = brain.store(query, responseText, provider, 0.92, filePaths);
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1355
|
+
// Storing a memory saves nothing — it only creates the chance of a saving later.
|
|
1356
|
+
// Crediting tokens here inflated the ledger on write and then credited the same
|
|
1357
|
+
// answer again on every read. The saving is booked by search_memory on a fresh hit.
|
|
1358
|
+
recordReceiptEvent("tool_call", `store_memory: ${query.substring(0, 100)}`, {
|
|
1359
|
+
tool: "store_memory",
|
|
1360
|
+
filePaths,
|
|
1361
|
+
note: "memory written — no tokens saved yet",
|
|
1282
1362
|
});
|
|
1283
1363
|
const trackingNote = filePaths && filePaths.length > 0 ? ` Tracking freshness against ${filePaths.length} file(s) — this memory auto-invalidates if they change.` : "";
|
|
1284
1364
|
return { content: [{ type: "text", text: `Success: Memory stored. ${storeRes.reason}${trackingNote}` }] };
|
|
@@ -1800,7 +1880,7 @@ async function handleGetProjectOnboarding(_args) {
|
|
|
1800
1880
|
],
|
|
1801
1881
|
};
|
|
1802
1882
|
}
|
|
1803
|
-
function extractSymbolsWithTsCompiler(filePath, relPath) {
|
|
1883
|
+
function extractSymbolsWithTsCompiler(filePath, relPath, parseErrors) {
|
|
1804
1884
|
try {
|
|
1805
1885
|
const src = fs.readFileSync(filePath, "utf8");
|
|
1806
1886
|
const sourceFile = ts.createSourceFile(filePath, src, ts.ScriptTarget.Latest, true);
|
|
@@ -1808,7 +1888,10 @@ function extractSymbolsWithTsCompiler(filePath, relPath) {
|
|
|
1808
1888
|
function visit(node) {
|
|
1809
1889
|
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
1810
1890
|
const isExport = mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
1811
|
-
|
|
1891
|
+
// node.parent is undefined on the SourceFile root itself, and ts.isSourceFile()
|
|
1892
|
+
// dereferences .kind without a guard — calling it unguarded threw on the very
|
|
1893
|
+
// first visit(), so the catch below swallowed it and every file returned [].
|
|
1894
|
+
if (isExport || (node.parent && ts.isSourceFile(node.parent))) {
|
|
1812
1895
|
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
1813
1896
|
symbols.push({
|
|
1814
1897
|
kind: "function",
|
|
@@ -1877,10 +1960,11 @@ function extractSymbolsWithTsCompiler(filePath, relPath) {
|
|
|
1877
1960
|
}
|
|
1878
1961
|
catch (err) {
|
|
1879
1962
|
logWarn("get_ast_hologram", `Could not parse ${relPath}: ${err}`);
|
|
1963
|
+
parseErrors?.push(`${relPath}: ${err}`);
|
|
1880
1964
|
return [];
|
|
1881
1965
|
}
|
|
1882
1966
|
}
|
|
1883
|
-
function walkDirForHologram(dir, rel, extensions) {
|
|
1967
|
+
function walkDirForHologram(dir, rel, extensions, stats) {
|
|
1884
1968
|
let allSymbols = [];
|
|
1885
1969
|
try {
|
|
1886
1970
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -1890,18 +1974,20 @@ function walkDirForHologram(dir, rel, extensions) {
|
|
|
1890
1974
|
const fullPath = path.join(dir, entry.name);
|
|
1891
1975
|
const relPath = rel ? path.join(rel, entry.name) : entry.name;
|
|
1892
1976
|
if (entry.isDirectory()) {
|
|
1893
|
-
allSymbols = allSymbols.concat(walkDirForHologram(fullPath, relPath, extensions));
|
|
1977
|
+
allSymbols = allSymbols.concat(walkDirForHologram(fullPath, relPath, extensions, stats));
|
|
1894
1978
|
}
|
|
1895
1979
|
else {
|
|
1896
1980
|
const ext = entry.name.split(".").pop() || "";
|
|
1897
1981
|
if (extensions.includes(ext)) {
|
|
1898
|
-
|
|
1982
|
+
stats.filesScanned++;
|
|
1983
|
+
allSymbols = allSymbols.concat(extractSymbolsWithTsCompiler(fullPath, relPath, stats.parseErrors));
|
|
1899
1984
|
}
|
|
1900
1985
|
}
|
|
1901
1986
|
}
|
|
1902
1987
|
}
|
|
1903
1988
|
catch (err) {
|
|
1904
1989
|
logWarn("get_ast_hologram", `Error walking dir ${dir}: ${err}`);
|
|
1990
|
+
stats.parseErrors.push(`walk ${rel || "."}: ${err}`);
|
|
1905
1991
|
}
|
|
1906
1992
|
return allSymbols;
|
|
1907
1993
|
}
|
|
@@ -1910,18 +1996,32 @@ async function handleGetAstHologram(args) {
|
|
|
1910
1996
|
const extensions = args?.extensions || ["ts", "tsx", "js", "jsx"];
|
|
1911
1997
|
const workspaceRoot = process.cwd();
|
|
1912
1998
|
const { resolved } = safeResolvePath(workspaceRoot, dirPath);
|
|
1913
|
-
const
|
|
1999
|
+
const stats = { filesScanned: 0, parseErrors: [] };
|
|
2000
|
+
const symbols = walkDirForHologram(resolved, dirPath, extensions, stats);
|
|
1914
2001
|
const byFile = {};
|
|
1915
2002
|
symbols.forEach((s) => {
|
|
1916
2003
|
if (!byFile[s.file])
|
|
1917
2004
|
byFile[s.file] = [];
|
|
1918
2005
|
byFile[s.file].push({ kind: s.kind, name: s.name, line: s.line });
|
|
1919
2006
|
});
|
|
2007
|
+
// An empty map is reported as a failure, not as an answer: a silent "0 symbols" is
|
|
2008
|
+
// indistinguishable from a working scan of an empty dir, which is how a parse bug
|
|
2009
|
+
// stayed invisible here before. Say when nothing could be read.
|
|
2010
|
+
const warning = stats.filesScanned === 0
|
|
2011
|
+
? `No files matching [${extensions.join(", ")}] were found under "${dirPath || "."}" — check the path and extensions.`
|
|
2012
|
+
: symbols.length === 0
|
|
2013
|
+
? `Scanned ${stats.filesScanned} file(s) but extracted 0 symbols — this is very likely an extraction failure, not an empty codebase. Do not treat this as "the directory has no exports".`
|
|
2014
|
+
: undefined;
|
|
1920
2015
|
const hologram = {
|
|
1921
2016
|
workspace: path.basename(workspaceRoot),
|
|
1922
2017
|
scannedDir: dirPath || ".",
|
|
1923
2018
|
totalSymbols: symbols.length,
|
|
2019
|
+
filesScanned: stats.filesScanned,
|
|
1924
2020
|
totalFiles: Object.keys(byFile).length,
|
|
2021
|
+
...(warning ? { warning } : {}),
|
|
2022
|
+
...(stats.parseErrors.length > 0
|
|
2023
|
+
? { parseErrors: stats.parseErrors.slice(0, 10), parseErrorCount: stats.parseErrors.length }
|
|
2024
|
+
: {}),
|
|
1925
2025
|
map: byFile,
|
|
1926
2026
|
};
|
|
1927
2027
|
return { content: [{ type: "text", text: JSON.stringify(hologram, null, 2) }] };
|
|
@@ -2324,7 +2424,7 @@ async function handleCompressContext(args) {
|
|
|
2324
2424
|
async function handleSmarterCache(args) {
|
|
2325
2425
|
const query = args?.query;
|
|
2326
2426
|
const context = args?.context || "";
|
|
2327
|
-
const threshold = typeof args?.threshold === "number" ? args.threshold :
|
|
2427
|
+
const threshold = typeof args?.threshold === "number" ? args.threshold : DEFAULT_MEMORY_SIMILARITY_FLOOR;
|
|
2328
2428
|
if (!query)
|
|
2329
2429
|
throw new Error("query is required");
|
|
2330
2430
|
const fullQuery = context ? `${query}\n\nContext: ${context}` : query;
|