sigmap 8.9.0 → 8.10.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,13 +11,13 @@ 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.9.0 | Benchmark: sigmap-v8.9-main (2026-07-06)
14
+ # Version: 8.10.0 | Benchmark: sigmap-v8.10-main (2026-07-09)
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.9-main, 2026-07-06)
20
+ ## Core metrics (benchmark: sigmap-v8.10-main, 2026-07-09)
21
21
 
22
22
  | Metric | Without SigMap | With SigMap |
23
23
  |--------|----------------|-------------|
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.9.0 | Benchmark: sigmap-v8.9-main (2026-07-06)
14
+ # Version: 8.10.0 | Benchmark: sigmap-v8.10-main (2026-07-09)
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,7 +23,7 @@ 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.9-main, 2026-07-06)
26
+ ## Core metrics (benchmark: sigmap-v8.10-main, 2026-07-09)
27
27
 
28
28
  - hit@5 retrieval: 87.8% vs 13.6% random baseline (6.5× lift)
29
29
  - Token reduction: 97.0% average across benchmark repos
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "8.9.0",
4
- "description": "97% token reduction for AI coding. Extracts function & class signatures with TF-IDF ranking to feed only the right files to Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP. Zero dependencies, runs offline via npx.",
3
+ "version": "8.10.0",
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": {
7
7
  ".": "./packages/core/index.js",
@@ -80,7 +80,13 @@
80
80
  "mcp",
81
81
  "function-signatures",
82
82
  "code-intelligence",
83
- "context-compression",
83
+ "ai-grounding",
84
+ "hallucination-detection",
85
+ "deterministic",
86
+ "verifiable-ai",
87
+ "code-signatures",
88
+ "ai-code-review",
89
+ "evidence-pack",
84
90
  "local-llm",
85
91
  "ollama",
86
92
  "ai-coding"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.9.0",
3
+ "version": "8.10.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.9.0",
3
+ "version": "8.10.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -26,8 +26,16 @@ const TIER_MOSTLY = 0.7;
26
26
 
27
27
  /**
28
28
  * Classify a file's base name (without extension) into a naming style.
29
+ *
30
+ * A single lowercase word (`user`, `index`, `loader`) is classified
31
+ * `single-word`, NOT `camelCase`: it has no case boundary or separator, so it is
32
+ * compatible with camelCase, kebab-case AND snake_case at once and expresses no
33
+ * distinguishable convention. `scoreConvention` treats it as style-neutral
34
+ * (excluded), so a repo of single-word files reports "unknown" rather than a
35
+ * spurious "100% camelCase".
36
+ *
29
37
  * @param {string} basename a file basename, e.g. "user-service.ts"
30
- * @returns {'PascalCase'|'camelCase'|'kebab-case'|'snake_case'|'other'}
38
+ * @returns {'PascalCase'|'camelCase'|'kebab-case'|'snake_case'|'single-word'|'other'}
31
39
  */
32
40
  function classifyNaming(basename) {
33
41
  let stem = String(basename || '');
@@ -38,7 +46,7 @@ function classifyNaming(basename) {
38
46
  if (/[_]/.test(stem) && /^[a-z0-9]+(?:_[a-z0-9]+)+$/.test(stem)) return 'snake_case';
39
47
  if (/^[A-Z][A-Za-z0-9]*$/.test(stem) && /[a-z]/.test(stem)) return 'PascalCase';
40
48
  if (/^[a-z][A-Za-z0-9]*$/.test(stem) && /[A-Z]/.test(stem)) return 'camelCase';
41
- if (/^[a-z][a-z0-9]*$/.test(stem)) return 'camelCase'; // single lowercase word
49
+ if (/^[a-z][a-z0-9]*$/.test(stem)) return 'single-word'; // style-neutral (no case boundary)
42
50
  return 'other';
43
51
  }
44
52
 
@@ -61,7 +69,9 @@ function scoreConvention(labels, refs) {
61
69
  let total = 0;
62
70
  for (let i = 0; i < all.length; i++) {
63
71
  const l = all[i];
64
- if (l == null || l === 'other') continue;
72
+ // 'other' = unclassifiable; 'single-word' = style-neutral. Both express no
73
+ // distinguishable convention and are excluded from the score.
74
+ if (l == null || l === 'other' || l === 'single-word') continue;
65
75
  total++;
66
76
  counts.set(l, (counts.get(l) || 0) + 1);
67
77
  if (refs && refs[i] != null) {
@@ -47,7 +47,10 @@ function buildFixList(cwd, files, conventions) {
47
47
  if (TEST_RE.test(f)) continue;
48
48
  const base = path.basename(f);
49
49
  const style = classifyNaming(base);
50
- if (style === 'other' || style === dominant) continue;
50
+ // Skip unclassifiable ('other') and style-neutral single-word names — a
51
+ // single lowercase word already satisfies any convention, so renaming it
52
+ // (e.g. user.js → user.js) is a no-op at best and noise at worst.
53
+ if (style === 'other' || style === 'single-word' || style === dominant) continue;
51
54
  const rel = path.relative(cwd, f).replace(/\\/g, '/');
52
55
  renames.push({ from: rel, to: _renamePath(rel, dominant), fromStyle: style });
53
56
  }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { lineAt, withAnchor } = require('./line-anchor');
4
+ const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
4
5
 
5
6
  /**
6
7
  * Extract signatures from JavaScript source code.
@@ -80,9 +81,8 @@ function extract(src) {
80
81
  anchors.push([startLn, fnEndLine(m.index + m[0].length, startLn)]);
81
82
  }
82
83
 
83
- return sigs
84
- .map((s, i) => (anchors[i] ? withAnchor(s, anchors[i][0], anchors[i][1]) : s))
85
- .slice(0, 25);
84
+ const withAnchors = sigs.map((s, i) => (anchors[i] ? withAnchor(s, anchors[i][0], anchors[i][1]) : s));
85
+ return capWithNotice(withAnchors, 25, 'signatures');
86
86
  }
87
87
 
88
88
  function extractBlock(src, startIndex) {
@@ -113,7 +113,7 @@ function extractClassMembers(block, returnHints) {
113
113
  const retStr = formatReturnHint(returnHints.get(m[1]));
114
114
  members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`, start, end });
115
115
  }
116
- return members.slice(0, 8);
116
+ return capMembersWithNotice(members, 8, 'methods');
117
117
  }
118
118
 
119
119
  function buildReturnHints(src) {
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { lineAt, withAnchor } = require('./line-anchor');
4
+ const { capWithNotice, capMembersWithNotice } = require('../util/truncate');
4
5
 
5
6
  /**
6
7
  * Extract signatures from TypeScript source code.
@@ -152,9 +153,8 @@ function extract(src) {
152
153
  }
153
154
  }
154
155
 
155
- return sigs
156
- .map((s, i) => (anchors[i] ? withAnchor(s, anchors[i][0], anchors[i][1]) : s))
157
- .slice(0, 35);
156
+ const withAnchors = sigs.map((s, i) => (anchors[i] ? withAnchor(s, anchors[i][0], anchors[i][1]) : s));
157
+ return capWithNotice(withAnchors, 35, 'signatures');
158
158
  }
159
159
 
160
160
  function extractBlock(src, startIndex) {
@@ -184,7 +184,7 @@ function extractInterfaceMembers(block) {
184
184
  const start = m.index + (m[0].length - m[0].replace(/^\s+/, '').length);
185
185
  members.push({ text: `${m[1]}(${normalizeParams(m[2])})`, start, end: m.index + m[0].length });
186
186
  }
187
- return members.slice(0, 8);
187
+ return capMembersWithNotice(members, 8, 'members');
188
188
  }
189
189
 
190
190
  const _CTRL_KEYWORDS = new Set(['if', 'for', 'while', 'switch', 'do', 'try', 'catch', 'finally', 'else', 'return']);
@@ -210,7 +210,7 @@ function extractClassMembers(block) {
210
210
  const retStr = retType ? ` → ${retType}` : '';
211
211
  members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(m[2])})${retStr}`, start, end });
212
212
  }
213
- return members.slice(0, 8);
213
+ return capMembersWithNotice(members, 8, 'methods');
214
214
  }
215
215
 
216
216
  function normalizeParams(params) {
@@ -31,20 +31,19 @@ const RB_EXTS = new Set(['.rb', '.rake']);
31
31
  const R_EXTS = new Set(['.r', '.R']);
32
32
 
33
33
  /**
34
- * Resolve a JS/TS relative import string to an absolute path in fileSet.
35
- * @param {string} dir - directory of the importing file
36
- * @param {string} importStr - raw import string (e.g. './utils', '../store')
34
+ * Probe an absolute base path for a JS/TS module file in fileSet, trying the
35
+ * usual extension and index-file candidates.
36
+ * @param {string} base - absolute path (no extension) to probe
37
37
  * @param {Set<string>} fileSet
38
38
  * @returns {string|null}
39
39
  */
40
- function resolveJsPath(dir, importStr, fileSet) {
41
- const base = path.resolve(dir, importStr);
40
+ function probeJs(base, fileSet) {
42
41
  const candidates = [
43
42
  base,
44
43
  base + '.ts', base + '.tsx',
45
44
  base + '.js', base + '.jsx', base + '.mjs', base + '.cjs',
46
- path.join(base, 'index.ts'),
47
- path.join(base, 'index.js'),
45
+ path.join(base, 'index.ts'), path.join(base, 'index.tsx'),
46
+ path.join(base, 'index.js'), path.join(base, 'index.jsx'),
48
47
  ];
49
48
  for (const c of candidates) {
50
49
  const normC = normalizePath(c);
@@ -53,6 +52,103 @@ function resolveJsPath(dir, importStr, fileSet) {
53
52
  return null;
54
53
  }
55
54
 
55
+ /**
56
+ * Resolve a JS/TS relative import string to an absolute path in fileSet.
57
+ * @param {string} dir - directory of the importing file
58
+ * @param {string} importStr - raw import string (e.g. './utils', '../store')
59
+ * @param {Set<string>} fileSet
60
+ * @returns {string|null}
61
+ */
62
+ function resolveJsPath(dir, importStr, fileSet) {
63
+ return probeJs(path.resolve(dir, importStr), fileSet);
64
+ }
65
+
66
+ /**
67
+ * Strip comments and trailing commas so a tsconfig/jsconfig (JSONC) parses.
68
+ * Deliberately conservative — leaves string contents alone.
69
+ */
70
+ function stripJsonc(src) {
71
+ let out = '';
72
+ let inStr = false, quote = '', inLine = false, inBlock = false;
73
+ for (let i = 0; i < src.length; i++) {
74
+ const c = src[i], n = src[i + 1];
75
+ if (inLine) { if (c === '\n') { inLine = false; out += c; } continue; }
76
+ if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++; } continue; }
77
+ if (inStr) { out += c; if (c === '\\') { out += (n || ''); i++; } else if (c === quote) inStr = false; continue; }
78
+ if (c === '"' || c === "'") { inStr = true; quote = c; out += c; continue; }
79
+ if (c === '/' && n === '/') { inLine = true; i++; continue; }
80
+ if (c === '/' && n === '*') { inBlock = true; i++; continue; }
81
+ out += c;
82
+ }
83
+ // remove trailing commas before } or ]
84
+ return out.replace(/,(\s*[}\]])/g, '$1');
85
+ }
86
+
87
+ /**
88
+ * Load the JS/TS path-alias map from tsconfig.json / jsconfig.json.
89
+ * Resolves `compilerOptions.paths` and `baseUrl` into absolute target bases so
90
+ * bare/aliased imports (e.g. `@/utils`, `components/Button`) can be resolved to
91
+ * on-disk files. Returns null when no config or no aliasing is configured.
92
+ *
93
+ * @param {string} cwd
94
+ * @returns {{ baseUrl: string|null, entries: Array<{prefix:string,wildcard:boolean,targets:string[]}> }|null}
95
+ */
96
+ function loadAliasMap(cwd) {
97
+ if (!cwd) return null;
98
+ for (const name of ['tsconfig.json', 'jsconfig.json']) {
99
+ let json;
100
+ try { json = JSON.parse(stripJsonc(fs.readFileSync(path.join(cwd, name), 'utf8'))); }
101
+ catch (_) { continue; }
102
+ const co = (json && json.compilerOptions) || {};
103
+ const baseUrl = co.baseUrl ? path.resolve(cwd, co.baseUrl) : null;
104
+ const base = baseUrl || cwd;
105
+ const entries = [];
106
+ for (const [pattern, targets] of Object.entries(co.paths || {})) {
107
+ const wildcard = pattern.includes('*');
108
+ const prefix = pattern.replace(/\*.*$/, '');
109
+ const tgs = (Array.isArray(targets) ? targets : [])
110
+ .map((t) => path.resolve(base, String(t).replace(/\*.*$/, '')));
111
+ if (tgs.length) entries.push({ prefix, wildcard, targets: tgs });
112
+ }
113
+ if (baseUrl || entries.length) return { baseUrl, entries };
114
+ return null;
115
+ }
116
+ return null;
117
+ }
118
+
119
+ /**
120
+ * Resolve a non-relative JS/TS import specifier through the alias map.
121
+ * @param {string} spec - e.g. '@/utils', '@app/Button', 'components/Nav'
122
+ * @param {object|null} aliasMap - from loadAliasMap
123
+ * @param {Set<string>} fileSet
124
+ * @returns {string|null}
125
+ */
126
+ function resolveAlias(spec, aliasMap, fileSet) {
127
+ if (!aliasMap) return null;
128
+ for (const e of aliasMap.entries) {
129
+ if (e.wildcard) {
130
+ if (spec.startsWith(e.prefix)) {
131
+ const rest = spec.slice(e.prefix.length);
132
+ for (const t of e.targets) {
133
+ const r = probeJs(rest ? path.join(t, rest) : t, fileSet);
134
+ if (r) return r;
135
+ }
136
+ }
137
+ } else if (spec === e.prefix) {
138
+ for (const t of e.targets) {
139
+ const r = probeJs(t, fileSet);
140
+ if (r) return r;
141
+ }
142
+ }
143
+ }
144
+ // Bare import resolved from baseUrl (tsconfig baseUrl without an explicit alias).
145
+ if (aliasMap.baseUrl) {
146
+ const r = probeJs(path.join(aliasMap.baseUrl, spec), fileSet);
147
+ if (r) return r;
148
+ }
149
+ return null;
150
+ }
151
+
56
152
  /**
57
153
  * Resolve an R `source(...)` argument to an absolute path in fileSet.
58
154
  * Tries the dir-relative path first, then a cwd-relative path so that
@@ -98,21 +194,29 @@ function extractFileDeps(filePath, content, fileSet, cwd, ctx) {
98
194
 
99
195
  // ── JS / TS ───────────────────────────────────────────────────────────────
100
196
  if (JS_EXTS.has(ext)) {
197
+ const aliasMap = ctx && ctx.aliasMap;
198
+ // Resolve any specifier: relative → dir-relative; otherwise via tsconfig/
199
+ // jsconfig path aliases + baseUrl. Bare npm packages (react, lodash) fall
200
+ // through to null because they are not in fileSet, so no false edges.
201
+ const resolveSpec = (spec) => spec.startsWith('.')
202
+ ? resolveJsPath(dir, spec, fileSet)
203
+ : resolveAlias(spec, aliasMap, fileSet);
204
+
101
205
  const stripped = content
102
206
  .replace(/\/\/.*$/gm, '')
103
207
  .replace(/\/\*[\s\S]*?\*\//g, '');
104
208
 
105
- // ES imports: import ... from './foo' or import './side-effect'
106
- const reEs = /(?:^|[\r\n])\s*import\s+(?:[^'";\r\n]*?\s+from\s+)?['"](\.[^'"]+)['"]/g;
107
209
  let m;
210
+ // ES imports: import ... from 'x' | import 'x' | export ... from 'x'
211
+ const reEs = /(?:^|[\r\n])\s*(?:import|export)\s+(?:[^'";\r\n]*?\s+from\s+)?['"]([^'"]+)['"]/g;
108
212
  while ((m = reEs.exec(stripped)) !== null) {
109
- const r = resolveJsPath(dir, m[1], fileSet);
213
+ const r = resolveSpec(m[1]);
110
214
  if (r) found.push(r);
111
215
  }
112
- // CommonJS: require('./foo')
113
- const reCjs = /\brequire\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
114
- while ((m = reCjs.exec(stripped)) !== null) {
115
- const r = resolveJsPath(dir, m[1], fileSet);
216
+ // CommonJS require('x') and dynamic import('x').
217
+ const reCall = /\b(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
218
+ while ((m = reCall.exec(stripped)) !== null) {
219
+ const r = resolveSpec(m[1]);
116
220
  if (r) found.push(r);
117
221
  }
118
222
  }
@@ -284,6 +388,10 @@ function build(files, cwd, ctx) {
284
388
  const fileSet = new Set(files.map((f) => path.resolve(f)));
285
389
  // Create a normalized version for cross-platform case-insensitive lookups
286
390
  const fileSetNormalized = new Set([...fileSet].map(normalizePath));
391
+ // Resolve the JS/TS path-alias map once (tsconfig/jsconfig paths + baseUrl),
392
+ // unless a caller supplied one explicitly via ctx.
393
+ const aliasMap = (ctx && 'aliasMap' in ctx) ? ctx.aliasMap : loadAliasMap(cwd);
394
+ const effectiveCtx = Object.assign({}, ctx, { aliasMap });
287
395
  const forward = new Map();
288
396
  const reverse = new Map();
289
397
 
@@ -304,7 +412,7 @@ function build(files, cwd, ctx) {
304
412
  }
305
413
 
306
414
  const normFilePath = normalizePath(filePath);
307
- const deps = extractFileDeps(filePath, content, fileSetNormalized, cwd, ctx);
415
+ const deps = extractFileDeps(filePath, content, fileSetNormalized, cwd, effectiveCtx);
308
416
  if (deps.length > 0) {
309
417
  forward.set(normFilePath, deps);
310
418
  for (const dep of deps) {
@@ -383,4 +491,4 @@ function buildFromCwd(cwd, opts) {
383
491
  return build(files, cwd, ctx);
384
492
  }
385
493
 
386
- module.exports = { build, buildFromCwd, extractFileDeps, normalizePath };
494
+ module.exports = { build, buildFromCwd, extractFileDeps, normalizePath, loadAliasMap, resolveAlias };
@@ -186,11 +186,11 @@ function formatImpact(result) {
186
186
  lines.push('');
187
187
 
188
188
  if (result.direct.length === 0 && result.transitive.length === 0) {
189
- lines.push('_No files import this filezero blast radius._');
189
+ lines.push('_No importers found via relative + aliased imports (lower bound dynamic/computed imports are not tracked)._');
190
190
  return lines.join('\n');
191
191
  }
192
192
 
193
- lines.push(`**Total impacted files:** ${result.totalImpact}`);
193
+ lines.push(`**Total impacted files:** ${result.totalImpact} _(lower bound — resolves relative + tsconfig/jsconfig-aliased imports)_`);
194
194
  lines.push('');
195
195
 
196
196
  if (result.direct.length > 0) {