sigmap 8.29.0 → 8.31.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 +24 -0
- package/README.md +2 -2
- package/gen-context.js +418 -30
- package/llms-full.txt +4 -2
- package/llms.txt +2 -2
- package/package.json +3 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/extractors/java.js +20 -4
- package/src/graph/builder.js +39 -3
- package/src/graph/call-graph.js +216 -13
- package/src/mcp/install.js +29 -2
- package/src/mcp/server.js +1 -1
- package/src/retrieval/ranker.js +35 -4
- package/src/skills/skills.js +26 -2
package/gen-context.js
CHANGED
|
@@ -6424,6 +6424,19 @@ __factories["./src/extractors/html"] = function(module, exports) {
|
|
|
6424
6424
|
__factories["./src/extractors/java"] = function(module, exports) {
|
|
6425
6425
|
|
|
6426
6426
|
const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
|
|
6427
|
+
const { capWithNotice, capMembersWithNotice } = __require('./src/util/truncate');
|
|
6428
|
+
|
|
6429
|
+
// Class bodies are scanned to this many characters. Generated JVM sources
|
|
6430
|
+
// (MyBatis/JPA entities) routinely run past 10KB, so the ceiling only guards
|
|
6431
|
+
// against pathological input rather than trimming ordinary classes.
|
|
6432
|
+
const MAX_CLASS_BODY_CHARS = 200000;
|
|
6433
|
+
|
|
6434
|
+
// Per-class member ceiling. Sits above the default `maxSigsPerFile` so the
|
|
6435
|
+
// caller's configured budget governs the output rather than this file.
|
|
6436
|
+
const MAX_MEMBERS_PER_CLASS = 120;
|
|
6437
|
+
|
|
6438
|
+
// Per-file signature ceiling, likewise above the configured default.
|
|
6439
|
+
const MAX_SIGS_PER_FILE = 200;
|
|
6427
6440
|
|
|
6428
6441
|
/**
|
|
6429
6442
|
* Extract signatures from Java source code.
|
|
@@ -6451,17 +6464,20 @@ __factories["./src/extractors/java"] = function(module, exports) {
|
|
|
6451
6464
|
const block = extractBlock(stripped, bodyStart);
|
|
6452
6465
|
sigs.push(hinted(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)), m[2]));
|
|
6453
6466
|
for (const meth of extractMembers(block)) {
|
|
6454
|
-
|
|
6467
|
+
// The disclosure marker carries no offsets; anchor it at the class body.
|
|
6468
|
+
const declIdx = meth.declIdx || 0;
|
|
6469
|
+
const endIdx = meth.endIdx || 0;
|
|
6470
|
+
sigs.push(hinted(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + declIdx), lineAt(stripped, bodyStart + endIdx)), meth.name));
|
|
6455
6471
|
}
|
|
6456
6472
|
}
|
|
6457
6473
|
|
|
6458
|
-
return sigs
|
|
6474
|
+
return capWithNotice(sigs, MAX_SIGS_PER_FILE, 'signatures');
|
|
6459
6475
|
}
|
|
6460
6476
|
|
|
6461
6477
|
function extractBlock(src, startIndex) {
|
|
6462
6478
|
let depth = 1;
|
|
6463
6479
|
let i = startIndex;
|
|
6464
|
-
const end = Math.min(src.length, startIndex +
|
|
6480
|
+
const end = Math.min(src.length, startIndex + MAX_CLASS_BODY_CHARS);
|
|
6465
6481
|
while (i < end && depth > 0) {
|
|
6466
6482
|
if (src[i] === '{') depth++;
|
|
6467
6483
|
else if (src[i] === '}') depth--;
|
|
@@ -6483,7 +6499,7 @@ __factories["./src/extractors/java"] = function(module, exports) {
|
|
|
6483
6499
|
endIdx: m.index + m[0].length,
|
|
6484
6500
|
});
|
|
6485
6501
|
}
|
|
6486
|
-
return members
|
|
6502
|
+
return capMembersWithNotice(members, MAX_MEMBERS_PER_CLASS);
|
|
6487
6503
|
}
|
|
6488
6504
|
|
|
6489
6505
|
function normalizeParams(params) {
|
|
@@ -11736,24 +11752,60 @@ __factories["./src/graph/builder"] = function(module, exports) {
|
|
|
11736
11752
|
return { forward, reverse };
|
|
11737
11753
|
}
|
|
11738
11754
|
|
|
11755
|
+
// Directory names assumed when neither the caller nor the project config says
|
|
11756
|
+
// otherwise. A Maven/Gradle module (`mall-portal/`, `service-api/`) matches none
|
|
11757
|
+
// of them, which is why the config is consulted first.
|
|
11758
|
+
const DEFAULT_SRC_DIRS = ['src', 'app', 'lib', 'R', 'inst'];
|
|
11759
|
+
|
|
11760
|
+
// Walk depth measured from EACH srcDir root, not from cwd — so this is not the
|
|
11761
|
+
// same quantity as the extractor's cwd-relative `maxDepth` and must not be read
|
|
11762
|
+
// from it. A standard Maven tree reaches `src/main/java/<group>/<artifact>/…`
|
|
11763
|
+
// nine directories below its module root, so the previous ceiling of 8 silently
|
|
11764
|
+
// dropped the deepest packages (on macrozheng/mall: every `service/impl/` class).
|
|
11765
|
+
const DEFAULT_WALK_DEPTH = 12;
|
|
11766
|
+
|
|
11767
|
+
/**
|
|
11768
|
+
* Source directories declared in the project's own config, or null when there
|
|
11769
|
+
* is no readable config. Read directly rather than through `loadConfig`, which
|
|
11770
|
+
* can fetch `extends` over the network and spawn a child process — neither is
|
|
11771
|
+
* acceptable inside a graph build.
|
|
11772
|
+
*/
|
|
11773
|
+
function _configuredSrcDirs(cwd) {
|
|
11774
|
+
try {
|
|
11775
|
+
const raw = fs.readFileSync(path.join(cwd, 'gen-context.config.json'), 'utf8');
|
|
11776
|
+
const cfg = JSON.parse(raw);
|
|
11777
|
+
if (Array.isArray(cfg.srcDirs) && cfg.srcDirs.length > 0) return cfg.srcDirs;
|
|
11778
|
+
} catch (_) { /* absent or unparsable — fall back to the defaults */ }
|
|
11779
|
+
return null;
|
|
11780
|
+
}
|
|
11781
|
+
|
|
11739
11782
|
/**
|
|
11740
11783
|
* Build a dependency graph scoped to a single cwd by walking all JS/TS/Py/Go
|
|
11741
11784
|
* files under srcDirs. Useful for the MCP tool handler.
|
|
11742
11785
|
*
|
|
11786
|
+
* srcDirs resolution order: explicit `opts.srcDirs` → `gen-context.config.json`
|
|
11787
|
+
* → DEFAULT_SRC_DIRS. Without the config step the graph is empty on any repo
|
|
11788
|
+
* whose sources do not sit under a conventionally-named directory.
|
|
11789
|
+
*
|
|
11743
11790
|
* @param {string} cwd
|
|
11744
11791
|
* @param {object} [opts]
|
|
11745
11792
|
* @param {string[]} [opts.srcDirs]
|
|
11746
11793
|
* @param {string[]} [opts.exclude]
|
|
11794
|
+
* @param {number} [opts.maxDepth] - walk depth from each srcDir root
|
|
11747
11795
|
* @returns {{ forward: Map<string,string[]>, reverse: Map<string,string[]> }}
|
|
11748
11796
|
*/
|
|
11749
11797
|
function buildFromCwd(cwd, opts) {
|
|
11750
11798
|
// R-package layouts use `R/` and `inst/`; Shiny apps put helpers in `R/`.
|
|
11751
11799
|
// The existence check below makes these no-ops in non-R projects.
|
|
11752
|
-
const {
|
|
11800
|
+
const {
|
|
11801
|
+
srcDirs = _configuredSrcDirs(cwd) || DEFAULT_SRC_DIRS,
|
|
11802
|
+
exclude = ['node_modules', '.git', 'dist', 'build'],
|
|
11803
|
+
maxDepth = DEFAULT_WALK_DEPTH,
|
|
11804
|
+
} = opts || {};
|
|
11753
11805
|
const excludeSet = new Set(exclude);
|
|
11754
11806
|
|
|
11755
11807
|
function walkDir(dir, depth) {
|
|
11756
|
-
if (depth >
|
|
11808
|
+
if (depth > maxDepth) return [];
|
|
11757
11809
|
let entries;
|
|
11758
11810
|
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return []; }
|
|
11759
11811
|
const out = [];
|
|
@@ -11802,7 +11854,7 @@ __factories["./src/graph/builder"] = function(module, exports) {
|
|
|
11802
11854
|
return build(files, cwd, ctx);
|
|
11803
11855
|
}
|
|
11804
11856
|
|
|
11805
|
-
module.exports = { build, buildFromCwd, extractFileDeps, normalizePath, loadAliasMap, resolveAlias };
|
|
11857
|
+
module.exports = { build, buildFromCwd, extractFileDeps, normalizePath, loadAliasMap, resolveAlias, _configuredSrcDirs, DEFAULT_SRC_DIRS, DEFAULT_WALK_DEPTH };
|
|
11806
11858
|
|
|
11807
11859
|
};
|
|
11808
11860
|
|
|
@@ -12057,6 +12109,44 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12057
12109
|
// Java: methods + constructors with braced bodies. Statement-shaped matches
|
|
12058
12110
|
// (calls, control flow) are rejected because their `)` is followed by `;`,
|
|
12059
12111
|
// and keyword headers (`if`, `while`, …) fall to the NON_CALL guard.
|
|
12112
|
+
// Words that can precede `name(` in a STATEMENT, so their presence means the
|
|
12113
|
+
// line is not a method declaration.
|
|
12114
|
+
const STMT_KEYWORDS = new Set([
|
|
12115
|
+
'return', 'throw', 'else', 'do', 'try', 'case', 'yield', 'assert', 'new',
|
|
12116
|
+
'if', 'while', 'for', 'switch', 'catch', 'synchronized', 'instanceof', 'await',
|
|
12117
|
+
]);
|
|
12118
|
+
|
|
12119
|
+
// Types a Java class declares it implements or extends, with generic arguments
|
|
12120
|
+
// stripped and any package qualifier dropped: `implements Foo<Bar, Baz>` yields
|
|
12121
|
+
// ['Foo'], never 'Baz>'. Also records whether the class is a Spring bean and
|
|
12122
|
+
// whether it is @Primary, which is what disambiguates several implementations.
|
|
12123
|
+
function javaTypeDecl(masked) {
|
|
12124
|
+
const m = /(?:^|\n)[^\n]*?\bclass\s+([A-Za-z_$][\w$]*)([^{]*)\{/.exec(masked);
|
|
12125
|
+
if (!m) return null;
|
|
12126
|
+
const [, className, tail] = m;
|
|
12127
|
+
const supers = [];
|
|
12128
|
+
for (const kw of ['implements', 'extends']) {
|
|
12129
|
+
const k = new RegExp('\\b' + kw + '\\s+([^{]*?)(?=\\b(?:implements|extends)\\b|$)').exec(tail);
|
|
12130
|
+
if (!k) continue;
|
|
12131
|
+
let depth = 0;
|
|
12132
|
+
let cur = '';
|
|
12133
|
+
for (const ch of k[1]) {
|
|
12134
|
+
if (ch === '<') { depth++; continue; }
|
|
12135
|
+
if (ch === '>') { depth--; continue; }
|
|
12136
|
+
if (ch === ',' && depth === 0) { if (cur.trim()) supers.push(cur.trim()); cur = ''; continue; }
|
|
12137
|
+
if (depth === 0) cur += ch;
|
|
12138
|
+
}
|
|
12139
|
+
if (cur.trim()) supers.push(cur.trim());
|
|
12140
|
+
}
|
|
12141
|
+
const head = masked.slice(0, m.index + m[0].length);
|
|
12142
|
+
return {
|
|
12143
|
+
className,
|
|
12144
|
+
supers: supers.map((t) => t.split('.').pop().trim()).filter(Boolean),
|
|
12145
|
+
isBean: /@(Service|Component|Repository|Controller|RestController)\b/.test(head),
|
|
12146
|
+
isPrimary: /@Primary\b/.test(head),
|
|
12147
|
+
};
|
|
12148
|
+
}
|
|
12149
|
+
|
|
12060
12150
|
function javaDefs(masked) {
|
|
12061
12151
|
const defs = [];
|
|
12062
12152
|
const seen = new Set();
|
|
@@ -12073,11 +12163,24 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12073
12163
|
// skip `throws A, B` up to the body `{` (same line — multi-line headers are skipped)
|
|
12074
12164
|
let k = close + 1;
|
|
12075
12165
|
while (k < masked.length && masked[k] !== '{' && masked[k] !== ';' && masked[k] !== '\n' && masked[k] !== '=') k++;
|
|
12076
|
-
|
|
12166
|
+
// A `;` here is an interface or abstract method DECLARATION. It owns no body,
|
|
12167
|
+
// so it emits no calls — but in Spring the declared interface is what callers
|
|
12168
|
+
// name, so without it as a node every controller→service edge has no target.
|
|
12169
|
+
// Recorded with an empty body range: can receive edges, never produces them.
|
|
12170
|
+
// `before` is everything between line start and the method name. A real
|
|
12171
|
+
// declaration has modifiers or a return type there (`void chargeCard(`);
|
|
12172
|
+
// a call statement has only whitespace (`identity(1);`) or a statement
|
|
12173
|
+
// keyword (`return helper(a);`) — neither may be read as a declaration,
|
|
12174
|
+
// or the call resolves to a phantom local def instead of the real target.
|
|
12175
|
+
const headWord = (before.match(/([A-Za-z_$][\w$]*)\s*$/) || [])[1];
|
|
12176
|
+
const isDecl = masked[k] === ';' && /\S/.test(before) && !STMT_KEYWORDS.has(headWord);
|
|
12177
|
+
if (masked[k] !== '{' && !isDecl) continue;
|
|
12077
12178
|
const key = name + ':' + k;
|
|
12078
12179
|
if (seen.has(key)) continue;
|
|
12079
12180
|
seen.add(key);
|
|
12080
|
-
defs.push(
|
|
12181
|
+
defs.push(isDecl
|
|
12182
|
+
? { name, line: lineAt(masked, m.index + 1), bodyStart: k, bodyEnd: k }
|
|
12183
|
+
: { name, line: lineAt(masked, m.index + 1), bodyStart: k, bodyEnd: matchDelim(masked, k, '{', '}') });
|
|
12081
12184
|
}
|
|
12082
12185
|
return defs;
|
|
12083
12186
|
}
|
|
@@ -12131,7 +12234,7 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12131
12234
|
const re = /([A-Za-z_$][\w$]*)\s*\(/g;
|
|
12132
12235
|
let m;
|
|
12133
12236
|
while ((m = re.exec(slice)) !== null) {
|
|
12134
|
-
// skip a `.name(` method access
|
|
12237
|
+
// skip a `.name(` method access — resolved separately via receiverCallsInRange
|
|
12135
12238
|
const before = slice[m.index - 1];
|
|
12136
12239
|
if (before === '.') continue;
|
|
12137
12240
|
if (!NON_CALL.has(m[1])) names.add(m[1]);
|
|
@@ -12139,16 +12242,75 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12139
12242
|
return names;
|
|
12140
12243
|
}
|
|
12141
12244
|
|
|
12245
|
+
// Collect `receiver.method(` pairs within [start,end). A chained or computed
|
|
12246
|
+
// receiver (`a.b().c(`, `arr[0].c(`) is skipped: only a plain identifier can be
|
|
12247
|
+
// looked up in the declaration map, and guessing is worse than no edge.
|
|
12248
|
+
function receiverCallsInRange(masked, start, end) {
|
|
12249
|
+
const slice = masked.slice(start, end);
|
|
12250
|
+
const out = [];
|
|
12251
|
+
const re = /([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)\s*\(/g;
|
|
12252
|
+
let m;
|
|
12253
|
+
while ((m = re.exec(slice)) !== null) {
|
|
12254
|
+
const before = slice[m.index - 1];
|
|
12255
|
+
if (before === '.' || before === ')' || before === ']') continue;
|
|
12256
|
+
if (NON_CALL.has(m[2])) continue;
|
|
12257
|
+
out.push({ receiver: m[1], method: m[2] });
|
|
12258
|
+
}
|
|
12259
|
+
return out;
|
|
12260
|
+
}
|
|
12261
|
+
|
|
12262
|
+
// `private UserService userService;` / `UserService svc = new UserService();`
|
|
12263
|
+
// / `for (OmsOrderItem item : list)` → { userService: 'UserService', … }.
|
|
12264
|
+
// Declarations only: a bare assignment carries no type and is not inferred.
|
|
12265
|
+
const DECL_RE = /(?:^|[;{}(,\n])\s*(?:(?:public|private|protected|static|final|volatile|transient)\s+)*([A-Z][\w$]*)(?:\s*<[^>;=(){}]*>)?(?:\s*\[\s*\])?\s+([a-z_$][\w$]*)\s*(?=[;=:)])/g;
|
|
12266
|
+
|
|
12267
|
+
function buildTypeMap(masked) {
|
|
12268
|
+
const map = new Map();
|
|
12269
|
+
let m;
|
|
12270
|
+
DECL_RE.lastIndex = 0;
|
|
12271
|
+
while ((m = DECL_RE.exec(masked)) !== null) {
|
|
12272
|
+
const [, type, name] = m;
|
|
12273
|
+
if (JVM_KEYWORDS.has(type) || JVM_KEYWORDS.has(name)) continue;
|
|
12274
|
+
if (!map.has(name)) map.set(name, type); // first declaration wins — deterministic
|
|
12275
|
+
}
|
|
12276
|
+
return map;
|
|
12277
|
+
}
|
|
12278
|
+
|
|
12279
|
+
// Type names that are never a user class, so never a resolvable receiver type.
|
|
12280
|
+
const JVM_KEYWORDS = new Set([
|
|
12281
|
+
'return', 'new', 'if', 'else', 'for', 'while', 'switch', 'case', 'throw', 'catch',
|
|
12282
|
+
'String', 'Integer', 'Long', 'Boolean', 'Double', 'Float', 'Object', 'List', 'Map',
|
|
12283
|
+
'Set', 'Collection', 'Optional', 'Override', 'Autowired', 'Resource', 'Deprecated',
|
|
12284
|
+
]);
|
|
12285
|
+
|
|
12142
12286
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
12143
12287
|
|
|
12144
|
-
|
|
12145
|
-
|
|
12288
|
+
// Walk depth from each srcDir root (not from cwd). A Maven module reaches
|
|
12289
|
+
// `src/main/java/<group>/<artifact>/service/impl` nine directories down, so the
|
|
12290
|
+
// previous ceiling of 8 never saw the classes that own the method bodies.
|
|
12291
|
+
const DEFAULT_WALK_DEPTH = 12;
|
|
12292
|
+
|
|
12293
|
+
/**
|
|
12294
|
+
* Source directories declared in the project's own config, or null. Read
|
|
12295
|
+
* directly rather than through `loadConfig`, which can fetch `extends` over the
|
|
12296
|
+
* network and spawn a child process — neither belongs inside a graph build.
|
|
12297
|
+
*/
|
|
12298
|
+
function _configuredSrcDirs(cwd) {
|
|
12299
|
+
try {
|
|
12300
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(cwd, 'gen-context.config.json'), 'utf8'));
|
|
12301
|
+
if (Array.isArray(cfg.srcDirs) && cfg.srcDirs.length > 0) return cfg.srcDirs;
|
|
12302
|
+
} catch (_) { /* absent or unparsable — fall back to the defaults */ }
|
|
12303
|
+
return null;
|
|
12304
|
+
}
|
|
12305
|
+
|
|
12306
|
+
function _walk(dir, excludeSet, out, depth, maxDepth) {
|
|
12307
|
+
if (depth > (maxDepth === undefined ? DEFAULT_WALK_DEPTH : maxDepth)) return;
|
|
12146
12308
|
let entries;
|
|
12147
12309
|
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
12148
12310
|
for (const e of entries) {
|
|
12149
12311
|
if (excludeSet.has(e.name) || e.name.startsWith('.')) continue;
|
|
12150
12312
|
const full = path.join(dir, e.name);
|
|
12151
|
-
if (e.isDirectory()) _walk(full, excludeSet, out, depth + 1);
|
|
12313
|
+
if (e.isDirectory()) _walk(full, excludeSet, out, depth + 1, maxDepth);
|
|
12152
12314
|
else if (e.isFile()) {
|
|
12153
12315
|
const ext = path.extname(e.name).toLowerCase();
|
|
12154
12316
|
if (JS_EXTS.has(ext) || PY_EXTS.has(ext) || JAVA_EXTS.has(ext) || GO_EXTS.has(ext) || RS_EXTS.has(ext)) out.push(full);
|
|
@@ -12174,9 +12336,13 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12174
12336
|
const excludeSet = new Set(opts.exclude || ['node_modules', '.git', 'dist', 'build', 'coverage', 'vendor']);
|
|
12175
12337
|
let files = opts.files ? opts.files.map((f) => path.resolve(f)) : [];
|
|
12176
12338
|
if (!opts.files) {
|
|
12177
|
-
|
|
12339
|
+
// Same resolution order as the dependency graph (#560): explicit opts →
|
|
12340
|
+
// the project's own config → the historical defaults. Without the config
|
|
12341
|
+
// step this is empty on any repo whose sources are not under src/app/lib.
|
|
12342
|
+
const srcDirs = opts.srcDirs || _configuredSrcDirs(cwd) || ['src', 'app', 'lib'];
|
|
12343
|
+
for (const sd of srcDirs) {
|
|
12178
12344
|
const abs = path.resolve(cwd, sd);
|
|
12179
|
-
if (fs.existsSync(abs)) _walk(abs, excludeSet, files, 0);
|
|
12345
|
+
if (fs.existsSync(abs)) _walk(abs, excludeSet, files, 0, opts.maxDepth);
|
|
12180
12346
|
}
|
|
12181
12347
|
}
|
|
12182
12348
|
|
|
@@ -12190,6 +12356,44 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12190
12356
|
const normToAbs = new Map(); // normalized abs → abs
|
|
12191
12357
|
const defs = new Map(); // symbolId → {file,name,line}
|
|
12192
12358
|
|
|
12359
|
+
// JVM convention: a public type lives in a file of the same name. This is the
|
|
12360
|
+
// deterministic type→file mapping receiver resolution needs, with no AST.
|
|
12361
|
+
const fileByTypeName = new Map(); // 'UserService' → [absFile]
|
|
12362
|
+
for (const f of files) {
|
|
12363
|
+
const ext = path.extname(f).toLowerCase();
|
|
12364
|
+
if (JAVA_EXTS.has(ext)) {
|
|
12365
|
+
const base = path.basename(f, path.extname(f));
|
|
12366
|
+
if (!fileByTypeName.has(base)) fileByTypeName.set(base, []);
|
|
12367
|
+
fileByTypeName.get(base).push(f);
|
|
12368
|
+
}
|
|
12369
|
+
}
|
|
12370
|
+
|
|
12371
|
+
// interface/superclass name → implementing files, for the Spring hop below.
|
|
12372
|
+
const implsByType = new Map(); // 'PaymentService' → [{ file, isBean, isPrimary }]
|
|
12373
|
+
for (const f of files) {
|
|
12374
|
+
if (!JAVA_EXTS.has(path.extname(f).toLowerCase())) continue;
|
|
12375
|
+
let decl;
|
|
12376
|
+
try { decl = javaTypeDecl(maskJs(fs.readFileSync(f, 'utf8'))); } catch (_) { continue; }
|
|
12377
|
+
if (!decl) continue;
|
|
12378
|
+
for (const sup of decl.supers) {
|
|
12379
|
+
if (!implsByType.has(sup)) implsByType.set(sup, []);
|
|
12380
|
+
implsByType.get(sup).push({ file: f, isBean: decl.isBean, isPrimary: decl.isPrimary });
|
|
12381
|
+
}
|
|
12382
|
+
}
|
|
12383
|
+
|
|
12384
|
+
/**
|
|
12385
|
+
* The single implementing file for a type, or null when it is ambiguous.
|
|
12386
|
+
* One implementation resolves outright; several resolve only via @Primary.
|
|
12387
|
+
* Anything still ambiguous yields no edge — polymorphism is not guessed.
|
|
12388
|
+
*/
|
|
12389
|
+
const soleImpl = (typeName) => {
|
|
12390
|
+
const cands = implsByType.get(typeName) || [];
|
|
12391
|
+
if (cands.length === 1) return cands[0].file;
|
|
12392
|
+
const primary = cands.filter((c) => c.isPrimary);
|
|
12393
|
+
if (primary.length === 1) return primary[0].file;
|
|
12394
|
+
return null;
|
|
12395
|
+
};
|
|
12396
|
+
|
|
12193
12397
|
for (const f of files) {
|
|
12194
12398
|
normToAbs.set(normalizePath(path.resolve(f)), path.resolve(f));
|
|
12195
12399
|
let src;
|
|
@@ -12209,12 +12413,20 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12209
12413
|
|
|
12210
12414
|
const forward = new Map();
|
|
12211
12415
|
const reverse = new Map();
|
|
12212
|
-
|
|
12416
|
+
// Additive: `forward`/`reverse` keep their existing shape, so every current
|
|
12417
|
+
// consumer is unaffected. Confidence is looked up by "from\u0000to".
|
|
12418
|
+
const edgeConfidence = new Map();
|
|
12419
|
+
const addEdge = (from, to, confidence) => {
|
|
12213
12420
|
if (from === to) return;
|
|
12214
12421
|
if (!forward.has(from)) forward.set(from, new Set());
|
|
12215
12422
|
forward.get(from).add(to);
|
|
12216
12423
|
if (!reverse.has(to)) reverse.set(to, new Set());
|
|
12217
12424
|
reverse.get(to).add(from);
|
|
12425
|
+
if (confidence) {
|
|
12426
|
+
const k = from + '\u0000' + to;
|
|
12427
|
+
// A 'high' resolution never loses to a later 'medium' one.
|
|
12428
|
+
if (edgeConfidence.get(k) !== 'high') edgeConfidence.set(k, confidence);
|
|
12429
|
+
}
|
|
12218
12430
|
};
|
|
12219
12431
|
|
|
12220
12432
|
for (const [f, fileDefs] of perFileDefs.entries()) {
|
|
@@ -12232,16 +12444,59 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12232
12444
|
.sort();
|
|
12233
12445
|
importedAbs.push(...siblings);
|
|
12234
12446
|
}
|
|
12447
|
+
// Receiver types come from declarations anywhere in the file: fields are
|
|
12448
|
+
// declared outside any method body, locals inside one.
|
|
12449
|
+
const typeMap = JAVA_EXTS.has(ext) ? buildTypeMap(masked) : null;
|
|
12450
|
+
// Types reachable from this file, by name — imports first, then same-package
|
|
12451
|
+
// siblings, so an import always wins over a coincidental sibling name.
|
|
12452
|
+
const scopeByTypeName = new Map();
|
|
12453
|
+
if (typeMap) {
|
|
12454
|
+
for (const imp of importedAbs) {
|
|
12455
|
+
const base = path.basename(imp, path.extname(imp));
|
|
12456
|
+
if (!scopeByTypeName.has(base)) scopeByTypeName.set(base, imp);
|
|
12457
|
+
}
|
|
12458
|
+
}
|
|
12459
|
+
|
|
12235
12460
|
for (const d of fileDefs) {
|
|
12236
12461
|
const callerId = symId(cwd, f, d.name);
|
|
12237
12462
|
if (!forward.has(callerId)) forward.set(callerId, new Set()); // ensure node exists
|
|
12238
12463
|
const callees = callsInRange(masked, d.bodyStart, d.bodyEnd);
|
|
12239
12464
|
for (const nm of callees) {
|
|
12240
12465
|
const local = (defsByName.get(f) || new Map()).get(nm);
|
|
12241
|
-
if (local && local.length) { for (const id of local) addEdge(callerId, id); continue; }
|
|
12466
|
+
if (local && local.length) { for (const id of local) addEdge(callerId, id, 'high'); continue; }
|
|
12242
12467
|
for (const imp of importedAbs) {
|
|
12243
12468
|
const ids = (defsByName.get(imp) || new Map()).get(nm);
|
|
12244
|
-
if (ids && ids.length) { for (const id of ids) addEdge(callerId, id); break; }
|
|
12469
|
+
if (ids && ids.length) { for (const id of ids) addEdge(callerId, id, 'high'); break; }
|
|
12470
|
+
}
|
|
12471
|
+
}
|
|
12472
|
+
|
|
12473
|
+
// `receiver.method(` — resolve the receiver's declared type to a file.
|
|
12474
|
+
if (!typeMap) continue;
|
|
12475
|
+
for (const { receiver, method } of receiverCallsInRange(masked, d.bodyStart, d.bodyEnd)) {
|
|
12476
|
+
// A receiver that is itself a type name is a static call: `Foo.bar()`.
|
|
12477
|
+
const typeName = typeMap.get(receiver)
|
|
12478
|
+
|| (fileByTypeName.has(receiver) ? receiver : null);
|
|
12479
|
+
if (!typeName) continue; // unknown receiver → no edge, never a guess
|
|
12480
|
+
|
|
12481
|
+
let target = scopeByTypeName.get(typeName);
|
|
12482
|
+
let confidence = 'high'; // typed receiver, resolved in scope
|
|
12483
|
+
if (!target) {
|
|
12484
|
+
const candidates = fileByTypeName.get(typeName) || [];
|
|
12485
|
+
if (candidates.length !== 1) continue; // ambiguous or absent → no edge
|
|
12486
|
+
target = candidates[0];
|
|
12487
|
+
confidence = 'medium'; // type known, but not in this file's scope
|
|
12488
|
+
}
|
|
12489
|
+
const ids = (defsByName.get(target) || new Map()).get(method);
|
|
12490
|
+
if (ids && ids.length) for (const id of ids) addEdge(callerId, id, confidence);
|
|
12491
|
+
|
|
12492
|
+
// Spring: the call names the interface, but the code that runs — and
|
|
12493
|
+
// that a reviewer changes — lives in the implementation. Both edges are
|
|
12494
|
+
// true, so both are recorded; without the second, blast radius on an
|
|
12495
|
+
// implementation is empty.
|
|
12496
|
+
const implFile = soleImpl(typeName);
|
|
12497
|
+
if (implFile && implFile !== target) {
|
|
12498
|
+
const implIds = (defsByName.get(implFile) || new Map()).get(method);
|
|
12499
|
+
if (implIds && implIds.length) for (const id of implIds) addEdge(callerId, id, confidence);
|
|
12245
12500
|
}
|
|
12246
12501
|
}
|
|
12247
12502
|
}
|
|
@@ -12252,7 +12507,7 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12252
12507
|
for (const [k, set] of mapOfSets.entries()) out.set(k, [...set]);
|
|
12253
12508
|
return out;
|
|
12254
12509
|
};
|
|
12255
|
-
return { forward: toArr(forward), reverse: toArr(reverse), defs };
|
|
12510
|
+
return { forward: toArr(forward), reverse: toArr(reverse), defs, edgeConfidence };
|
|
12256
12511
|
}
|
|
12257
12512
|
|
|
12258
12513
|
/**
|
|
@@ -12374,7 +12629,7 @@ __factories["./src/graph/call-graph"] = function(module, exports) {
|
|
|
12374
12629
|
}
|
|
12375
12630
|
|
|
12376
12631
|
module.exports = {
|
|
12377
|
-
buildCallGraph, buildCallFileGraph, methodImpact, methodCallees,
|
|
12632
|
+
buildCallGraph, buildTypeMap, receiverCallsInRange, javaTypeDecl, DEFAULT_WALK_DEPTH, buildCallFileGraph, methodImpact, methodCallees,
|
|
12378
12633
|
formatCallGraph, formatCallGraphJSON,
|
|
12379
12634
|
extractDefs, maskJs, maskPy, maskRust,
|
|
12380
12635
|
};
|
|
@@ -15250,6 +15505,7 @@ __factories["./src/mcp/install"] = function(module, exports) {
|
|
|
15250
15505
|
|
|
15251
15506
|
// Config shapes the supported clients use.
|
|
15252
15507
|
// - 'json' → { mcpServers: { sigmap: { command, args } } }
|
|
15508
|
+
// - 'vscode'→ { servers: { sigmap: { type: 'stdio', command, args } } }
|
|
15253
15509
|
// - 'zed' → { context_servers: { sigmap: { command: { path, args } } } }
|
|
15254
15510
|
// - 'yaml' → Codex CLI ~/.codex/config.yaml (mcpServers block, appended)
|
|
15255
15511
|
const CLIENTS = {
|
|
@@ -15258,7 +15514,7 @@ __factories["./src/mcp/install"] = function(module, exports) {
|
|
|
15258
15514
|
windsurf: { label: 'Windsurf', format: 'json', scope: 'both',
|
|
15259
15515
|
project: ['.windsurf', 'mcp.json'],
|
|
15260
15516
|
global: ['.codeium', 'windsurf', 'mcp_config.json'] },
|
|
15261
|
-
vscode: { label: 'VS Code', format: '
|
|
15517
|
+
vscode: { label: 'VS Code', format: 'vscode', scope: 'project', project: ['.vscode', 'mcp.json'] },
|
|
15262
15518
|
opencode: { label: 'OpenCode', format: 'json', scope: 'both',
|
|
15263
15519
|
project: ['opencode.json'],
|
|
15264
15520
|
global: ['.config', 'opencode', 'config.json'] },
|
|
@@ -15313,6 +15569,31 @@ __factories["./src/mcp/install"] = function(module, exports) {
|
|
|
15313
15569
|
return 'installed';
|
|
15314
15570
|
}
|
|
15315
15571
|
|
|
15572
|
+
/**
|
|
15573
|
+
* Install into VS Code's `.vscode/mcp.json`, which keys servers under `servers`
|
|
15574
|
+
* (not `mcpServers`) and expects an explicit transport `type`. A config written
|
|
15575
|
+
* by an older SigMap under `mcpServers` is migrated rather than left in place,
|
|
15576
|
+
* so re-running repairs it instead of leaving two entries VS Code cannot read.
|
|
15577
|
+
*/
|
|
15578
|
+
function _installVscode(filePath, scriptPath) {
|
|
15579
|
+
let settings = {};
|
|
15580
|
+
if (fs.existsSync(filePath)) {
|
|
15581
|
+
try { settings = JSON.parse(fs.readFileSync(filePath, 'utf8')) || {}; }
|
|
15582
|
+
catch (_) { settings = {}; }
|
|
15583
|
+
}
|
|
15584
|
+
const stale = settings.mcpServers && settings.mcpServers.sigmap;
|
|
15585
|
+
if (stale) {
|
|
15586
|
+
delete settings.mcpServers.sigmap;
|
|
15587
|
+
if (Object.keys(settings.mcpServers).length === 0) delete settings.mcpServers;
|
|
15588
|
+
}
|
|
15589
|
+
if (!settings.servers) settings.servers = {};
|
|
15590
|
+
if (settings.servers.sigmap && !stale) return 'already';
|
|
15591
|
+
settings.servers.sigmap = { type: 'stdio', command: 'node', args: serverArgs(scriptPath) };
|
|
15592
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
15593
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
|
|
15594
|
+
return stale ? 'updated' : 'installed';
|
|
15595
|
+
}
|
|
15596
|
+
|
|
15316
15597
|
/** Install into Zed's `context_servers` config (create file/dir if absent). */
|
|
15317
15598
|
function _installZed(filePath, scriptPath) {
|
|
15318
15599
|
let settings = {};
|
|
@@ -15365,7 +15646,8 @@ __factories["./src/mcp/install"] = function(module, exports) {
|
|
|
15365
15646
|
const filePath = resolveTarget(spec, cwd, home, opts.global);
|
|
15366
15647
|
|
|
15367
15648
|
let status;
|
|
15368
|
-
if (spec.format === '
|
|
15649
|
+
if (spec.format === 'vscode') status = _installVscode(filePath, scriptPath);
|
|
15650
|
+
else if (spec.format === 'zed') status = _installZed(filePath, scriptPath);
|
|
15369
15651
|
else if (spec.format === 'yaml') status = _installYaml(filePath, scriptPath);
|
|
15370
15652
|
else status = _installJson(filePath, scriptPath);
|
|
15371
15653
|
|
|
@@ -15397,7 +15679,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
15397
15679
|
|
|
15398
15680
|
const SERVER_INFO = {
|
|
15399
15681
|
name: 'sigmap',
|
|
15400
|
-
version: '8.
|
|
15682
|
+
version: '8.31.0',
|
|
15401
15683
|
description: 'SigMap MCP server — code signatures on demand',
|
|
15402
15684
|
};
|
|
15403
15685
|
|
|
@@ -16760,26 +17042,52 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
16760
17042
|
generatedCode: 0.3, // dist/build/.next in path
|
|
16761
17043
|
docsFile: 0.2, // docs/doc/README in path
|
|
16762
17044
|
nodeModules: 0.0, // node_modules (zero score)
|
|
17045
|
+
dataHolder: 0.3, // generated POJO/entity: almost entirely accessors
|
|
16763
17046
|
};
|
|
16764
17047
|
|
|
17048
|
+
// A file whose members are overwhelmingly trivial accessors is a data holder,
|
|
17049
|
+
// not logic. Path-based detection cannot see these: generated JPA/MyBatis
|
|
17050
|
+
// entities live in ordinary source trees. They match a query on any column
|
|
17051
|
+
// name they happen to carry (`getNote`/`setNote` matches "note" as strongly as
|
|
17052
|
+
// the service that actually implements order notes), so on an entity-heavy
|
|
17053
|
+
// repo they crowd real code out of the top results.
|
|
17054
|
+
const ACCESSOR_RE = /^\s*(get|set|is)[A-Z]\w*\s*\(/;
|
|
17055
|
+
const DATA_HOLDER_RATIO = 0.8;
|
|
17056
|
+
const DATA_HOLDER_MIN_MEMBERS = 6;
|
|
17057
|
+
|
|
16765
17058
|
// Query terms that mean the penalised category IS the target. Read from the
|
|
16766
17059
|
// query tokens directly, NOT via detectIntent: that classifier is first-match-
|
|
16767
17060
|
// wins over its pattern object, and `debug` precedes `test`, so "fix the failing
|
|
16768
17061
|
// test" classifies as debug and never reaches the test branch.
|
|
16769
17062
|
const WANTS_TESTS = new Set(['test', 'tests', 'spec', 'specs', 'unit', 'integration', 'e2e', 'assertion', 'assert', 'mock', 'fixture', 'coverage', 'testing']);
|
|
16770
17063
|
const WANTS_DOCS = new Set(['doc', 'docs', 'documentation', 'readme', 'changelog', 'guide', 'tutorial']);
|
|
17064
|
+
const WANTS_MODELS = new Set(['entity', 'entities', 'model', 'models', 'pojo', 'dto', 'bean', 'getter', 'getters', 'setter', 'setters', 'accessor', 'accessors', 'field', 'fields', 'column', 'columns', 'schema']);
|
|
16771
17065
|
|
|
16772
17066
|
/** Which penalised categories the query is explicitly asking for. */
|
|
16773
17067
|
function _queryWants(queryTokens) {
|
|
16774
|
-
const wants = { tests: false, docs: false };
|
|
17068
|
+
const wants = { tests: false, docs: false, models: false };
|
|
16775
17069
|
for (const t of queryTokens || []) {
|
|
16776
17070
|
if (WANTS_TESTS.has(t)) wants.tests = true;
|
|
16777
17071
|
if (WANTS_DOCS.has(t)) wants.docs = true;
|
|
17072
|
+
if (WANTS_MODELS.has(t)) wants.models = true;
|
|
16778
17073
|
}
|
|
16779
17074
|
return wants;
|
|
16780
17075
|
}
|
|
16781
17076
|
|
|
16782
|
-
|
|
17077
|
+
/**
|
|
17078
|
+
* True when a file's members are overwhelmingly trivial accessors — a generated
|
|
17079
|
+
* entity or POJO rather than logic. Type declarations are excluded from the
|
|
17080
|
+
* ratio so a small class is not misjudged by its own `class X` line.
|
|
17081
|
+
*/
|
|
17082
|
+
function _isDataHolder(sigs) {
|
|
17083
|
+
if (!Array.isArray(sigs)) return false;
|
|
17084
|
+
const members = sigs.filter((line) => /^\s/.test(line) || !/^(class|interface|enum|struct|function|module\.exports)\b/.test(line));
|
|
17085
|
+
if (members.length < DATA_HOLDER_MIN_MEMBERS) return false;
|
|
17086
|
+
const accessors = members.filter((line) => ACCESSOR_RE.test(line)).length;
|
|
17087
|
+
return accessors / members.length >= DATA_HOLDER_RATIO;
|
|
17088
|
+
}
|
|
17089
|
+
|
|
17090
|
+
function _computePenalty(filePath, wants, sigs) {
|
|
16783
17091
|
const pathLower = filePath.toLowerCase();
|
|
16784
17092
|
if (pathLower.includes('node_modules')) return PENALTY_SIGNALS.nodeModules;
|
|
16785
17093
|
// A penalty must never fire on the very thing the user asked for. Before
|
|
@@ -16792,6 +17100,11 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
16792
17100
|
if (/(^|\/)(docs|doc|readme|changelog)($|\/)/.test(pathLower)) {
|
|
16793
17101
|
return (wants && wants.docs) ? 1.0 : PENALTY_SIGNALS.docsFile;
|
|
16794
17102
|
}
|
|
17103
|
+
// Content-based, and last: a data holder is still a real source file, so it
|
|
17104
|
+
// is only demoted once the path-based categories have had their say.
|
|
17105
|
+
if (_isDataHolder(sigs)) {
|
|
17106
|
+
return (wants && wants.models) ? 1.0 : PENALTY_SIGNALS.dataHolder;
|
|
17107
|
+
}
|
|
16795
17108
|
return 1.0;
|
|
16796
17109
|
}
|
|
16797
17110
|
|
|
@@ -16849,7 +17162,7 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
16849
17162
|
if (!sigs || sigs.length === 0) return { score: 0, signals: { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: 1.0 } };
|
|
16850
17163
|
|
|
16851
17164
|
const w = weights || DEFAULT_WEIGHTS;
|
|
16852
|
-
const signals = { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: _computePenalty(filePath, wants) };
|
|
17165
|
+
const signals = { exactToken: 0, symbolMatch: 0, prefixMatch: 0, pathMatch: 0, penalty: _computePenalty(filePath, wants, sigs) };
|
|
16853
17166
|
|
|
16854
17167
|
// Module-doc prose is excluded here on purpose. This signal measures overlap
|
|
16855
17168
|
// with DECLARED IDENTIFIERS; prose relevance is BM25's job, where it is scored
|
|
@@ -17445,7 +17758,7 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
17445
17758
|
return detectIntents(query)[0];
|
|
17446
17759
|
}
|
|
17447
17760
|
|
|
17448
|
-
module.exports = { rank, buildSigIndex, scoreFile, _queryWants, detectIntents, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, CENTRALITY_BLEND_WEIGHT, detectIntent };
|
|
17761
|
+
module.exports = { rank, buildSigIndex, scoreFile, _queryWants, _isDataHolder, detectIntents, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, CENTRALITY_BLEND_WEIGHT, detectIntent };
|
|
17449
17762
|
|
|
17450
17763
|
};
|
|
17451
17764
|
|
|
@@ -18731,13 +19044,31 @@ __factories["./src/skills/skills"] = function(module, exports) {
|
|
|
18731
19044
|
'Follow this loop before any file exploration in a repo with SigMap installed.',
|
|
18732
19045
|
'',
|
|
18733
19046
|
'1. **Ask before reading.** `sigmap ask "<task>"` (or the `query_context` MCP tool) ranks the relevant files as ~hundreds of tokens of signatures instead of thousands of raw-file tokens. Never open files to "look around".',
|
|
18734
|
-
'2. **Read ranges, not files.** Use the `get_lines` MCP tool with the `:start-end` line anchors carried on every signature to pull only the lines you need.',
|
|
19047
|
+
'2. **Read ranges, not files.** Use the `get_lines` MCP tool — or `sigmap lines <file> :<line> --context <n>` where MCP is unavailable — with the `:start-end` line anchors carried on every signature to pull only the lines you need.',
|
|
18735
19048
|
'3. **Ground before trusting.** Run the `verify_suggestion` MCP tool (or `sigmap verify-ai-output`) on generated code before applying it — it flags fabricated files, imports, symbols, and npm scripts against the live index.',
|
|
18736
19049
|
'4. **Squeeze big pastes.** Any stack trace, CI/build log, or JSON blob goes through `sigmap squeeze` (or the `squeeze_output` MCP tool) before it enters context — the signal survives, the noise does not.',
|
|
18737
19050
|
'5. **Checkpoint progress.** Use the `create_checkpoint` MCP tool or `sigmap note "<decision>"` so a follow-up session resumes without re-deriving state.',
|
|
18738
19051
|
'6. **Watch the budget.** Check the `get_budget` MCP tool or `sigmap budget` (estimates from SigMap\'s local ledger — no LLM calls). Near the budget: summarize-then-drop older context instead of accumulating, and prefer terse output.',
|
|
18739
19052
|
].join('\n'),
|
|
18740
19053
|
},
|
|
19054
|
+
'sigmap-task': {
|
|
19055
|
+
title: 'SigMap task loop',
|
|
19056
|
+
kind: 'prompt',
|
|
19057
|
+
description: 'Do a coding task grounded in SigMap: look up before reading, edit by line anchor, verify before reporting.',
|
|
19058
|
+
argumentHint: 'the change you want, in plain words',
|
|
19059
|
+
body: [
|
|
19060
|
+
'Work through these steps **in order**. Do not open any file before step 2.',
|
|
19061
|
+
'Every command runs from the integrated terminal — do not ask the user to run them for you.',
|
|
19062
|
+
'',
|
|
19063
|
+
'1. **Look up, do not search.** `npx sigmap ask "<the task>"` — this writes `.context/query-context.md`.',
|
|
19064
|
+
'2. **Read the map.** `cat .context/query-context.md`. It ranks the relevant files and lists their signatures with `:start-end` line anchors — a few hundred tokens where the same files read whole are tens of thousands. Say which files it surfaced before continuing. If nothing relevant appears, re-run step 1 with different wording; fall back to search only after two attempts, and say so.',
|
|
19065
|
+
'3. **Read the anchored range, by command.** A signature ending `:425-425` means line 425, not the 547-line file. Run `npx sigmap lines <file> :425 --context 10` — paste the anchor straight off the signature. Never `cat` a whole file when you hold an anchor for it: on a real repo a 220-line span costs ~2,700 tokens where the anchored window costs ~220.',
|
|
19066
|
+
'4. **Make the change.** Follow the conventions visible in the signatures — same layering, same response wrapper, same annotation style. Add no dependencies.',
|
|
19067
|
+
'5. **Verify before reporting.** Write what you changed to `.sigmap-notes.md`, naming every file by its **full repository-relative path** (a bare filename is reported as fake), then run `npx sigmap verify-ai-output .sigmap-notes.md`. It checks every name against the real index, offline, with no model call. Fix anything it flags and re-run before you reply.',
|
|
19068
|
+
'6. **Refresh the map.** `npx sigmap` — your edits made it stale.',
|
|
19069
|
+
'7. **Report.** The files you changed, the ranges you actually read, the step-1 token count, and the step-5 verify result. Say so if you fell back to searching or if verify flagged something.',
|
|
19070
|
+
].join('\n'),
|
|
19071
|
+
},
|
|
18741
19072
|
'sigmap-config-optimizer': {
|
|
18742
19073
|
title: 'SigMap config optimizer',
|
|
18743
19074
|
description: 'Playbook for getting a correct SigMap config on any repo: detect with sigmap tune, review the per-change reasons, apply, validate.',
|
|
@@ -18762,7 +19093,9 @@ __factories["./src/skills/skills"] = function(module, exports) {
|
|
|
18762
19093
|
windsurf: { label: 'Windsurf', parent: ['.windsurf'],
|
|
18763
19094
|
target: (cwd, skill) => path.join(cwd, '.windsurf', 'rules', `${skill}.md`) },
|
|
18764
19095
|
copilot: { label: 'GitHub Copilot', parent: ['.github'],
|
|
18765
|
-
target: (cwd, skill) =>
|
|
19096
|
+
target: (cwd, skill) => (SKILLS[skill] && SKILLS[skill].kind === 'prompt'
|
|
19097
|
+
? path.join(cwd, '.github', 'prompts', `${skill}.prompt.md`)
|
|
19098
|
+
: path.join(cwd, '.github', 'instructions', `${skill}.instructions.md`)) },
|
|
18766
19099
|
codex: { label: 'Codex CLI (AGENTS.md)', parent: ['AGENTS.md'],
|
|
18767
19100
|
target: (cwd) => path.join(cwd, 'AGENTS.md'), inject: true },
|
|
18768
19101
|
};
|
|
@@ -18783,6 +19116,10 @@ __factories["./src/skills/skills"] = function(module, exports) {
|
|
|
18783
19116
|
return `---\ndescription: ${skill.description}\nalwaysApply: false\n---\n\n${body}`;
|
|
18784
19117
|
}
|
|
18785
19118
|
if (client === 'copilot') {
|
|
19119
|
+
if (skill.kind === 'prompt') {
|
|
19120
|
+
return `---\nname: ${skillName}\nagent: 'agent'\ndescription: ${skill.description}\n`
|
|
19121
|
+
+ `argument-hint: ${skill.argumentHint}\n---\n\n${body}`;
|
|
19122
|
+
}
|
|
18786
19123
|
return `---\napplyTo: "**"\n---\n\n${body}`;
|
|
18787
19124
|
}
|
|
18788
19125
|
return body; // windsurf: plain markdown
|
|
@@ -21636,7 +21973,7 @@ function __tryGit(args, opts = {}) {
|
|
|
21636
21973
|
catch (_) { return ''; }
|
|
21637
21974
|
}
|
|
21638
21975
|
|
|
21639
|
-
const VERSION = '8.
|
|
21976
|
+
const VERSION = '8.31.0';
|
|
21640
21977
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
21641
21978
|
|
|
21642
21979
|
function requireSourceOrBundled(key) {
|
|
@@ -23642,6 +23979,8 @@ Usage:
|
|
|
23642
23979
|
${cmd} tune --apply Write the recommendations into gen-context.config.json (merges; your keys preserved)
|
|
23643
23980
|
${cmd} skills list List skill clients (Claude/Cursor/Windsurf/Copilot/AGENTS.md) and install state (--json)
|
|
23644
23981
|
${cmd} skills install Install the SigMap agent playbooks for detected clients (--client <name> | --all)
|
|
23982
|
+
${cmd} lines <file> <start>-<end> Print an exact line range — CLI twin of get_lines (secrets redacted)
|
|
23983
|
+
${cmd} lines <file> :<line> --context <n> Window around one signature anchor (default ±10)
|
|
23645
23984
|
${cmd} note "<text>" Append a note to the cross-session decision log
|
|
23646
23985
|
${cmd} note List recent notes (also: note --list <N>)
|
|
23647
23986
|
${cmd} status Show repo state — branch, dirty files, index freshness, notes
|
|
@@ -25245,6 +25584,55 @@ function main() {
|
|
|
25245
25584
|
process.exit(0);
|
|
25246
25585
|
}
|
|
25247
25586
|
|
|
25587
|
+
// `sigmap lines <file> <start>-<end>` — the CLI twin of the get_lines MCP
|
|
25588
|
+
// tool. Without it, an agent in an MCP-less environment receives precise
|
|
25589
|
+
// `:start-end` anchors from `ask` and has no sanctioned way to spend them,
|
|
25590
|
+
// so it falls back to reading whole files and throws the saving away.
|
|
25591
|
+
// Delegates to the same handler as MCP so both paths share the sandbox,
|
|
25592
|
+
// the bounds clamping and the secret redaction.
|
|
25593
|
+
if (args[0] === 'lines') {
|
|
25594
|
+
const valOf = (f) => { const i = args.indexOf(f); return i >= 0 && args[i + 1] ? args[i + 1] : null; };
|
|
25595
|
+
const positional = [];
|
|
25596
|
+
const VALUE_FLAGS = new Set(['--cwd', '--context']);
|
|
25597
|
+
for (let i = 1; i < args.length; i++) {
|
|
25598
|
+
const a = args[i];
|
|
25599
|
+
if (a.startsWith('--')) { if (VALUE_FLAGS.has(a)) i++; continue; }
|
|
25600
|
+
positional.push(a);
|
|
25601
|
+
}
|
|
25602
|
+
const file = positional[0];
|
|
25603
|
+
const range = positional[1];
|
|
25604
|
+
if (!file || !range) {
|
|
25605
|
+
console.error('[sigmap] usage: sigmap lines <file> <start>-<end> (or :<line> --context <n>)');
|
|
25606
|
+
process.exit(2);
|
|
25607
|
+
}
|
|
25608
|
+
|
|
25609
|
+
// Accept `84-104`, a bare `94`, or the `:94` form copied straight off a
|
|
25610
|
+
// signature anchor — the whole point is to paste what `ask` printed.
|
|
25611
|
+
const ctx = Math.max(0, parseInt(valOf('--context') || '10', 10));
|
|
25612
|
+
let start;
|
|
25613
|
+
let end;
|
|
25614
|
+
const span = /^:?(\d+)\s*-\s*(\d+)$/.exec(range);
|
|
25615
|
+
const single = /^:?(\d+)$/.exec(range);
|
|
25616
|
+
if (span) { start = parseInt(span[1], 10); end = parseInt(span[2], 10); }
|
|
25617
|
+
else if (single) { const n = parseInt(single[1], 10); start = Math.max(1, n - ctx); end = n + ctx; }
|
|
25618
|
+
else {
|
|
25619
|
+
console.error(`[sigmap] lines: could not read range "${range}" — expected <start>-<end> or :<line>`);
|
|
25620
|
+
process.exit(2);
|
|
25621
|
+
}
|
|
25622
|
+
|
|
25623
|
+
const { getLines } = requireSourceOrBundled('./src/mcp/handlers');
|
|
25624
|
+
const out = getLines({ file, start, end }, cwd);
|
|
25625
|
+
// The handler reports its own failures as prose; surface them on stderr
|
|
25626
|
+
// with a non-zero exit so a script can tell a hit from a miss.
|
|
25627
|
+
if (/^(Missing required argument|Refused:|File not found:|Could not read |Arguments )/.test(out)
|
|
25628
|
+
|| /has only \d+ lines; requested/.test(out)) {
|
|
25629
|
+
console.error('[sigmap] ' + out);
|
|
25630
|
+
process.exit(1);
|
|
25631
|
+
}
|
|
25632
|
+
process.stdout.write(out + '\n');
|
|
25633
|
+
process.exit(0);
|
|
25634
|
+
}
|
|
25635
|
+
|
|
25248
25636
|
if (args[0] === 'note') {
|
|
25249
25637
|
const jsonOut = args.includes('--json');
|
|
25250
25638
|
const { addNote, readNotes, formatNotes } = requireSourceOrBundled('./src/session/notes');
|