engramx 2.0.2 → 3.0.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/CHANGELOG.md +271 -0
- package/README.md +161 -17
- package/dist/{aider-context-BC5R2ZTA.js → aider-context-6IDE3R7U.js} +1 -1
- package/dist/check-2Z3MPZEJ.js +12 -0
- package/dist/{chunk-PEH54LYC.js → chunk-645NBY6L.js} +42 -5
- package/dist/chunk-73IBCRFI.js +215 -0
- package/dist/{chunk-SJT7VS2G.js → chunk-B4UOE64J.js} +46 -11
- package/dist/chunk-FKY6HIT2.js +99 -0
- package/dist/{chunk-533LR4I7.js → chunk-G4U3QOOW.js} +13 -97
- package/dist/chunk-RJC6RNXJ.js +1405 -0
- package/dist/chunk-RM2TBOVW.js +121 -0
- package/dist/chunk-SMU4WR3D.js +187 -0
- package/dist/{chunk-C6GBUOAL.js → chunk-VLTWBTQ7.js} +14 -15
- package/dist/chunk-XVYE4OX2.js +232 -0
- package/dist/chunk-ZUC6OXSL.js +178 -0
- package/dist/cli.js +818 -1533
- package/dist/{core-6IY5L6II.js → core-77F2BVYV.js} +2 -2
- package/dist/{cursor-mdc-GJ7E5LDD.js → cursor-mdc-EEO7PYZ3.js} +1 -1
- package/dist/{exporter-GWU2GF23.js → exporter-ZYJ4WM2F.js} +1 -1
- package/dist/{importer-V62NGZRK.js → importer-4UWQDH4W.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/install-YVMVCFQW.js +121 -0
- package/dist/mcp-client-ROOJF76V.js +9 -0
- package/dist/mcp-config-QD4NPVXB.js +12 -0
- package/dist/{migrate-UKCO6BUU.js → migrate-KJ5K5NWO.js} +1 -1
- package/dist/notify-5POGKMRX.js +36 -0
- package/dist/{plugin-loader-STTGYIL5.js → plugin-loader-SQQB6V74.js} +69 -23
- package/dist/report-C3GTM3HY.js +12 -0
- package/dist/resolver-H7GXVP73.js +21 -0
- package/dist/serve.js +5 -4
- package/dist/{server-KUG7U6SG.js → server-2ZQKXJ5M.js} +74 -4
- package/dist/{windsurf-rules-C7SVDHBL.js → windsurf-rules-XF7MYF6J.js} +1 -1
- package/dist/wizard-UH27IO4I.js +274 -0
- package/package.json +3 -2
- package/dist/{tuner-KFNNGKG3.js → tuner-Y2YENAZC.js} +3 -3
|
@@ -0,0 +1,1405 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readConfig
|
|
3
|
+
} from "./chunk-22INHMKB.js";
|
|
4
|
+
import {
|
|
5
|
+
getStore,
|
|
6
|
+
renderFileStructure
|
|
7
|
+
} from "./chunk-B4UOE64J.js";
|
|
8
|
+
|
|
9
|
+
// src/providers/types.ts
|
|
10
|
+
var PROVIDER_PRIORITY = [
|
|
11
|
+
"engram:ast",
|
|
12
|
+
"engram:structure",
|
|
13
|
+
"engram:mistakes",
|
|
14
|
+
// anthropic:memory sits between mistakes and mempalace — it's cheap
|
|
15
|
+
// (tier 1, single local file read), strictly relevant when present,
|
|
16
|
+
// and complements mistakes (mistakes = 'this broke'; anthropic:memory
|
|
17
|
+
// = 'here's what we learned about this area'). Placing it above
|
|
18
|
+
// mempalace keeps Claude Code users' own curated memory first.
|
|
19
|
+
"anthropic:memory",
|
|
20
|
+
"mempalace",
|
|
21
|
+
"context7",
|
|
22
|
+
"engram:git",
|
|
23
|
+
"obsidian",
|
|
24
|
+
"engram:lsp"
|
|
25
|
+
];
|
|
26
|
+
var DEFAULT_CACHE_TTL_SEC = 3600;
|
|
27
|
+
|
|
28
|
+
// src/providers/ast.ts
|
|
29
|
+
import { readFileSync } from "fs";
|
|
30
|
+
|
|
31
|
+
// src/providers/grammar-loader.ts
|
|
32
|
+
import { existsSync } from "fs";
|
|
33
|
+
import { join, dirname } from "path";
|
|
34
|
+
import { createRequire } from "module";
|
|
35
|
+
import { fileURLToPath } from "url";
|
|
36
|
+
var require2 = createRequire(import.meta.url);
|
|
37
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
38
|
+
var tsParserInit = false;
|
|
39
|
+
var EXT_TO_LANG = {
|
|
40
|
+
ts: "typescript",
|
|
41
|
+
tsx: "tsx",
|
|
42
|
+
js: "javascript",
|
|
43
|
+
jsx: "javascript",
|
|
44
|
+
mjs: "javascript",
|
|
45
|
+
cjs: "javascript",
|
|
46
|
+
py: "python",
|
|
47
|
+
go: "go",
|
|
48
|
+
rs: "rust",
|
|
49
|
+
rb: "ruby",
|
|
50
|
+
java: "java",
|
|
51
|
+
c: "c",
|
|
52
|
+
cpp: "cpp",
|
|
53
|
+
h: "c",
|
|
54
|
+
hpp: "cpp",
|
|
55
|
+
php: "php"
|
|
56
|
+
};
|
|
57
|
+
var LANG_TO_PKG = {
|
|
58
|
+
typescript: "tree-sitter-typescript",
|
|
59
|
+
tsx: "tree-sitter-typescript",
|
|
60
|
+
javascript: "tree-sitter-javascript",
|
|
61
|
+
python: "tree-sitter-python",
|
|
62
|
+
go: "tree-sitter-go",
|
|
63
|
+
rust: "tree-sitter-rust"
|
|
64
|
+
};
|
|
65
|
+
function getSupportedLang(filePath) {
|
|
66
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
67
|
+
return ext ? EXT_TO_LANG[ext] ?? null : null;
|
|
68
|
+
}
|
|
69
|
+
function findGrammarWasm(lang) {
|
|
70
|
+
const pkg = LANG_TO_PKG[lang];
|
|
71
|
+
if (!pkg) return null;
|
|
72
|
+
const wasmName = lang === "tsx" ? "tree-sitter-tsx.wasm" : `tree-sitter-${lang}.wasm`;
|
|
73
|
+
const candidates = [];
|
|
74
|
+
try {
|
|
75
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
76
|
+
candidates.push(join(here, "..", "grammars", wasmName));
|
|
77
|
+
candidates.push(join(here, "grammars", wasmName));
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
82
|
+
candidates.push(join(here, "..", "..", "node_modules", pkg, wasmName));
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const pkgMain = require2.resolve(`${pkg}/package.json`);
|
|
87
|
+
const pkgDir = dirname(pkgMain);
|
|
88
|
+
candidates.push(join(pkgDir, wasmName));
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
return candidates.find((c) => existsSync(c)) ?? null;
|
|
92
|
+
}
|
|
93
|
+
async function getParser(lang) {
|
|
94
|
+
const cached = parserCache.get(lang);
|
|
95
|
+
if (cached) return cached;
|
|
96
|
+
try {
|
|
97
|
+
const { Parser, Language } = await import("web-tree-sitter");
|
|
98
|
+
if (!tsParserInit) {
|
|
99
|
+
await Parser.init();
|
|
100
|
+
tsParserInit = true;
|
|
101
|
+
}
|
|
102
|
+
const wasmPath = findGrammarWasm(lang);
|
|
103
|
+
if (!wasmPath) return null;
|
|
104
|
+
const language = await Language.load(wasmPath);
|
|
105
|
+
const parser = new Parser();
|
|
106
|
+
parser.setLanguage(language);
|
|
107
|
+
parserCache.set(lang, parser);
|
|
108
|
+
return parser;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/providers/ast.ts
|
|
115
|
+
function extractParams(node) {
|
|
116
|
+
const paramsNode = node.childForFieldName("parameters") ?? node.childForFieldName("formal_parameters");
|
|
117
|
+
if (!paramsNode) return "";
|
|
118
|
+
return paramsNode.text.replace(/\n/g, " ").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
119
|
+
}
|
|
120
|
+
function extractSymbols(rootNode) {
|
|
121
|
+
const symbols = [];
|
|
122
|
+
function visit(node) {
|
|
123
|
+
switch (node.type) {
|
|
124
|
+
// ── Functions ───────────────────────────────────────────────
|
|
125
|
+
case "function_declaration":
|
|
126
|
+
case "function_definition": {
|
|
127
|
+
const nameNode = node.childForFieldName("name");
|
|
128
|
+
if (nameNode) {
|
|
129
|
+
symbols.push({
|
|
130
|
+
name: nameNode.text,
|
|
131
|
+
kind: "function",
|
|
132
|
+
line: node.startPosition.row + 1,
|
|
133
|
+
params: extractParams(node)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
// ── Classes ─────────────────────────────────────────────────
|
|
139
|
+
case "class_declaration":
|
|
140
|
+
case "class_definition": {
|
|
141
|
+
const nameNode = node.childForFieldName("name");
|
|
142
|
+
if (nameNode) {
|
|
143
|
+
symbols.push({
|
|
144
|
+
name: nameNode.text,
|
|
145
|
+
kind: "class",
|
|
146
|
+
line: node.startPosition.row + 1
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
// ── Methods ─────────────────────────────────────────────────
|
|
152
|
+
case "method_definition":
|
|
153
|
+
case "method_declaration": {
|
|
154
|
+
const nameNode = node.childForFieldName("name");
|
|
155
|
+
if (nameNode) {
|
|
156
|
+
symbols.push({
|
|
157
|
+
name: nameNode.text,
|
|
158
|
+
kind: "method",
|
|
159
|
+
line: node.startPosition.row + 1,
|
|
160
|
+
params: extractParams(node)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
// ── TypeScript interfaces ────────────────────────────────────
|
|
166
|
+
case "interface_declaration": {
|
|
167
|
+
const nameNode = node.childForFieldName("name");
|
|
168
|
+
if (nameNode) {
|
|
169
|
+
symbols.push({
|
|
170
|
+
name: nameNode.text,
|
|
171
|
+
kind: "interface",
|
|
172
|
+
line: node.startPosition.row + 1
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
// ── TypeScript type aliases ──────────────────────────────────
|
|
178
|
+
case "type_alias_declaration": {
|
|
179
|
+
const nameNode = node.childForFieldName("name");
|
|
180
|
+
if (nameNode) {
|
|
181
|
+
symbols.push({
|
|
182
|
+
name: nameNode.text,
|
|
183
|
+
kind: "type",
|
|
184
|
+
line: node.startPosition.row + 1
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
// ── Exported variable declarations (incl. arrow functions) ──
|
|
190
|
+
case "lexical_declaration":
|
|
191
|
+
case "variable_declaration": {
|
|
192
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
193
|
+
const child = node.child(i);
|
|
194
|
+
if (!child || child.type !== "variable_declarator") continue;
|
|
195
|
+
const nameNode = child.childForFieldName("name");
|
|
196
|
+
const valueNode = child.childForFieldName("value");
|
|
197
|
+
if (!nameNode) continue;
|
|
198
|
+
const isArrow = valueNode?.type === "arrow_function" || valueNode?.type === "function";
|
|
199
|
+
symbols.push({
|
|
200
|
+
name: nameNode.text,
|
|
201
|
+
kind: isArrow ? "function" : "variable",
|
|
202
|
+
line: node.startPosition.row + 1,
|
|
203
|
+
params: isArrow && valueNode ? extractParams(valueNode) : void 0
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
default:
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
212
|
+
const child = node.child(i);
|
|
213
|
+
if (child) visit(child);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
visit(rootNode);
|
|
217
|
+
return symbols;
|
|
218
|
+
}
|
|
219
|
+
function formatSymbols(symbols, tokenBudget) {
|
|
220
|
+
const lines = symbols.map((s) => {
|
|
221
|
+
const params = s.params !== void 0 ? `(${s.params})` : "";
|
|
222
|
+
return `${s.kind.toUpperCase()} ${s.name}${params} L${s.line}`;
|
|
223
|
+
});
|
|
224
|
+
const charBudget = tokenBudget * 4;
|
|
225
|
+
let text = lines.join("\n");
|
|
226
|
+
if (text.length > charBudget) {
|
|
227
|
+
text = text.slice(0, charBudget).trimEnd() + "\n... (truncated)";
|
|
228
|
+
}
|
|
229
|
+
return text;
|
|
230
|
+
}
|
|
231
|
+
var astProvider = {
|
|
232
|
+
name: "engram:ast",
|
|
233
|
+
label: "AST STRUCTURE",
|
|
234
|
+
tier: 1,
|
|
235
|
+
tokenBudget: 300,
|
|
236
|
+
timeoutMs: 200,
|
|
237
|
+
async resolve(filePath, _context) {
|
|
238
|
+
const lang = getSupportedLang(filePath);
|
|
239
|
+
if (!lang) return null;
|
|
240
|
+
const parser = await getParser(lang);
|
|
241
|
+
if (!parser) return null;
|
|
242
|
+
try {
|
|
243
|
+
const source = readFileSync(filePath, "utf-8");
|
|
244
|
+
const tree = parser.parse(source);
|
|
245
|
+
if (!tree) return null;
|
|
246
|
+
const symbols = extractSymbols(tree.rootNode);
|
|
247
|
+
if (symbols.length === 0) return null;
|
|
248
|
+
return {
|
|
249
|
+
provider: "engram:ast",
|
|
250
|
+
content: formatSymbols(symbols, this.tokenBudget),
|
|
251
|
+
confidence: 1,
|
|
252
|
+
cached: false
|
|
253
|
+
};
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
async isAvailable() {
|
|
259
|
+
try {
|
|
260
|
+
const { Parser } = await import("web-tree-sitter");
|
|
261
|
+
await Parser.init();
|
|
262
|
+
return true;
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// src/providers/engram-structure.ts
|
|
270
|
+
var structureProvider = {
|
|
271
|
+
name: "engram:structure",
|
|
272
|
+
label: "STRUCTURE",
|
|
273
|
+
tier: 1,
|
|
274
|
+
tokenBudget: 250,
|
|
275
|
+
timeoutMs: 500,
|
|
276
|
+
async resolve(filePath, context) {
|
|
277
|
+
try {
|
|
278
|
+
const store = await getStore(context.projectRoot);
|
|
279
|
+
try {
|
|
280
|
+
const result = renderFileStructure(store, filePath);
|
|
281
|
+
if (!result || result.nodeCount === 0) return null;
|
|
282
|
+
return {
|
|
283
|
+
provider: "engram:structure",
|
|
284
|
+
content: result.text,
|
|
285
|
+
confidence: result.avgConfidence,
|
|
286
|
+
cached: false
|
|
287
|
+
};
|
|
288
|
+
} finally {
|
|
289
|
+
store.close();
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
async isAvailable() {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/providers/engram-mistakes.ts
|
|
301
|
+
var mistakesProvider = {
|
|
302
|
+
name: "engram:mistakes",
|
|
303
|
+
label: "KNOWN ISSUES",
|
|
304
|
+
tier: 1,
|
|
305
|
+
tokenBudget: 50,
|
|
306
|
+
timeoutMs: 200,
|
|
307
|
+
async resolve(filePath, context) {
|
|
308
|
+
try {
|
|
309
|
+
const store = await getStore(context.projectRoot);
|
|
310
|
+
try {
|
|
311
|
+
const now = Date.now();
|
|
312
|
+
const allMistakes = store.getNodesByFile(filePath).filter((n) => n.kind === "mistake").filter((n) => n.validUntil === void 0 || n.validUntil > now);
|
|
313
|
+
if (allMistakes.length === 0) return null;
|
|
314
|
+
const lines = allMistakes.slice(0, 5).map((m) => ` ! ${m.label} (flagged ${formatAge(m.lastVerified)})`).join("\n");
|
|
315
|
+
return {
|
|
316
|
+
provider: "engram:mistakes",
|
|
317
|
+
content: lines,
|
|
318
|
+
confidence: 0.95,
|
|
319
|
+
cached: false
|
|
320
|
+
};
|
|
321
|
+
} finally {
|
|
322
|
+
store.close();
|
|
323
|
+
}
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
async isAvailable() {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
function formatAge(timestampMs) {
|
|
333
|
+
if (timestampMs === 0) return "unknown";
|
|
334
|
+
const days = Math.floor((Date.now() - timestampMs) / (1e3 * 60 * 60 * 24));
|
|
335
|
+
if (days === 0) return "today";
|
|
336
|
+
if (days === 1) return "yesterday";
|
|
337
|
+
if (days < 30) return `${days}d ago`;
|
|
338
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/providers/engram-git.ts
|
|
342
|
+
import { execFileSync } from "child_process";
|
|
343
|
+
var gitProvider = {
|
|
344
|
+
name: "engram:git",
|
|
345
|
+
label: "CHANGES",
|
|
346
|
+
tier: 1,
|
|
347
|
+
tokenBudget: 50,
|
|
348
|
+
timeoutMs: 200,
|
|
349
|
+
async resolve(filePath, context) {
|
|
350
|
+
try {
|
|
351
|
+
const cwd = context.projectRoot;
|
|
352
|
+
const lastLog = git(
|
|
353
|
+
["log", "-1", "--format=%ar|%an|%s", "--", filePath],
|
|
354
|
+
cwd
|
|
355
|
+
);
|
|
356
|
+
if (!lastLog) return null;
|
|
357
|
+
const [timeAgo, author, message] = lastLog.split("|", 3);
|
|
358
|
+
const recentCount = git(
|
|
359
|
+
[
|
|
360
|
+
"rev-list",
|
|
361
|
+
"--count",
|
|
362
|
+
"--since=30.days",
|
|
363
|
+
"HEAD",
|
|
364
|
+
"--",
|
|
365
|
+
filePath
|
|
366
|
+
],
|
|
367
|
+
cwd
|
|
368
|
+
);
|
|
369
|
+
const churnNote = context.churnRate > 0.3 ? "high churn" : context.churnRate > 0.1 ? "moderate" : "stable";
|
|
370
|
+
const parts = [
|
|
371
|
+
` Last modified: ${timeAgo} by ${author} (${truncate(message, 50)})`,
|
|
372
|
+
` Churn: ${context.churnRate.toFixed(2)} (${churnNote}) | ${recentCount || "0"} changes in 30d`
|
|
373
|
+
];
|
|
374
|
+
return {
|
|
375
|
+
provider: "engram:git",
|
|
376
|
+
content: parts.join("\n"),
|
|
377
|
+
confidence: 0.9,
|
|
378
|
+
cached: false
|
|
379
|
+
};
|
|
380
|
+
} catch {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
async isAvailable() {
|
|
385
|
+
try {
|
|
386
|
+
execFileSync("git", ["--version"], {
|
|
387
|
+
encoding: "utf-8",
|
|
388
|
+
timeout: 2e3
|
|
389
|
+
});
|
|
390
|
+
return true;
|
|
391
|
+
} catch {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
function git(args, cwd) {
|
|
397
|
+
try {
|
|
398
|
+
return execFileSync("git", args, {
|
|
399
|
+
cwd,
|
|
400
|
+
encoding: "utf-8",
|
|
401
|
+
timeout: 3e3,
|
|
402
|
+
maxBuffer: 1024 * 1024
|
|
403
|
+
}).trim();
|
|
404
|
+
} catch {
|
|
405
|
+
return "";
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function truncate(s, max) {
|
|
409
|
+
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/providers/mempalace.ts
|
|
413
|
+
import { execFile } from "child_process";
|
|
414
|
+
var MAX_SEARCH_RESULTS = 3;
|
|
415
|
+
var mempalaceProvider = {
|
|
416
|
+
name: "mempalace",
|
|
417
|
+
label: "DECISIONS",
|
|
418
|
+
tier: 2,
|
|
419
|
+
tokenBudget: 100,
|
|
420
|
+
timeoutMs: 200,
|
|
421
|
+
async resolve(filePath, context) {
|
|
422
|
+
try {
|
|
423
|
+
const store = await getStore(context.projectRoot);
|
|
424
|
+
try {
|
|
425
|
+
const cached = store.getCachedContextForProvider(
|
|
426
|
+
"mempalace",
|
|
427
|
+
filePath
|
|
428
|
+
);
|
|
429
|
+
if (cached) {
|
|
430
|
+
return {
|
|
431
|
+
provider: "mempalace",
|
|
432
|
+
content: cached.content,
|
|
433
|
+
confidence: 0.8,
|
|
434
|
+
cached: true
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
} finally {
|
|
438
|
+
store.close();
|
|
439
|
+
}
|
|
440
|
+
const query = buildQuery(filePath, context);
|
|
441
|
+
const raw = await searchMempalace(query);
|
|
442
|
+
if (!raw) return null;
|
|
443
|
+
const content = formatResults(raw);
|
|
444
|
+
if (!content) return null;
|
|
445
|
+
const store2 = await getStore(context.projectRoot);
|
|
446
|
+
try {
|
|
447
|
+
store2.setCachedContext(
|
|
448
|
+
"mempalace",
|
|
449
|
+
filePath,
|
|
450
|
+
content,
|
|
451
|
+
DEFAULT_CACHE_TTL_SEC,
|
|
452
|
+
query
|
|
453
|
+
);
|
|
454
|
+
store2.save();
|
|
455
|
+
} finally {
|
|
456
|
+
store2.close();
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
provider: "mempalace",
|
|
460
|
+
content,
|
|
461
|
+
confidence: 0.8,
|
|
462
|
+
cached: false
|
|
463
|
+
};
|
|
464
|
+
} catch {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
async warmup(projectRoot) {
|
|
469
|
+
const start = Date.now();
|
|
470
|
+
const entries = [];
|
|
471
|
+
try {
|
|
472
|
+
const store = await getStore(projectRoot);
|
|
473
|
+
let projectName;
|
|
474
|
+
try {
|
|
475
|
+
projectName = store.getStat("project_name") ?? projectRoot.split("/").pop() ?? "";
|
|
476
|
+
} finally {
|
|
477
|
+
store.close();
|
|
478
|
+
}
|
|
479
|
+
if (!projectName) {
|
|
480
|
+
return { provider: "mempalace", entries, durationMs: Date.now() - start };
|
|
481
|
+
}
|
|
482
|
+
const raw = await searchMempalace(
|
|
483
|
+
`${projectName} decisions architecture patterns`
|
|
484
|
+
);
|
|
485
|
+
if (!raw) {
|
|
486
|
+
return { provider: "mempalace", entries, durationMs: Date.now() - start };
|
|
487
|
+
}
|
|
488
|
+
const content = formatResults(raw);
|
|
489
|
+
if (content) {
|
|
490
|
+
entries.push({ filePath: "__project__", content });
|
|
491
|
+
}
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
return { provider: "mempalace", entries, durationMs: Date.now() - start };
|
|
495
|
+
},
|
|
496
|
+
async isAvailable() {
|
|
497
|
+
try {
|
|
498
|
+
const result = await execFilePromise("mcp-mempalace", [
|
|
499
|
+
"mempalace-status"
|
|
500
|
+
]);
|
|
501
|
+
return result.includes("palace") || result.includes("drawers");
|
|
502
|
+
} catch {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
function buildQuery(filePath, context) {
|
|
508
|
+
const fileName = filePath.split("/").pop()?.replace(/\.\w+$/, "") ?? "";
|
|
509
|
+
const importTerms = context.imports.slice(0, 3).join(" ");
|
|
510
|
+
return `${fileName} ${importTerms}`.trim();
|
|
511
|
+
}
|
|
512
|
+
function searchMempalace(query) {
|
|
513
|
+
return new Promise((resolve) => {
|
|
514
|
+
const timeout = setTimeout(() => resolve(null), 3e3);
|
|
515
|
+
execFile(
|
|
516
|
+
"mcp-mempalace",
|
|
517
|
+
["mempalace-search", "--query", query],
|
|
518
|
+
{ encoding: "utf-8", timeout: 3e3, maxBuffer: 1024 * 1024 },
|
|
519
|
+
(err, stdout) => {
|
|
520
|
+
clearTimeout(timeout);
|
|
521
|
+
if (err || !stdout.trim()) {
|
|
522
|
+
resolve(null);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
resolve(stdout.trim());
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
function formatResults(raw) {
|
|
531
|
+
try {
|
|
532
|
+
const parsed = JSON.parse(raw);
|
|
533
|
+
const results = Array.isArray(parsed) ? parsed : parsed?.results ?? parsed?.drawers ?? [];
|
|
534
|
+
if (results.length === 0) return null;
|
|
535
|
+
const lines = results.slice(0, MAX_SEARCH_RESULTS).map((r) => {
|
|
536
|
+
const content = r.content ?? r.text ?? r.summary ?? "";
|
|
537
|
+
const truncated = content.split(/\s+/).slice(0, 30).join(" ");
|
|
538
|
+
return ` - ${truncated}`;
|
|
539
|
+
}).filter((l) => l.length > 4);
|
|
540
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
541
|
+
} catch {
|
|
542
|
+
const lines = raw.split("\n").filter((l) => l.trim()).slice(0, MAX_SEARCH_RESULTS).map((l) => ` - ${l.trim().slice(0, 120)}`);
|
|
543
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function execFilePromise(cmd, args) {
|
|
547
|
+
return new Promise((resolve, reject) => {
|
|
548
|
+
execFile(
|
|
549
|
+
cmd,
|
|
550
|
+
args,
|
|
551
|
+
{ encoding: "utf-8", timeout: 3e3 },
|
|
552
|
+
(err, stdout) => {
|
|
553
|
+
if (err) reject(err);
|
|
554
|
+
else resolve(stdout.trim());
|
|
555
|
+
}
|
|
556
|
+
);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/providers/context7.ts
|
|
561
|
+
import { execFile as execFile2 } from "child_process";
|
|
562
|
+
var LIBRARY_CACHE_TTL = 4 * 3600;
|
|
563
|
+
var context7Provider = {
|
|
564
|
+
name: "context7",
|
|
565
|
+
label: "LIBRARY",
|
|
566
|
+
tier: 2,
|
|
567
|
+
tokenBudget: 100,
|
|
568
|
+
timeoutMs: 200,
|
|
569
|
+
async resolve(filePath, context) {
|
|
570
|
+
if (context.imports.length === 0) return null;
|
|
571
|
+
try {
|
|
572
|
+
const store = await getStore(context.projectRoot);
|
|
573
|
+
try {
|
|
574
|
+
const cached = store.getCachedContextForProvider("context7", filePath);
|
|
575
|
+
if (cached) {
|
|
576
|
+
return {
|
|
577
|
+
provider: "context7",
|
|
578
|
+
content: cached.content,
|
|
579
|
+
confidence: 0.85,
|
|
580
|
+
cached: true
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
} finally {
|
|
584
|
+
store.close();
|
|
585
|
+
}
|
|
586
|
+
const primaryImport = context.imports[0];
|
|
587
|
+
const docs = await queryContext7(primaryImport);
|
|
588
|
+
if (!docs) return null;
|
|
589
|
+
const content = formatDocs(primaryImport, docs);
|
|
590
|
+
if (!content) return null;
|
|
591
|
+
const store2 = await getStore(context.projectRoot);
|
|
592
|
+
try {
|
|
593
|
+
store2.setCachedContext(
|
|
594
|
+
"context7",
|
|
595
|
+
filePath,
|
|
596
|
+
content,
|
|
597
|
+
LIBRARY_CACHE_TTL,
|
|
598
|
+
primaryImport
|
|
599
|
+
);
|
|
600
|
+
store2.save();
|
|
601
|
+
} finally {
|
|
602
|
+
store2.close();
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
provider: "context7",
|
|
606
|
+
content,
|
|
607
|
+
confidence: 0.85,
|
|
608
|
+
cached: false
|
|
609
|
+
};
|
|
610
|
+
} catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
async warmup(projectRoot) {
|
|
615
|
+
const start = Date.now();
|
|
616
|
+
const entries = [];
|
|
617
|
+
try {
|
|
618
|
+
const store = await getStore(projectRoot);
|
|
619
|
+
let importEdges;
|
|
620
|
+
try {
|
|
621
|
+
const allEdges = store.getAllEdges();
|
|
622
|
+
importEdges = allEdges.filter((e) => e.relation === "imports").map((e) => ({ source: e.sourceFile, target: e.target }));
|
|
623
|
+
} finally {
|
|
624
|
+
store.close();
|
|
625
|
+
}
|
|
626
|
+
const packages = [
|
|
627
|
+
...new Set(
|
|
628
|
+
importEdges.map((e) => {
|
|
629
|
+
const parts = e.target.split("::");
|
|
630
|
+
return parts[parts.length - 1];
|
|
631
|
+
}).filter(isExternalPackage)
|
|
632
|
+
)
|
|
633
|
+
].slice(0, 10);
|
|
634
|
+
for (const pkg of packages) {
|
|
635
|
+
const docs = await queryContext7(pkg);
|
|
636
|
+
if (docs) {
|
|
637
|
+
const content = formatDocs(pkg, docs);
|
|
638
|
+
if (content) {
|
|
639
|
+
const files = importEdges.filter((e) => e.target.includes(pkg)).map((e) => e.source);
|
|
640
|
+
for (const file of [...new Set(files)]) {
|
|
641
|
+
entries.push({ filePath: file, content });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
} catch {
|
|
647
|
+
}
|
|
648
|
+
return { provider: "context7", entries, durationMs: Date.now() - start };
|
|
649
|
+
},
|
|
650
|
+
async isAvailable() {
|
|
651
|
+
try {
|
|
652
|
+
const result = await execFilePromise2("mcp-context7", ["--list"]);
|
|
653
|
+
return result.includes("resolve-library-id");
|
|
654
|
+
} catch {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
function isExternalPackage(name) {
|
|
660
|
+
if (!name) return false;
|
|
661
|
+
if (name.startsWith(".") || name.startsWith("/")) return false;
|
|
662
|
+
if ([
|
|
663
|
+
"fs",
|
|
664
|
+
"path",
|
|
665
|
+
"os",
|
|
666
|
+
"url",
|
|
667
|
+
"http",
|
|
668
|
+
"https",
|
|
669
|
+
"crypto",
|
|
670
|
+
"stream",
|
|
671
|
+
"util",
|
|
672
|
+
"events",
|
|
673
|
+
"child_process",
|
|
674
|
+
"node:fs",
|
|
675
|
+
"node:path",
|
|
676
|
+
"node:os",
|
|
677
|
+
"node:url",
|
|
678
|
+
"node:http",
|
|
679
|
+
"node:https",
|
|
680
|
+
"node:crypto",
|
|
681
|
+
"node:stream",
|
|
682
|
+
"node:util",
|
|
683
|
+
"node:events",
|
|
684
|
+
"node:child_process"
|
|
685
|
+
].includes(name))
|
|
686
|
+
return false;
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
function queryContext7(packageName) {
|
|
690
|
+
return new Promise((resolve) => {
|
|
691
|
+
const timeout = setTimeout(() => resolve(null), 5e3);
|
|
692
|
+
execFile2(
|
|
693
|
+
"mcp-context7",
|
|
694
|
+
[
|
|
695
|
+
"query-docs",
|
|
696
|
+
"--context7CompatibleLibraryID",
|
|
697
|
+
packageName,
|
|
698
|
+
"--topic",
|
|
699
|
+
"API reference quick start"
|
|
700
|
+
],
|
|
701
|
+
{ encoding: "utf-8", timeout: 5e3, maxBuffer: 2 * 1024 * 1024 },
|
|
702
|
+
(err, stdout) => {
|
|
703
|
+
clearTimeout(timeout);
|
|
704
|
+
if (err || !stdout.trim()) {
|
|
705
|
+
resolve(null);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
resolve(stdout.trim());
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
function formatDocs(pkg, raw) {
|
|
714
|
+
const truncated = raw.slice(0, 400);
|
|
715
|
+
const lines = truncated.split("\n").filter((l) => l.trim()).slice(0, 5).map((l) => ` ${l.trim()}`);
|
|
716
|
+
if (lines.length === 0) return null;
|
|
717
|
+
return ` ${pkg}:
|
|
718
|
+
${lines.join("\n")}`;
|
|
719
|
+
}
|
|
720
|
+
function execFilePromise2(cmd, args) {
|
|
721
|
+
return new Promise((resolve, reject) => {
|
|
722
|
+
execFile2(
|
|
723
|
+
cmd,
|
|
724
|
+
args,
|
|
725
|
+
{ encoding: "utf-8", timeout: 3e3 },
|
|
726
|
+
(err, stdout) => {
|
|
727
|
+
if (err) reject(err);
|
|
728
|
+
else resolve(stdout.trim());
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/providers/obsidian.ts
|
|
735
|
+
var OBSIDIAN_PORT = 27124;
|
|
736
|
+
var OBSIDIAN_BASE = `http://127.0.0.1:${OBSIDIAN_PORT}`;
|
|
737
|
+
var obsidianProvider = {
|
|
738
|
+
name: "obsidian",
|
|
739
|
+
label: "PROJECT NOTES",
|
|
740
|
+
tier: 2,
|
|
741
|
+
tokenBudget: 50,
|
|
742
|
+
timeoutMs: 200,
|
|
743
|
+
async resolve(filePath, context) {
|
|
744
|
+
try {
|
|
745
|
+
const store = await getStore(context.projectRoot);
|
|
746
|
+
try {
|
|
747
|
+
const cached = store.getCachedContextForProvider("obsidian", filePath);
|
|
748
|
+
if (cached) {
|
|
749
|
+
return {
|
|
750
|
+
provider: "obsidian",
|
|
751
|
+
content: cached.content,
|
|
752
|
+
confidence: 0.7,
|
|
753
|
+
cached: true
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
} finally {
|
|
757
|
+
store.close();
|
|
758
|
+
}
|
|
759
|
+
const projectName = context.projectRoot.split("/").pop() ?? "";
|
|
760
|
+
const fileName = filePath.split("/").pop()?.replace(/\.\w+$/, "") ?? "";
|
|
761
|
+
const query = `${projectName} ${fileName}`;
|
|
762
|
+
const results = await searchObsidian(query);
|
|
763
|
+
if (!results) return null;
|
|
764
|
+
const content = formatResults2(results);
|
|
765
|
+
if (!content) return null;
|
|
766
|
+
const store2 = await getStore(context.projectRoot);
|
|
767
|
+
try {
|
|
768
|
+
store2.setCachedContext(
|
|
769
|
+
"obsidian",
|
|
770
|
+
filePath,
|
|
771
|
+
content,
|
|
772
|
+
DEFAULT_CACHE_TTL_SEC,
|
|
773
|
+
query
|
|
774
|
+
);
|
|
775
|
+
store2.save();
|
|
776
|
+
} finally {
|
|
777
|
+
store2.close();
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
provider: "obsidian",
|
|
781
|
+
content,
|
|
782
|
+
confidence: 0.7,
|
|
783
|
+
cached: false
|
|
784
|
+
};
|
|
785
|
+
} catch {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
async warmup(projectRoot) {
|
|
790
|
+
const start = Date.now();
|
|
791
|
+
const entries = [];
|
|
792
|
+
try {
|
|
793
|
+
const projectName = projectRoot.split("/").pop() ?? "";
|
|
794
|
+
if (!projectName) {
|
|
795
|
+
return { provider: "obsidian", entries, durationMs: Date.now() - start };
|
|
796
|
+
}
|
|
797
|
+
const results = await searchObsidian(
|
|
798
|
+
`${projectName} architecture design decisions`
|
|
799
|
+
);
|
|
800
|
+
if (results) {
|
|
801
|
+
const content = formatResults2(results);
|
|
802
|
+
if (content) {
|
|
803
|
+
entries.push({ filePath: "__project__", content });
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
return { provider: "obsidian", entries, durationMs: Date.now() - start };
|
|
809
|
+
},
|
|
810
|
+
async isAvailable() {
|
|
811
|
+
try {
|
|
812
|
+
const response = await fetchWithTimeout(
|
|
813
|
+
`${OBSIDIAN_BASE}/`,
|
|
814
|
+
1e3
|
|
815
|
+
);
|
|
816
|
+
return response.ok;
|
|
817
|
+
} catch {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
async function searchObsidian(query) {
|
|
823
|
+
try {
|
|
824
|
+
const response = await fetchWithTimeout(
|
|
825
|
+
`${OBSIDIAN_BASE}/search/simple/?query=${encodeURIComponent(query)}`,
|
|
826
|
+
2e3
|
|
827
|
+
);
|
|
828
|
+
if (!response.ok) return null;
|
|
829
|
+
const data = await response.json();
|
|
830
|
+
if (!Array.isArray(data) || data.length === 0) return null;
|
|
831
|
+
return data.slice(0, 3);
|
|
832
|
+
} catch {
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
function formatResults2(results) {
|
|
837
|
+
if (results.length === 0) return null;
|
|
838
|
+
const lines = results.slice(0, 3).map((r) => {
|
|
839
|
+
const name = r.filename.replace(/\.md$/, "");
|
|
840
|
+
return ` Related: ${name}`;
|
|
841
|
+
});
|
|
842
|
+
return lines.join("\n");
|
|
843
|
+
}
|
|
844
|
+
async function fetchWithTimeout(url, timeoutMs) {
|
|
845
|
+
const controller = new AbortController();
|
|
846
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
847
|
+
try {
|
|
848
|
+
return await fetch(url, { signal: controller.signal });
|
|
849
|
+
} finally {
|
|
850
|
+
clearTimeout(timer);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/providers/lsp-connection.ts
|
|
855
|
+
import { connect } from "net";
|
|
856
|
+
import { existsSync as existsSync2 } from "fs";
|
|
857
|
+
import { tmpdir } from "os";
|
|
858
|
+
import { join as join2 } from "path";
|
|
859
|
+
function candidateSockets() {
|
|
860
|
+
const uid = process.getuid?.() ?? 0;
|
|
861
|
+
const tmp = tmpdir();
|
|
862
|
+
return [
|
|
863
|
+
// TypeScript language server (used by VS Code)
|
|
864
|
+
join2(tmp, `tsserver-${uid}.sock`),
|
|
865
|
+
// Generic LSP socket (some editors, e.g. Helix)
|
|
866
|
+
join2(tmp, "lsp-server.sock"),
|
|
867
|
+
// TypeScript language server alternate path
|
|
868
|
+
join2(tmp, "typescript-language-server.sock"),
|
|
869
|
+
// Pyright (Python)
|
|
870
|
+
join2(tmp, `pyright-${uid}.sock`),
|
|
871
|
+
// rust-analyzer
|
|
872
|
+
join2(tmp, "rust-analyzer.sock")
|
|
873
|
+
];
|
|
874
|
+
}
|
|
875
|
+
var LspConnection = class _LspConnection {
|
|
876
|
+
socket = null;
|
|
877
|
+
_requestId = 0;
|
|
878
|
+
/**
|
|
879
|
+
* Attempt to connect to any currently-running LSP server socket.
|
|
880
|
+
* Returns null — not throws — if no socket is found or connection fails.
|
|
881
|
+
* Timeout per candidate: 500ms.
|
|
882
|
+
*/
|
|
883
|
+
static async tryConnect() {
|
|
884
|
+
const candidates = candidateSockets().filter((p) => existsSync2(p));
|
|
885
|
+
if (candidates.length === 0) return null;
|
|
886
|
+
for (const path of candidates) {
|
|
887
|
+
try {
|
|
888
|
+
const conn = new _LspConnection();
|
|
889
|
+
await conn._connect(path);
|
|
890
|
+
return conn;
|
|
891
|
+
} catch {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
/** Internal: open a socket to the given path with a 500ms timeout. */
|
|
898
|
+
_connect(socketPath) {
|
|
899
|
+
return new Promise((resolve, reject) => {
|
|
900
|
+
const socket = connect(socketPath);
|
|
901
|
+
const timeout = setTimeout(() => {
|
|
902
|
+
socket.destroy();
|
|
903
|
+
reject(new Error("LSP connect timeout"));
|
|
904
|
+
}, 500);
|
|
905
|
+
socket.on("connect", () => {
|
|
906
|
+
clearTimeout(timeout);
|
|
907
|
+
this.socket = socket;
|
|
908
|
+
resolve();
|
|
909
|
+
});
|
|
910
|
+
socket.on("error", (err) => {
|
|
911
|
+
clearTimeout(timeout);
|
|
912
|
+
reject(err);
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Request hover info for a position.
|
|
918
|
+
*
|
|
919
|
+
* Stub: returns null. A full implementation would send a JSON-RPC
|
|
920
|
+
* textDocument/hover request and parse the response. Left as a stub
|
|
921
|
+
* because the response requires a request/response correlation loop
|
|
922
|
+
* over a streaming socket — non-trivial, and out of scope for v0.5.x.
|
|
923
|
+
* The provider benefits from the availability check alone.
|
|
924
|
+
*/
|
|
925
|
+
async hover(_filePath, _line, _character) {
|
|
926
|
+
if (!this.socket) return null;
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Fetch diagnostics for a file.
|
|
931
|
+
*
|
|
932
|
+
* Stub: returns []. A full implementation would use the
|
|
933
|
+
* textDocument/diagnostic pull request (LSP 3.17+) or subscribe to
|
|
934
|
+
* publishDiagnostics push notifications. Deferred to a future sprint.
|
|
935
|
+
*/
|
|
936
|
+
async getDiagnostics(_filePath) {
|
|
937
|
+
if (!this.socket) return [];
|
|
938
|
+
return [];
|
|
939
|
+
}
|
|
940
|
+
/** Whether this connection has a live socket. */
|
|
941
|
+
get connected() {
|
|
942
|
+
return this.socket !== null && !this.socket.destroyed;
|
|
943
|
+
}
|
|
944
|
+
/** Close and destroy the socket. Safe to call multiple times. */
|
|
945
|
+
close() {
|
|
946
|
+
this.socket?.destroy();
|
|
947
|
+
this.socket = null;
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
// src/providers/lsp.ts
|
|
952
|
+
var cachedConnection = void 0;
|
|
953
|
+
async function getConnection() {
|
|
954
|
+
if (cachedConnection instanceof LspConnection) {
|
|
955
|
+
if (cachedConnection.connected) return cachedConnection;
|
|
956
|
+
cachedConnection.close();
|
|
957
|
+
cachedConnection = void 0;
|
|
958
|
+
}
|
|
959
|
+
if (cachedConnection === null) return null;
|
|
960
|
+
cachedConnection = await LspConnection.tryConnect();
|
|
961
|
+
return cachedConnection;
|
|
962
|
+
}
|
|
963
|
+
var lspProvider = {
|
|
964
|
+
name: "engram:lsp",
|
|
965
|
+
label: "LSP CONTEXT",
|
|
966
|
+
tier: 1,
|
|
967
|
+
tokenBudget: 100,
|
|
968
|
+
timeoutMs: 100,
|
|
969
|
+
async resolve(filePath, _context) {
|
|
970
|
+
try {
|
|
971
|
+
const conn = await getConnection();
|
|
972
|
+
if (!conn) return null;
|
|
973
|
+
const hover = await conn.hover(filePath, 0, 0);
|
|
974
|
+
if (!hover?.contents) return null;
|
|
975
|
+
const content = typeof hover.contents === "string" ? hover.contents : JSON.stringify(hover.contents);
|
|
976
|
+
const charBudget = this.tokenBudget * 4;
|
|
977
|
+
const truncated = content.length > charBudget ? content.slice(0, charBudget) + "..." : content;
|
|
978
|
+
return {
|
|
979
|
+
provider: "engram:lsp",
|
|
980
|
+
content: truncated,
|
|
981
|
+
confidence: 0.95,
|
|
982
|
+
cached: false
|
|
983
|
+
};
|
|
984
|
+
} catch {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
async isAvailable() {
|
|
989
|
+
try {
|
|
990
|
+
const conn = await getConnection();
|
|
991
|
+
return conn !== null;
|
|
992
|
+
} catch {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
// src/providers/anthropic-memory.ts
|
|
999
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync } from "fs";
|
|
1000
|
+
import { join as join3 } from "path";
|
|
1001
|
+
import { homedir } from "os";
|
|
1002
|
+
function encodeProjectPath(absPath) {
|
|
1003
|
+
const posix = absPath.split(/[\\/]/).join("/");
|
|
1004
|
+
const trimmed = posix.replace(/\/+$/, "");
|
|
1005
|
+
return trimmed.replace(/\//g, "-");
|
|
1006
|
+
}
|
|
1007
|
+
function getMemoryIndexPath(projectRoot) {
|
|
1008
|
+
const encoded = encodeProjectPath(projectRoot);
|
|
1009
|
+
return join3(homedir(), ".claude", "projects", encoded, "memory", "MEMORY.md");
|
|
1010
|
+
}
|
|
1011
|
+
function parseMemoryIndex(content) {
|
|
1012
|
+
const entries = [];
|
|
1013
|
+
const lines = content.split("\n");
|
|
1014
|
+
const bullet = /^-\s*\[([^\]]+)\]\(([^)]+)\)\s*(?:[—–-]\s*)?(.*)$/;
|
|
1015
|
+
for (const raw of lines) {
|
|
1016
|
+
const line = raw.trim();
|
|
1017
|
+
if (line.length === 0) continue;
|
|
1018
|
+
if (!line.startsWith("-")) continue;
|
|
1019
|
+
const match = bullet.exec(line);
|
|
1020
|
+
if (!match) continue;
|
|
1021
|
+
const [, title, file, rest] = match;
|
|
1022
|
+
entries.push({
|
|
1023
|
+
title: title.trim(),
|
|
1024
|
+
file: file.trim(),
|
|
1025
|
+
description: (rest ?? "").trim()
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
return entries;
|
|
1029
|
+
}
|
|
1030
|
+
function scoreEntry(entry, ctx) {
|
|
1031
|
+
const basename = ctx.filePath.split(/[\\/]/).pop()?.replace(/\.[^.]+$/, "") ?? "";
|
|
1032
|
+
const segments = ctx.filePath.split(/[\\/]/).filter((s) => s.length > 2);
|
|
1033
|
+
const t = entry.title.toLowerCase();
|
|
1034
|
+
const d = entry.description.toLowerCase();
|
|
1035
|
+
let score = 0;
|
|
1036
|
+
if (basename.length > 2 && t.includes(basename.toLowerCase())) score += 3;
|
|
1037
|
+
if (basename.length > 2 && d.includes(basename.toLowerCase())) score += 2;
|
|
1038
|
+
for (const imp of ctx.imports) {
|
|
1039
|
+
const lower = imp.toLowerCase();
|
|
1040
|
+
if (lower.length < 3) continue;
|
|
1041
|
+
if (t.includes(lower) || d.includes(lower)) {
|
|
1042
|
+
score += 2;
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
for (const seg of segments) {
|
|
1047
|
+
const lower = seg.toLowerCase();
|
|
1048
|
+
if (lower.length < 3) continue;
|
|
1049
|
+
if (t.includes(lower) || d.includes(lower)) {
|
|
1050
|
+
score += 1;
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return score;
|
|
1055
|
+
}
|
|
1056
|
+
var OVERRIDE_ENV = "ENGRAM_ANTHROPIC_MEMORY_PATH";
|
|
1057
|
+
var MAX_INDEX_BYTES = 1048576;
|
|
1058
|
+
var anthropicMemoryProvider = {
|
|
1059
|
+
name: "anthropic:memory",
|
|
1060
|
+
label: "ANTHROPIC MEMORY",
|
|
1061
|
+
tier: 1,
|
|
1062
|
+
tokenBudget: 120,
|
|
1063
|
+
timeoutMs: 200,
|
|
1064
|
+
async isAvailable() {
|
|
1065
|
+
try {
|
|
1066
|
+
const override = process.env[OVERRIDE_ENV];
|
|
1067
|
+
if (override) return existsSync3(override);
|
|
1068
|
+
return true;
|
|
1069
|
+
} catch {
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
async resolve(filePath, context) {
|
|
1074
|
+
try {
|
|
1075
|
+
const path = process.env[OVERRIDE_ENV] || getMemoryIndexPath(context.projectRoot);
|
|
1076
|
+
if (!existsSync3(path)) return null;
|
|
1077
|
+
const size = statSync(path).size;
|
|
1078
|
+
if (size === 0) return null;
|
|
1079
|
+
if (size > MAX_INDEX_BYTES) return null;
|
|
1080
|
+
const content = readFileSync2(path, "utf-8");
|
|
1081
|
+
const entries = parseMemoryIndex(content);
|
|
1082
|
+
if (entries.length === 0) return null;
|
|
1083
|
+
const scored = entries.map((e) => ({
|
|
1084
|
+
entry: e,
|
|
1085
|
+
score: scoreEntry(e, { filePath, imports: context.imports })
|
|
1086
|
+
})).filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
|
|
1087
|
+
if (scored.length === 0) return null;
|
|
1088
|
+
const top = scored.slice(0, 3);
|
|
1089
|
+
const lines = top.map((s) => {
|
|
1090
|
+
const desc = s.entry.description ? ` \u2014 ${s.entry.description}` : "";
|
|
1091
|
+
return ` \u2022 ${s.entry.title}${desc}`;
|
|
1092
|
+
});
|
|
1093
|
+
return {
|
|
1094
|
+
provider: "anthropic:memory",
|
|
1095
|
+
content: lines.join("\n"),
|
|
1096
|
+
confidence: 0.85,
|
|
1097
|
+
cached: false
|
|
1098
|
+
};
|
|
1099
|
+
} catch {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
// src/providers/resolver.ts
|
|
1106
|
+
var BUILTIN_PROVIDERS = [
|
|
1107
|
+
astProvider,
|
|
1108
|
+
structureProvider,
|
|
1109
|
+
mistakesProvider,
|
|
1110
|
+
anthropicMemoryProvider,
|
|
1111
|
+
gitProvider,
|
|
1112
|
+
mempalaceProvider,
|
|
1113
|
+
context7Provider,
|
|
1114
|
+
obsidianProvider,
|
|
1115
|
+
lspProvider
|
|
1116
|
+
];
|
|
1117
|
+
var BUILTIN_NAMES = new Set(BUILTIN_PROVIDERS.map((p) => p.name));
|
|
1118
|
+
var mcpProvidersCache = null;
|
|
1119
|
+
async function getMcpProviders() {
|
|
1120
|
+
if (mcpProvidersCache) return mcpProvidersCache;
|
|
1121
|
+
try {
|
|
1122
|
+
const [{ loadMcpConfigs }, { createMcpProvider }] = await Promise.all([
|
|
1123
|
+
import("./mcp-config-QD4NPVXB.js"),
|
|
1124
|
+
import("./mcp-client-ROOJF76V.js")
|
|
1125
|
+
]);
|
|
1126
|
+
const { configs, failed } = loadMcpConfigs();
|
|
1127
|
+
if (failed.length > 0) {
|
|
1128
|
+
for (const f of failed) {
|
|
1129
|
+
process.stderr.write(
|
|
1130
|
+
`[engram] mcp-providers.json entry ${f.index}: ${f.reason}
|
|
1131
|
+
`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
mcpProvidersCache = configs.map(createMcpProvider);
|
|
1136
|
+
} catch {
|
|
1137
|
+
mcpProvidersCache = [];
|
|
1138
|
+
}
|
|
1139
|
+
return mcpProvidersCache;
|
|
1140
|
+
}
|
|
1141
|
+
function _resetMcpProvidersCache() {
|
|
1142
|
+
mcpProvidersCache = null;
|
|
1143
|
+
}
|
|
1144
|
+
async function getAllProviders() {
|
|
1145
|
+
const [{ getLoadedPlugins }, mcpProviders] = await Promise.all([
|
|
1146
|
+
import("./plugin-loader-SQQB6V74.js"),
|
|
1147
|
+
getMcpProviders()
|
|
1148
|
+
]);
|
|
1149
|
+
const { loaded } = await getLoadedPlugins();
|
|
1150
|
+
const safePlugins = loaded.filter((p) => !BUILTIN_NAMES.has(p.name));
|
|
1151
|
+
const safeMcp = mcpProviders.filter((p) => !BUILTIN_NAMES.has(p.name));
|
|
1152
|
+
return [...BUILTIN_PROVIDERS, ...safePlugins, ...safeMcp];
|
|
1153
|
+
}
|
|
1154
|
+
var ALL_PROVIDERS = BUILTIN_PROVIDERS;
|
|
1155
|
+
function estimateTokens(text) {
|
|
1156
|
+
return Math.ceil(text.length / 4);
|
|
1157
|
+
}
|
|
1158
|
+
async function resolveRichPacket(filePath, context, enabledProviders) {
|
|
1159
|
+
const start = Date.now();
|
|
1160
|
+
let allProviders;
|
|
1161
|
+
try {
|
|
1162
|
+
allProviders = await getAllProviders();
|
|
1163
|
+
} catch {
|
|
1164
|
+
allProviders = BUILTIN_PROVIDERS;
|
|
1165
|
+
}
|
|
1166
|
+
const providers = allProviders.filter((p) => {
|
|
1167
|
+
if (enabledProviders && !enabledProviders.includes(p.name)) return false;
|
|
1168
|
+
return true;
|
|
1169
|
+
});
|
|
1170
|
+
const available = await filterAvailable(providers);
|
|
1171
|
+
if (available.length === 0) return null;
|
|
1172
|
+
const settled = await Promise.allSettled(
|
|
1173
|
+
available.map((p) => resolveWithTimeout(p, filePath, context))
|
|
1174
|
+
);
|
|
1175
|
+
const results = [];
|
|
1176
|
+
for (const outcome of settled) {
|
|
1177
|
+
if (outcome.status === "fulfilled" && outcome.value) {
|
|
1178
|
+
results.push(outcome.value);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (results.length === 0) return null;
|
|
1182
|
+
const hasAst = results.some((r) => r.provider === "engram:ast");
|
|
1183
|
+
const deduped = hasAst ? results.filter((r) => r.provider !== "engram:structure") : results;
|
|
1184
|
+
const budgetedResults = enforcePerProviderBudget(deduped, allProviders);
|
|
1185
|
+
const boosted = boostByMistakes(budgetedResults);
|
|
1186
|
+
const sorted = [...boosted].sort((a, b) => {
|
|
1187
|
+
const aIdx = PROVIDER_PRIORITY.indexOf(a.provider);
|
|
1188
|
+
const bIdx = PROVIDER_PRIORITY.indexOf(b.provider);
|
|
1189
|
+
const pa = aIdx === -1 ? 99 : aIdx;
|
|
1190
|
+
const pb = bIdx === -1 ? 99 : bIdx;
|
|
1191
|
+
if (pa !== pb) return pa - pb;
|
|
1192
|
+
return b.confidence - a.confidence;
|
|
1193
|
+
});
|
|
1194
|
+
const config = readConfig(context.projectRoot);
|
|
1195
|
+
const budget = config.totalTokenBudget;
|
|
1196
|
+
const sections = [];
|
|
1197
|
+
let totalTokens = 0;
|
|
1198
|
+
for (const result of sorted) {
|
|
1199
|
+
const sectionTokens = estimateTokens(result.content);
|
|
1200
|
+
if (totalTokens + sectionTokens > budget) {
|
|
1201
|
+
break;
|
|
1202
|
+
}
|
|
1203
|
+
const provider = allProviders.find((p) => p.name === result.provider);
|
|
1204
|
+
const label = provider?.label ?? result.provider.toUpperCase();
|
|
1205
|
+
const cacheTag = result.cached ? ", cached" : "";
|
|
1206
|
+
sections.push(`${label} (${result.provider}${cacheTag}):
|
|
1207
|
+
${result.content}`);
|
|
1208
|
+
totalTokens += sectionTokens;
|
|
1209
|
+
}
|
|
1210
|
+
if (sections.length === 0) return null;
|
|
1211
|
+
const providerNames = sorted.filter((_, i) => i < sections.length).map((r) => r.provider);
|
|
1212
|
+
const isEnrichment = enabledProviders && !enabledProviders.includes("engram:structure");
|
|
1213
|
+
const header = isEnrichment ? `[engram] Additional context (${providerNames.length} providers, ~${totalTokens} tokens)` : `[engram] Rich context for ${filePath} (${providerNames.length} providers, ~${totalTokens} tokens)`;
|
|
1214
|
+
const text = `${header}
|
|
1215
|
+
|
|
1216
|
+
${sections.join("\n\n")}`;
|
|
1217
|
+
return {
|
|
1218
|
+
text,
|
|
1219
|
+
providerCount: providerNames.length,
|
|
1220
|
+
providers: providerNames,
|
|
1221
|
+
estimatedTokens: totalTokens + estimateTokens(header),
|
|
1222
|
+
durationMs: Date.now() - start
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
async function* resolveRichPacketStreaming(filePath, context, enabledProviders) {
|
|
1226
|
+
const start = Date.now();
|
|
1227
|
+
let allProviders;
|
|
1228
|
+
try {
|
|
1229
|
+
allProviders = await getAllProviders();
|
|
1230
|
+
} catch {
|
|
1231
|
+
allProviders = BUILTIN_PROVIDERS;
|
|
1232
|
+
}
|
|
1233
|
+
const providers = allProviders.filter(
|
|
1234
|
+
(p) => !enabledProviders || enabledProviders.includes(p.name)
|
|
1235
|
+
);
|
|
1236
|
+
const available = await filterAvailable(providers);
|
|
1237
|
+
if (available.length === 0) {
|
|
1238
|
+
yield { type: "done", providerCount: 0, durationMs: Date.now() - start };
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const queue = [];
|
|
1242
|
+
let wake = null;
|
|
1243
|
+
let remaining = available.length;
|
|
1244
|
+
for (const p of available) {
|
|
1245
|
+
resolveWithTimeout(p, filePath, context).then((r) => queue.push({ result: r, provider: p })).catch(() => queue.push({ result: null, provider: p })).finally(() => {
|
|
1246
|
+
remaining--;
|
|
1247
|
+
wake?.();
|
|
1248
|
+
wake = null;
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
let yielded = 0;
|
|
1252
|
+
while (remaining > 0 || queue.length > 0) {
|
|
1253
|
+
while (queue.length > 0) {
|
|
1254
|
+
const outcome = queue.shift();
|
|
1255
|
+
if (outcome.result) {
|
|
1256
|
+
yielded++;
|
|
1257
|
+
yield { type: "provider", result: outcome.result };
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (remaining > 0) {
|
|
1261
|
+
await new Promise((r) => {
|
|
1262
|
+
wake = r;
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
yield {
|
|
1267
|
+
type: "done",
|
|
1268
|
+
providerCount: yielded,
|
|
1269
|
+
durationMs: Date.now() - start
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
async function warmAllProviders(projectRoot, enabledProviders) {
|
|
1273
|
+
const start = Date.now();
|
|
1274
|
+
const warmed = [];
|
|
1275
|
+
const tier2 = ALL_PROVIDERS.filter(
|
|
1276
|
+
(p) => p.tier === 2 && p.warmup && (!enabledProviders || enabledProviders.includes(p.name))
|
|
1277
|
+
);
|
|
1278
|
+
const available = await filterAvailable(tier2);
|
|
1279
|
+
const settled = await Promise.allSettled(
|
|
1280
|
+
available.map(async (p) => {
|
|
1281
|
+
try {
|
|
1282
|
+
const result = await withTimeout(p.warmup(projectRoot), 5e3);
|
|
1283
|
+
if (result && result.entries.length > 0) {
|
|
1284
|
+
const { getStore: getStore2 } = await import("./core-77F2BVYV.js");
|
|
1285
|
+
const store = await getStore2(projectRoot);
|
|
1286
|
+
try {
|
|
1287
|
+
store.warmCache(
|
|
1288
|
+
result.provider,
|
|
1289
|
+
[...result.entries],
|
|
1290
|
+
result.provider === "context7" ? 4 * 3600 : 3600
|
|
1291
|
+
);
|
|
1292
|
+
store.save();
|
|
1293
|
+
} finally {
|
|
1294
|
+
store.close();
|
|
1295
|
+
}
|
|
1296
|
+
warmed.push(p.name);
|
|
1297
|
+
}
|
|
1298
|
+
} catch {
|
|
1299
|
+
}
|
|
1300
|
+
})
|
|
1301
|
+
);
|
|
1302
|
+
return { warmed, durationMs: Date.now() - start };
|
|
1303
|
+
}
|
|
1304
|
+
function enforcePerProviderBudget(results, providers) {
|
|
1305
|
+
const out = [];
|
|
1306
|
+
for (const r of results) {
|
|
1307
|
+
const provider = providers.find((p) => p.name === r.provider);
|
|
1308
|
+
const budget = provider?.tokenBudget ?? 200;
|
|
1309
|
+
if (estimateTokens(r.content) <= budget) {
|
|
1310
|
+
out.push(r);
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
const lines = r.content.split("\n");
|
|
1314
|
+
const kept = [];
|
|
1315
|
+
let used = 0;
|
|
1316
|
+
for (const line of lines) {
|
|
1317
|
+
const lineTokens = estimateTokens(line) + 1;
|
|
1318
|
+
if (used + lineTokens > budget) break;
|
|
1319
|
+
kept.push(line);
|
|
1320
|
+
used += lineTokens;
|
|
1321
|
+
}
|
|
1322
|
+
const truncated = kept.length > 0 ? kept.join("\n") + "\n\u2026 [truncated]" : r.content.slice(0, budget * 4 - 20) + "\u2026 [truncated]";
|
|
1323
|
+
out.push({ ...r, content: truncated });
|
|
1324
|
+
}
|
|
1325
|
+
return out;
|
|
1326
|
+
}
|
|
1327
|
+
function extractMistakeLabels(mistakesContent) {
|
|
1328
|
+
const labels = [];
|
|
1329
|
+
for (const line of mistakesContent.split("\n")) {
|
|
1330
|
+
const match = line.match(/^\s*!\s+(.+?)\s+\(flagged/);
|
|
1331
|
+
if (match && match[1]) {
|
|
1332
|
+
labels.push(match[1].trim().toLowerCase());
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return labels;
|
|
1336
|
+
}
|
|
1337
|
+
function boostByMistakes(results) {
|
|
1338
|
+
const mistakesResult = results.find((r) => r.provider === "engram:mistakes");
|
|
1339
|
+
if (!mistakesResult) return [...results];
|
|
1340
|
+
const labels = extractMistakeLabels(mistakesResult.content);
|
|
1341
|
+
if (labels.length === 0) return [...results];
|
|
1342
|
+
return results.map((r) => {
|
|
1343
|
+
if (r.provider === "engram:mistakes") return r;
|
|
1344
|
+
const lower = r.content.toLowerCase();
|
|
1345
|
+
const matched = labels.some((label) => lower.includes(label));
|
|
1346
|
+
if (!matched) return r;
|
|
1347
|
+
return {
|
|
1348
|
+
...r,
|
|
1349
|
+
confidence: Math.min(1, r.confidence * 1.5)
|
|
1350
|
+
};
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
var availabilityCache = /* @__PURE__ */ new Map();
|
|
1354
|
+
function _resetAvailabilityCache() {
|
|
1355
|
+
availabilityCache.clear();
|
|
1356
|
+
}
|
|
1357
|
+
async function filterAvailable(providers) {
|
|
1358
|
+
const checks = providers.map(async (p) => {
|
|
1359
|
+
let available = availabilityCache.get(p.name);
|
|
1360
|
+
if (available === void 0) {
|
|
1361
|
+
try {
|
|
1362
|
+
const timeout = p.tier === 1 ? 200 : 500;
|
|
1363
|
+
available = await withTimeout(p.isAvailable(), timeout);
|
|
1364
|
+
} catch {
|
|
1365
|
+
available = false;
|
|
1366
|
+
}
|
|
1367
|
+
availabilityCache.set(p.name, available);
|
|
1368
|
+
}
|
|
1369
|
+
return { provider: p, available };
|
|
1370
|
+
});
|
|
1371
|
+
const settled = await Promise.all(checks);
|
|
1372
|
+
return settled.filter((c) => c.available).map((c) => c.provider);
|
|
1373
|
+
}
|
|
1374
|
+
async function resolveWithTimeout(provider, filePath, context) {
|
|
1375
|
+
try {
|
|
1376
|
+
return await withTimeout(
|
|
1377
|
+
provider.resolve(filePath, context),
|
|
1378
|
+
provider.timeoutMs
|
|
1379
|
+
);
|
|
1380
|
+
} catch {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function withTimeout(promise, ms) {
|
|
1385
|
+
return new Promise((resolve, reject) => {
|
|
1386
|
+
const timer = setTimeout(() => reject(new Error("timeout")), ms);
|
|
1387
|
+
promise.then((val) => {
|
|
1388
|
+
clearTimeout(timer);
|
|
1389
|
+
resolve(val);
|
|
1390
|
+
}).catch((err) => {
|
|
1391
|
+
clearTimeout(timer);
|
|
1392
|
+
reject(err);
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
export {
|
|
1398
|
+
_resetMcpProvidersCache,
|
|
1399
|
+
resolveRichPacket,
|
|
1400
|
+
resolveRichPacketStreaming,
|
|
1401
|
+
warmAllProviders,
|
|
1402
|
+
enforcePerProviderBudget,
|
|
1403
|
+
boostByMistakes,
|
|
1404
|
+
_resetAvailabilityCache
|
|
1405
|
+
};
|