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/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.32.0 | Benchmark: sigmap-v8.32-main (2026-09-12)
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.32-main, 2026-09-12)
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) | 81.1% (1.73× lift) |
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) | — | 64.8% |
27
- | Prompts per task | 2.84 | 1.54 (45.7% fewer) |
28
- | Supported languages | — | 33 |
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 (33 extractors)
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, vue, vue_sfc, xml, yaml
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.32.0 | Benchmark: sigmap-v8.32-main (2026-09-12)
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.32-main, 2026-09-12)
26
+ ## Core metrics (benchmark: sigmap-v8.33-main, 2026-09-13)
27
27
 
28
- - hit@5 retrieval: 81.1% vs 44.0% single-shot grep baseline (1.73× lift)
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: 64.8% (modeled from retrieval tiers, not measured LLM sessions)
31
- - Prompts per task: 1.54 vs 2.84 baseline (45.7% fewer, modeled)
32
- - Languages: 33 supported · MCP tools: 21
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.32.0",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.32.0",
3
+ "version": "8.33.0",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "8.32.0",
3
+ "version": "8.33.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
- return merged;
355
+
356
+ return _applyJvmDepth(merged, cwd, userConfig.maxDepth !== undefined);
307
357
  }
308
358
 
309
359
  function deepClone(obj) {
@@ -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
- const EXT_MAP = {
18
- '.ts': 'typescript', '.tsx': 'typescript',
19
- '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
20
- '.py': 'python', '.pyw': 'python',
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
- const base = path.basename(filePath);
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)).slice(0, 40);
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)).slice(0, 50);
39
+ return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'keys');
35
40
  }
36
41
 
37
42
  module.exports = { extract };
@@ -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 && sigs.length < 30) {
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 && sigs.length < 30) {
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 && sigs.length < 30) {
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 && sigs.length < 30) {
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
 
@@ -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)).slice(0, 40);
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)).slice(0, 50);
62
+ return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'signatures');
58
63
  }
59
64
 
60
65
  module.exports = { extract };
@@ -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 decorators: @Get('/path') @Post('/path')
52
- const re2 = /@(Get|Post|Put|Patch|Delete|Head|Options|All)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
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
- routes.push({ method: m[1].toUpperCase(), path: m[2], file: rel });
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
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '8.32.0',
21
+ version: '8.33.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -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 };