sigmap 8.16.1 → 8.18.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 +164 -192
- package/CHANGELOG.md +24 -0
- package/README.md +12 -10
- package/gen-context.js +333 -70
- package/llms-full.txt +6 -6
- package/llms.txt +5 -5
- package/package.json +2 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/config/defaults.js +2 -0
- package/src/extractors/csharp.js +17 -5
- package/src/extractors/dart.js +32 -8
- package/src/extractors/go.js +25 -9
- package/src/extractors/java.js +16 -5
- package/src/extractors/kotlin.js +32 -8
- package/src/extractors/php.js +31 -6
- package/src/extractors/rust.js +38 -10
- package/src/extractors/scala.js +18 -6
- package/src/extractors/swift.js +31 -7
- package/src/map/route-table.js +14 -2
- package/src/mcp/handlers.js +4 -1
- package/src/mcp/server.js +1 -1
- package/src/retrieval/bm25.js +4 -1
- package/src/retrieval/enrich-from-maps.js +55 -0
package/src/extractors/swift.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { lineAt, withAnchor } = require('./line-anchor');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Extract signatures from Swift source code.
|
|
7
|
+
* Signatures carry `:start-end` line anchors (Surgical Context); the comment
|
|
8
|
+
* strip below is newline-preserving so anchor lines match the original file.
|
|
5
9
|
* @param {string} src - Raw file content
|
|
6
10
|
* @returns {string[]} Array of signature strings
|
|
7
11
|
*/
|
|
@@ -11,21 +15,37 @@ function extract(src) {
|
|
|
11
15
|
|
|
12
16
|
const stripped = src
|
|
13
17
|
.replace(/\/\/.*$/gm, '')
|
|
14
|
-
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
18
|
+
.replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
|
|
19
|
+
|
|
20
|
+
// Anchor range: scan past same-line modifiers to a body `{` (range) else single line.
|
|
21
|
+
const rangeFor = (declIdx, afterIdx) => {
|
|
22
|
+
let k = afterIdx;
|
|
23
|
+
while (k < stripped.length && /[ \tA-Za-z0-9_>-]/.test(stripped[k])) k++;
|
|
24
|
+
if (stripped[k] === '{') {
|
|
25
|
+
const end = k + 1 + extractBlock(stripped, k + 1).length;
|
|
26
|
+
return [lineAt(stripped, declIdx), lineAt(stripped, end)];
|
|
27
|
+
}
|
|
28
|
+
const line = lineAt(stripped, declIdx);
|
|
29
|
+
return [line, line];
|
|
30
|
+
};
|
|
15
31
|
|
|
16
32
|
// Classes, structs, protocols, enums
|
|
17
33
|
const typeRe = /^(?:public\s+|internal\s+|open\s+)?(?:final\s+)?(class|struct|protocol|enum|actor)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w, <>.]+)?\s*\{/gm;
|
|
18
34
|
for (const m of stripped.matchAll(typeRe)) {
|
|
19
|
-
|
|
20
|
-
const block = extractBlock(stripped,
|
|
21
|
-
|
|
35
|
+
const bodyStart = m.index + m[0].length;
|
|
36
|
+
const block = extractBlock(stripped, bodyStart);
|
|
37
|
+
sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
38
|
+
for (const fn of extractMembers(block)) {
|
|
39
|
+
sigs.push(withAnchor(` ${fn.text}`, lineAt(stripped, bodyStart + fn.declIdx), lineAt(stripped, bodyStart + fn.endIdx)));
|
|
40
|
+
}
|
|
22
41
|
}
|
|
23
42
|
|
|
24
43
|
// Top-level public functions — capture everything after ) to end of line for arrow type
|
|
25
44
|
for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:static\s+)?(?:async\s+)?func\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{\n]*)/gm)) {
|
|
26
45
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
27
46
|
const retStr = extractArrowType(m[3]);
|
|
28
|
-
|
|
47
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
48
|
+
sigs.push(withAnchor(`${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
return sigs.slice(0, 25);
|
|
@@ -48,7 +68,11 @@ function extractMembers(block) {
|
|
|
48
68
|
if (m[1].startsWith('_')) continue;
|
|
49
69
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
50
70
|
const retStr = extractArrowType(m[3]);
|
|
51
|
-
members.push(
|
|
71
|
+
members.push({
|
|
72
|
+
text: `${asyncKw}func ${m[1]}(${normalizeParams(m[2])})${retStr}`,
|
|
73
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
74
|
+
endIdx: m.index + m[0].length,
|
|
75
|
+
});
|
|
52
76
|
}
|
|
53
77
|
return members.slice(0, 8);
|
|
54
78
|
}
|
|
@@ -67,7 +91,7 @@ function extractArrowType(str) {
|
|
|
67
91
|
const m = str.match(/->\s*([^\n{]+)/);
|
|
68
92
|
if (!m) return '';
|
|
69
93
|
const rt = m[1].trim().replace(/\s+/g, ' ');
|
|
70
|
-
return `
|
|
94
|
+
return ` → ${rt.length > 25 ? rt.slice(0, 22) + '...' : rt}`;
|
|
71
95
|
}
|
|
72
96
|
|
|
73
97
|
module.exports = { extract };
|
package/src/map/route-table.js
CHANGED
|
@@ -20,7 +20,14 @@ function shouldSkipFile(rel) {
|
|
|
20
20
|
return /(^|\/)(gen-context|gen-project-map)\.js$/.test(normalized);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Structured route rows across the supported frameworks — the data behind
|
|
25
|
+
* `analyze`, exposed for retrieval surface-enrichment (#488).
|
|
26
|
+
* @param {string[]} files absolute paths
|
|
27
|
+
* @param {string} cwd
|
|
28
|
+
* @returns {{ method:string, path:string, file:string }[]} file is cwd-relative
|
|
29
|
+
*/
|
|
30
|
+
function collectRoutes(files, cwd) {
|
|
24
31
|
const routes = [];
|
|
25
32
|
|
|
26
33
|
for (const filePath of files) {
|
|
@@ -112,6 +119,11 @@ function analyze(files, cwd) {
|
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
return routes;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function analyze(files, cwd) {
|
|
126
|
+
const routes = collectRoutes(files, cwd);
|
|
115
127
|
if (routes.length === 0) return '';
|
|
116
128
|
|
|
117
129
|
const lines = [
|
|
@@ -124,4 +136,4 @@ function analyze(files, cwd) {
|
|
|
124
136
|
return lines.join('\n');
|
|
125
137
|
}
|
|
126
138
|
|
|
127
|
-
module.exports = { analyze };
|
|
139
|
+
module.exports = { analyze, collectRoutes };
|
package/src/mcp/handlers.js
CHANGED
|
@@ -421,7 +421,7 @@ function queryContext(args, cwd) {
|
|
|
421
421
|
// Build dependency graph for neighbor boost — non-fatal if it fails
|
|
422
422
|
let graph = null;
|
|
423
423
|
try { graph = buildFromCwd(cwd); } catch (_) {}
|
|
424
|
-
// Opt-in call-graph neighbor boost
|
|
424
|
+
// Opt-in call-graph neighbor boost + surface enrichment — non-fatal
|
|
425
425
|
let callGraph = null;
|
|
426
426
|
try {
|
|
427
427
|
const { loadConfig } = require('../config/loader');
|
|
@@ -429,6 +429,9 @@ function queryContext(args, cwd) {
|
|
|
429
429
|
if (retrieval && retrieval.callGraphBoost) {
|
|
430
430
|
callGraph = require('../graph/call-graph').buildCallFileGraph(cwd);
|
|
431
431
|
}
|
|
432
|
+
if (retrieval && retrieval.surfaceEnrichment) {
|
|
433
|
+
require('../retrieval/enrich-from-maps').enrichWithSurfaces(index, cwd);
|
|
434
|
+
}
|
|
432
435
|
} catch (_) {}
|
|
433
436
|
const results = rank(args.query, index, { topK, cwd, graph, callGraph });
|
|
434
437
|
return formatRankTable(results, args.query);
|
package/src/mcp/server.js
CHANGED
package/src/retrieval/bm25.js
CHANGED
|
@@ -159,7 +159,10 @@ function bm25rank(query, candidates) {
|
|
|
159
159
|
|
|
160
160
|
const docs = candidates.map((c) => {
|
|
161
161
|
const pathToks = tokenize(c.file || '');
|
|
162
|
-
|
|
162
|
+
// Ranking is anchor-invariant: `:start-end` line anchors are metadata,
|
|
163
|
+
// not content — strip them before tokenizing so adding anchors to an
|
|
164
|
+
// extractor never shifts BM25 length normalization or token counts.
|
|
165
|
+
const toks = tokenize((c.sigs || []).map((s) => String(s).replace(/\s*:\d+(?:-\d+)?\s*$/, '')).join(' '));
|
|
163
166
|
for (let i = 0; i < PATH_BOOST; i++) toks.push(...pathToks);
|
|
164
167
|
const tf = new Map();
|
|
165
168
|
for (const t of toks) tf.set(t, (tf.get(t) || 0) + 1);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Retrieval surface-enrichment (#488, §7.4 Retrieval ceiling — opt-in via
|
|
5
|
+
* `retrieval.surfaceEnrichment`, measure-gated).
|
|
6
|
+
*
|
|
7
|
+
* The map analyzers extract surfaces (routes) that never reach the rankable
|
|
8
|
+
* signature index — a query like "payment webhook route" cannot match a
|
|
9
|
+
* controller whose signatures never mention the route path. This module
|
|
10
|
+
* appends deterministic pseudo-signatures (`route GET /api/users`) to the
|
|
11
|
+
* defining file's signature list so the ranker's tokenizer can see them.
|
|
12
|
+
*
|
|
13
|
+
* Deterministic: rows are deduped and sorted; entries are copy-on-write so
|
|
14
|
+
* arrays shared with the signature cache are never mutated.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Enrich a signature index with route pseudo-signatures.
|
|
21
|
+
* @param {Map<string,string[]>} index cwd-relative file → sigs (mutated: enriched entries are replaced with fresh arrays)
|
|
22
|
+
* @param {string} cwd
|
|
23
|
+
* @returns {number} pseudo-signatures added
|
|
24
|
+
*/
|
|
25
|
+
function enrichWithSurfaces(index, cwd) {
|
|
26
|
+
if (!(index instanceof Map) || index.size === 0) return 0;
|
|
27
|
+
|
|
28
|
+
let collectRoutes;
|
|
29
|
+
try { ({ collectRoutes } = require('../map/route-table')); } catch (_) { return 0; }
|
|
30
|
+
|
|
31
|
+
const rels = [...index.keys()];
|
|
32
|
+
const files = rels.map((rel) => path.join(cwd, rel));
|
|
33
|
+
let routes = [];
|
|
34
|
+
try { routes = collectRoutes(files, cwd) || []; } catch (_) { return 0; }
|
|
35
|
+
|
|
36
|
+
const byFile = new Map();
|
|
37
|
+
for (const r of routes) {
|
|
38
|
+
const rel = String(r.file).replace(/\\/g, '/');
|
|
39
|
+
if (!byFile.has(rel)) byFile.set(rel, new Set());
|
|
40
|
+
byFile.get(rel).add(`route ${r.method} ${r.path}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let added = 0;
|
|
44
|
+
for (const [rel, extras] of [...byFile.entries()].sort()) {
|
|
45
|
+
const sigs = index.get(rel);
|
|
46
|
+
if (!sigs) continue;
|
|
47
|
+
const fresh = [...extras].sort().filter((x) => !sigs.includes(x));
|
|
48
|
+
if (!fresh.length) continue;
|
|
49
|
+
index.set(rel, [...sigs, ...fresh]); // copy-on-write: never mutate cached arrays
|
|
50
|
+
added += fresh.length;
|
|
51
|
+
}
|
|
52
|
+
return added;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { enrichWithSurfaces };
|