sigmap 8.12.0 → 8.14.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/AGENTS.md +116 -106
- package/CHANGELOG.md +18 -0
- package/README.md +3 -3
- package/gen-context.js +378 -21
- package/llms-full.txt +13 -5
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/graph/blast-radius.js +132 -0
- package/src/graph/call-graph.js +140 -10
- package/src/mcp/handlers.js +23 -1
- package/src/mcp/server.js +3 -2
- package/src/mcp/tools.js +34 -5
- package/src/review/pr-evidence.js +17 -0
- package/src/review/review-pr.js +23 -1
package/gen-context.js
CHANGED
|
@@ -10304,6 +10304,142 @@ __factories["./src/format/verify-report"] = function(module, exports) {
|
|
|
10304
10304
|
|
|
10305
10305
|
};
|
|
10306
10306
|
|
|
10307
|
+
// ── ./src/graph/blast-radius ──
|
|
10308
|
+
__factories["./src/graph/blast-radius"] = function(module, exports) {
|
|
10309
|
+
|
|
10310
|
+
/**
|
|
10311
|
+
* Method-level blast-radius scoring (GR2).
|
|
10312
|
+
*
|
|
10313
|
+
* Consumes the D4 call-graph (src/graph/call-graph.js): for each changed file,
|
|
10314
|
+
* resolve the functions it defines, BFS the reverse call edges, and score how
|
|
10315
|
+
* much of the codebase transitively calls into the change. The score is a
|
|
10316
|
+
* documented deterministic formula — no heuristics that vary run to run — so
|
|
10317
|
+
* review-pr findings and PR Evidence lines are byte-stable for a fixed tree.
|
|
10318
|
+
*
|
|
10319
|
+
* Score: min(100, direct×4 + transitive×1). Tiers:
|
|
10320
|
+
* 0 → none · 1–9 → low · 10–29 → medium · 30–59 → high · 60+ → critical
|
|
10321
|
+
*/
|
|
10322
|
+
|
|
10323
|
+
const path = require('path');
|
|
10324
|
+
const { buildCallGraph } = __require('./src/graph/call-graph');
|
|
10325
|
+
|
|
10326
|
+
const DIRECT_WEIGHT = 4;
|
|
10327
|
+
const TRANSITIVE_WEIGHT = 1;
|
|
10328
|
+
const IMPACTED_FUNCTIONS_CAP = 12;
|
|
10329
|
+
|
|
10330
|
+
const TEST_FILE_RE = /\.(test|spec)\.[jt]sx?$|(^|\/)test_|_test\.(py|go)$|(^|\/)(tests?|__tests__|spec)\//;
|
|
10331
|
+
|
|
10332
|
+
function tierFor(score) {
|
|
10333
|
+
if (score >= 60) return 'critical';
|
|
10334
|
+
if (score >= 30) return 'high';
|
|
10335
|
+
if (score >= 10) return 'medium';
|
|
10336
|
+
if (score >= 1) return 'low';
|
|
10337
|
+
return 'none';
|
|
10338
|
+
}
|
|
10339
|
+
|
|
10340
|
+
function _normRel(p) {
|
|
10341
|
+
return String(p).replace(/\\/g, '/');
|
|
10342
|
+
}
|
|
10343
|
+
|
|
10344
|
+
// BFS the reverse edges from a set of symbol ids; returns direct/transitive id sets.
|
|
10345
|
+
function _bfs(seedIds, reverse, maxDepth) {
|
|
10346
|
+
const direct = new Set();
|
|
10347
|
+
const transitive = new Set();
|
|
10348
|
+
const visited = new Set(seedIds);
|
|
10349
|
+
let frontier = [];
|
|
10350
|
+
for (const s of seedIds) {
|
|
10351
|
+
for (const nb of (reverse.get(s) || [])) {
|
|
10352
|
+
if (!visited.has(nb)) { direct.add(nb); visited.add(nb); frontier.push(nb); }
|
|
10353
|
+
}
|
|
10354
|
+
}
|
|
10355
|
+
let depth = 1;
|
|
10356
|
+
while (frontier.length && (maxDepth === 0 || depth < maxDepth)) {
|
|
10357
|
+
const next = [];
|
|
10358
|
+
for (const node of frontier) {
|
|
10359
|
+
for (const nb of (reverse.get(node) || [])) {
|
|
10360
|
+
if (!visited.has(nb)) { transitive.add(nb); visited.add(nb); next.push(nb); }
|
|
10361
|
+
}
|
|
10362
|
+
}
|
|
10363
|
+
frontier = next;
|
|
10364
|
+
depth++;
|
|
10365
|
+
}
|
|
10366
|
+
return { direct, transitive };
|
|
10367
|
+
}
|
|
10368
|
+
|
|
10369
|
+
/**
|
|
10370
|
+
* Score the method-level blast radius of a changed-file list.
|
|
10371
|
+
*
|
|
10372
|
+
* @param {string[]} changedFiles repo-relative paths
|
|
10373
|
+
* @param {string} cwd
|
|
10374
|
+
* @param {object} [opts]
|
|
10375
|
+
* @param {object} [opts.graph] injected call graph (tests); else built from cwd
|
|
10376
|
+
* @param {number} [opts.depth=0] BFS depth limit (0 = unlimited)
|
|
10377
|
+
* @returns {{
|
|
10378
|
+
* available: boolean,
|
|
10379
|
+
* files: Array<{ file:string, symbols:number, directCallers:number,
|
|
10380
|
+
* transitiveCallers:number, testCallers:number,
|
|
10381
|
+
* impactedFunctions:string[], score:number, tier:string }>,
|
|
10382
|
+
* aggregate: { score:number, tier:string, impactedFunctions:number }
|
|
10383
|
+
* }}
|
|
10384
|
+
*/
|
|
10385
|
+
function methodBlastRadius(changedFiles, cwd, opts = {}) {
|
|
10386
|
+
const empty = { available: false, files: [], aggregate: { score: 0, tier: 'none', impactedFunctions: 0 } };
|
|
10387
|
+
let graph;
|
|
10388
|
+
try {
|
|
10389
|
+
graph = opts.graph || buildCallGraph(cwd, opts);
|
|
10390
|
+
} catch (_) {
|
|
10391
|
+
return empty;
|
|
10392
|
+
}
|
|
10393
|
+
if (!graph || !graph.defs || graph.defs.size === 0) return empty;
|
|
10394
|
+
|
|
10395
|
+
// Group defined symbol ids by their (normalized) defining file.
|
|
10396
|
+
const idsByFile = new Map();
|
|
10397
|
+
for (const [id, def] of graph.defs.entries()) {
|
|
10398
|
+
const rel = _normRel(def.file);
|
|
10399
|
+
if (!idsByFile.has(rel)) idsByFile.set(rel, []);
|
|
10400
|
+
idsByFile.get(rel).push(id);
|
|
10401
|
+
}
|
|
10402
|
+
|
|
10403
|
+
const depth = Number.isFinite(opts.depth) ? opts.depth : 0;
|
|
10404
|
+
const files = [];
|
|
10405
|
+
const allImpacted = new Set();
|
|
10406
|
+
|
|
10407
|
+
for (const changed of (changedFiles || []).map(_normRel).sort()) {
|
|
10408
|
+
const ids = idsByFile.get(changed);
|
|
10409
|
+
if (!ids || !ids.length) continue;
|
|
10410
|
+
const { direct, transitive } = _bfs(ids, graph.reverse, depth);
|
|
10411
|
+
const impacted = [...direct, ...transitive].sort();
|
|
10412
|
+
for (const id of impacted) allImpacted.add(id);
|
|
10413
|
+
const testCallers = impacted.filter((id) => {
|
|
10414
|
+
const def = graph.defs.get(id);
|
|
10415
|
+
return def && TEST_FILE_RE.test(_normRel(def.file));
|
|
10416
|
+
}).length;
|
|
10417
|
+
const score = Math.min(100, direct.size * DIRECT_WEIGHT + transitive.size * TRANSITIVE_WEIGHT);
|
|
10418
|
+
files.push({
|
|
10419
|
+
file: changed,
|
|
10420
|
+
symbols: ids.length,
|
|
10421
|
+
directCallers: direct.size,
|
|
10422
|
+
transitiveCallers: transitive.size,
|
|
10423
|
+
testCallers,
|
|
10424
|
+
impactedFunctions: impacted.slice(0, IMPACTED_FUNCTIONS_CAP),
|
|
10425
|
+
score,
|
|
10426
|
+
tier: tierFor(score),
|
|
10427
|
+
});
|
|
10428
|
+
}
|
|
10429
|
+
|
|
10430
|
+
if (!files.length) return empty;
|
|
10431
|
+
const maxScore = files.reduce((m, f) => Math.max(m, f.score), 0);
|
|
10432
|
+
return {
|
|
10433
|
+
available: true,
|
|
10434
|
+
files,
|
|
10435
|
+
aggregate: { score: maxScore, tier: tierFor(maxScore), impactedFunctions: allImpacted.size },
|
|
10436
|
+
};
|
|
10437
|
+
}
|
|
10438
|
+
|
|
10439
|
+
module.exports = { methodBlastRadius, tierFor, DIRECT_WEIGHT, TRANSITIVE_WEIGHT };
|
|
10440
|
+
|
|
10441
|
+
};
|
|
10442
|
+
|
|
10307
10443
|
// ── ./src/graph/builder ──
|
|
10308
10444
|
__factories["./src/graph/builder"] = function(module, exports) {
|
|
10309
10445
|
|
|
@@ -10806,14 +10942,16 @@ __factories["./src/graph/builder"] = function(module, exports) {
|
|
|
10806
10942
|
__factories["./src/graph/call-graph"] = function(module, exports) {
|
|
10807
10943
|
|
|
10808
10944
|
/**
|
|
10809
|
-
* Method/caller-level call-graph (D4 v1).
|
|
10945
|
+
* Method/caller-level call-graph (D4 v1, languages expanded in GR1).
|
|
10810
10946
|
*
|
|
10811
|
-
* Builds symbol-level edges — which function calls which function — for JS/TS
|
|
10812
|
-
* and
|
|
10813
|
-
* Call sites are resolved with high precision: a call
|
|
10814
|
-
* of that name in the *same file* first, then in a
|
|
10815
|
-
* (via the existing file-level import graph). Names
|
|
10816
|
-
* definition produce no edge — over-approximation
|
|
10947
|
+
* Builds symbol-level edges — which function calls which function — for JS/TS,
|
|
10948
|
+
* Python, Java, Go, and Rust. Deterministic, zero-dependency, regex +
|
|
10949
|
+
* brace/indent matching. Call sites are resolved with high precision: a call
|
|
10950
|
+
* resolves to a definition of that name in the *same file* first, then in a
|
|
10951
|
+
* *directly-imported* file (via the existing file-level import graph). Names
|
|
10952
|
+
* that resolve to no repo definition produce no edge — over-approximation
|
|
10953
|
+
* noise is avoided. Constructs that can't be parsed dependency-free are
|
|
10954
|
+
* skipped (less fidelity, never a parser dep).
|
|
10817
10955
|
*
|
|
10818
10956
|
* Symbol IDs are `relPath#symbolName` (forward-slashed, relative to cwd).
|
|
10819
10957
|
*
|
|
@@ -10826,6 +10964,9 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
10826
10964
|
|
|
10827
10965
|
const JS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
|
|
10828
10966
|
const PY_EXTS = new Set(['.py', '.pyw']);
|
|
10967
|
+
const JAVA_EXTS = new Set(['.java']);
|
|
10968
|
+
const GO_EXTS = new Set(['.go']);
|
|
10969
|
+
const RS_EXTS = new Set(['.rs']);
|
|
10829
10970
|
|
|
10830
10971
|
// Tokens that look like `name(` calls or definition headers but are language
|
|
10831
10972
|
// keywords, not user symbols — never treated as a call or a definition.
|
|
@@ -10835,6 +10976,7 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
10835
10976
|
'in', 'of', 'case', 'throw', 'print', 'and', 'or', 'not', 'assert',
|
|
10836
10977
|
'lambda', 'class', 'def', 'elif', 'except', 'finally', 'raise', 'import',
|
|
10837
10978
|
'from', 'global', 'nonlocal', 'del', 'pass', 'async', 'require', 'constructor',
|
|
10979
|
+
'synchronized',
|
|
10838
10980
|
]);
|
|
10839
10981
|
|
|
10840
10982
|
function normalizePath(p) { return path.normalize(p).toLowerCase(); }
|
|
@@ -10863,6 +11005,32 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
10863
11005
|
return out.join('');
|
|
10864
11006
|
}
|
|
10865
11007
|
|
|
11008
|
+
// Rust: `//`, `/* */`, and `"..."` mask like JS, but a bare `'` is usually a
|
|
11009
|
+
// lifetime (`'a`), not a string — masking to the "closing" quote would corrupt
|
|
11010
|
+
// offsets. Only char literals (`'x'`, `'\n'`) are masked; lifetimes pass through.
|
|
11011
|
+
function maskRust(src) {
|
|
11012
|
+
const out = src.split('');
|
|
11013
|
+
const blank = (a, b) => { for (let k = a; k < b; k++) if (out[k] !== '\n') out[k] = ' '; };
|
|
11014
|
+
let i = 0; const n = src.length;
|
|
11015
|
+
while (i < n) {
|
|
11016
|
+
const c = src[i], d = src[i + 1];
|
|
11017
|
+
if (c === '/' && d === '/') { let j = i + 2; while (j < n && src[j] !== '\n') j++; blank(i, j); i = j; continue; }
|
|
11018
|
+
if (c === '/' && d === '*') { let j = i + 2; while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j++; j = Math.min(n, j + 2); blank(i, j); i = j; continue; }
|
|
11019
|
+
if (c === '"') {
|
|
11020
|
+
let j = i + 1;
|
|
11021
|
+
while (j < n) { if (src[j] === '\\') { j += 2; continue; } if (src[j] === '"') break; j++; }
|
|
11022
|
+
j = Math.min(n, j + 1); blank(i, j); i = j; continue;
|
|
11023
|
+
}
|
|
11024
|
+
if (c === "'") {
|
|
11025
|
+
if (d === '\\' && src[i + 3] === "'") { blank(i, i + 4); i += 4; continue; } // '\n'
|
|
11026
|
+
if (d !== undefined && src[i + 2] === "'") { blank(i, i + 3); i += 3; continue; } // 'x'
|
|
11027
|
+
i++; continue; // lifetime `'a` — leave untouched
|
|
11028
|
+
}
|
|
11029
|
+
i++;
|
|
11030
|
+
}
|
|
11031
|
+
return out.join('');
|
|
11032
|
+
}
|
|
11033
|
+
|
|
10866
11034
|
function maskPy(src) {
|
|
10867
11035
|
const out = src.split('');
|
|
10868
11036
|
const blank = (a, b) => { for (let k = a; k < b; k++) if (out[k] !== '\n') out[k] = ' '; };
|
|
@@ -10992,10 +11160,98 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
10992
11160
|
return defs;
|
|
10993
11161
|
}
|
|
10994
11162
|
|
|
11163
|
+
// Go: func name(...) { } | func (r Recv) name(...) (T, error) { }
|
|
11164
|
+
// The return list may itself be parenthesized, so scan past it to the body `{`.
|
|
11165
|
+
function goDefs(masked) {
|
|
11166
|
+
const defs = [];
|
|
11167
|
+
const re = /(?:^|\n)func\s+(?:\([^)\n]*\)\s*)?([A-Za-z_]\w*)\s*\(/g;
|
|
11168
|
+
let m;
|
|
11169
|
+
while ((m = re.exec(masked)) !== null) {
|
|
11170
|
+
const paren = masked.indexOf('(', m.index + m[0].length - 1);
|
|
11171
|
+
const close = matchDelim(masked, paren, '(', ')');
|
|
11172
|
+
let k = close + 1;
|
|
11173
|
+
let depth = 0;
|
|
11174
|
+
while (k < masked.length) {
|
|
11175
|
+
const ch = masked[k];
|
|
11176
|
+
if (ch === '(') depth++;
|
|
11177
|
+
else if (ch === ')') depth--;
|
|
11178
|
+
else if (ch === '{' && depth === 0) break;
|
|
11179
|
+
else if (ch === '\n' && depth === 0) { k = -1; break; } // no body on this header
|
|
11180
|
+
k++;
|
|
11181
|
+
}
|
|
11182
|
+
if (k === -1 || k >= masked.length) continue;
|
|
11183
|
+
defs.push({ name: m[1], line: lineAt(masked, m.index + 1), bodyStart: k, bodyEnd: matchDelim(masked, k, '{', '}') });
|
|
11184
|
+
}
|
|
11185
|
+
return defs;
|
|
11186
|
+
}
|
|
11187
|
+
|
|
11188
|
+
// Java: methods + constructors with braced bodies. Statement-shaped matches
|
|
11189
|
+
// (calls, control flow) are rejected because their `)` is followed by `;`,
|
|
11190
|
+
// and keyword headers (`if`, `while`, …) fall to the NON_CALL guard.
|
|
11191
|
+
function javaDefs(masked) {
|
|
11192
|
+
const defs = [];
|
|
11193
|
+
const seen = new Set();
|
|
11194
|
+
const re = /(?:^|\n)[ \t]*((?:(?:public|private|protected|static|final|abstract|synchronized|native|default|strictfp)\s+)*)(?:<[^>\n]{0,80}>\s*)?(?:[\w$][\w$.<>\[\],?\s]*?\s+)?([A-Za-z_$][\w$]*)\s*\(/g;
|
|
11195
|
+
let m;
|
|
11196
|
+
while ((m = re.exec(masked)) !== null) {
|
|
11197
|
+
const name = m[2];
|
|
11198
|
+
if (NON_CALL.has(name)) continue;
|
|
11199
|
+
// `new Foo() { … }` anonymous classes are uses, not definitions.
|
|
11200
|
+
const before = masked.slice(Math.max(0, m.index), m.index + m[0].length - name.length - 1);
|
|
11201
|
+
if (/\bnew\s*$/.test(before)) continue;
|
|
11202
|
+
const paren = masked.indexOf('(', m.index + m[0].length - 1);
|
|
11203
|
+
const close = matchDelim(masked, paren, '(', ')');
|
|
11204
|
+
// skip `throws A, B` up to the body `{` (same line — multi-line headers are skipped)
|
|
11205
|
+
let k = close + 1;
|
|
11206
|
+
while (k < masked.length && masked[k] !== '{' && masked[k] !== ';' && masked[k] !== '\n' && masked[k] !== '=') k++;
|
|
11207
|
+
if (masked[k] !== '{') continue;
|
|
11208
|
+
const key = name + ':' + k;
|
|
11209
|
+
if (seen.has(key)) continue;
|
|
11210
|
+
seen.add(key);
|
|
11211
|
+
defs.push({ name, line: lineAt(masked, m.index + 1), bodyStart: k, bodyEnd: matchDelim(masked, k, '{', '}') });
|
|
11212
|
+
}
|
|
11213
|
+
return defs;
|
|
11214
|
+
}
|
|
11215
|
+
|
|
11216
|
+
// Rust: fn name(...) { } | fn name<T>(...) -> T where … { } — inside or
|
|
11217
|
+
// outside impl/trait blocks. A `;` before the body brace (trait declaration)
|
|
11218
|
+
// means no body: skipped.
|
|
11219
|
+
function rustDefs(masked) {
|
|
11220
|
+
const defs = [];
|
|
11221
|
+
const re = /\bfn\s+([A-Za-z_]\w*)/g;
|
|
11222
|
+
let m;
|
|
11223
|
+
while ((m = re.exec(masked)) !== null) {
|
|
11224
|
+
let k = m.index + m[0].length;
|
|
11225
|
+
while (k < masked.length && /\s/.test(masked[k])) k++;
|
|
11226
|
+
if (masked[k] === '<') k = matchDelim(masked, k, '<', '>') + 1;
|
|
11227
|
+
while (k < masked.length && /\s/.test(masked[k])) k++;
|
|
11228
|
+
if (masked[k] !== '(') continue;
|
|
11229
|
+
const close = matchDelim(masked, k, '(', ')');
|
|
11230
|
+
// return type / where clause may span lines; stop at body `{` or decl `;`
|
|
11231
|
+
let b = close + 1;
|
|
11232
|
+
while (b < masked.length && masked[b] !== '{' && masked[b] !== ';') b++;
|
|
11233
|
+
if (masked[b] !== '{') continue;
|
|
11234
|
+
defs.push({ name: m[1], line: lineAt(masked, m.index), bodyStart: b, bodyEnd: matchDelim(masked, b, '{', '}') });
|
|
11235
|
+
}
|
|
11236
|
+
return defs;
|
|
11237
|
+
}
|
|
11238
|
+
|
|
11239
|
+
// Pick the masker whose comment/string syntax matches the language.
|
|
11240
|
+
// Java and Go share JS syntax (Go raw strings mask like template literals).
|
|
11241
|
+
function maskFor(filePath, src) {
|
|
11242
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
11243
|
+
if (PY_EXTS.has(ext)) return maskPy(src);
|
|
11244
|
+
if (RS_EXTS.has(ext)) return maskRust(src);
|
|
11245
|
+
return maskJs(src);
|
|
11246
|
+
}
|
|
11247
|
+
|
|
10995
11248
|
function extractDefs(filePath, src) {
|
|
10996
11249
|
const ext = path.extname(filePath).toLowerCase();
|
|
10997
11250
|
if (JS_EXTS.has(ext)) return jsDefs(maskJs(src));
|
|
10998
11251
|
if (PY_EXTS.has(ext)) return pyDefs(maskPy(src));
|
|
11252
|
+
if (JAVA_EXTS.has(ext)) return javaDefs(maskJs(src));
|
|
11253
|
+
if (GO_EXTS.has(ext)) return goDefs(maskJs(src));
|
|
11254
|
+
if (RS_EXTS.has(ext)) return rustDefs(maskRust(src));
|
|
10999
11255
|
return null; // unsupported language
|
|
11000
11256
|
}
|
|
11001
11257
|
|
|
@@ -11026,7 +11282,7 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
11026
11282
|
if (e.isDirectory()) _walk(full, excludeSet, out, depth + 1);
|
|
11027
11283
|
else if (e.isFile()) {
|
|
11028
11284
|
const ext = path.extname(e.name).toLowerCase();
|
|
11029
|
-
if (JS_EXTS.has(ext) || PY_EXTS.has(ext)) out.push(full);
|
|
11285
|
+
if (JS_EXTS.has(ext) || PY_EXTS.has(ext) || JAVA_EXTS.has(ext) || GO_EXTS.has(ext) || RS_EXTS.has(ext)) out.push(full);
|
|
11030
11286
|
}
|
|
11031
11287
|
}
|
|
11032
11288
|
}
|
|
@@ -11093,10 +11349,20 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
11093
11349
|
};
|
|
11094
11350
|
|
|
11095
11351
|
for (const [f, fileDefs] of perFileDefs.entries()) {
|
|
11096
|
-
const masked =
|
|
11352
|
+
const masked = maskFor(f, fs.readFileSync(f, 'utf8'));
|
|
11097
11353
|
// resolution scope: this file's defs, then directly-imported files' defs
|
|
11098
11354
|
const importedAbs = (fileGraph.forward.get(normalizePath(path.resolve(f))) || [])
|
|
11099
11355
|
.map((nf) => normToAbs.get(nf)).filter(Boolean);
|
|
11356
|
+
// Go/Java: same-package symbols are visible with no import statement, and
|
|
11357
|
+
// a package is (in practice) a directory — extend the scope to same-dir
|
|
11358
|
+
// same-language siblings. Sorted for deterministic resolution order.
|
|
11359
|
+
const ext = path.extname(f).toLowerCase();
|
|
11360
|
+
if (GO_EXTS.has(ext) || JAVA_EXTS.has(ext)) {
|
|
11361
|
+
const siblings = [...perFileDefs.keys()]
|
|
11362
|
+
.filter((o) => o !== f && path.dirname(o) === path.dirname(f) && path.extname(o).toLowerCase() === ext)
|
|
11363
|
+
.sort();
|
|
11364
|
+
importedAbs.push(...siblings);
|
|
11365
|
+
}
|
|
11100
11366
|
for (const d of fileDefs) {
|
|
11101
11367
|
const callerId = symId(cwd, f, d.name);
|
|
11102
11368
|
if (!forward.has(callerId)) forward.set(callerId, new Set()); // ensure node exists
|
|
@@ -11202,7 +11468,7 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
11202
11468
|
module.exports = {
|
|
11203
11469
|
buildCallGraph, methodImpact, methodCallees,
|
|
11204
11470
|
formatCallGraph, formatCallGraphJSON,
|
|
11205
|
-
extractDefs, maskJs, maskPy,
|
|
11471
|
+
extractDefs, maskJs, maskPy, maskRust,
|
|
11206
11472
|
};
|
|
11207
11473
|
|
|
11208
11474
|
};
|
|
@@ -13359,6 +13625,28 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
|
|
|
13359
13625
|
}
|
|
13360
13626
|
}
|
|
13361
13627
|
|
|
13628
|
+
/**
|
|
13629
|
+
* get_method_impact({ symbol, direction?, depth? }) → string
|
|
13630
|
+
*
|
|
13631
|
+
* Method-level blast radius (GR2): every function that transitively calls
|
|
13632
|
+
* `symbol` (direction 'callers', default), or everything it calls ('callees').
|
|
13633
|
+
*/
|
|
13634
|
+
function getMethodImpact(args, cwd) {
|
|
13635
|
+
if (!args || !args.symbol) return 'Missing required argument: symbol';
|
|
13636
|
+
|
|
13637
|
+
try {
|
|
13638
|
+
const { methodImpact, methodCallees, formatCallGraph } = __require('./src/graph/call-graph');
|
|
13639
|
+
const kind = args.direction === 'callees' ? 'callees' : 'callers';
|
|
13640
|
+
const depth = Math.max(0, parseInt(args.depth, 10) || 0);
|
|
13641
|
+
const result = kind === 'callees'
|
|
13642
|
+
? methodCallees(args.symbol, cwd, { depth })
|
|
13643
|
+
: methodImpact(args.symbol, cwd, { depth });
|
|
13644
|
+
return formatCallGraph(result, kind);
|
|
13645
|
+
} catch (err) {
|
|
13646
|
+
return `_get_method_impact failed: ${err.message}_`;
|
|
13647
|
+
}
|
|
13648
|
+
}
|
|
13649
|
+
|
|
13362
13650
|
/**
|
|
13363
13651
|
* get_impact({ file, depth? }) → string
|
|
13364
13652
|
*
|
|
@@ -13872,7 +14160,7 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
|
|
|
13872
14160
|
return header + sq.squeezed;
|
|
13873
14161
|
}
|
|
13874
14162
|
|
|
13875
|
-
module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion, squeezeOutput };
|
|
14163
|
+
module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getMethodImpact, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion, squeezeOutput };
|
|
13876
14164
|
|
|
13877
14165
|
};
|
|
13878
14166
|
|
|
@@ -14039,11 +14327,11 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
14039
14327
|
|
|
14040
14328
|
const readline = require('readline');
|
|
14041
14329
|
const { TOOLS } = __require('./src/mcp/tools');
|
|
14042
|
-
const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion, squeezeOutput } = __require('./src/mcp/handlers');
|
|
14330
|
+
const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getMethodImpact, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion, squeezeOutput } = __require('./src/mcp/handlers');
|
|
14043
14331
|
|
|
14044
14332
|
const SERVER_INFO = {
|
|
14045
14333
|
name: 'sigmap',
|
|
14046
|
-
version: '8.
|
|
14334
|
+
version: '8.14.0',
|
|
14047
14335
|
description: 'SigMap MCP server — code signatures on demand',
|
|
14048
14336
|
};
|
|
14049
14337
|
|
|
@@ -14099,6 +14387,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
14099
14387
|
else if (name === 'explain_file') text = explainFile(args, cwd);
|
|
14100
14388
|
else if (name === 'list_modules') text = listModules(args, cwd);
|
|
14101
14389
|
else if (name === 'query_context') text = queryContext(args, cwd);
|
|
14390
|
+
else if (name === 'get_method_impact') text = getMethodImpact(args, cwd);
|
|
14102
14391
|
else if (name === 'get_impact') text = getImpact(args, cwd);
|
|
14103
14392
|
else if (name === 'get_lines') text = getLines(args, cwd);
|
|
14104
14393
|
else if (name === 'read_memory') text = readMemory(args, cwd);
|
|
@@ -14170,12 +14459,12 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
14170
14459
|
__factories["./src/mcp/tools"] = function(module, exports) {
|
|
14171
14460
|
|
|
14172
14461
|
/**
|
|
14173
|
-
* MCP tool definitions for SigMap (
|
|
14462
|
+
* MCP tool definitions for SigMap (20 tools).
|
|
14174
14463
|
* read_context, search_signatures, get_map, create_checkpoint, get_routing,
|
|
14175
|
-
* explain_file, list_modules, query_context,
|
|
14176
|
-
* get_callee_signatures, sigmap_notify_file_created,
|
|
14177
|
-
* sigmap_notify_file_deleted, get_diff_context,
|
|
14178
|
-
* verify_suggestion, squeeze_output.
|
|
14464
|
+
* explain_file, list_modules, query_context, get_method_impact, get_impact,
|
|
14465
|
+
* get_lines, read_memory, get_callee_signatures, sigmap_notify_file_created,
|
|
14466
|
+
* sigmap_notify_symbol_added, sigmap_notify_file_deleted, get_diff_context,
|
|
14467
|
+
* get_architecture_overview, verify_suggestion, squeeze_output.
|
|
14179
14468
|
*/
|
|
14180
14469
|
|
|
14181
14470
|
const TOOLS = [
|
|
@@ -14317,6 +14606,35 @@ __factories["./src/mcp/tools"] = function(module, exports) {
|
|
|
14317
14606
|
required: ['query'],
|
|
14318
14607
|
},
|
|
14319
14608
|
},
|
|
14609
|
+
{
|
|
14610
|
+
name: 'get_method_impact',
|
|
14611
|
+
description:
|
|
14612
|
+
'Method-level blast radius for a symbol: every FUNCTION that (transitively) calls it — ' +
|
|
14613
|
+
'or, with direction "callees", every repo function it calls. Finer-grained than the ' +
|
|
14614
|
+
'file-level get_impact: tells an agent which functions break, not just which files. ' +
|
|
14615
|
+
'JS/TS, Python, Java, Go, and Rust call-graph; deterministic, no LLM.',
|
|
14616
|
+
inputSchema: {
|
|
14617
|
+
type: 'object',
|
|
14618
|
+
properties: {
|
|
14619
|
+
symbol: {
|
|
14620
|
+
type: 'string',
|
|
14621
|
+
description:
|
|
14622
|
+
'Function/method name (e.g. "validateToken") or a full "file#name" id ' +
|
|
14623
|
+
'(e.g. "src/auth/session.js#validateToken") to disambiguate.',
|
|
14624
|
+
},
|
|
14625
|
+
direction: {
|
|
14626
|
+
type: 'string',
|
|
14627
|
+
enum: ['callers', 'callees'],
|
|
14628
|
+
description: '"callers" (default) = blast radius; "callees" = what the symbol calls.',
|
|
14629
|
+
},
|
|
14630
|
+
depth: {
|
|
14631
|
+
type: 'number',
|
|
14632
|
+
description: 'BFS depth limit (default 0 = unlimited).',
|
|
14633
|
+
},
|
|
14634
|
+
},
|
|
14635
|
+
required: ['symbol'],
|
|
14636
|
+
},
|
|
14637
|
+
},
|
|
14320
14638
|
{
|
|
14321
14639
|
name: 'get_impact',
|
|
14322
14640
|
description:
|
|
@@ -15723,6 +16041,12 @@ __factories["./src/review/pr-evidence"] = function(module, exports) {
|
|
|
15723
16041
|
impactByFile = new Map(analyzeImpact(srcPaths, cwd, { depth }).map((r) => [r.file, r.impact]));
|
|
15724
16042
|
} catch (_) { /* graph optional */ }
|
|
15725
16043
|
|
|
16044
|
+
// GR2: method-level blast radius per changed file (reviewPr already computed
|
|
16045
|
+
// it when the call graph resolved — reuse, don't rebuild the graph).
|
|
16046
|
+
const methodBlastByFile = new Map(
|
|
16047
|
+
(review.methodBlast && review.methodBlast.files || []).map((m) => [m.file, m])
|
|
16048
|
+
);
|
|
16049
|
+
|
|
15726
16050
|
const fileReports = files.map((f) => {
|
|
15727
16051
|
const deleted = f.status === 'D';
|
|
15728
16052
|
let signatures = [];
|
|
@@ -15731,6 +16055,7 @@ __factories["./src/review/pr-evidence"] = function(module, exports) {
|
|
|
15731
16055
|
}
|
|
15732
16056
|
const impact = impactByFile.get(f.path) || null;
|
|
15733
16057
|
return {
|
|
16058
|
+
methodBlast: methodBlastByFile.get(f.path.replace(/\\/g, '/')) || null,
|
|
15734
16059
|
path: f.path,
|
|
15735
16060
|
status: f.status,
|
|
15736
16061
|
riskLabel: riskLabelFor(f.path),
|
|
@@ -15773,6 +16098,7 @@ __factories["./src/review/pr-evidence"] = function(module, exports) {
|
|
|
15773
16098
|
else if (f.type === 'security-file') L.push(`- ⚠️ **sensitive path touched** (path heuristic, not a content scan) — \`${f.file}\``);
|
|
15774
16099
|
else if (f.type === 'secret-detected') L.push(`- 🔑 **secret detected** (${f.secret}) — \`${f.file}\``);
|
|
15775
16100
|
else if (f.type === 'god-node') L.push(`- ⚠️ **god node** — \`${f.file}\` → ${f.count} dependents (high blast radius)`);
|
|
16101
|
+
else if (f.type === 'method-blast') L.push(`- ⚠️ **method blast radius ${f.tier}** — \`${f.file}\` → ${f.functions} function(s) transitively call into this change (score ${f.score}/100)`);
|
|
15776
16102
|
else if (f.type === 'scope-drift') L.push(`- ⚠️ **scope drift** — ${f.count} top-level dirs touched (${f.dirs.join(', ')})`);
|
|
15777
16103
|
}
|
|
15778
16104
|
L.push('');
|
|
@@ -15794,6 +16120,15 @@ __factories["./src/review/pr-evidence"] = function(module, exports) {
|
|
|
15794
16120
|
} else {
|
|
15795
16121
|
L.push('**Blast radius:** _(not in dependency graph — new or leaf file)_');
|
|
15796
16122
|
}
|
|
16123
|
+
if (f.methodBlast && (f.methodBlast.directCallers + f.methodBlast.transitiveCallers) > 0) {
|
|
16124
|
+
const mb = f.methodBlast;
|
|
16125
|
+
const total = mb.directCallers + mb.transitiveCallers;
|
|
16126
|
+
L.push(
|
|
16127
|
+
`**Method blast radius:** ${total} function(s) impacted (score ${mb.score}/100, ${mb.tier}) — ` +
|
|
16128
|
+
mb.impactedFunctions.slice(0, 6).map((id) => '`' + id + '`').join(', ') +
|
|
16129
|
+
(total > 6 ? ` +${total - 6} more` : '')
|
|
16130
|
+
);
|
|
16131
|
+
}
|
|
15797
16132
|
if (f.relatedTests.length) L.push(`Related tests: ${f.relatedTests.slice(0, 8).map((t) => '`' + t + '`').join(', ')}`);
|
|
15798
16133
|
|
|
15799
16134
|
if (f.signatures.length) {
|
|
@@ -15860,7 +16195,7 @@ __factories["./src/review/review-pr"] = function(module, exports) {
|
|
|
15860
16195
|
* @param {object} [opts]
|
|
15861
16196
|
* @param {number} [opts.godNodeThreshold=15]
|
|
15862
16197
|
* @param {number} [opts.scopeThreshold=5]
|
|
15863
|
-
* @returns {{ findings: object[], blast: object[], summary: object }}
|
|
16198
|
+
* @returns {{ findings: object[], blast: object[], methodBlast: object|null, summary: object }}
|
|
15864
16199
|
*/
|
|
15865
16200
|
function reviewPr(changedFiles, cwd, opts = {}) {
|
|
15866
16201
|
const godThreshold = opts.godNodeThreshold != null ? opts.godNodeThreshold : GOD_NODE_THRESHOLD;
|
|
@@ -15921,6 +16256,27 @@ __factories["./src/review/review-pr"] = function(module, exports) {
|
|
|
15921
16256
|
blast.sort((a, b) => b.totalImpact - a.totalImpact);
|
|
15922
16257
|
}
|
|
15923
16258
|
|
|
16259
|
+
// 3b. Method-level blast radius (GR2) — how many FUNCTIONS transitively call
|
|
16260
|
+
// into the change, scored deterministically. Graph optional, like 3.
|
|
16261
|
+
let methodBlast = null;
|
|
16262
|
+
if (srcChanged.length) {
|
|
16263
|
+
try {
|
|
16264
|
+
const { methodBlastRadius } = __require('./src/graph/blast-radius');
|
|
16265
|
+
const mb = methodBlastRadius(srcChanged, cwd, opts.methodBlastOpts || {});
|
|
16266
|
+
if (mb.available) {
|
|
16267
|
+
methodBlast = mb;
|
|
16268
|
+
for (const f of mb.files) {
|
|
16269
|
+
if (f.tier === 'high' || f.tier === 'critical') {
|
|
16270
|
+
findings.push({
|
|
16271
|
+
type: 'method-blast', file: f.file, severity: 'warn',
|
|
16272
|
+
functions: f.directCallers + f.transitiveCallers, score: f.score, tier: f.tier,
|
|
16273
|
+
});
|
|
16274
|
+
}
|
|
16275
|
+
}
|
|
16276
|
+
}
|
|
16277
|
+
} catch (_) { /* call graph optional */ }
|
|
16278
|
+
}
|
|
16279
|
+
|
|
15924
16280
|
// 4. Scope drift: distinct top-level directories touched.
|
|
15925
16281
|
const dirs = [...new Set(paths.map((p) => (p.includes('/') ? p.split('/')[0] : '.')))];
|
|
15926
16282
|
if (dirs.length > scopeThreshold) {
|
|
@@ -15931,6 +16287,7 @@ __factories["./src/review/review-pr"] = function(module, exports) {
|
|
|
15931
16287
|
return {
|
|
15932
16288
|
findings,
|
|
15933
16289
|
blast,
|
|
16290
|
+
methodBlast,
|
|
15934
16291
|
summary: {
|
|
15935
16292
|
filesChanged: files.length,
|
|
15936
16293
|
sourceChanged: srcChanged.length,
|
|
@@ -18987,7 +19344,7 @@ function __tryGit(args, opts = {}) {
|
|
|
18987
19344
|
catch (_) { return ''; }
|
|
18988
19345
|
}
|
|
18989
19346
|
|
|
18990
|
-
const VERSION = '8.
|
|
19347
|
+
const VERSION = '8.14.0';
|
|
18991
19348
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
18992
19349
|
|
|
18993
19350
|
function requireSourceOrBundled(key) {
|
|
@@ -20848,7 +21205,7 @@ Usage:
|
|
|
20848
21205
|
${cmd} --impact <file> Show every file impacted by changing <file>
|
|
20849
21206
|
${cmd} --impact <file> --json Impact as JSON {changed, direct, transitive, tests, routes}
|
|
20850
21207
|
${cmd} --impact <file> --depth <n> BFS depth limit (default 3, 0=unlimited)
|
|
20851
|
-
${cmd} --callers <symbol> Method-level blast radius — every function that (transitively) calls <symbol> (JS/TS
|
|
21208
|
+
${cmd} --callers <symbol> Method-level blast radius — every function that (transitively) calls <symbol> (JS/TS, Python, Java, Go, Rust)
|
|
20852
21209
|
${cmd} --callees <symbol> Every repo function that <symbol> (transitively) calls
|
|
20853
21210
|
${cmd} --callers <symbol> --json --depth <n> Call-graph edges as JSON (depth 0 = unlimited)
|
|
20854
21211
|
${cmd} verify <answer.md> Flagship grounding guard — flag fake files/tests/imports/symbols/npm-scripts in an AI answer (alias of verify-ai-output)
|
package/llms-full.txt
CHANGED
|
@@ -11,13 +11,13 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
|
|
|
11
11
|
effect), with no LLM calls, embeddings, or vector database. Works with Claude,
|
|
12
12
|
Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
13
13
|
|
|
14
|
-
# Version: 8.
|
|
14
|
+
# Version: 8.14.0 | Benchmark: sigmap-v8.14-main (2026-07-11)
|
|
15
15
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
16
16
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
## Core metrics (benchmark: sigmap-v8.
|
|
20
|
+
## Core metrics (benchmark: sigmap-v8.14-main, 2026-07-11)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
|
@@ -26,7 +26,7 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
26
26
|
| Task success proxy | 10% | 67.8% |
|
|
27
27
|
| Prompts per task | 2.84 | 1.44 (49.2% fewer) |
|
|
28
28
|
| Supported languages | — | 33 |
|
|
29
|
-
| MCP tools | — |
|
|
29
|
+
| MCP tools | — | 20 |
|
|
30
30
|
| npm runtime dependencies | — | 0 |
|
|
31
31
|
|
|
32
32
|
---
|
|
@@ -101,7 +101,7 @@ sigmap weights --json Learned weights as JSON
|
|
|
101
101
|
sigmap --impact <file> Show every file impacted by changing <file>
|
|
102
102
|
sigmap --impact <file> --json Impact as JSON {changed, direct, transitive, tests, routes}
|
|
103
103
|
sigmap --impact <file> --depth <n> BFS depth limit (default 3, 0=unlimited)
|
|
104
|
-
sigmap --callers <symbol> Method-level blast radius — every function that (transitively) calls <symbol> (JS/TS
|
|
104
|
+
sigmap --callers <symbol> Method-level blast radius — every function that (transitively) calls <symbol> (JS/TS, Python, Java, Go, Rust)
|
|
105
105
|
sigmap --callees <symbol> Every repo function that <symbol> (transitively) calls
|
|
106
106
|
sigmap --callers <symbol> --json --depth <n> Call-graph edges as JSON (depth 0 = unlimited)
|
|
107
107
|
sigmap verify <answer.md> Flagship grounding guard — flag fake files/tests/imports/symbols/npm-scripts in an AI answer (alias of verify-ai-output)
|
|
@@ -136,7 +136,7 @@ sigmap --version Show version
|
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
139
|
-
## MCP server —
|
|
139
|
+
## MCP server — 20 tools
|
|
140
140
|
|
|
141
141
|
Start with `sigmap --mcp` (stdio JSON-RPC). Configure once:
|
|
142
142
|
|
|
@@ -208,6 +208,14 @@ Rank and return the most relevant files for a specific task or question. Uses ke
|
|
|
208
208
|
Input: { query: string, topK?: number }
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
### get_method_impact
|
|
212
|
+
|
|
213
|
+
Method-level blast radius for a symbol: every FUNCTION that (transitively) calls it — or, with direction "callees", every repo function it calls. Finer-grained than the file-level get_impact: tells an agent which functions break, not just which files. JS/TS, Python, Java, Go, and Rust call-graph; deterministic, no LLM.
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
Input: { symbol: string, direction?: string, depth?: number }
|
|
217
|
+
```
|
|
218
|
+
|
|
211
219
|
### get_impact
|
|
212
220
|
|
|
213
221
|
Show every file that is impacted when a given file changes — direct importers, transitive importers, affected tests, and affected routes/controllers. Gives agents instant blast-radius awareness before making a change. Handles circular dependencies safely (no infinite loops).
|
package/llms.txt
CHANGED
|
@@ -11,7 +11,7 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
|
|
|
11
11
|
effect), with no LLM calls, embeddings, or vector database. Works with Claude,
|
|
12
12
|
Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
13
13
|
|
|
14
|
-
# Version: 8.
|
|
14
|
+
# Version: 8.14.0 | Benchmark: sigmap-v8.14-main (2026-07-11)
|
|
15
15
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
16
16
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
17
17
|
|
|
@@ -23,13 +23,13 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
23
23
|
- No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
|
|
24
24
|
- Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
|
|
25
25
|
|
|
26
|
-
## Core metrics (benchmark: sigmap-v8.
|
|
26
|
+
## Core metrics (benchmark: sigmap-v8.14-main, 2026-07-11)
|
|
27
27
|
|
|
28
28
|
- hit@5 retrieval: 87.8% vs 13.6% random baseline (6.5× lift)
|
|
29
29
|
- Token reduction: 97.0% average across benchmark repos
|
|
30
30
|
- Task success: 67.8% vs 10% without SigMap
|
|
31
31
|
- Prompts per task: 1.44 vs 2.84 baseline (49.2% fewer)
|
|
32
|
-
- Languages: 33 supported · MCP tools:
|
|
32
|
+
- Languages: 33 supported · MCP tools: 20
|
|
33
33
|
- Dependencies: zero npm runtime dependencies · fully offline
|
|
34
34
|
|
|
35
35
|
## Quick start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.14.0",
|
|
4
4
|
"description": "The deterministic, verifiable grounding layer for AI code work — a zero-dependency signature-and-evidence map that grounds Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP agents against your real code (repo + installed libraries) so they stop hallucinating files, imports & APIs. Runs offline via npx; byte-stable output; ~97% token reduction as proof.",
|
|
5
5
|
"main": "packages/core/index.js",
|
|
6
6
|
"exports": {
|