@maxgfr/codeindex 2.10.0 → 2.11.1
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/LICENSE +21 -0
- package/README.md +54 -2
- package/package.json +1 -1
- package/scripts/engine.d.mts +18 -3
- package/scripts/engine.mjs +326 -147
- package/src/embed/encode.ts +26 -16
- package/src/embed/endpoint.ts +110 -21
- package/src/embed/index.ts +30 -14
- package/src/embed/search.ts +9 -3
- package/src/engine-cli.ts +99 -22
- package/src/engine.ts +17 -5
- package/src/extract/code.ts +55 -47
- package/src/lang/common.ts +64 -0
- package/src/lang/registry.ts +21 -6
- package/src/mcp.ts +31 -17
- package/src/types.ts +10 -3
package/src/extract/code.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CodeSymbol, RawRef } from "../types.js";
|
|
2
2
|
import { extractSymbols } from "../lang/registry.js";
|
|
3
3
|
import { extractAst } from "../ast/extract.js";
|
|
4
|
+
import { extractReexports } from "../lang/common.js";
|
|
4
5
|
|
|
5
6
|
export interface CodeInfo {
|
|
6
7
|
symbols: CodeSymbol[];
|
|
@@ -253,50 +254,6 @@ function extractImports(ext: string, content: string): RawRef[] {
|
|
|
253
254
|
return [...specs].map((spec) => ({ kind: "import" as const, spec }));
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
// Barrel re-exports (`export { A, B as C } from './x'`, `export * from './y'`).
|
|
257
|
-
// The line-based lang extractor can't capture multi-name lists, but these ARE
|
|
258
|
-
// the public facade of a module — so list them as exported symbols here.
|
|
259
|
-
function extractReexports(rel: string, content: string): CodeSymbol[] {
|
|
260
|
-
if (!JS_TS.has(rel.slice(rel.lastIndexOf(".")))) return [];
|
|
261
|
-
const lang = /\.(ts|tsx|mts|cts)$/.test(rel) ? "typescript" : "javascript";
|
|
262
|
-
const out: CodeSymbol[] = [];
|
|
263
|
-
const seen = new Set<string>();
|
|
264
|
-
const lineAt = (idx: number): number => content.slice(0, idx).split(/\r?\n/).length;
|
|
265
|
-
|
|
266
|
-
const named = /export\s*\{([\s\S]*?)\}\s*(?:from\s*['"]([^'"]+)['"])?\s*;?/g;
|
|
267
|
-
let m: RegExpExecArray | null;
|
|
268
|
-
while ((m = named.exec(content)) && out.length < 60) {
|
|
269
|
-
const from = m[2];
|
|
270
|
-
for (const part of m[1]!.split(",")) {
|
|
271
|
-
const p = part.trim().replace(/^type\s+/, "");
|
|
272
|
-
const as = /^(\S+)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(p);
|
|
273
|
-
const name = as ? as[2]! : p;
|
|
274
|
-
if (!/^[A-Za-z_$][\w$]*$/.test(name) || name === "default" || seen.has(name)) continue;
|
|
275
|
-
seen.add(name);
|
|
276
|
-
out.push({
|
|
277
|
-
name, kind: "reexport", file: rel, line: lineAt(m.index),
|
|
278
|
-
signature: from ? `export { ${name} } from "${from}"` : `export { ${name} }`,
|
|
279
|
-
exported: true, lang,
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const star = /export\s*\*\s*(?:as\s+([A-Za-z_$][\w$]*)\s+)?from\s*['"]([^'"]+)['"]/g;
|
|
285
|
-
while ((m = star.exec(content)) && out.length < 60) {
|
|
286
|
-
const ns = m[1];
|
|
287
|
-
const from = m[2]!;
|
|
288
|
-
const key = "*" + (ns ?? from);
|
|
289
|
-
if (seen.has(key)) continue;
|
|
290
|
-
seen.add(key);
|
|
291
|
-
out.push({
|
|
292
|
-
name: ns ?? `* (${from})`, kind: ns ? "reexport" : "reexport-all", file: rel,
|
|
293
|
-
line: lineAt(m.index), signature: `export * ${ns ? `as ${ns} ` : ""}from "${from}"`,
|
|
294
|
-
exported: true, lang,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
return out;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
257
|
// Control-flow and declaration keywords that syntactically precede `(` but are
|
|
301
258
|
// never call targets — the union across supported languages. Deliberately does
|
|
302
259
|
// NOT list real builtins (python's `print`, go's `make`…): a false call to a
|
|
@@ -319,8 +276,35 @@ const DEF_INTRODUCERS = /(?:\bfunction|\bdef|\bfunc|\bfun|\bfn|\bclass|\bsub|\bm
|
|
|
319
276
|
// immediate `receiver.` prefix is captured too (`axios.get(` → receiver
|
|
320
277
|
// "axios"; `a.b.c(` → receiver "b" — the group anchors to the segment right
|
|
321
278
|
// before the called name); bare calls carry no receiver.
|
|
322
|
-
|
|
279
|
+
//
|
|
280
|
+
// `symbols` is the file's OWN regex-extracted symbols (name + definition
|
|
281
|
+
// line). DEF_INTRODUCERS already excludes definitions that read `function
|
|
282
|
+
// foo(`/`def foo(`/etc. (per OCCURRENCE, wherever on the line it sits), but
|
|
283
|
+
// C/C++ function definitions have no such introducer (`void load(void) {`) —
|
|
284
|
+
// the bare name reads exactly like a call to itself on its own definition
|
|
285
|
+
// line. For those, a call candidate whose (name, line) exactly matches one of
|
|
286
|
+
// `symbols` is excluded too — but ONLY its first (leftmost) occurrence on that
|
|
287
|
+
// line, never every same-named occurrence: dense/minified one-liners can pack
|
|
288
|
+
// a genuine call to the same name on the same physical line as its own
|
|
289
|
+
// definition (`function aa(){}function bb(){return aa()+cc()}function cc(){}`
|
|
290
|
+
// — bb's calls to aa() and cc() must survive), and even a bodyless
|
|
291
|
+
// single-line recursive definition (`function foo(){foo();}`) has a real self
|
|
292
|
+
// -call to keep. Two-tier: if ANY occurrence of a def'd name on this line is
|
|
293
|
+
// already caught by DEF_INTRODUCERS (JS/Python/…), that occurrence alone is
|
|
294
|
+
// excluded (existing per-occurrence check below) and no further exclusion is
|
|
295
|
+
// applied — every OTHER occurrence is a genuine call. Only when NO occurrence
|
|
296
|
+
// carries an introducer (C/C++) does this fall back to excluding just the
|
|
297
|
+
// first occurrence: C/C++'s own definition regex requires column 0, so on a
|
|
298
|
+
// line where it matches at all, the first occurrence IS that definition.
|
|
299
|
+
// Exported for direct testing (extraction-v8.test.ts): once a wasm sidecar/
|
|
300
|
+
// grammar is loaded, extractCode never reaches this path for C/C++, so tests
|
|
301
|
+
// exercise it directly rather than through extractCode.
|
|
302
|
+
export function collectCallsRegex(
|
|
303
|
+
content: string,
|
|
304
|
+
symbols: Pick<CodeSymbol, "name" | "line">[] = [],
|
|
305
|
+
): { name: string; line: number; receiver?: string }[] {
|
|
323
306
|
const out = new Map<string, { name: string; line: number; receiver?: string }>();
|
|
307
|
+
const ownDefLines = new Set(symbols.map((s) => `${s.name} ${s.line}`));
|
|
324
308
|
const lines = content.split("\n");
|
|
325
309
|
const CALL_RE = /(?:\bnew\s+)?(?:([A-Za-z_$][\w$]*)\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\(/g;
|
|
326
310
|
for (let i = 0; i < lines.length && out.size < 512; i++) {
|
|
@@ -330,14 +314,36 @@ function collectCallsRegex(content: string): { name: string; line: number; recei
|
|
|
330
314
|
// regexes — noise resolves to nothing in the global call pass).
|
|
331
315
|
const trimmed = line.trimStart();
|
|
332
316
|
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*")) continue;
|
|
317
|
+
|
|
318
|
+
// Pass 1: which own-def keys on this line have at least one occurrence
|
|
319
|
+
// DEF_INTRODUCERS already catches? Those are fully handled per-occurrence
|
|
320
|
+
// below — no fallback exclusion needed (or wanted) for them.
|
|
321
|
+
CALL_RE.lastIndex = 0;
|
|
322
|
+
let probe: RegExpExecArray | null;
|
|
323
|
+
const introducerCaught = new Set<string>();
|
|
324
|
+
while ((probe = CALL_RE.exec(line)) !== null) {
|
|
325
|
+
const name = probe[2]!;
|
|
326
|
+
const key = `${name} ${i + 1}`;
|
|
327
|
+
if (ownDefLines.has(key) && DEF_INTRODUCERS.test(line.slice(0, probe.index))) introducerCaught.add(key);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Pass 2: the real collection. Own-def keys with no introducer occurrence
|
|
331
|
+
// fall back to excluding just their first occurrence on the line.
|
|
333
332
|
CALL_RE.lastIndex = 0;
|
|
334
333
|
let m: RegExpExecArray | null;
|
|
334
|
+
const fallbackExcluded = new Set<string>();
|
|
335
335
|
while ((m = CALL_RE.exec(line)) !== null && out.size < 512) {
|
|
336
336
|
const receiver = m[1];
|
|
337
337
|
const name = m[2]!;
|
|
338
338
|
if (name.length < 2 || CALL_KEYWORDS.has(name)) continue;
|
|
339
339
|
if (DEF_INTRODUCERS.test(line.slice(0, m.index))) continue;
|
|
340
340
|
const key = `${name} ${i + 1}`;
|
|
341
|
+
if (ownDefLines.has(key) && !introducerCaught.has(key)) {
|
|
342
|
+
if (!fallbackExcluded.has(key)) {
|
|
343
|
+
fallbackExcluded.add(key);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
341
347
|
if (!out.has(key)) out.set(key, receiver ? { name, line: i + 1, receiver } : { name, line: i + 1 });
|
|
342
348
|
}
|
|
343
349
|
}
|
|
@@ -354,7 +360,7 @@ export function extractCode(rel: string, ext: string, content: string): CodeInfo
|
|
|
354
360
|
const symbols = (ast ? ast.symbols : extractSymbols(rel, ext, content)).slice(0, 400);
|
|
355
361
|
// Add barrel re-exports the local def didn't already cover.
|
|
356
362
|
const known = new Set(symbols.map((s) => s.name));
|
|
357
|
-
const reexports = extractReexports(rel, content).filter((s) => !known.has(s.name));
|
|
363
|
+
const reexports = extractReexports(rel, content, symbols).filter((s) => !known.has(s.name));
|
|
358
364
|
return {
|
|
359
365
|
symbols: [...symbols, ...reexports],
|
|
360
366
|
summary: topDocComment(content),
|
|
@@ -370,7 +376,9 @@ export function extractCode(rel: string, ext: string, content: string): CodeInfo
|
|
|
370
376
|
idents: ast?.idents,
|
|
371
377
|
// AST call sites when a grammar parsed the file; the conservative regex
|
|
372
378
|
// collector otherwise, so caller indexes exist without the wasm sidecar.
|
|
373
|
-
|
|
379
|
+
// `symbols` (this file's own regex-extracted defs) lets the collector
|
|
380
|
+
// exclude a definition's own name+line from its call candidates.
|
|
381
|
+
calls: ast ? ast.calls : collectCallsRegex(content, symbols),
|
|
374
382
|
importedNames: ast?.importedNames,
|
|
375
383
|
};
|
|
376
384
|
}
|
package/src/lang/common.ts
CHANGED
|
@@ -66,3 +66,67 @@ const EXT_LANG: Record<string, string> = {
|
|
|
66
66
|
export function extToLang(ext: string): string {
|
|
67
67
|
return EXT_LANG[ext] ?? "other";
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
const REEXPORT_EXTS = new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
71
|
+
|
|
72
|
+
// Barrel re-exports (`export { A, B as C } from './x'`, `export * from './y'`).
|
|
73
|
+
// The line-based lang extractor can't capture multi-name lists, but these ARE
|
|
74
|
+
// the public facade of a module — so list them as exported symbols here.
|
|
75
|
+
//
|
|
76
|
+
// An ALIAS with no `from` clause (`export { b as c }`) renames an in-file
|
|
77
|
+
// declaration — `localSymbols` (already extracted by the AST or regex tier)
|
|
78
|
+
// lets us resolve `b` and mirror ITS kind onto `c` (e.g. "function"), so the
|
|
79
|
+
// alias reads as the real symbol it is rather than the generic "reexport".
|
|
80
|
+
// A true cross-module re-export (`export { b as c } from "./mod"`) has no
|
|
81
|
+
// local `b` to resolve — and an alias the local pass genuinely can't see
|
|
82
|
+
// (destructured/ambient/etc.) falls back the same way — both keep "reexport".
|
|
83
|
+
//
|
|
84
|
+
// Shared by extractCode (extract/code.ts) AND the standalone extractSymbols
|
|
85
|
+
// (lang/registry.ts) — ultradoc and other direct extractSymbols consumers hit
|
|
86
|
+
// the same barrels a repo scan does, so both entry points must agree; this is
|
|
87
|
+
// the one place the alias-mirroring logic lives, reused rather than
|
|
88
|
+
// reimplemented on either side.
|
|
89
|
+
export function extractReexports(rel: string, content: string, localSymbols: CodeSymbol[]): CodeSymbol[] {
|
|
90
|
+
if (!REEXPORT_EXTS.has(rel.slice(rel.lastIndexOf(".")))) return [];
|
|
91
|
+
const lang = /\.(ts|tsx|mts|cts)$/.test(rel) ? "typescript" : "javascript";
|
|
92
|
+
const out: CodeSymbol[] = [];
|
|
93
|
+
const seen = new Set<string>();
|
|
94
|
+
const lineAt = (idx: number): number => content.slice(0, idx).split(/\r?\n/).length;
|
|
95
|
+
const localKindOf = new Map<string, string>();
|
|
96
|
+
for (const s of localSymbols) if (!localKindOf.has(s.name)) localKindOf.set(s.name, s.kind);
|
|
97
|
+
|
|
98
|
+
const named = /export\s*\{([\s\S]*?)\}\s*(?:from\s*['"]([^'"]+)['"])?\s*;?/g;
|
|
99
|
+
let m: RegExpExecArray | null;
|
|
100
|
+
while ((m = named.exec(content)) && out.length < 60) {
|
|
101
|
+
const from = m[2];
|
|
102
|
+
for (const part of m[1]!.split(",")) {
|
|
103
|
+
const p = part.trim().replace(/^type\s+/, "");
|
|
104
|
+
const as = /^(\S+)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(p);
|
|
105
|
+
const orig = as ? as[1]! : p;
|
|
106
|
+
const name = as ? as[2]! : p;
|
|
107
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name) || name === "default" || seen.has(name)) continue;
|
|
108
|
+
seen.add(name);
|
|
109
|
+
const mirroredKind = !from ? localKindOf.get(orig) : undefined;
|
|
110
|
+
out.push({
|
|
111
|
+
name, kind: mirroredKind ?? "reexport", file: rel, line: lineAt(m.index),
|
|
112
|
+
signature: from ? `export { ${name} } from "${from}"` : `export { ${name} }`,
|
|
113
|
+
exported: true, lang,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const star = /export\s*\*\s*(?:as\s+([A-Za-z_$][\w$]*)\s+)?from\s*['"]([^'"]+)['"]/g;
|
|
119
|
+
while ((m = star.exec(content)) && out.length < 60) {
|
|
120
|
+
const ns = m[1];
|
|
121
|
+
const from = m[2]!;
|
|
122
|
+
const key = "*" + (ns ?? from);
|
|
123
|
+
if (seen.has(key)) continue;
|
|
124
|
+
seen.add(key);
|
|
125
|
+
out.push({
|
|
126
|
+
name: ns ?? `* (${from})`, kind: ns ? "reexport" : "reexport-all", file: rel,
|
|
127
|
+
line: lineAt(m.index), signature: `export * ${ns ? `as ${ns} ` : ""}from "${from}"`,
|
|
128
|
+
exported: true, lang,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
package/src/lang/registry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CodeSymbol } from "../types.js";
|
|
2
|
-
import { extToLang } from "./common.js";
|
|
2
|
+
import { extToLang, extractReexports } from "./common.js";
|
|
3
3
|
import { jsTs } from "./js-ts.js";
|
|
4
4
|
import { python } from "./python.js";
|
|
5
5
|
import { go } from "./go.js";
|
|
@@ -35,14 +35,29 @@ for (const e of EXTRACTORS) for (const ext of e.exts) BY_EXT.set(ext, e);
|
|
|
35
35
|
|
|
36
36
|
// Extract declared symbols from one file. Returns [] for languages without a
|
|
37
37
|
// dedicated extractor (their content is still fully searchable via ripgrep).
|
|
38
|
+
//
|
|
39
|
+
// Also mirrors extractCode's barrel re-export/alias pass (extractReexports):
|
|
40
|
+
// a direct extractSymbols consumer (ultradoc, or any tool that doesn't go
|
|
41
|
+
// through extractCode) hits the same `export { a, b as c }` barrels a repo
|
|
42
|
+
// scan does, and should see the same alias-mirrors-b's-own-kind symbols —
|
|
43
|
+
// not the bare "reexport" kind extractReexports falls back to when there's
|
|
44
|
+
// no local declaration to mirror. extractCode already runs extractReexports
|
|
45
|
+
// itself and filters by name, so this can't double-add: whichever entry point
|
|
46
|
+
// adds "c" first wins, the other's re-run is a no-op duplicate filtered out.
|
|
38
47
|
export function extractSymbols(rel: string, ext: string, content: string): CodeSymbol[] {
|
|
39
48
|
const extractor = BY_EXT.get(ext);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
let symbols: CodeSymbol[];
|
|
50
|
+
if (!extractor) symbols = [];
|
|
51
|
+
else {
|
|
52
|
+
try {
|
|
53
|
+
symbols = extractor.extract(rel, content);
|
|
54
|
+
} catch {
|
|
55
|
+
symbols = [];
|
|
56
|
+
}
|
|
45
57
|
}
|
|
58
|
+
const known = new Set(symbols.map((s) => s.name));
|
|
59
|
+
const reexports = extractReexports(rel, content, symbols).filter((s) => !known.has(s.name));
|
|
60
|
+
return reexports.length ? [...symbols, ...reexports] : symbols;
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
// Human-readable language label for an extension (used for the language
|
package/src/mcp.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { checkRules, parseRules } from "./rules.js";
|
|
|
28
28
|
import { EMBED_VERSION, resolveEmbedModelDir, loadEmbedModel } from "./embed/model.js";
|
|
29
29
|
import { buildEmbeddingIndex } from "./embed/index.js";
|
|
30
30
|
import { searchSemantic } from "./embed/search.js";
|
|
31
|
+
import { resolveEmbedEndpoint, buildEndpointIndex, encodeQueryViaEndpoint, probeEndpoint } from "./embed/endpoint.js";
|
|
31
32
|
|
|
32
33
|
interface RpcRequest {
|
|
33
34
|
jsonrpc: "2.0";
|
|
@@ -285,7 +286,7 @@ const TOOLS = [
|
|
|
285
286
|
semantic: {
|
|
286
287
|
type: "boolean",
|
|
287
288
|
description:
|
|
288
|
-
"RRF-fuse
|
|
289
|
+
"RRF-fuse an embedding tier with lexical (default false). Precedence: the HTTP endpoint (CODEINDEX_EMBED_ENDPOINT) if set, else a local static model. Degrades silently to lexical-only when neither is available/reachable — see embed_status.",
|
|
289
290
|
},
|
|
290
291
|
},
|
|
291
292
|
required: ["repo", "query"],
|
|
@@ -294,7 +295,7 @@ const TOOLS = [
|
|
|
294
295
|
{
|
|
295
296
|
name: "embed_status",
|
|
296
297
|
description:
|
|
297
|
-
"Report the
|
|
298
|
+
"Report the embedding tier: the effective mode (none/static/endpoint; endpoint > static model), the resolved model (opt-in, never shipped in the package) with its modelId/dim, EMBED_VERSION, and the configured HTTP endpoint with its reachability. Use to check whether `search` with semantic:true will fuse embeddings or degrade to lexical.",
|
|
298
299
|
inputSchema: { type: "object", properties: { ...repoProp }, required: ["repo"] },
|
|
299
300
|
},
|
|
300
301
|
{
|
|
@@ -320,7 +321,7 @@ function strArray(v: unknown): string[] | undefined {
|
|
|
320
321
|
return Array.isArray(v) && v.every((x) => typeof x === "string") && v.length ? (v as string[]) : undefined;
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
function callTool(name: string, args: Record<string, unknown>): string {
|
|
324
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<string> {
|
|
324
325
|
const repo = str(args.repo);
|
|
325
326
|
if (!repo) throw new Error("`repo` is required (absolute path to the repository root)");
|
|
326
327
|
const scanOpts = { scope: str(args.scope), include: strArray(args.include), exclude: strArray(args.exclude) };
|
|
@@ -458,6 +459,18 @@ function callTool(name: string, args: Record<string, unknown>): string {
|
|
|
458
459
|
const limit = typeof args.limit === "number" ? args.limit : undefined;
|
|
459
460
|
const fuzzy = typeof args.fuzzy === "boolean" ? args.fuzzy : undefined;
|
|
460
461
|
if (args.semantic === true) {
|
|
462
|
+
const endpoint = resolveEmbedEndpoint();
|
|
463
|
+
if (endpoint) {
|
|
464
|
+
// Rich tier — endpoint takes PRECEDENCE over a local static model. An
|
|
465
|
+
// unreachable/malformed endpoint degrades to lexical (no throw).
|
|
466
|
+
try {
|
|
467
|
+
const index = await buildEndpointIndex(scan);
|
|
468
|
+
const queryVec = await encodeQueryViaEndpoint(query);
|
|
469
|
+
return JSON.stringify(searchSemantic(scan, query, index, { queryVec, limit, fuzzy }), null, 2);
|
|
470
|
+
} catch {
|
|
471
|
+
return JSON.stringify(searchIndex(scan, query, { limit, fuzzy }), null, 2);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
461
474
|
const modelDir = resolveEmbedModelDir(repo);
|
|
462
475
|
const model = modelDir ? loadEmbedModel(modelDir) : undefined;
|
|
463
476
|
if (model) {
|
|
@@ -471,17 +484,18 @@ function callTool(name: string, args: Record<string, unknown>): string {
|
|
|
471
484
|
if (name === "embed_status") {
|
|
472
485
|
const modelDir = resolveEmbedModelDir(repo);
|
|
473
486
|
const model = modelDir ? loadEmbedModel(modelDir) : undefined;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
null,
|
|
483
|
-
|
|
484
|
-
);
|
|
487
|
+
const endpoint = resolveEmbedEndpoint();
|
|
488
|
+
const mode: "none" | "static" | "endpoint" = endpoint ? "endpoint" : model ? "static" : "none";
|
|
489
|
+
const status: Record<string, unknown> = {
|
|
490
|
+
embedVersion: EMBED_VERSION,
|
|
491
|
+
mode,
|
|
492
|
+
model: model
|
|
493
|
+
? { present: true, dir: modelDir, modelId: model.modelId, dim: model.dim, vocabSize: model.vocabSize }
|
|
494
|
+
: { present: false },
|
|
495
|
+
endpoint: endpoint ?? null,
|
|
496
|
+
};
|
|
497
|
+
if (endpoint) status.endpointReachable = await probeEndpoint(endpoint);
|
|
498
|
+
return JSON.stringify(status, null, 2);
|
|
485
499
|
}
|
|
486
500
|
if (name === "check_rules") {
|
|
487
501
|
const rules = parseRules(args.rules); // throws a descriptive error on a malformed payload
|
|
@@ -512,10 +526,10 @@ export async function runMcpServer(): Promise<void> {
|
|
|
512
526
|
// JSON-RPC 2.0 batch: answer each member (a batching client would
|
|
513
527
|
// otherwise hang forever on a silently dropped array).
|
|
514
528
|
const requests = Array.isArray(parsed) ? (parsed as RpcRequest[]) : [parsed as RpcRequest];
|
|
515
|
-
for (const req of requests) handle(req);
|
|
529
|
+
for (const req of requests) await handle(req);
|
|
516
530
|
}
|
|
517
531
|
|
|
518
|
-
function handle(req: RpcRequest): void {
|
|
532
|
+
async function handle(req: RpcRequest): Promise<void> {
|
|
519
533
|
if (req.id === undefined || req.id === null) return; // notification — no response
|
|
520
534
|
|
|
521
535
|
try {
|
|
@@ -537,7 +551,7 @@ export async function runMcpServer(): Promise<void> {
|
|
|
537
551
|
const name = str(params.name) ?? "";
|
|
538
552
|
const args = (params.arguments ?? {}) as Record<string, unknown>;
|
|
539
553
|
try {
|
|
540
|
-
const text = callTool(name, args);
|
|
554
|
+
const text = await callTool(name, args);
|
|
541
555
|
send({ id: req.id, result: { content: [{ type: "text", text }] } });
|
|
542
556
|
} catch (e) {
|
|
543
557
|
send({
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Single source of truth for the engine version the bundle reports. Kept in
|
|
2
2
|
// lockstep with package.json by the release pipeline. Do not edit by hand
|
|
3
3
|
// outside a release.
|
|
4
|
-
export const ENGINE_VERSION = "2.
|
|
4
|
+
export const ENGINE_VERSION = "2.11.1";
|
|
5
5
|
|
|
6
6
|
// Bumped whenever the on-disk artifact shape changes, so a consumer can reject
|
|
7
7
|
// an index written by an incompatible engine instead of misreading it. The
|
|
@@ -24,8 +24,15 @@ export const SCHEMA_VERSION = 4;
|
|
|
24
24
|
// (the immediate receiver of a qualified call, both tiers) and JS/TS export
|
|
25
25
|
// parity with ultradoc (CJS `exports.foo =` / `module.exports = {…}` named
|
|
26
26
|
// exports, `export { a, b as c }` local marking, anonymous `export default`
|
|
27
|
-
// named after the file stem, `export default Foo` marking the declaration)
|
|
28
|
-
export
|
|
27
|
+
// named after the file stem, `export default Foo` marking the declaration);
|
|
28
|
+
// v7 makes an export-alias symbol (`export { b as c }`) mirror the aliased
|
|
29
|
+
// local declaration's own kind (e.g. "function") instead of the generic
|
|
30
|
+
// "reexport" when it resolves in-file. v8 fixes the C/C++ regex tier
|
|
31
|
+
// reporting a function DEFINITION as a call to itself (`void load(void) {`
|
|
32
|
+
// yielding a spurious call `load@<defline>`, found during the ultrasec
|
|
33
|
+
// consumer migration) by excluding call candidates whose name+line match one
|
|
34
|
+
// of the file's own extracted symbols.
|
|
35
|
+
export const EXTRACTOR_VERSION = 8;
|
|
29
36
|
|
|
30
37
|
// How a file is classified. `code` gets symbol/import extraction; `doc` gets
|
|
31
38
|
// link/heading extraction; the rest are catalogued but not deeply parsed.
|