sigmap 8.32.0 → 8.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [8.32.1] — 2026-09-13
14
+
15
+
16
+ ### Fixed
17
+ - **Retraction of a v8.32.0 claim.** That release stated "every extractor now discloses what a ceiling dropped". It did not. `vue.js` was registered but unreachable (`.vue` dispatches to `vue_sfc`), so disclosure was added to dead code while the live `.vue` handler kept truncating silently; `.tsx` (every React component), `.properties`, `.toml` and `.md` still cut output with a bare `slice()`; and `r.js` called `capWithNotice` but eight inner caps stopped collection at the ceiling so it never fired — and when forced to, reported `+1 more` where 50 signatures were hidden. All fixed, ceilings unchanged, with a test that fails against the pre-fix extractors. The root cause was that three of those languages have no test fixture (#582, #583, #584, #588)
18
+ - The walk depth hid most code in JVM package layouts: `maxDepth: 6` indexed **6 of 47** Java files on spring-petclinic, because Java puts one directory per package segment. #561 had already raised the *graph* walk to 12, so extraction was the shallower half of an inconsistent pair. Depth now rises to 12 only for JVM layouts; an explicit `maxDepth` always wins. Gated JVM corpus 16.4% → 23.0% (#590)
19
+
20
+ ### Changed
21
+ - Published retrieval hit@5 moves 81.1% → 78.9%. One of 18 repos accounts for it: spring-petclinic 100% → 60%, previously measured against an index holding 6 of 47 Java files. The leak-free `mined` corpus is flat and the leak-free `jvm` corpus improves, so the prior figure was inflated by under-indexing rather than this being a ranking regression. The two affected tasks are tracked as a ranking weakness the missing files were concealing (#592)
22
+
23
+ ---
24
+
13
25
  ## [8.32.0] — 2026-09-12
14
26
 
15
27
  ### Added
@@ -18,6 +30,7 @@ Format: [Semantic Versioning](https://semver.org/)
18
30
 
19
31
  ### Fixed
20
32
  - Every extractor now **discloses what a ceiling dropped** instead of truncating silently. 20 extractors gained `… +N more` markers on their member and per-file caps (23 now carry them in total). The ceilings themselves are unchanged — raising them is a separate, measured decision (#578, #576)
33
+ > **Correction (2026-09-13):** "every extractor" was wrong as published. `vue.js` was dead code — `.vue` dispatches to `vue_sfc` — so one of the 20 was unreachable; four reachable extractors (`.tsx`, `.properties`, `.toml`, `.md`) still truncated silently; and `r.js`'s disclosure was defeated by eight inner caps. Corrected in #589 (#582, #583, #584).
21
34
  - The retrieval gate reused a gitignored index, so a "regression" could be pure staleness — this produced three separate false investigations, including one re-baseline. It now regenerates every index it scores, including one per JVM repo driven from `benchmarks/config-overrides.json` rather than a hand-written config (#579)
22
35
  - The `sigmap lines` CLI example nested a fenced block inside a text fence; the inner fence closed early and a brace expression became a Vue interpolation. This failed the Pages build *after* v8.31.0 was tagged and published (#574)
23
36
 
package/README.md CHANGED
@@ -59,12 +59,12 @@ That map is exactly what agentic grep is worst at: reproducible, auditable conte
59
59
 
60
60
  **Proof it pays off** (full benchmark below):
61
61
  <!--SM:whyMetrics-->
62
- - **81.1% hit@5** — right file in top 5 results (vs 44.0% single-shot grep baseline — 1.73× lift)
62
+ - **78.9% hit@5** — right file in top 5 results (vs 44.0% single-shot grep baseline — 1.73× lift)
63
63
  - **96.8% token reduction** — average across 21 real repos
64
- - **64.8% task-success proxy** — modeled from retrieval tiers, not measured LLM sessions
65
- - **1.54 prompts per task** — down from 2.84 (45.7% fewer retries, modeled)
64
+ - **62.9% task-success proxy** — modeled from retrieval tiers, not measured LLM sessions
65
+ - **1.58 prompts per task** — down from 2.84 (44.4% fewer retries, modeled)
66
66
  <!--/SM:whyMetrics-->
67
- - **<!--SM:languages-->33<!--/SM:languages--> languages supported** — TypeScript, Python, Go, Rust, Java, R, and more
67
+ - **<!--SM:languages-->32<!--/SM:languages--> languages supported** — TypeScript, Python, Go, Rust, Java, R, and more
68
68
  - **No vendor lock-in** — works with any AI assistant or local LLM
69
69
  - **No API costs** — use local models (Ollama, llama.cpp, vLLM) with zero token fees
70
70
  - **Full privacy** — keep your code and context on your machine
@@ -98,7 +98,7 @@ sigmap verify answer.md --report # standalone red/amber/green HTML report
98
98
  | Without SigMap | With SigMap |
99
99
  |---|---|
100
100
  | ❌ Non-reproducible agent guesses | ✅ Deterministic map — same input, same output, every time |
101
- | ❌ "Trust me" AI answers | ✅ Grounded — right file in context <!--SM:hitWhole-->81%<!--/SM:hitWhole--> of the time, every symbol on a real line anchor |
101
+ | ❌ "Trust me" AI answers | ✅ Grounded — right file in context <!--SM:hitWhole-->79%<!--/SM:hitWhole--> of the time, every symbol on a real line anchor |
102
102
  | ❌ Embeddings / vector DB required | ✅ Zero deps, no infra, fully offline |
103
103
 
104
104
  ---
@@ -125,10 +125,10 @@ Ask → Rank → Context → Validate → Judge → Learn
125
125
  Benchmark : sigmap-v8.32-main (21 repositories, including R language)
126
126
  Date : 2026-09-12
127
127
 
128
- Hit@5 : 81.1% (grep-agent baseline 44.0% — 1.73× lift)
128
+ Hit@5 : 78.9% (grep-agent baseline 44.0% — 1.73× lift)
129
129
  Token reduction: 96.8% (across 21 repos)
130
- Prompt reduction : 45.7% (2.84 → 1.54 prompts per task, modeled)
131
- Task success : 64.8% (proxy — modeled from retrieval tiers)
130
+ Prompt reduction : 44.4% (2.84 → 1.58 prompts per task, modeled)
131
+ Task success : 62.9% (proxy — modeled from retrieval tiers)
132
132
  Repos tested : 21 (JavaScript, Python, Go, Rust, Java, R, C++, C#, Dart, Swift, Ruby, PHP, Scala, Kotlin, and more)
133
133
  ```
134
134
  <!--/SM:benchmarkBlock-->
@@ -332,7 +332,7 @@ sigmap --health
332
332
  | Benchmark methodology | [benchmark.html](https://sigmap.io/guide/benchmark.html) |
333
333
  | Config reference | [config.html](https://sigmap.io/guide/config.html) |
334
334
  | Roadmap | [roadmap.html](https://sigmap.io/guide/roadmap.html) |
335
- | <!--SM:languages-->33<!--/SM:languages--> languages | [generalization.html](https://sigmap.io/guide/generalization.html) |
335
+ | <!--SM:languages-->32<!--/SM:languages--> languages | [generalization.html](https://sigmap.io/guide/generalization.html) |
336
336
 
337
337
  ---
338
338
 
@@ -389,7 +389,7 @@ See [.github/PULL_REQUEST_TEMPLATE.md](.github/PULL_REQUEST_TEMPLATE.md) for the
389
389
 
390
390
  ---
391
391
 
392
- ## <!--SM:languages-->33<!--/SM:languages--> languages
392
+ ## <!--SM:languages-->32<!--/SM:languages--> languages
393
393
 
394
394
  TypeScript · JavaScript · Python · Java · Kotlin · Go · Rust · C# · C/C++ · Ruby · PHP · Swift · Dart · Scala · Vue · Svelte · HTML · CSS/SCSS · YAML · Shell · SQL · GraphQL · Terraform · Protobuf · Dockerfile · TOML · XML · Properties · Markdown · R · GDScript
395
395
 
package/gen-context.js CHANGED
@@ -1790,6 +1790,55 @@ __factories["./src/config/loader"] = function(module, exports) {
1790
1790
  });
1791
1791
  }
1792
1792
 
1793
+ /**
1794
+ * Directory depth needed to reach source under a JVM package layout.
1795
+ *
1796
+ * `maxDepth: 6` is right for the JS/Python-shaped trees it was tuned on, but a
1797
+ * JVM project puts one directory per package segment — real code in
1798
+ * `src/main/java/com/company/project/module/Class.java` sits 8-10 levels down.
1799
+ * At depth 6 only the top-level package was indexed: on spring-petclinic, 6 of
1800
+ * 47 Java files (#590).
1801
+ *
1802
+ * #561 already raised the dependency-graph walk to 12 for exactly this reason,
1803
+ * so extraction was the shallower half of an inconsistent pair.
1804
+ *
1805
+ * Deepening globally is not free — it adds candidates to every repo and cost
1806
+ * 2.2pp on the (filename-leaky) 105-task matrix corpus while the unbiased
1807
+ * `mined` corpus stayed flat. So the depth is raised only where the layout
1808
+ * demands it, leaving non-JVM repos byte-identical.
1809
+ *
1810
+ * @param {string} cwd
1811
+ * @returns {boolean} true when the repo looks like a Maven/Gradle/sbt project
1812
+ */
1813
+ function _isJvmLayout(cwd) {
1814
+ const MARKERS = ['pom.xml', 'build.gradle', 'build.gradle.kts', 'build.sbt', 'settings.gradle', 'settings.gradle.kts'];
1815
+ for (const m of MARKERS) {
1816
+ try { if (fs.existsSync(path.join(cwd, m))) return true; } catch { /* unreadable cwd */ }
1817
+ }
1818
+ for (const d of ['src/main/java', 'src/main/kotlin', 'src/main/scala']) {
1819
+ try { if (fs.existsSync(path.join(cwd, d))) return true; } catch { /* ignore */ }
1820
+ }
1821
+ return false;
1822
+ }
1823
+
1824
+ /** Walk depth for a JVM package layout — matches the graph walk from #561. */
1825
+ const JVM_MAX_DEPTH = 12;
1826
+
1827
+ /**
1828
+ * Raise `maxDepth` to the JVM depth when the layout needs it (#590).
1829
+ * An explicit user value always wins, including a deliberately shallow one.
1830
+ * @param {object} cfg resolved config (mutated and returned)
1831
+ * @param {string} cwd
1832
+ * @param {boolean} userSetDepth
1833
+ * @returns {object} cfg
1834
+ */
1835
+ function _applyJvmDepth(cfg, cwd, userSetDepth) {
1836
+ if (!userSetDepth && cfg.maxDepth < JVM_MAX_DEPTH && _isJvmLayout(cwd)) {
1837
+ cfg.maxDepth = JVM_MAX_DEPTH;
1838
+ }
1839
+ return cfg;
1840
+ }
1841
+
1793
1842
  /**
1794
1843
  * Load and merge configuration for a given working directory.
1795
1844
  *
@@ -1802,7 +1851,7 @@ __factories["./src/config/loader"] = function(module, exports) {
1802
1851
  const cfg = deepClone(DEFAULTS);
1803
1852
  const detected = detectAutoSrcDirs(cwd, cfg.exclude);
1804
1853
  if (detected.length > 0) cfg.srcDirs = detected;
1805
- return cfg;
1854
+ return _applyJvmDepth(cfg, cwd, false);
1806
1855
  }
1807
1856
 
1808
1857
  let userConfig;
@@ -1814,7 +1863,7 @@ __factories["./src/config/loader"] = function(module, exports) {
1814
1863
  const cfg = deepClone(DEFAULTS);
1815
1864
  const detected = detectAutoSrcDirs(cwd, cfg.exclude);
1816
1865
  if (detected.length > 0) cfg.srcDirs = detected;
1817
- return cfg;
1866
+ return _applyJvmDepth(cfg, cwd, false);
1818
1867
  }
1819
1868
 
1820
1869
  // Warn on unknown keys (helps catch typos)
@@ -1866,7 +1915,8 @@ __factories["./src/config/loader"] = function(module, exports) {
1866
1915
  } else if (Array.isArray(merged.adapters) && !userConfig.outputs) {
1867
1916
  merged.outputs = merged.adapters.filter((a) => ['copilot','claude','cursor','windsurf'].includes(a));
1868
1917
  }
1869
- return merged;
1918
+
1919
+ return _applyJvmDepth(merged, cwd, userConfig.maxDepth !== undefined);
1870
1920
  }
1871
1921
 
1872
1922
  function deepClone(obj) {
@@ -4118,7 +4168,6 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
4118
4168
  '.scala': 'scala', '.sc': 'scala',
4119
4169
  '.gd': 'gdscript',
4120
4170
  '.r': 'r', '.R': 'r',
4121
- '.vue': 'vue',
4122
4171
  '.svelte': 'svelte',
4123
4172
  '.html': 'html', '.htm': 'html',
4124
4173
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
@@ -5922,7 +5971,6 @@ __factories["./src/extractors/dispatch"] = function(module, exports) {
5922
5971
  scala: __require('./src/extractors/scala'),
5923
5972
  gdscript: __require('./src/extractors/gdscript'),
5924
5973
  r: __require('./src/extractors/r'),
5925
- vue: __require('./src/extractors/vue'),
5926
5974
  vue_sfc: __require('./src/extractors/vue_sfc'),
5927
5975
  svelte: __require('./src/extractors/svelte'),
5928
5976
  html: __require('./src/extractors/html'),
@@ -6986,6 +7034,11 @@ __factories["./src/extractors/line-anchor"] = function(module, exports) {
6986
7034
  // ── ./src/extractors/markdown ──
6987
7035
  __factories["./src/extractors/markdown"] = function(module, exports) {
6988
7036
 
7037
+ const { capWithNotice } = __require('./src/util/truncate');
7038
+
7039
+ // Ceiling discloses what it drops rather than truncating silently (#583).
7040
+ const PER_FILE_LIMIT = 40;
7041
+
6989
7042
  /**
6990
7043
  * Lightweight markdown technical indexer.
6991
7044
  * Captures headings and fenced code block language hints only.
@@ -7010,7 +7063,7 @@ __factories["./src/extractors/markdown"] = function(module, exports) {
7010
7063
  sigs.push(`code-fence ${lang}`);
7011
7064
  }
7012
7065
 
7013
- return Array.from(new Set(sigs)).slice(0, 40);
7066
+ return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'headings');
7014
7067
  }
7015
7068
 
7016
7069
  module.exports = { extract };
@@ -7341,6 +7394,11 @@ __factories["./src/extractors/prdiff"] = function(module, exports) {
7341
7394
  // ── ./src/extractors/properties ──
7342
7395
  __factories["./src/extractors/properties"] = function(module, exports) {
7343
7396
 
7397
+ const { capWithNotice } = __require('./src/util/truncate');
7398
+
7399
+ // Ceiling discloses what it drops rather than truncating silently (#583).
7400
+ const PER_FILE_LIMIT = 50;
7401
+
7344
7402
  /**
7345
7403
  * Extract signatures from .properties configuration files.
7346
7404
  * Captures key names, grouped by prefixes where possible.
@@ -7372,7 +7430,7 @@ __factories["./src/extractors/properties"] = function(module, exports) {
7372
7430
  sigs.push(`key ${key}`);
7373
7431
  }
7374
7432
 
7375
- return Array.from(new Set(sigs)).slice(0, 50);
7433
+ return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'keys');
7376
7434
  }
7377
7435
 
7378
7436
  module.exports = { extract };
@@ -7845,7 +7903,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
7845
7903
  // ClassName <- R6::R6Class(...)
7846
7904
  const r6Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\s*\(/g;
7847
7905
  let m;
7848
- while ((m = r6Re.exec(stripped)) !== null && sigs.length < 30) {
7906
+ while ((m = r6Re.exec(stripped)) !== null) {
7849
7907
  const name = m[1];
7850
7908
  if (name.startsWith('.')) continue;
7851
7909
  const openIdx = r6Re.lastIndex - 1;
@@ -7856,7 +7914,6 @@ __factories["./src/extractors/r"] = function(module, exports) {
7856
7914
  sigs.push(`${name} <- R6Class("${classNameLit}")` + applyHint(docHints, name));
7857
7915
  for (const memberSig of extractListMethods(body, 8)) {
7858
7916
  sigs.push(' ' + memberSig);
7859
- if (sigs.length >= 30) break;
7860
7917
  }
7861
7918
  consumedRanges.push([m.index, closeIdx]);
7862
7919
  r6Re.lastIndex = closeIdx;
@@ -7866,7 +7923,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
7866
7923
  // ClassName <- new_class("ClassName", properties = list(...))
7867
7924
  const s7Classes = new Set();
7868
7925
  const s7Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\s*\(/g;
7869
- while ((m = s7Re.exec(stripped)) !== null && sigs.length < 30) {
7926
+ while ((m = s7Re.exec(stripped)) !== null) {
7870
7927
  const name = m[1];
7871
7928
  if (name.startsWith('.')) continue;
7872
7929
  const openIdx = s7Re.lastIndex - 1;
@@ -7883,7 +7940,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
7883
7940
 
7884
7941
  // S7 method dispatch: `method(generic, ClassName) <- function(args)`
7885
7942
  const s7MethodRe = /^[ \t]*method\s*\(\s*([\w.]+)\s*,\s*([\w.]+)\s*\)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
7886
- while ((m = s7MethodRe.exec(stripped)) !== null && sigs.length < 30) {
7943
+ while ((m = s7MethodRe.exec(stripped)) !== null) {
7887
7944
  if (!s7Classes.has(m[2])) continue;
7888
7945
  const argsStart = s7MethodRe.lastIndex - 1;
7889
7946
  const args = readBalancedParens(stripped, argsStart);
@@ -7896,7 +7953,7 @@ __factories["./src/extractors/r"] = function(module, exports) {
7896
7953
  // Skip matches whose position falls inside an R6/S7 class body — those have
7897
7954
  // already been emitted as indented members.
7898
7955
  const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
7899
- while ((m = funcRe.exec(stripped)) !== null && sigs.length < 30) {
7956
+ while ((m = funcRe.exec(stripped)) !== null) {
7900
7957
  const name = m[1];
7901
7958
  if (name.startsWith('.')) continue;
7902
7959
  if (inAnyRange(m.index, consumedRanges)) continue;
@@ -7908,15 +7965,12 @@ __factories["./src/extractors/r"] = function(module, exports) {
7908
7965
 
7909
7966
  // ── S4 ────────────────────────────────────────────────────────────────────
7910
7967
  for (const sm of stripped.matchAll(/^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm)) {
7911
- if (sigs.length >= 30) break;
7912
7968
  sigs.push(`setGeneric("${sm[1]}")`);
7913
7969
  }
7914
7970
  for (const sm of stripped.matchAll(/^[ \t]*setMethod\s*\(\s*["']([\w.]+)["']\s*,\s*["']([\w.]+)["']/gm)) {
7915
- if (sigs.length >= 30) break;
7916
7971
  sigs.push(`setMethod("${sm[1]}", "${sm[2]}")`);
7917
7972
  }
7918
7973
  for (const sm of stripped.matchAll(/^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm)) {
7919
- if (sigs.length >= 30) break;
7920
7974
  sigs.push(`setClass("${sm[1]}")`);
7921
7975
  }
7922
7976
 
@@ -8925,6 +8979,11 @@ __factories["./src/extractors/todos"] = function(module, exports) {
8925
8979
  // ── ./src/extractors/toml ──
8926
8980
  __factories["./src/extractors/toml"] = function(module, exports) {
8927
8981
 
8982
+ const { capWithNotice } = __require('./src/util/truncate');
8983
+
8984
+ // Ceiling discloses what it drops rather than truncating silently (#583).
8985
+ const PER_FILE_LIMIT = 40;
8986
+
8928
8987
  /**
8929
8988
  * Extract signatures from TOML configuration files.
8930
8989
  * Focuses on section/table names and high-value keys.
@@ -8961,7 +9020,7 @@ __factories["./src/extractors/toml"] = function(module, exports) {
8961
9020
  }
8962
9021
  }
8963
9022
 
8964
- return Array.from(new Set(sigs)).slice(0, 40);
9023
+ return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'entries');
8965
9024
  }
8966
9025
 
8967
9026
  module.exports = { extract };
@@ -9301,6 +9360,11 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
9301
9360
  // ── ./src/extractors/typescript_react ──
9302
9361
  __factories["./src/extractors/typescript_react"] = function(module, exports) {
9303
9362
 
9363
+ const { capWithNotice } = __require('./src/util/truncate');
9364
+
9365
+ // Ceiling discloses what it drops rather than truncating silently (#583).
9366
+ const PER_FILE_LIMIT = 50;
9367
+
9304
9368
  /**
9305
9369
  * Extract React component signatures from .tsx files.
9306
9370
  * Captures component props interfaces, hooks usage, and exports.
@@ -9355,97 +9419,7 @@ __factories["./src/extractors/typescript_react"] = function(module, exports) {
9355
9419
  sigs.push(`handler on${h}`);
9356
9420
  }
9357
9421
 
9358
- return Array.from(new Set(sigs)).slice(0, 50);
9359
- }
9360
-
9361
- module.exports = { extract };
9362
-
9363
- };
9364
-
9365
- // ── ./src/extractors/vue ──
9366
- __factories["./src/extractors/vue"] = function(module, exports) {
9367
-
9368
- const { capWithNotice } = __require('./src/util/truncate');
9369
-
9370
- // Ceiling sits above the default `maxSigsPerFile` so the configured budget
9371
- // governs output rather than a literal buried here, and omissions are disclosed (#576).
9372
- const PER_FILE_LIMIT = 25;
9373
-
9374
- /**
9375
- * Extract signatures from Vue single-file components.
9376
- * @param {string} src - Raw file content
9377
- * @returns {string[]} Array of signature strings
9378
- */
9379
- function extract(src) {
9380
- if (!src || typeof src !== 'string') return [];
9381
- const sigs = [];
9382
-
9383
- // Extract component name from filename hint if present or defineComponent
9384
- const nameMatch = src.match(/name\s*:\s*['"](\w+)['"]/);
9385
- if (nameMatch) sigs.push(`component ${nameMatch[1]}`);
9386
-
9387
- // Extract <script> block
9388
- const scriptMatch = src.match(/<script(?:\s[^>]*)?>(?:\s*)([\s\S]*?)<\/script>/i);
9389
- if (!scriptMatch) return sigs;
9390
-
9391
- const script = scriptMatch[1]
9392
- .replace(/\/\/.*$/gm, '')
9393
- .replace(/\/\*[\s\S]*?\*\//g, '');
9394
-
9395
- // Props
9396
- const propsMatch = script.match(/props\s*:\s*(\{[\s\S]*?\})/);
9397
- if (propsMatch) {
9398
- const propNames = [];
9399
- for (const m of propsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
9400
- propNames.push(m[1]);
9401
- }
9402
- if (propNames.length > 0) sigs.push(`props: [${propNames.join(', ')}]`);
9403
- }
9404
-
9405
- // Methods in options API
9406
- const methodsMatch = script.match(/methods\s*:\s*\{([\s\S]*?)\},?\s*(?:computed|watch|mounted|created|data|\})/);
9407
- if (methodsMatch) {
9408
- for (const m of methodsMatch[1].matchAll(/^\s+(?:async\s+)?(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
9409
- if (m[1].startsWith('_')) continue;
9410
- const asyncKw = m[0].includes('async') ? 'async ' : '';
9411
- const retStr = m[3] ? ` → ${normalizeType(m[3])}` : '';
9412
- sigs.push(` ${asyncKw}${m[1]}(${normalizeParams(m[2])})${retStr}`);
9413
- }
9414
- }
9415
-
9416
- // Top-level functions in <script> (e.g., composition API helpers)
9417
- for (const m of script.matchAll(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{=\n]+))?/gm)) {
9418
- if (m[1].startsWith('_')) continue;
9419
- const asyncKw = m[0].includes('async') ? 'async ' : '';
9420
- const retStr = m[3] ? ` → ${normalizeType(m[3])}` : '';
9421
- sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(m[2])})${retStr}`);
9422
- }
9423
-
9424
- // defineProps (Composition API)
9425
- const definePropsMatch = script.match(/defineProps(?:<[^>]*>)?\s*\(\s*(\{[\s\S]*?\})\s*\)/);
9426
- if (definePropsMatch) {
9427
- const propNames = [];
9428
- for (const m of definePropsMatch[1].matchAll(/^\s+(\w+)\s*:/gm)) {
9429
- propNames.push(m[1]);
9430
- }
9431
- if (propNames.length > 0) sigs.push(`defineProps: [${propNames.join(', ')}]`);
9432
- }
9433
-
9434
- // Emits
9435
- const emitsMatch = script.match(/(?:defineEmits|emits)\s*(?::\s*|\(\s*)(\[[\s\S]*?\])/);
9436
- if (emitsMatch) sigs.push(`emits: ${emitsMatch[1].replace(/\s+/g, ' ')}`);
9437
-
9438
- return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
9439
- }
9440
-
9441
- function normalizeParams(params) {
9442
- if (!params) return '';
9443
- return params.trim().replace(/\s+/g, ' ');
9444
- }
9445
-
9446
- function normalizeType(type) {
9447
- if (!type) return '';
9448
- return type.trim().replace(/[;\s]+$/g, '').replace(/\s+/g, ' ').slice(0, 25);
9422
+ return capWithNotice(Array.from(new Set(sigs)), PER_FILE_LIMIT, 'signatures');
9449
9423
  }
9450
9424
 
9451
9425
  module.exports = { extract };
@@ -15814,7 +15788,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
15814
15788
 
15815
15789
  const SERVER_INFO = {
15816
15790
  name: 'sigmap',
15817
- version: '8.32.0',
15791
+ version: '8.32.1',
15818
15792
  description: 'SigMap MCP server — code signatures on demand',
15819
15793
  };
15820
15794
 
@@ -22108,7 +22082,7 @@ function __tryGit(args, opts = {}) {
22108
22082
  catch (_) { return ''; }
22109
22083
  }
22110
22084
 
22111
- const VERSION = '8.32.0';
22085
+ const VERSION = '8.32.1';
22112
22086
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
22113
22087
 
22114
22088
  function requireSourceOrBundled(key) {
@@ -26881,7 +26855,7 @@ function main() {
26881
26855
  '.cs': 'csharp', '.cpp': 'cpp', '.rb': 'ruby', '.php': 'php',
26882
26856
  '.swift': 'swift', '.dart': 'dart', '.scala': 'scala',
26883
26857
  '.r': 'r', '.R': 'r',
26884
- '.vue': 'vue', '.svelte': 'svelte', '.html': 'html',
26858
+ '.vue': 'vue_sfc', '.svelte': 'svelte', '.html': 'html',
26885
26859
  '.css': 'css', '.yml': 'yaml', '.sh': 'shell',
26886
26860
  };
26887
26861
  const SPECIAL = { 'Dockerfile': 'dockerfile' };
package/llms-full.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.32.1 | Benchmark: sigmap-v8.32-main (2026-09-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
 
@@ -21,11 +21,11 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
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.32.1 | Benchmark: sigmap-v8.32-main (2026-09-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
 
@@ -25,11 +25,11 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
25
25
 
26
26
  ## Core metrics (benchmark: sigmap-v8.32-main, 2026-09-12)
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.32.1",
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.32.1",
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.32.1",
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) {
@@ -31,7 +31,6 @@ const EXT_MAP = {
31
31
  '.scala': 'scala', '.sc': 'scala',
32
32
  '.gd': 'gdscript',
33
33
  '.r': 'r', '.R': 'r',
34
- '.vue': 'vue',
35
34
  '.svelte': 'svelte',
36
35
  '.html': 'html', '.htm': 'html',
37
36
  '.css': 'css', '.scss': 'css', '.sass': 'css', '.less': 'css',
@@ -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'),
@@ -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 };
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.32.1',
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 };