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/llms-full.txt
CHANGED
|
@@ -11,19 +11,19 @@ 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.18.0 | Benchmark: sigmap-v8.18-main (2026-07-12)
|
|
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.18-main, 2026-07-12)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
|
24
|
-
| Retrieval hit@5 | 13.6% (random) |
|
|
25
|
-
| Token reduction | — |
|
|
26
|
-
| Task success proxy | 10% |
|
|
24
|
+
| Retrieval hit@5 | 13.6% (random) | 86.7% (6.4× lift) |
|
|
25
|
+
| Token reduction | — | 96.9% average |
|
|
26
|
+
| Task success proxy | 10% | 68.9% |
|
|
27
27
|
| Prompts per task | 2.84 | 1.44 (49.2% fewer) |
|
|
28
28
|
| Supported languages | — | 33 |
|
|
29
29
|
| MCP tools | — | 20 |
|
|
@@ -343,7 +343,7 @@ testCoverage = false
|
|
|
343
343
|
testDirs = ["tests","test","__tests__","spec"]
|
|
344
344
|
sigCache = false
|
|
345
345
|
impactRadius = false
|
|
346
|
-
retrieval = {"topK":10,"recencyBoost":1.5,"callGraphBoost":false}
|
|
346
|
+
retrieval = {"topK":10,"recencyBoost":1.5,"callGraphBoost":false,"surfaceEnrichment":false}
|
|
347
347
|
impact = {"depth":3,"includeSigs":true}
|
|
348
348
|
```
|
|
349
349
|
|
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.18.0 | Benchmark: sigmap-v8.18-main (2026-07-12)
|
|
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,11 +23,11 @@ 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.18-main, 2026-07-12)
|
|
27
27
|
|
|
28
|
-
- hit@5 retrieval:
|
|
29
|
-
- Token reduction:
|
|
30
|
-
- Task success:
|
|
28
|
+
- hit@5 retrieval: 86.7% vs 13.6% random baseline (6.4× lift)
|
|
29
|
+
- Token reduction: 96.9% average across benchmark repos
|
|
30
|
+
- Task success: 68.9% vs 10% without SigMap
|
|
31
31
|
- Prompts per task: 1.44 vs 2.84 baseline (49.2% fewer)
|
|
32
32
|
- Languages: 33 supported · MCP tools: 20
|
|
33
33
|
- Dependencies: zero npm runtime dependencies · fully offline
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.18.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": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"benchmark:test-discovery": "node scripts/run-test-discovery-benchmark.mjs --save",
|
|
32
32
|
"benchmark:terse": "node scripts/run-terse-benchmark.mjs --save",
|
|
33
33
|
"benchmark:callgraph-boost": "node scripts/run-callgraph-boost-benchmark.mjs --save",
|
|
34
|
+
"benchmark:surface-enrichment": "node scripts/run-surface-enrichment-benchmark.mjs --save",
|
|
34
35
|
"validate:squeeze": "node scripts/run-squeeze-benchmark.mjs --gate",
|
|
35
36
|
"health": "node gen-context.js --health",
|
|
36
37
|
"map": "node gen-project-map.js",
|
package/src/config/defaults.js
CHANGED
|
@@ -149,6 +149,8 @@ const DEFAULTS = {
|
|
|
149
149
|
recencyBoost: 1.5,
|
|
150
150
|
// Boost files call-graph-connected to query matches (opt-in, measure-gated)
|
|
151
151
|
callGraphBoost: false,
|
|
152
|
+
// Append route pseudo-signatures to the rankable index (opt-in, measure-gated)
|
|
153
|
+
surfaceEnrichment: false,
|
|
152
154
|
},
|
|
153
155
|
|
|
154
156
|
// Impact layer settings (v2.5)
|
package/src/extractors/csharp.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 C# 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,14 +15,18 @@ 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, ' '));
|
|
15
19
|
|
|
16
20
|
// Classes and interfaces
|
|
17
21
|
const typeRe = /^\s*(?:public\s+|internal\s+|protected\s+)?(?:abstract\s+|sealed\s+|static\s+)?(class|interface|enum|record|struct)\s+(\w+)(?:<[^{]*>)?(?:\s*:\s*[\w<>, .]+)?\s*\{/gm;
|
|
18
22
|
for (const m of stripped.matchAll(typeRe)) {
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
23
|
+
const declIdx = m.index + (m[0].length - m[0].trimStart().length);
|
|
24
|
+
const bodyStart = m.index + m[0].length;
|
|
25
|
+
const block = extractBlock(stripped, bodyStart);
|
|
26
|
+
sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, declIdx), lineAt(stripped, bodyStart + block.length)));
|
|
27
|
+
for (const meth of extractMembers(block)) {
|
|
28
|
+
sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
return sigs.slice(0, 25);
|
|
@@ -41,7 +49,11 @@ function extractMembers(block) {
|
|
|
41
49
|
for (const m of block.matchAll(methodRe)) {
|
|
42
50
|
const ret = normalizeType(m[1]);
|
|
43
51
|
const retStr = ret ? ` → ${ret}` : '';
|
|
44
|
-
members.push(
|
|
52
|
+
members.push({
|
|
53
|
+
text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
|
|
54
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
55
|
+
endIdx: m.index + m[0].length,
|
|
56
|
+
});
|
|
45
57
|
}
|
|
46
58
|
return members.slice(0, 8);
|
|
47
59
|
}
|
package/src/extractors/dart.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 Dart 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 trivia (`async`, `=>` stops) to a body `{`.
|
|
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 and abstract classes
|
|
17
33
|
for (const m of stripped.matchAll(/^(?:abstract\s+)?class\s+(\w+)(?:<[^{]*>)?(?:\s+extends\s+[\w<>, ]+)?(?:\s+(?:implements|with|on)\s+[\w<>, ]+)?\s*\{/gm)) {
|
|
18
34
|
const abs = m[0].trimStart().startsWith('abstract') ? 'abstract ' : '';
|
|
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(`${abs}class ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
38
|
+
for (const meth of extractMembers(block)) {
|
|
39
|
+
sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
|
|
40
|
+
}
|
|
22
41
|
}
|
|
23
42
|
|
|
24
43
|
// Top-level functions — capture return type (prefix before name) and show as suffix
|
|
25
44
|
for (const m of stripped.matchAll(/^((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
26
45
|
if (m[2].startsWith('_')) continue;
|
|
27
|
-
const retStr = (m[1] && m[1] !== 'void') ? `
|
|
28
|
-
|
|
46
|
+
const retStr = (m[1] && m[1] !== 'void') ? ` → ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
|
|
47
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
48
|
+
sigs.push(withAnchor(`${m[2]}(${normalizeParams(m[3])})${retStr}`, s, e));
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
return sigs.slice(0, 25);
|
|
@@ -46,8 +66,12 @@ function extractMembers(block) {
|
|
|
46
66
|
const members = [];
|
|
47
67
|
for (const m of block.matchAll(/^\s+(?:@override\s+)?(?:@\w+\s+)*((?:Future<[\w<>?,\s]*>|[\w<>?]+))\s+(\w+)\s*\(([^)]*)\)/gm)) {
|
|
48
68
|
if (m[2].startsWith('_')) continue;
|
|
49
|
-
const retStr = (m[1] && m[1] !== 'void') ? `
|
|
50
|
-
members.push(
|
|
69
|
+
const retStr = (m[1] && m[1] !== 'void') ? ` → ${m[1].replace(/\s+/g, '').slice(0, 25)}` : '';
|
|
70
|
+
members.push({
|
|
71
|
+
text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
|
|
72
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
73
|
+
endIdx: m.index + m[0].length,
|
|
74
|
+
});
|
|
51
75
|
}
|
|
52
76
|
return members.slice(0, 8);
|
|
53
77
|
}
|
package/src/extractors/go.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 Go 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,26 +15,34 @@ 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
|
+
// Index of the closing brace for a block opened just before startIndex.
|
|
21
|
+
const blockEndIdx = (startIndex) => startIndex + extractBlock(stripped, startIndex).length;
|
|
15
22
|
|
|
16
23
|
// Structs
|
|
17
24
|
for (const m of stripped.matchAll(/^type\s+(\w+)\s+struct\s*\{/gm)) {
|
|
18
|
-
|
|
25
|
+
const end = blockEndIdx(m.index + m[0].length);
|
|
26
|
+
sigs.push(withAnchor(`type ${m[1]} struct`, lineAt(stripped, m.index), lineAt(stripped, end)));
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
// Interfaces
|
|
22
30
|
for (const m of stripped.matchAll(/^type\s+(\w+)\s+interface\s*\{/gm)) {
|
|
23
|
-
|
|
24
|
-
const block = extractBlock(stripped,
|
|
25
|
-
|
|
31
|
+
const bodyStart = m.index + m[0].length;
|
|
32
|
+
const block = extractBlock(stripped, bodyStart);
|
|
33
|
+
sigs.push(withAnchor(`type ${m[1]} interface`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
34
|
+
for (const meth of extractInterfaceMethods(block)) {
|
|
35
|
+
sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
|
|
36
|
+
}
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
// Functions and methods — capture return type between ) and {
|
|
29
40
|
for (const m of stripped.matchAll(/^func\s+(?:\((\w+)\s+[\w*]+\)\s+)?(\w+)\s*\(([^)]*)\)([^{]*)\{/gm)) {
|
|
30
41
|
const receiver = m[1] ? `(${m[1]}) ` : '';
|
|
31
42
|
const retType = m[4] ? m[4].trim().replace(/\s+/g, ' ') : '';
|
|
32
|
-
const retStr = retType ? `
|
|
33
|
-
|
|
43
|
+
const retStr = retType ? ` → ${retType.slice(0, 30)}` : '';
|
|
44
|
+
const end = blockEndIdx(m.index + m[0].length);
|
|
45
|
+
sigs.push(withAnchor(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`, lineAt(stripped, m.index), lineAt(stripped, end)));
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
return sigs.slice(0, 25);
|
|
@@ -51,8 +63,12 @@ function extractInterfaceMethods(block) {
|
|
|
51
63
|
const methods = [];
|
|
52
64
|
for (const m of block.matchAll(/^\s+(\w+)\s*\(([^)]*)\)([^\n]*)/gm)) {
|
|
53
65
|
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
54
|
-
const retStr = retType ? `
|
|
55
|
-
methods.push(
|
|
66
|
+
const retStr = retType ? ` → ${retType.slice(0, 30)}` : '';
|
|
67
|
+
methods.push({
|
|
68
|
+
text: `${m[1]}(${normalizeParams(m[2])})${retStr}`,
|
|
69
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
70
|
+
endIdx: m.index + m[0].length,
|
|
71
|
+
});
|
|
56
72
|
}
|
|
57
73
|
return methods.slice(0, 8);
|
|
58
74
|
}
|
package/src/extractors/java.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 Java 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,14 +15,17 @@ 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, ' '));
|
|
15
19
|
|
|
16
20
|
// Classes and interfaces
|
|
17
21
|
const typeRegex = /^(?:public\s+|protected\s+)?(?:abstract\s+|final\s+)?(class|interface|enum)\s+(\w+)(?:\s+extends\s+[\w<>, .]+)?(?:\s+implements\s+[\w<>, .]+)?\s*\{/gm;
|
|
18
22
|
for (const m of stripped.matchAll(typeRegex)) {
|
|
19
|
-
|
|
20
|
-
const block = extractBlock(stripped,
|
|
21
|
-
|
|
23
|
+
const bodyStart = m.index + m[0].length;
|
|
24
|
+
const block = extractBlock(stripped, bodyStart);
|
|
25
|
+
sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
26
|
+
for (const meth of extractMembers(block)) {
|
|
27
|
+
sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
|
|
28
|
+
}
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
return sigs.slice(0, 25);
|
|
@@ -42,7 +49,11 @@ function extractMembers(block) {
|
|
|
42
49
|
for (const m of block.matchAll(methodRe)) {
|
|
43
50
|
const ret = normalizeType(m[1]);
|
|
44
51
|
const retStr = ret ? ` → ${ret}` : '';
|
|
45
|
-
members.push(
|
|
52
|
+
members.push({
|
|
53
|
+
text: `${m[2]}(${normalizeParams(m[3])})${retStr}`,
|
|
54
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
55
|
+
endIdx: m.index + m[0].length,
|
|
56
|
+
});
|
|
46
57
|
}
|
|
47
58
|
return members.slice(0, 8);
|
|
48
59
|
}
|
package/src/extractors/kotlin.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 Kotlin 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, objects, interfaces
|
|
17
33
|
for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:data\s+|sealed\s+|abstract\s+|open\s+)?(class|object|interface)\s+(\w+)(?:[^{]*)\{/gm)) {
|
|
18
|
-
|
|
19
|
-
const block = extractBlock(stripped,
|
|
20
|
-
|
|
34
|
+
const bodyStart = m.index + m[0].length;
|
|
35
|
+
const block = extractBlock(stripped, bodyStart);
|
|
36
|
+
sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
37
|
+
for (const meth of extractMembers(block)) {
|
|
38
|
+
sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
|
|
39
|
+
}
|
|
21
40
|
}
|
|
22
41
|
|
|
23
42
|
// Top-level functions — capture `: RetType` after params
|
|
24
43
|
for (const m of stripped.matchAll(/^(?:public\s+|internal\s+)?(?:suspend\s+)?fun\s+(\w+)\s*(?:<[^(]*>)?\s*\(([^)]*)\)(?:\s*:\s*([^\n{=]+))?/gm)) {
|
|
25
44
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
26
45
|
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
27
|
-
const retStr = retType ? `
|
|
28
|
-
|
|
46
|
+
const retStr = retType ? ` → ${retType.slice(0, 25)}` : '';
|
|
47
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
48
|
+
sigs.push(withAnchor(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
return sigs.slice(0, 25);
|
|
@@ -48,8 +68,12 @@ function extractMembers(block) {
|
|
|
48
68
|
if (m[1].startsWith('_')) continue;
|
|
49
69
|
const suspend = m[0].includes('suspend') ? 'suspend ' : '';
|
|
50
70
|
const retType = m[3] ? m[3].trim().replace(/\s+/g, ' ') : '';
|
|
51
|
-
const retStr = retType ? `
|
|
52
|
-
members.push(
|
|
71
|
+
const retStr = retType ? ` → ${retType.slice(0, 25)}` : '';
|
|
72
|
+
members.push({
|
|
73
|
+
text: `${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`,
|
|
74
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
75
|
+
endIdx: m.index + m[0].length,
|
|
76
|
+
});
|
|
53
77
|
}
|
|
54
78
|
return members.slice(0, 8);
|
|
55
79
|
}
|
package/src/extractors/php.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 PHP source code.
|
|
7
|
+
* Signatures carry `:start-end` line anchors (Surgical Context); the comment
|
|
8
|
+
* strips below are 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
|
*/
|
|
@@ -12,23 +16,40 @@ function extract(src) {
|
|
|
12
16
|
const stripped = src
|
|
13
17
|
.replace(/\/\/.*$/gm, '')
|
|
14
18
|
.replace(/#.*$/gm, '')
|
|
15
|
-
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
19
|
+
.replace(/\/\*[\s\S]*?\*\//g, (s) => s.replace(/[^\n]/g, ' '));
|
|
20
|
+
|
|
21
|
+
// Anchor range: scan past same-line trivia to a body `{` (range) else single line.
|
|
22
|
+
const rangeFor = (declIdx, afterIdx) => {
|
|
23
|
+
let k = afterIdx;
|
|
24
|
+
while (k < stripped.length && /[ \tA-Za-z0-9_:?\\]/.test(stripped[k])) k++;
|
|
25
|
+
if (stripped[k] === '{' || (stripped[k] === '\n' && stripped[k + 1] === '{')) {
|
|
26
|
+
const open = stripped[k] === '{' ? k : k + 1;
|
|
27
|
+
const end = open + 1 + extractBlock(stripped, open + 1).length;
|
|
28
|
+
return [lineAt(stripped, declIdx), lineAt(stripped, end)];
|
|
29
|
+
}
|
|
30
|
+
const line = lineAt(stripped, declIdx);
|
|
31
|
+
return [line, line];
|
|
32
|
+
};
|
|
16
33
|
|
|
17
34
|
// Classes and interfaces
|
|
18
35
|
const typeRe = /^(?:abstract\s+)?(?:class|interface|trait)\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w, ]+)?\s*\{/gm;
|
|
19
36
|
for (const m of stripped.matchAll(typeRe)) {
|
|
20
37
|
const kind = m[0].trimStart().startsWith('interface') ? 'interface' :
|
|
21
38
|
m[0].trimStart().startsWith('trait') ? 'trait' : 'class';
|
|
22
|
-
|
|
23
|
-
const block = extractBlock(stripped,
|
|
24
|
-
|
|
39
|
+
const bodyStart = m.index + m[0].length;
|
|
40
|
+
const block = extractBlock(stripped, bodyStart);
|
|
41
|
+
sigs.push(withAnchor(`${kind} ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
42
|
+
for (const meth of extractMembers(block)) {
|
|
43
|
+
sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
|
|
44
|
+
}
|
|
25
45
|
}
|
|
26
46
|
|
|
27
47
|
// Top-level functions
|
|
28
48
|
for (const m of stripped.matchAll(/^function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/gm)) {
|
|
29
49
|
const ret = normalizeType(m[3]);
|
|
30
50
|
const retStr = ret ? ` → ${ret}` : '';
|
|
31
|
-
|
|
51
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
52
|
+
sigs.push(withAnchor(`function ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
|
|
32
53
|
}
|
|
33
54
|
|
|
34
55
|
return sigs.slice(0, 25);
|
|
@@ -53,7 +74,11 @@ function extractMembers(block) {
|
|
|
53
74
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
54
75
|
const ret = normalizeType(m[3]);
|
|
55
76
|
const retStr = ret ? ` → ${ret}` : '';
|
|
56
|
-
members.push(
|
|
77
|
+
members.push({
|
|
78
|
+
text: `${isStatic}function ${m[1]}(${normalizeParams(m[2])})${retStr}`,
|
|
79
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
80
|
+
endIdx: m.index + m[0].length,
|
|
81
|
+
});
|
|
57
82
|
}
|
|
58
83
|
return members.slice(0, 8);
|
|
59
84
|
}
|
package/src/extractors/rust.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 Rust 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,35 +15,55 @@ 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 for a declaration at declIdx whose header ends at afterIdx:
|
|
21
|
+
// if a `{` body follows, range to its closing brace; else single-line.
|
|
22
|
+
const rangeFor = (declIdx, afterIdx) => {
|
|
23
|
+
let k = afterIdx;
|
|
24
|
+
while (k < stripped.length && /[ \t]/.test(stripped[k])) k++;
|
|
25
|
+
if (stripped[k] === '{') {
|
|
26
|
+
const end = k + 1 + extractBlock(stripped, k + 1).length;
|
|
27
|
+
return [lineAt(stripped, declIdx), lineAt(stripped, end)];
|
|
28
|
+
}
|
|
29
|
+
const line = lineAt(stripped, declIdx);
|
|
30
|
+
return [line, line];
|
|
31
|
+
};
|
|
15
32
|
|
|
16
33
|
// Structs
|
|
17
34
|
for (const m of stripped.matchAll(/^pub\s+struct\s+(\w+)(?:<[^{]*>)?/gm)) {
|
|
18
|
-
|
|
35
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
36
|
+
sigs.push(withAnchor(`pub struct ${m[1]}`, s, e));
|
|
19
37
|
}
|
|
20
38
|
|
|
21
39
|
// Enums
|
|
22
40
|
for (const m of stripped.matchAll(/^pub\s+enum\s+(\w+)(?:<[^{]*>)?/gm)) {
|
|
23
|
-
|
|
41
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
42
|
+
sigs.push(withAnchor(`pub enum ${m[1]}`, s, e));
|
|
24
43
|
}
|
|
25
44
|
|
|
26
45
|
// Traits
|
|
27
46
|
for (const m of stripped.matchAll(/^pub\s+trait\s+(\w+)(?:<[^{]*>)?/gm)) {
|
|
28
|
-
|
|
47
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
48
|
+
sigs.push(withAnchor(`pub trait ${m[1]}`, s, e));
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
// impl blocks
|
|
32
52
|
for (const m of stripped.matchAll(/^impl(?:<[^>]*>)?\s+(?:[\w:]+\s+for\s+)?(\w+)(?:<[^{]*>)?\s*\{/gm)) {
|
|
33
|
-
|
|
34
|
-
const block = extractBlock(stripped,
|
|
35
|
-
|
|
53
|
+
const bodyStart = m.index + m[0].length;
|
|
54
|
+
const block = extractBlock(stripped, bodyStart);
|
|
55
|
+
sigs.push(withAnchor(`impl ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
56
|
+
for (const fn of extractMethods(block)) {
|
|
57
|
+
sigs.push(withAnchor(` ${fn.text}`, lineAt(stripped, bodyStart + fn.declIdx), lineAt(stripped, bodyStart + fn.endIdx)));
|
|
58
|
+
}
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
// Top-level pub fns — capture everything after ) up to { or ; for return type
|
|
39
62
|
for (const m of stripped.matchAll(/^pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
|
|
40
63
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
41
64
|
const retStr = extractReturnType(m[3]);
|
|
42
|
-
|
|
65
|
+
const [s, e] = rangeFor(m.index, m.index + m[0].length);
|
|
66
|
+
sigs.push(withAnchor(`pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
|
|
43
67
|
}
|
|
44
68
|
|
|
45
69
|
return sigs.slice(0, 25);
|
|
@@ -61,7 +85,11 @@ function extractMethods(block) {
|
|
|
61
85
|
for (const m of block.matchAll(/^\s+pub(?:\s+async)?\s+fn\s+(\w+)(?:<[^(]*>)?\s*\(([^)]*)\)([^{;]*)/gm)) {
|
|
62
86
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
63
87
|
const retStr = extractReturnType(m[3]);
|
|
64
|
-
methods.push(
|
|
88
|
+
methods.push({
|
|
89
|
+
text: `pub ${asyncKw}fn ${m[1]}(${normalizeParams(m[2])})${retStr}`,
|
|
90
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
91
|
+
endIdx: m.index + m[0].length,
|
|
92
|
+
});
|
|
65
93
|
}
|
|
66
94
|
return methods.slice(0, 8);
|
|
67
95
|
}
|
|
@@ -76,7 +104,7 @@ function extractReturnType(afterParen) {
|
|
|
76
104
|
const m = afterParen.match(/->\s*([^{;]+)/);
|
|
77
105
|
if (!m) return '';
|
|
78
106
|
const rt = m[1].trim().replace(/\s+/g, ' ');
|
|
79
|
-
return `
|
|
107
|
+
return ` → ${rt.length > 30 ? rt.slice(0, 27) + '...' : rt}`;
|
|
80
108
|
}
|
|
81
109
|
|
|
82
110
|
module.exports = { extract };
|
package/src/extractors/scala.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 Scala 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,7 +15,7 @@ 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, ' '));
|
|
15
19
|
|
|
16
20
|
// Classes, traits, objects
|
|
17
21
|
const typeRe = /^(?:case\s+)?(?:class|trait|object)\s+(\w+)(?:\[[\w, ]+\])?(?:[^{]*)\{/gm;
|
|
@@ -19,9 +23,12 @@ function extract(src) {
|
|
|
19
23
|
const kind = m[0].trimStart().startsWith('case class') ? 'case class' :
|
|
20
24
|
m[0].trimStart().startsWith('trait') ? 'trait' :
|
|
21
25
|
m[0].trimStart().startsWith('object') ? 'object' : 'class';
|
|
22
|
-
|
|
23
|
-
const block = extractBlock(stripped,
|
|
24
|
-
|
|
26
|
+
const bodyStart = m.index + m[0].length;
|
|
27
|
+
const block = extractBlock(stripped, bodyStart);
|
|
28
|
+
sigs.push(withAnchor(`${kind} ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
|
|
29
|
+
for (const fn of extractMembers(block)) {
|
|
30
|
+
sigs.push(withAnchor(` ${fn.text}`, lineAt(stripped, bodyStart + fn.declIdx), lineAt(stripped, bodyStart + fn.endIdx)));
|
|
31
|
+
}
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
// Top-level defs
|
|
@@ -30,7 +37,8 @@ function extract(src) {
|
|
|
30
37
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
31
38
|
const ret = normalizeType(m[3]);
|
|
32
39
|
const retStr = ret ? ` → ${ret}` : '';
|
|
33
|
-
|
|
40
|
+
const line = lineAt(stripped, m.index);
|
|
41
|
+
sigs.push(withAnchor(`def ${m[1]}${params}${retStr}`, line, line));
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
return sigs.slice(0, 25);
|
|
@@ -54,7 +62,11 @@ function extractMembers(block) {
|
|
|
54
62
|
const params = m[2] ? `(${normalizeParams(m[2])})` : '';
|
|
55
63
|
const ret = normalizeType(m[3]);
|
|
56
64
|
const retStr = ret ? ` → ${ret}` : '';
|
|
57
|
-
members.push(
|
|
65
|
+
members.push({
|
|
66
|
+
text: `def ${m[1]}${params}${retStr}`,
|
|
67
|
+
declIdx: m.index + (m[0].length - m[0].trimStart().length),
|
|
68
|
+
endIdx: m.index + m[0].length,
|
|
69
|
+
});
|
|
58
70
|
}
|
|
59
71
|
return members.slice(0, 8);
|
|
60
72
|
}
|