sigmap 8.31.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/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.31.0 | Benchmark: sigmap-v8.31-main (2026-09-08)
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
 
18
18
  ---
19
19
 
20
- ## Core metrics (benchmark: sigmap-v8.31-main, 2026-09-08)
20
+ ## Core metrics (benchmark: sigmap-v8.32-main, 2026-09-12)
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.31.0 | Benchmark: sigmap-v8.31-main (2026-09-08)
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
 
@@ -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.31-main, 2026-09-08)
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.31.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.31.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.31.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',
@@ -1,5 +1,13 @@
1
1
  'use strict';
2
2
 
3
+ const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
4
+
5
+ // Ceilings sit above the default `maxSigsPerFile` so the configured budget
6
+ // governs output rather than a literal buried here, and omissions are disclosed
7
+ // — an undisclosed cap looks like a class that simply has eight methods (#576).
8
+ const MEMBER_LIMIT = 8;
9
+ const PER_FILE_LIMIT = 25;
10
+
3
11
  /**
4
12
  * Extract signatures from C/C++ source code.
5
13
  * @param {string} src - Raw file content
@@ -30,7 +38,7 @@ function extract(src) {
30
38
  sigs.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
31
39
  }
32
40
 
33
- return sigs.slice(0, 25);
41
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
34
42
  }
35
43
 
36
44
  function extractBlock(src, startIndex) {
@@ -53,7 +61,7 @@ function extractMembers(block) {
53
61
  const retStr = ret ? ` → ${ret}` : '';
54
62
  members.push(`${m[2]}(${normalizeParams(m[3])})${retStr}`);
55
63
  }
56
- return members.slice(0, 8);
64
+ return capWithNotice(members, MEMBER_LIMIT, 'members');
57
65
  }
58
66
 
59
67
  function normalizeParams(params) {
@@ -1,6 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const { lineAt, withAnchor } = require('./line-anchor');
4
+ const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
5
+
6
+ // Ceilings sit above the default `maxSigsPerFile` so the configured budget
7
+ // governs output rather than a literal buried here, and omissions are disclosed
8
+ // — an undisclosed cap looks like a class that simply has eight methods (#576).
9
+ const MEMBER_LIMIT = 8;
10
+ const PER_FILE_LIMIT = 25;
4
11
 
5
12
  /**
6
13
  * Extract signatures from C# source code.
@@ -25,11 +32,12 @@ function extract(src) {
25
32
  const block = extractBlock(stripped, bodyStart);
26
33
  sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, declIdx), lineAt(stripped, bodyStart + block.length)));
27
34
  for (const meth of extractMembers(block)) {
28
- sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
35
+ // The disclosure marker carries no offsets; anchor it at the class body.
36
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + (meth.declIdx || 0)), lineAt(stripped, bodyStart + (meth.endIdx || 0))));
29
37
  }
30
38
  }
31
39
 
32
- return sigs.slice(0, 25);
40
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
33
41
  }
34
42
 
35
43
  function extractBlock(src, startIndex) {
@@ -55,7 +63,7 @@ function extractMembers(block) {
55
63
  endIdx: m.index + m[0].length,
56
64
  });
57
65
  }
58
- return members.slice(0, 8);
66
+ return capMembersWithNotice(members, MEMBER_LIMIT);
59
67
  }
60
68
 
61
69
  function normalizeParams(params) {
@@ -1,5 +1,11 @@
1
1
  'use strict';
2
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
+
3
9
  /**
4
10
  * Extract signatures from CSS/SCSS/SASS/Less source code.
5
11
  * @param {string} src - Raw file content
@@ -63,7 +69,7 @@ function extract(src) {
63
69
  for (const name of selected) sigs.push(`.${name}`);
64
70
  }
65
71
 
66
- return sigs.slice(0, 25);
72
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
67
73
  }
68
74
 
69
75
  module.exports = { extract };
@@ -1,6 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const { lineAt, withAnchor } = require('./line-anchor');
4
+ const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
5
+
6
+ // Ceilings sit above the default `maxSigsPerFile` so the configured budget
7
+ // governs output rather than a literal buried here, and omissions are disclosed
8
+ // — an undisclosed cap looks like a class that simply has eight methods (#576).
9
+ const MEMBER_LIMIT = 8;
10
+ const PER_FILE_LIMIT = 25;
4
11
 
5
12
  /**
6
13
  * Extract signatures from Dart source code.
@@ -36,7 +43,8 @@ function extract(src) {
36
43
  const block = extractBlock(stripped, bodyStart);
37
44
  sigs.push(withAnchor(`${abs}class ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
38
45
  for (const meth of extractMembers(block)) {
39
- sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
46
+ // The disclosure marker carries no offsets; anchor it at the class body.
47
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + (meth.declIdx || 0)), lineAt(stripped, bodyStart + (meth.endIdx || 0))));
40
48
  }
41
49
  }
42
50
 
@@ -48,7 +56,7 @@ function extract(src) {
48
56
  sigs.push(withAnchor(`${m[2]}(${normalizeParams(m[3])})${retStr}`, s, e));
49
57
  }
50
58
 
51
- return sigs.slice(0, 25);
59
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
52
60
  }
53
61
 
54
62
  function extractBlock(src, startIndex) {
@@ -73,7 +81,7 @@ function extractMembers(block) {
73
81
  endIdx: m.index + m[0].length,
74
82
  });
75
83
  }
76
- return members.slice(0, 8);
84
+ return capMembersWithNotice(members, MEMBER_LIMIT);
77
85
  }
78
86
 
79
87
  function normalizeParams(params) {
@@ -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,11 @@
1
1
  'use strict';
2
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
+
3
9
  /**
4
10
  * Extract signatures from Dockerfiles.
5
11
  * @param {string} src - Raw file content
@@ -43,7 +49,7 @@ function extract(src) {
43
49
  if (m) sigs.push(`ARG ${m[1]}`);
44
50
  }
45
51
 
46
- return sigs.slice(0, 25);
52
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
47
53
  }
48
54
 
49
55
  module.exports = { extract };
@@ -1,5 +1,15 @@
1
1
  'use strict';
2
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
+ // Ceilings disclose what they drop rather than truncating silently (#576).
10
+ const MEMBER_LIMIT = 6;
11
+ const ENUM_LIMIT = 24;
12
+
3
13
  /**
4
14
  * Extract signatures from Godot GDScript source code.
5
15
  * @param {string} src - Raw file content
@@ -44,7 +54,7 @@ function extract(src) {
44
54
  .split(',')
45
55
  .map((s) => s.trim().split(/\s*=/)[0].trim())
46
56
  .filter(Boolean);
47
- sigs.push(`${indent}enum ${m[1]} { ${members.slice(0, 6).join(', ')} }`);
57
+ sigs.push(`${indent}enum ${m[1]} { ${capWithNotice(members, ENUM_LIMIT, 'values').join(', ')} }`);
48
58
  }
49
59
 
50
60
  let constCount = 0;
@@ -92,7 +102,7 @@ function extract(src) {
92
102
  }
93
103
  }
94
104
 
95
- return sigs.slice(0, 25);
105
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
96
106
  }
97
107
 
98
108
  function extractInnerMembers(stripped, startIndex) {
@@ -110,7 +120,7 @@ function extractInnerMembers(stripped, startIndex) {
110
120
  members.push(`${staticKw}func ${fm[2]}(${params})${retStr}`);
111
121
  }
112
122
  }
113
- return members.slice(0, 6);
123
+ return capWithNotice(members, MEMBER_LIMIT, 'members');
114
124
  }
115
125
 
116
126
  function normalizeParams(params) {
@@ -1,6 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const { lineAt, withAnchor } = require('./line-anchor');
4
+ const { capWithNotice } = require('../util/truncate');
5
+
6
+ // Ceiling sits above the default `maxSigsPerFile` so the configured budget
7
+ // governs output rather than a literal buried here, and omissions are disclosed (#576).
8
+ const PER_FILE_LIMIT = 25;
4
9
 
5
10
  /**
6
11
  * Extract signatures from Go source code.
@@ -49,7 +54,7 @@ function extract(src) {
49
54
  sigs.push(hinted(withAnchor(`func ${receiver}${m[2]}(${normalizeParams(m[3])})${retStr}`, lineAt(stripped, m.index), lineAt(stripped, end)), m[2]));
50
55
  }
51
56
 
52
- return sigs.slice(0, 25);
57
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
53
58
  }
54
59
 
55
60
  function extractBlock(src, startIndex) {
@@ -1,5 +1,11 @@
1
1
  'use strict';
2
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
+
3
9
  /**
4
10
  * Extract signatures from HTML files.
5
11
  * Focuses on id/class attributes, forms, and script tags.
@@ -33,7 +39,7 @@ function extract(src) {
33
39
  sigs.push(`data-${m[0].match(/data-(\w[\w-]*)/i)[1]}: ${m[1]}`);
34
40
  }
35
41
 
36
- return sigs.slice(0, 25);
42
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
37
43
  }
38
44
 
39
45
  module.exports = { extract };
@@ -1,6 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const { lineAt, withAnchor } = require('./line-anchor');
4
+ const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
5
+
6
+ // Ceilings sit above the default `maxSigsPerFile` so the configured budget
7
+ // governs output rather than a literal buried here, and omissions are disclosed
8
+ // — an undisclosed cap looks like a class that simply has eight methods (#576).
9
+ const MEMBER_LIMIT = 8;
10
+ const PER_FILE_LIMIT = 25;
4
11
 
5
12
  /**
6
13
  * Extract signatures from Kotlin source code.
@@ -35,7 +42,8 @@ function extract(src) {
35
42
  const block = extractBlock(stripped, bodyStart);
36
43
  sigs.push(withAnchor(`${m[1]} ${m[2]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
37
44
  for (const meth of extractMembers(block)) {
38
- sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
45
+ // The disclosure marker carries no offsets; anchor it at the class body.
46
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + (meth.declIdx || 0)), lineAt(stripped, bodyStart + (meth.endIdx || 0))));
39
47
  }
40
48
  }
41
49
 
@@ -48,7 +56,7 @@ function extract(src) {
48
56
  sigs.push(withAnchor(`${suspend}fun ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
49
57
  }
50
58
 
51
- return sigs.slice(0, 25);
59
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
52
60
  }
53
61
 
54
62
  function extractBlock(src, startIndex) {
@@ -75,7 +83,7 @@ function extractMembers(block) {
75
83
  endIdx: m.index + m[0].length,
76
84
  });
77
85
  }
78
- return members.slice(0, 8);
86
+ return capMembersWithNotice(members, MEMBER_LIMIT);
79
87
  }
80
88
 
81
89
  function normalizeParams(params) {
@@ -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,6 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const { lineAt, withAnchor } = require('./line-anchor');
4
+ const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
5
+
6
+ // Ceilings sit above the default `maxSigsPerFile` so the configured budget
7
+ // governs output rather than a literal buried here, and omissions are disclosed
8
+ // — an undisclosed cap looks like a class that simply has eight methods (#576).
9
+ const MEMBER_LIMIT = 8;
10
+ const PER_FILE_LIMIT = 25;
4
11
 
5
12
  /**
6
13
  * Extract signatures from PHP source code.
@@ -40,7 +47,8 @@ function extract(src) {
40
47
  const block = extractBlock(stripped, bodyStart);
41
48
  sigs.push(withAnchor(`${kind} ${m[1]}`, lineAt(stripped, m.index), lineAt(stripped, bodyStart + block.length)));
42
49
  for (const meth of extractMembers(block)) {
43
- sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + meth.declIdx), lineAt(stripped, bodyStart + meth.endIdx)));
50
+ // The disclosure marker carries no offsets; anchor it at the class body.
51
+ sigs.push(withAnchor(` ${meth.text}`, lineAt(stripped, bodyStart + (meth.declIdx || 0)), lineAt(stripped, bodyStart + (meth.endIdx || 0))));
44
52
  }
45
53
  }
46
54
 
@@ -52,7 +60,7 @@ function extract(src) {
52
60
  sigs.push(withAnchor(`function ${m[1]}(${normalizeParams(m[2])})${retStr}`, s, e));
53
61
  }
54
62
 
55
- return sigs.slice(0, 25);
63
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
56
64
  }
57
65
 
58
66
  function extractBlock(src, startIndex) {
@@ -80,7 +88,7 @@ function extractMembers(block) {
80
88
  endIdx: m.index + m[0].length,
81
89
  });
82
90
  }
83
- return members.slice(0, 8);
91
+ return capMembersWithNotice(members, MEMBER_LIMIT);
84
92
  }
85
93
 
86
94
  function normalizeParams(params) {
@@ -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 };
@@ -2,6 +2,11 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { lineAt } = require('./line-anchor');
5
+ const { capWithNotice } = require('../util/truncate');
6
+
7
+ // Ceiling sits above the default `maxSigsPerFile` so the configured budget
8
+ // governs output rather than a literal buried here, and omissions are disclosed (#576).
9
+ const PER_FILE_LIMIT = 30;
5
10
 
6
11
  /**
7
12
  * 1-based line of the last source line belonging to a top-level (indent 0)
@@ -136,7 +141,7 @@ function extract(src, filePath) {
136
141
  }
137
142
  }
138
143
 
139
- return sigs.slice(0, 30);
144
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
140
145
  }
141
146
 
142
147
  function extractClassMethods(stripped, startIndex) {
@@ -1,5 +1,11 @@
1
1
  'use strict';
2
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 = 30;
8
+
3
9
  /**
4
10
  * Extract signatures from R source code.
5
11
  *
@@ -38,7 +44,7 @@ function extract(src) {
38
44
  // ClassName <- R6::R6Class(...)
39
45
  const r6Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\s*\(/g;
40
46
  let m;
41
- while ((m = r6Re.exec(stripped)) !== null && sigs.length < 30) {
47
+ while ((m = r6Re.exec(stripped)) !== null) {
42
48
  const name = m[1];
43
49
  if (name.startsWith('.')) continue;
44
50
  const openIdx = r6Re.lastIndex - 1;
@@ -49,7 +55,6 @@ function extract(src) {
49
55
  sigs.push(`${name} <- R6Class("${classNameLit}")` + applyHint(docHints, name));
50
56
  for (const memberSig of extractListMethods(body, 8)) {
51
57
  sigs.push(' ' + memberSig);
52
- if (sigs.length >= 30) break;
53
58
  }
54
59
  consumedRanges.push([m.index, closeIdx]);
55
60
  r6Re.lastIndex = closeIdx;
@@ -59,7 +64,7 @@ function extract(src) {
59
64
  // ClassName <- new_class("ClassName", properties = list(...))
60
65
  const s7Classes = new Set();
61
66
  const s7Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\s*\(/g;
62
- while ((m = s7Re.exec(stripped)) !== null && sigs.length < 30) {
67
+ while ((m = s7Re.exec(stripped)) !== null) {
63
68
  const name = m[1];
64
69
  if (name.startsWith('.')) continue;
65
70
  const openIdx = s7Re.lastIndex - 1;
@@ -76,7 +81,7 @@ function extract(src) {
76
81
 
77
82
  // S7 method dispatch: `method(generic, ClassName) <- function(args)`
78
83
  const s7MethodRe = /^[ \t]*method\s*\(\s*([\w.]+)\s*,\s*([\w.]+)\s*\)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
79
- while ((m = s7MethodRe.exec(stripped)) !== null && sigs.length < 30) {
84
+ while ((m = s7MethodRe.exec(stripped)) !== null) {
80
85
  if (!s7Classes.has(m[2])) continue;
81
86
  const argsStart = s7MethodRe.lastIndex - 1;
82
87
  const args = readBalancedParens(stripped, argsStart);
@@ -89,7 +94,7 @@ function extract(src) {
89
94
  // Skip matches whose position falls inside an R6/S7 class body — those have
90
95
  // already been emitted as indented members.
91
96
  const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
92
- while ((m = funcRe.exec(stripped)) !== null && sigs.length < 30) {
97
+ while ((m = funcRe.exec(stripped)) !== null) {
93
98
  const name = m[1];
94
99
  if (name.startsWith('.')) continue;
95
100
  if (inAnyRange(m.index, consumedRanges)) continue;
@@ -101,19 +106,16 @@ function extract(src) {
101
106
 
102
107
  // ── S4 ────────────────────────────────────────────────────────────────────
103
108
  for (const sm of stripped.matchAll(/^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm)) {
104
- if (sigs.length >= 30) break;
105
109
  sigs.push(`setGeneric("${sm[1]}")`);
106
110
  }
107
111
  for (const sm of stripped.matchAll(/^[ \t]*setMethod\s*\(\s*["']([\w.]+)["']\s*,\s*["']([\w.]+)["']/gm)) {
108
- if (sigs.length >= 30) break;
109
112
  sigs.push(`setMethod("${sm[1]}", "${sm[2]}")`);
110
113
  }
111
114
  for (const sm of stripped.matchAll(/^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm)) {
112
- if (sigs.length >= 30) break;
113
115
  sigs.push(`setClass("${sm[1]}")`);
114
116
  }
115
117
 
116
- return sigs.slice(0, 30);
118
+ return capWithNotice(sigs, PER_FILE_LIMIT, 'signatures');
117
119
  }
118
120
 
119
121
  /**