sigmap 8.32.0 → 8.33.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 +317 -293
- package/CHANGELOG.md +27 -0
- package/README.md +12 -12
- package/gen-context.js +180 -170
- package/llms-full.txt +8 -8
- package/llms.txt +6 -6
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/config/loader.js +53 -3
- package/src/eval/analyzer.js +5 -40
- package/src/extractors/dispatch.js +16 -2
- package/src/extractors/markdown.js +6 -1
- package/src/extractors/properties.js +6 -1
- package/src/extractors/r.js +4 -8
- package/src/extractors/toml.js +6 -1
- package/src/extractors/typescript_react.js +6 -1
- package/src/map/route-table.js +46 -9
- package/src/mcp/server.js +1 -1
- package/src/extractors/vue.js +0 -86
package/llms-full.txt
CHANGED
|
@@ -11,21 +11,21 @@ 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.33.0 | Benchmark: sigmap-v8.33-main (2026-09-13)
|
|
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.33-main, 2026-09-13)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
|
24
|
-
| Retrieval hit@5 | 44.0% (single-shot grep) |
|
|
24
|
+
| Retrieval hit@5 | 44.0% (single-shot grep) | 78.9% (1.73× lift) |
|
|
25
25
|
| Token reduction | — | 96.8% average |
|
|
26
|
-
| Task-success proxy (modeled) | — |
|
|
27
|
-
| Prompts per task | 2.84 | 1.
|
|
28
|
-
| Supported languages | — |
|
|
26
|
+
| Task-success proxy (modeled) | — | 62.9% |
|
|
27
|
+
| Prompts per task | 2.84 | 1.58 (44.4% fewer) |
|
|
28
|
+
| Supported languages | — | 32 |
|
|
29
29
|
| MCP tools | — | 21 |
|
|
30
30
|
| npm runtime dependencies | — | 0 |
|
|
31
31
|
|
|
@@ -370,9 +370,9 @@ impact = {"depth":3,"includeSigs":true}
|
|
|
370
370
|
|
|
371
371
|
---
|
|
372
372
|
|
|
373
|
-
## Supported languages (
|
|
373
|
+
## Supported languages (32 extractors)
|
|
374
374
|
|
|
375
|
-
cpp, csharp, css, dart, dockerfile, gdscript, go, graphql, html, java, javascript, kotlin, markdown, php, properties, protobuf, python, r, ruby, rust, scala, shell, sql, svelte, swift, terraform, toml, typescript, typescript_react,
|
|
375
|
+
cpp, csharp, css, dart, dockerfile, gdscript, go, graphql, html, java, javascript, kotlin, markdown, php, properties, protobuf, python, r, ruby, rust, scala, shell, sql, svelte, swift, terraform, toml, typescript, typescript_react, vue_sfc, xml, yaml
|
|
376
376
|
|
|
377
377
|
---
|
|
378
378
|
|
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.33.0 | Benchmark: sigmap-v8.33-main (2026-09-13)
|
|
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.33-main, 2026-09-13)
|
|
27
27
|
|
|
28
|
-
- hit@5 retrieval:
|
|
28
|
+
- hit@5 retrieval: 78.9% vs 44.0% single-shot grep baseline (1.73× lift)
|
|
29
29
|
- Token reduction: 96.8% average across benchmark repos
|
|
30
|
-
- Task-success proxy:
|
|
31
|
-
- Prompts per task: 1.
|
|
32
|
-
- Languages:
|
|
30
|
+
- Task-success proxy: 62.9% (modeled from retrieval tiers, not measured LLM sessions)
|
|
31
|
+
- Prompts per task: 1.58 vs 2.84 baseline (44.4% fewer, modeled)
|
|
32
|
+
- Languages: 32 supported · MCP tools: 21
|
|
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.33.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": {
|
package/src/config/loader.js
CHANGED
|
@@ -227,6 +227,55 @@ function _legacyDetectAutoSrcDirs(cwd, excludeList) {
|
|
|
227
227
|
});
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Directory depth needed to reach source under a JVM package layout.
|
|
232
|
+
*
|
|
233
|
+
* `maxDepth: 6` is right for the JS/Python-shaped trees it was tuned on, but a
|
|
234
|
+
* JVM project puts one directory per package segment — real code in
|
|
235
|
+
* `src/main/java/com/company/project/module/Class.java` sits 8-10 levels down.
|
|
236
|
+
* At depth 6 only the top-level package was indexed: on spring-petclinic, 6 of
|
|
237
|
+
* 47 Java files (#590).
|
|
238
|
+
*
|
|
239
|
+
* #561 already raised the dependency-graph walk to 12 for exactly this reason,
|
|
240
|
+
* so extraction was the shallower half of an inconsistent pair.
|
|
241
|
+
*
|
|
242
|
+
* Deepening globally is not free — it adds candidates to every repo and cost
|
|
243
|
+
* 2.2pp on the (filename-leaky) 105-task matrix corpus while the unbiased
|
|
244
|
+
* `mined` corpus stayed flat. So the depth is raised only where the layout
|
|
245
|
+
* demands it, leaving non-JVM repos byte-identical.
|
|
246
|
+
*
|
|
247
|
+
* @param {string} cwd
|
|
248
|
+
* @returns {boolean} true when the repo looks like a Maven/Gradle/sbt project
|
|
249
|
+
*/
|
|
250
|
+
function _isJvmLayout(cwd) {
|
|
251
|
+
const MARKERS = ['pom.xml', 'build.gradle', 'build.gradle.kts', 'build.sbt', 'settings.gradle', 'settings.gradle.kts'];
|
|
252
|
+
for (const m of MARKERS) {
|
|
253
|
+
try { if (fs.existsSync(path.join(cwd, m))) return true; } catch { /* unreadable cwd */ }
|
|
254
|
+
}
|
|
255
|
+
for (const d of ['src/main/java', 'src/main/kotlin', 'src/main/scala']) {
|
|
256
|
+
try { if (fs.existsSync(path.join(cwd, d))) return true; } catch { /* ignore */ }
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Walk depth for a JVM package layout — matches the graph walk from #561. */
|
|
262
|
+
const JVM_MAX_DEPTH = 12;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Raise `maxDepth` to the JVM depth when the layout needs it (#590).
|
|
266
|
+
* An explicit user value always wins, including a deliberately shallow one.
|
|
267
|
+
* @param {object} cfg resolved config (mutated and returned)
|
|
268
|
+
* @param {string} cwd
|
|
269
|
+
* @param {boolean} userSetDepth
|
|
270
|
+
* @returns {object} cfg
|
|
271
|
+
*/
|
|
272
|
+
function _applyJvmDepth(cfg, cwd, userSetDepth) {
|
|
273
|
+
if (!userSetDepth && cfg.maxDepth < JVM_MAX_DEPTH && _isJvmLayout(cwd)) {
|
|
274
|
+
cfg.maxDepth = JVM_MAX_DEPTH;
|
|
275
|
+
}
|
|
276
|
+
return cfg;
|
|
277
|
+
}
|
|
278
|
+
|
|
230
279
|
/**
|
|
231
280
|
* Load and merge configuration for a given working directory.
|
|
232
281
|
*
|
|
@@ -239,7 +288,7 @@ function loadConfig(cwd) {
|
|
|
239
288
|
const cfg = deepClone(DEFAULTS);
|
|
240
289
|
const detected = detectAutoSrcDirs(cwd, cfg.exclude);
|
|
241
290
|
if (detected.length > 0) cfg.srcDirs = detected;
|
|
242
|
-
return cfg;
|
|
291
|
+
return _applyJvmDepth(cfg, cwd, false);
|
|
243
292
|
}
|
|
244
293
|
|
|
245
294
|
let userConfig;
|
|
@@ -251,7 +300,7 @@ function loadConfig(cwd) {
|
|
|
251
300
|
const cfg = deepClone(DEFAULTS);
|
|
252
301
|
const detected = detectAutoSrcDirs(cwd, cfg.exclude);
|
|
253
302
|
if (detected.length > 0) cfg.srcDirs = detected;
|
|
254
|
-
return cfg;
|
|
303
|
+
return _applyJvmDepth(cfg, cwd, false);
|
|
255
304
|
}
|
|
256
305
|
|
|
257
306
|
// Warn on unknown keys (helps catch typos)
|
|
@@ -303,7 +352,8 @@ function loadConfig(cwd) {
|
|
|
303
352
|
} else if (Array.isArray(merged.adapters) && !userConfig.outputs) {
|
|
304
353
|
merged.outputs = merged.adapters.filter((a) => ['copilot','claude','cursor','windsurf'].includes(a));
|
|
305
354
|
}
|
|
306
|
-
|
|
355
|
+
|
|
356
|
+
return _applyJvmDepth(merged, cwd, userConfig.maxDepth !== undefined);
|
|
307
357
|
}
|
|
308
358
|
|
|
309
359
|
function deepClone(obj) {
|
package/src/eval/analyzer.js
CHANGED
|
@@ -14,48 +14,13 @@ const fs = require('fs');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
|
|
16
16
|
// Extension → extractor name (mirrors EXT_MAP in gen-context.js)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'.java': 'java',
|
|
22
|
-
'.kt': 'kotlin', '.kts': 'kotlin',
|
|
23
|
-
'.go': 'go',
|
|
24
|
-
'.rs': 'rust',
|
|
25
|
-
'.cs': 'csharp',
|
|
26
|
-
'.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.cc': 'cpp',
|
|
27
|
-
'.rb': 'ruby', '.rake': 'ruby',
|
|
28
|
-
'.php': 'php',
|
|
29
|
-
'.swift': 'swift',
|
|
30
|
-
'.dart': 'dart',
|
|
31
|
-
'.scala': 'scala', '.sc': 'scala',
|
|
32
|
-
'.gd': 'gdscript',
|
|
33
|
-
'.r': 'r', '.R': 'r',
|
|
34
|
-
'.vue': 'vue',
|
|
35
|
-
'.svelte': 'svelte',
|
|
36
|
-
'.html': 'html', '.htm': 'html',
|
|
37
|
-
'.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
|
|
38
|
-
'.yml': 'yaml', '.yaml': 'yaml',
|
|
39
|
-
'.sh': 'shell', '.bash': 'shell', '.zsh': 'shell', '.fish': 'shell',
|
|
40
|
-
'.toml': 'toml',
|
|
41
|
-
'.properties': 'properties',
|
|
42
|
-
'.xml': 'xml',
|
|
43
|
-
'.md': 'markdown',
|
|
44
|
-
// Phase C specialized extractors
|
|
45
|
-
'.tsx': 'typescript_react',
|
|
46
|
-
'.vue': 'vue_sfc',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
function isDockerfile(name) {
|
|
50
|
-
return name === 'Dockerfile' || name.startsWith('Dockerfile.');
|
|
51
|
-
}
|
|
17
|
+
// Extractor resolution goes through the dispatcher — the single source of
|
|
18
|
+
// truth (#591). This file previously kept its own copy, which had drifted to
|
|
19
|
+
// a dead duplicate `.vue` key.
|
|
20
|
+
const { langFor } = require('../extractors/dispatch');
|
|
52
21
|
|
|
53
22
|
function getExtractorName(filePath) {
|
|
54
|
-
|
|
55
|
-
const ext = path.extname(base).toLowerCase();
|
|
56
|
-
if (EXT_MAP[ext]) return EXT_MAP[ext];
|
|
57
|
-
if (isDockerfile(base)) return 'dockerfile';
|
|
58
|
-
return null;
|
|
23
|
+
return langFor(filePath);
|
|
59
24
|
}
|
|
60
25
|
|
|
61
26
|
/** Rough token estimate: chars / 4 */
|
|
@@ -30,7 +30,6 @@ const EXTRACTORS = {
|
|
|
30
30
|
scala: require('./scala'),
|
|
31
31
|
gdscript: require('./gdscript'),
|
|
32
32
|
r: require('./r'),
|
|
33
|
-
vue: require('./vue'),
|
|
34
33
|
vue_sfc: require('./vue_sfc'),
|
|
35
34
|
svelte: require('./svelte'),
|
|
36
35
|
html: require('./html'),
|
|
@@ -49,6 +48,21 @@ const EXTRACTORS = {
|
|
|
49
48
|
generic: require('./generic'),
|
|
50
49
|
};
|
|
51
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Extension → extractor module name. **The single source of truth for
|
|
53
|
+
* extractor resolution** (#591).
|
|
54
|
+
*
|
|
55
|
+
* Anything that decides *which extractor module to load* must go through
|
|
56
|
+
* `langFor` rather than declaring its own copy. Three copies existed and two
|
|
57
|
+
* had drifted: `src/eval/analyzer.js` carried a dead duplicate `.vue` key, and
|
|
58
|
+
* the `--diagnose-extractors` map pointed at `vue.js` after that module was
|
|
59
|
+
* deleted — which is how an unreachable extractor survived unnoticed (#582).
|
|
60
|
+
*
|
|
61
|
+
* Not every extension map in the codebase belongs here. `language-detector.js`
|
|
62
|
+
* maps `.tsx → typescript` for language *statistics*, and `dashboard.js` keeps
|
|
63
|
+
* short display *labels*. Both are correct for their purpose and deliberately
|
|
64
|
+
* differ from resolution — folding them in would miscount languages.
|
|
65
|
+
*/
|
|
52
66
|
const EXT_MAP = {
|
|
53
67
|
'.ts': 'typescript', '.tsx': 'typescript_react',
|
|
54
68
|
'.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
|
|
@@ -109,4 +123,4 @@ function extractFile(filePathOrName, src) {
|
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
125
|
|
|
112
|
-
module.exports = { extractFile, langFor };
|
|
126
|
+
module.exports = { extractFile, langFor, EXT_MAP };
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { capWithNotice } = require('../util/truncate');
|
|
4
|
+
|
|
5
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
6
|
+
const PER_FILE_LIMIT = 40;
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Lightweight markdown technical indexer.
|
|
5
10
|
* Captures headings and fenced code block language hints only.
|
|
@@ -24,7 +29,7 @@ function extract(src) {
|
|
|
24
29
|
sigs.push(`code-fence ${lang}`);
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
return Array.from(new Set(sigs))
|
|
32
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'headings');
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
module.exports = { extract };
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { capWithNotice } = require('../util/truncate');
|
|
4
|
+
|
|
5
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
6
|
+
const PER_FILE_LIMIT = 50;
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Extract signatures from .properties configuration files.
|
|
5
10
|
* Captures key names, grouped by prefixes where possible.
|
|
@@ -31,7 +36,7 @@ function extract(src) {
|
|
|
31
36
|
sigs.push(`key ${key}`);
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
return Array.from(new Set(sigs))
|
|
39
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'keys');
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
module.exports = { extract };
|
package/src/extractors/r.js
CHANGED
|
@@ -44,7 +44,7 @@ function extract(src) {
|
|
|
44
44
|
// ClassName <- R6::R6Class(...)
|
|
45
45
|
const r6Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\s*\(/g;
|
|
46
46
|
let m;
|
|
47
|
-
while ((m = r6Re.exec(stripped)) !== null
|
|
47
|
+
while ((m = r6Re.exec(stripped)) !== null) {
|
|
48
48
|
const name = m[1];
|
|
49
49
|
if (name.startsWith('.')) continue;
|
|
50
50
|
const openIdx = r6Re.lastIndex - 1;
|
|
@@ -55,7 +55,6 @@ function extract(src) {
|
|
|
55
55
|
sigs.push(`${name} <- R6Class("${classNameLit}")` + applyHint(docHints, name));
|
|
56
56
|
for (const memberSig of extractListMethods(body, 8)) {
|
|
57
57
|
sigs.push(' ' + memberSig);
|
|
58
|
-
if (sigs.length >= 30) break;
|
|
59
58
|
}
|
|
60
59
|
consumedRanges.push([m.index, closeIdx]);
|
|
61
60
|
r6Re.lastIndex = closeIdx;
|
|
@@ -65,7 +64,7 @@ function extract(src) {
|
|
|
65
64
|
// ClassName <- new_class("ClassName", properties = list(...))
|
|
66
65
|
const s7Classes = new Set();
|
|
67
66
|
const s7Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\s*\(/g;
|
|
68
|
-
while ((m = s7Re.exec(stripped)) !== null
|
|
67
|
+
while ((m = s7Re.exec(stripped)) !== null) {
|
|
69
68
|
const name = m[1];
|
|
70
69
|
if (name.startsWith('.')) continue;
|
|
71
70
|
const openIdx = s7Re.lastIndex - 1;
|
|
@@ -82,7 +81,7 @@ function extract(src) {
|
|
|
82
81
|
|
|
83
82
|
// S7 method dispatch: `method(generic, ClassName) <- function(args)`
|
|
84
83
|
const s7MethodRe = /^[ \t]*method\s*\(\s*([\w.]+)\s*,\s*([\w.]+)\s*\)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
|
|
85
|
-
while ((m = s7MethodRe.exec(stripped)) !== null
|
|
84
|
+
while ((m = s7MethodRe.exec(stripped)) !== null) {
|
|
86
85
|
if (!s7Classes.has(m[2])) continue;
|
|
87
86
|
const argsStart = s7MethodRe.lastIndex - 1;
|
|
88
87
|
const args = readBalancedParens(stripped, argsStart);
|
|
@@ -95,7 +94,7 @@ function extract(src) {
|
|
|
95
94
|
// Skip matches whose position falls inside an R6/S7 class body — those have
|
|
96
95
|
// already been emitted as indented members.
|
|
97
96
|
const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
|
|
98
|
-
while ((m = funcRe.exec(stripped)) !== null
|
|
97
|
+
while ((m = funcRe.exec(stripped)) !== null) {
|
|
99
98
|
const name = m[1];
|
|
100
99
|
if (name.startsWith('.')) continue;
|
|
101
100
|
if (inAnyRange(m.index, consumedRanges)) continue;
|
|
@@ -107,15 +106,12 @@ function extract(src) {
|
|
|
107
106
|
|
|
108
107
|
// ── S4 ────────────────────────────────────────────────────────────────────
|
|
109
108
|
for (const sm of stripped.matchAll(/^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm)) {
|
|
110
|
-
if (sigs.length >= 30) break;
|
|
111
109
|
sigs.push(`setGeneric("${sm[1]}")`);
|
|
112
110
|
}
|
|
113
111
|
for (const sm of stripped.matchAll(/^[ \t]*setMethod\s*\(\s*["']([\w.]+)["']\s*,\s*["']([\w.]+)["']/gm)) {
|
|
114
|
-
if (sigs.length >= 30) break;
|
|
115
112
|
sigs.push(`setMethod("${sm[1]}", "${sm[2]}")`);
|
|
116
113
|
}
|
|
117
114
|
for (const sm of stripped.matchAll(/^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm)) {
|
|
118
|
-
if (sigs.length >= 30) break;
|
|
119
115
|
sigs.push(`setClass("${sm[1]}")`);
|
|
120
116
|
}
|
|
121
117
|
|
package/src/extractors/toml.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { capWithNotice } = require('../util/truncate');
|
|
4
|
+
|
|
5
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
6
|
+
const PER_FILE_LIMIT = 40;
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Extract signatures from TOML configuration files.
|
|
5
10
|
* Focuses on section/table names and high-value keys.
|
|
@@ -36,7 +41,7 @@ function extract(src) {
|
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
return Array.from(new Set(sigs))
|
|
44
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'entries');
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
module.exports = { extract };
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { capWithNotice } = require('../util/truncate');
|
|
4
|
+
|
|
5
|
+
// Ceiling discloses what it drops rather than truncating silently (#583).
|
|
6
|
+
const PER_FILE_LIMIT = 50;
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Extract React component signatures from .tsx files.
|
|
5
10
|
* Captures component props interfaces, hooks usage, and exports.
|
|
@@ -54,7 +59,7 @@ function extract(src) {
|
|
|
54
59
|
sigs.push(`handler on${h}`);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
return Array.from(new Set(sigs))
|
|
62
|
+
return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'signatures');
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
module.exports = { extract };
|
package/src/map/route-table.js
CHANGED
|
@@ -20,6 +20,45 @@ function shouldSkipFile(rel) {
|
|
|
20
20
|
return /(^|\/)(gen-context|gen-project-map)\.js$/.test(normalized);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Byte offsets and prefixes of every `@Controller(...)` in a file.
|
|
25
|
+
* A file may declare several controllers, so each route is attributed to the
|
|
26
|
+
* nearest one above it rather than to a single file-wide prefix.
|
|
27
|
+
* @param {string} content
|
|
28
|
+
* @returns {Array<{index:number, prefix:string}>} ascending by index
|
|
29
|
+
*/
|
|
30
|
+
function nestControllerPrefixes(content) {
|
|
31
|
+
const out = [];
|
|
32
|
+
const re = /@Controller\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
33
|
+
let m;
|
|
34
|
+
while ((m = re.exec(content)) !== null) {
|
|
35
|
+
out.push({ index: m.index, prefix: m[1] || '' });
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Prefix of the nearest `@Controller` above `index`, or '' when there is none. */
|
|
41
|
+
function prefixBefore(controllers, index) {
|
|
42
|
+
let prefix = '';
|
|
43
|
+
for (const c of controllers) {
|
|
44
|
+
if (c.index > index) break;
|
|
45
|
+
prefix = c.prefix;
|
|
46
|
+
}
|
|
47
|
+
return prefix;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Join a controller prefix and a method path into one route path.
|
|
52
|
+
* Either side may be empty, absent, or carry its own slashes.
|
|
53
|
+
* @returns {string} always slash-prefixed; never a trailing slash except '/'
|
|
54
|
+
*/
|
|
55
|
+
function joinRoute(prefix, methodPath) {
|
|
56
|
+
const parts = [prefix, methodPath]
|
|
57
|
+
.map((p) => String(p || '').trim().replace(/^\/+|\/+$/g, ''))
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
return parts.length ? '/' + parts.join('/') : '/';
|
|
60
|
+
}
|
|
61
|
+
|
|
23
62
|
/**
|
|
24
63
|
* Structured route rows across the supported frameworks — the data behind
|
|
25
64
|
* `analyze`, exposed for retrieval surface-enrichment (#488).
|
|
@@ -48,16 +87,14 @@ function collectRoutes(files, cwd) {
|
|
|
48
87
|
routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
|
|
49
88
|
}
|
|
50
89
|
|
|
51
|
-
// NestJS
|
|
52
|
-
|
|
90
|
+
// NestJS: @Get(':id') / @Post() — composed with the enclosing
|
|
91
|
+
// @Controller('prefix'). Without the prefix the emitted path matches
|
|
92
|
+
// nothing real, which defeats the point of route pseudo-signatures (#585).
|
|
93
|
+
const controllers = nestControllerPrefixes(content);
|
|
94
|
+
const re2 = /@(Get|Post|Put|Patch|Delete|Head|Options|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g;
|
|
53
95
|
while ((m = re2.exec(content)) !== null) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// NestJS: @Get() with no path
|
|
58
|
-
const re3 = /@(Get|Post|Put|Patch|Delete)\s*\(\s*\)/g;
|
|
59
|
-
while ((m = re3.exec(content)) !== null) {
|
|
60
|
-
routes.push({ method: m[1].toUpperCase(), path: '/', file: rel });
|
|
96
|
+
const prefix = prefixBefore(controllers, m.index);
|
|
97
|
+
routes.push({ method: m[1].toUpperCase(), path: joinRoute(prefix, m[2]), file: rel });
|
|
61
98
|
}
|
|
62
99
|
}
|
|
63
100
|
|
package/src/mcp/server.js
CHANGED
package/src/extractors/vue.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { capWithNotice } = require('../util/truncate');
|
|
4
|
-
|
|
5
|
-
// Ceiling sits above the default `maxSigsPerFile` so the configured budget
|
|
6
|
-
// governs output rather than a literal buried here, and omissions are disclosed (#576).
|
|
7
|
-
const PER_FILE_LIMIT = 25;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Extract signatures from Vue single-file components.
|
|
11
|
-
* @param {string} src - Raw file content
|
|
12
|
-
* @returns {string[]} Array of signature strings
|
|
13
|
-
*/
|
|
14
|
-
function extract(src) {
|
|
15
|
-
if (!src || typeof src !== 'string') return [];
|
|
16
|
-
const sigs = [];
|
|
17
|
-
|
|
18
|
-
// Extract component name from filename hint if present or defineComponent
|
|
19
|
-
const nameMatch = src.match(/name\s*:\s*['"](\w+)['"]/);
|
|
20
|
-
if (nameMatch) sigs.push(`component ${nameMatch[1]}`);
|
|
21
|
-
|
|
22
|
-
// Extract <script> block
|
|
23
|
-
const scriptMatch = src.match(/<script(?:\s[^>]*)?>(?:\s*)([\s\S]*?)<\/script>/i);
|
|
24
|
-
if (!scriptMatch) return sigs;
|
|
25
|
-
|
|
26
|
-
const script = scriptMatch[1]
|
|
27
|
-
.replace(/\/\/.*$/gm, '')
|
|
28
|
-
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
29
|
-
|
|
30
|
-
// Props
|
|
31
|
-
const propsMatch = script.match(/props\s*:\s*(\{[\s\S]*?\})/);
|
|
32
|
-
if (propsMatch) {
|
|
33
|
-
const propNames = [];
|
|
34
|
-
for (const m of propsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
|
|
35
|
-
propNames.push(m[1]);
|
|
36
|
-
}
|
|
37
|
-
if (propNames.length > 0) sigs.push(`props: [${propNames.join(', ')}]`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Methods in options API
|
|
41
|
-
const methodsMatch = script.match(/methods\s*:\s*\{([\s\S]*?)\},?\s*(?:computed|watch|mounted|created|data|\})/);
|
|
42
|
-
if (methodsMatch) {
|
|
43
|
-
for (const m of methodsMatch[1].matchAll(/^\s+(?:async\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
44
|
-
if (m[1].startsWith('_')) continue;
|
|
45
|
-
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
46
|
-
const retStr = m[3] ? ` → ${normalizeType(m[3])}` : '';
|
|
47
|
-
sigs.push(` ${asyncKw}${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Top-level functions in <script> (e.g., composition API helpers)
|
|
52
|
-
for (const m of script.matchAll(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
|
|
53
|
-
if (m[1].startsWith('_')) continue;
|
|
54
|
-
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
55
|
-
const retStr = m[3] ? ` → ${normalizeType(m[3])}` : '';
|
|
56
|
-
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// defineProps (Composition API)
|
|
60
|
-
const definePropsMatch = script.match(/defineProps(?:<[^>]*>)?\s*\(\s*(\{[\s\S]*?\})\s*\)/);
|
|
61
|
-
if (definePropsMatch) {
|
|
62
|
-
const propNames = [];
|
|
63
|
-
for (const m of definePropsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
|
|
64
|
-
propNames.push(m[1]);
|
|
65
|
-
}
|
|
66
|
-
if (propNames.length > 0) sigs.push(`defineProps: [${propNames.join(', ')}]`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Emits
|
|
70
|
-
const emitsMatch = script.match(/(?:defineEmits|emits)\s*(?::\s*|\(\s*)(\[[\s\S]*?\])/);
|
|
71
|
-
if (emitsMatch) sigs.push(`emits: ${emitsMatch[1].replace(/\s+/g, ' ')}`);
|
|
72
|
-
|
|
73
|
-
return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function normalizeParams(params) {
|
|
77
|
-
if (!params) return '';
|
|
78
|
-
return params.trim().replace(/\s+/g, ' ');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function normalizeType(type) {
|
|
82
|
-
if (!type) return '';
|
|
83
|
-
return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
module.exports = { extract };
|