sigmap 7.22.1 → 7.22.2

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,15 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [7.22.2] — 2026-06-19
14
+
15
+ Patch release — clears the two remaining `verify-ai-output` false-positive classes surfaced by the §9 ablation.
16
+
17
+ ### Fixed
18
+ - **`verify-ai-output` no longer flags camelCase placeholders or documentation-placeholder imports (#350):** continuing from #347, the Hallucination Guard now also skips camelCase/Pascal placeholder filenames (`myExample.js`, `exampleConfig.ts`) via a case-boundary rule that still flags ordinary words (`resample.js`), and the `fake-import` detector skips obvious documentation placeholders (`@scope/utils`, `some-module`, `./local-file`, `./path/to/…`) while still flagging genuine missing packages and unresolved relative imports. In the §9 re-run after #347, grounding genuinely fixed 6 mis-path flags but the guard re-flagged 4 illustrative tokens (net +2); suppressing those exposes the true grounding signal (on those outputs, with-grounding flags drop 10 → 6, delta +2 → +9). The bundled `src/verify/parsers` and `src/verify/hallucination-guard` factories were regenerated for standalone-binary parity.
19
+
20
+ ---
21
+
13
22
  ## [7.22.1] — 2026-06-18
14
23
 
15
24
  Patch release — hardens the `verify-ai-output` file-path extractor against the dominant false-positive class.
package/README.md CHANGED
@@ -88,7 +88,7 @@ Ask → Rank → Context → Validate → Judge → Learn
88
88
 
89
89
  ```
90
90
  Benchmark : sigmap-v7.0-main (21 repositories, including R language)
91
- Date : 2026-06-18
91
+ Date : 2026-06-19
92
92
 
93
93
  Hit@5 : 75.6% (baseline 13.6% — 5.6× lift)
94
94
  Token reduction: 97.0% (across 21 repos)
package/gen-context.js CHANGED
@@ -7931,7 +7931,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
7931
7931
 
7932
7932
  const SERVER_INFO = {
7933
7933
  name: 'sigmap',
7934
- version: '7.22.1',
7934
+ version: '7.22.2',
7935
7935
  description: 'SigMap MCP server — code signatures on demand',
7936
7936
  };
7937
7937
 
@@ -12787,7 +12787,10 @@ __factories["./src/verify/parsers"] = function(module, exports) {
12787
12787
 
12788
12788
  // Illustrative placeholder names the model writes in prose, not repo claims:
12789
12789
  // e.g. example.js, minimal-example.js, sample.ts, demo.js, placeholder.js.
12790
- const PLACEHOLDER_RE = /(?:^|[-_.])(?:example|sample|demo|placeholder)(?:[-_.]|$)/i;
12790
+ const PLACEHOLDER_RE = /(?:^|[-_.])(?:example|sample|demo|placeholder)(?:[-_.]|s?$)/i;
12791
+ // camelCase / Pascal placeholders: myExample.js, exampleConfig.js, fooSample.ts.
12792
+ // Requires a case boundary so ordinary words (resample.js) are NOT suppressed.
12793
+ const PLACEHOLDER_CAMEL_RE = /(?:^|[a-z])(?:Example|Sample|Demo|Placeholder)|(?:^|[-_.])(?:example|sample|demo|placeholder)(?=[A-Z])/;
12791
12794
 
12792
12795
  /**
12793
12796
  * Extract fenced code blocks.
@@ -12843,7 +12846,7 @@ __factories["./src/verify/parsers"] = function(module, exports) {
12843
12846
  if (!hasSlash && !KNOWN_CODE_EXT.has(ext)) continue;
12844
12847
  if (LIBRARY_TOKENS.has(p.toLowerCase())) continue;
12845
12848
  const base = p.split('/').pop();
12846
- if (PLACEHOLDER_RE.test(base)) continue;
12849
+ if (PLACEHOLDER_RE.test(base) || PLACEHOLDER_CAMEL_RE.test(base)) continue;
12847
12850
  if (!seen.has(p)) seen.set(p, i + 1);
12848
12851
  }
12849
12852
  }
@@ -13293,317 +13296,326 @@ module.exports = { renderReportHtml, renderReportMarkdown, escapeHtml };
13293
13296
 
13294
13297
  // ── ./src/verify/hallucination-guard ──
13295
13298
  __factories["./src/verify/hallucination-guard"] = function(module, exports) {
13296
- 'use strict';
13299
+
13300
+ /**
13301
+ * Hallucination Guard — deterministic core (Reliable MVP, v6.15.0).
13302
+ *
13303
+ * Given the text of an AI answer, flag claims that do not match the repo:
13304
+ * - fake-file : a referenced path is not on disk
13305
+ * - fake-test-file : a referenced *test* path is not on disk (sub-type)
13306
+ * - fake-import : a relative import does not resolve; a bare import is
13307
+ * absent from package.json deps (builtins allow-listed)
13308
+ * - fake-symbol : a called function/class is absent from the symbol index
13309
+ * - fake-npm-script: `npm run X` where X is not a package.json script
13310
+ *
13311
+ * Each issue carries a `confidence` (detection certainty) and, where a near
13312
+ * match exists, a heuristic `suggestion` ("Did you mean …?"). No network, no
13313
+ * LLM. Reuses SigMap primitives (buildSigIndex) but every external dependency
13314
+ * is injectable via `opts` so the core stays unit-testable.
13315
+ */
13297
13316
 
13298
- /**
13299
- * Hallucination Guard — deterministic core (Reliable MVP, v6.15.0).
13300
- *
13301
- * Given the text of an AI answer, flag claims that do not match the repo:
13302
- * - fake-file : a referenced path is not on disk
13303
- * - fake-test-file : a referenced *test* path is not on disk (sub-type)
13304
- * - fake-import : a relative import does not resolve; a bare import is
13305
- * absent from package.json deps (builtins allow-listed)
13306
- * - fake-symbol : a called function/class is absent from the symbol index
13307
- * - fake-npm-script: `npm run X` where X is not a package.json script
13308
- *
13309
- * Each issue carries a `confidence` (detection certainty) and, where a near
13310
- * match exists, a heuristic `suggestion` ("Did you mean …?"). No network, no
13311
- * LLM. Reuses SigMap primitives (buildSigIndex) but every external dependency
13312
- * is injectable via `opts` so the core stays unit-testable.
13313
- */
13317
+ const fs = require('fs');
13318
+ const path = require('path');
13319
+ const parsers = __require('./src/verify/parsers');
13320
+ const { closestMatch, buildSymbolCandidates, formatSuggestion } = __require('./src/verify/closest-match');
13321
+
13322
+ // A path that looks like a test file (JS/TS spec/test, Python test_/_test, or
13323
+ // a tests/__tests__ directory). Used to flag fake-test-file separately.
13324
+ const TEST_PATH_RE = /(?:\.(?:test|spec)\.[mc]?[jt]sx?$)|(?:(?:^|\/)__tests__\/)|(?:(?:^|\/)test_[^/]+\.py$)|(?:_test\.py$)|(?:(?:^|\/)tests?\/)/i;
13325
+ function isTestPath(p) { return TEST_PATH_RE.test(p); }
13326
+
13327
+ const NODE_BUILTINS = new Set([
13328
+ 'fs', 'path', 'os', 'util', 'events', 'stream', 'http', 'https', 'crypto',
13329
+ 'child_process', 'url', 'querystring', 'assert', 'zlib', 'readline', 'net',
13330
+ 'tls', 'dns', 'buffer', 'process', 'vm', 'module', 'console', 'timers',
13331
+ 'string_decoder', 'perf_hooks', 'worker_threads', 'cluster', 'dgram', 'v8',
13332
+ 'tty', 'repl', 'async_hooks', 'inspector', 'fs/promises', 'path/posix',
13333
+ ]);
13314
13334
 
13315
- const fs = require('fs');
13316
- const path = require('path');
13317
- const parsers = __require('./src/verify/parsers');
13318
- const { closestMatch, buildSymbolCandidates, formatSuggestion } = __require('./src/verify/closest-match');
13319
-
13320
- // A path that looks like a test file (JS/TS spec/test, Python test_/_test, or
13321
- // a tests/__tests__ directory). Used to flag fake-test-file separately.
13322
- const TEST_PATH_RE = /(?:\.(?:test|spec)\.[mc]?[jt]sx?$)|(?:(?:^|\/)__tests__\/)|(?:(?:^|\/)test_[^/]+\.py$)|(?:_test\.py$)|(?:(?:^|\/)tests?\/)/i;
13323
- function isTestPath(p) { return TEST_PATH_RE.test(p); }
13324
-
13325
- const NODE_BUILTINS = new Set([
13326
- 'fs', 'path', 'os', 'util', 'events', 'stream', 'http', 'https', 'crypto',
13327
- 'child_process', 'url', 'querystring', 'assert', 'zlib', 'readline', 'net',
13328
- 'tls', 'dns', 'buffer', 'process', 'vm', 'module', 'console', 'timers',
13329
- 'string_decoder', 'perf_hooks', 'worker_threads', 'cluster', 'dgram', 'v8',
13330
- 'tty', 'repl', 'async_hooks', 'inspector', 'fs/promises', 'path/posix',
13331
- ]);
13332
-
13333
- const PY_BUILTINS = new Set([
13334
- 'os', 'sys', 're', 'json', 'math', 'typing', 'collections', 'itertools',
13335
- 'functools', 'datetime', 'pathlib', 'subprocess', 'abc', 'dataclasses',
13336
- 'enum', 'io', 'time', 'random', 'logging', 'argparse', 'unittest', 'asyncio',
13337
- 'copy', 'hashlib', 'threading', 'string', 'csv', 'glob', 'shutil', 'tempfile',
13338
- ]);
13339
-
13340
- const LANG_GLOBALS = new Set([
13341
- // JS
13342
- 'console', 'require', 'module', 'exports', 'process', 'Object', 'Array',
13343
- 'String', 'Number', 'Boolean', 'Math', 'JSON', 'Date', 'Promise', 'Map',
13344
- 'Set', 'WeakMap', 'WeakSet', 'RegExp', 'Error', 'Symbol', 'parseInt',
13345
- 'parseFloat', 'isNaN', 'setTimeout', 'setInterval', 'clearTimeout', 'fetch',
13346
- 'Buffer', 'Function', 'eval', 'encodeURIComponent', 'decodeURIComponent',
13347
- // Python
13348
- 'print', 'len', 'range', 'str', 'int', 'float', 'dict', 'list', 'tuple',
13349
- 'set', 'bool', 'open', 'enumerate', 'zip', 'map', 'filter', 'sorted',
13350
- 'sum', 'min', 'max', 'abs', 'isinstance', 'super', 'type', 'getattr',
13351
- 'setattr', 'hasattr',
13352
- ]);
13353
-
13354
- const REL_EXTS = ['', '.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs', '.json', '.py', '.r', '.R', '.vue'];
13355
- const REL_INDEX = ['index.js', 'index.ts', 'index.tsx', 'index.jsx', '__init__.py'];
13335
+ const PY_BUILTINS = new Set([
13336
+ 'os', 'sys', 're', 'json', 'math', 'typing', 'collections', 'itertools',
13337
+ 'functools', 'datetime', 'pathlib', 'subprocess', 'abc', 'dataclasses',
13338
+ 'enum', 'io', 'time', 'random', 'logging', 'argparse', 'unittest', 'asyncio',
13339
+ 'copy', 'hashlib', 'threading', 'string', 'csv', 'glob', 'shutil', 'tempfile',
13340
+ ]);
13356
13341
 
13357
- /**
13358
- * Build the set of known symbol identifiers from the SigMap signature index,
13359
- * plus `{ name, file, line }` candidates (for closest-match suggestions).
13360
- */
13361
- function buildSymbolSet(cwd) {
13362
- const set = new Set();
13363
- let fileKeys = [];
13364
- let symbolCandidates = [];
13365
- try {
13366
- const { buildSigIndex } = __require('./src/retrieval/ranker');
13367
- const idx = buildSigIndex(cwd);
13368
- fileKeys = [...idx.keys()];
13369
- for (const sigs of idx.values()) {
13370
- for (const sig of sigs) {
13371
- const cleaned = String(sig).replace(/\s*:\d+(?:-\d+)?\s*$/, '');
13372
- const ids = cleaned.match(/[A-Za-z_$][\w$]*/g) || [];
13373
- for (const id of ids) set.add(id);
13374
- }
13375
- }
13376
- symbolCandidates = buildSymbolCandidates(idx);
13377
- } catch (_) {}
13378
- return { set, fileKeys, symbolCandidates };
13379
- }
13342
+ const LANG_GLOBALS = new Set([
13343
+ // JS
13344
+ 'console', 'require', 'module', 'exports', 'process', 'Object', 'Array',
13345
+ 'String', 'Number', 'Boolean', 'Math', 'JSON', 'Date', 'Promise', 'Map',
13346
+ 'Set', 'WeakMap', 'WeakSet', 'RegExp', 'Error', 'Symbol', 'parseInt',
13347
+ 'parseFloat', 'isNaN', 'setTimeout', 'setInterval', 'clearTimeout', 'fetch',
13348
+ 'Buffer', 'Function', 'eval', 'encodeURIComponent', 'decodeURIComponent',
13349
+ // Python
13350
+ 'print', 'len', 'range', 'str', 'int', 'float', 'dict', 'list', 'tuple',
13351
+ 'set', 'bool', 'open', 'enumerate', 'zip', 'map', 'filter', 'sorted',
13352
+ 'sum', 'min', 'max', 'abs', 'isinstance', 'super', 'type', 'getattr',
13353
+ 'setattr', 'hasattr',
13354
+ ]);
13380
13355
 
13381
- /** Load declared dependency names from package.json. */
13382
- function loadDeps(cwd) {
13383
- const deps = new Set();
13384
- let hasPkg = false;
13385
- try {
13386
- const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
13387
- hasPkg = true;
13388
- for (const k of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
13389
- if (pkg[k] && typeof pkg[k] === 'object') {
13390
- for (const name of Object.keys(pkg[k])) deps.add(name);
13391
- }
13392
- }
13393
- } catch (_) {}
13394
- return { deps, hasPkg };
13395
- }
13356
+ const REL_EXTS = ['', '.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs', '.json', '.py', '.r', '.R', '.vue'];
13357
+ const REL_INDEX = ['index.js', 'index.ts', 'index.tsx', 'index.jsx', '__init__.py'];
13396
13358
 
13397
- /** Load the set of npm script names declared in package.json. */
13398
- function loadScripts(cwd) {
13399
- const scripts = new Set();
13400
- try {
13401
- const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
13402
- if (pkg.scripts && typeof pkg.scripts === 'object') {
13403
- for (const name of Object.keys(pkg.scripts)) scripts.add(name);
13404
- }
13405
- } catch (_) {}
13406
- return scripts;
13407
- }
13359
+ // Obvious documentation-placeholder imports the model writes in illustrative
13360
+ // snippets — not real dependency claims. e.g. @scope/utils, some-module, ./local-file.
13361
+ const PLACEHOLDER_IMPORT_RE = new RegExp([
13362
+ '^@(?:scope|org|your-org|my-org|company|example)(?:/|$)', // @scope/utils
13363
+ '(?:^|/)(?:some|your|my)-(?:module|package|lib|component|file|dep)(?:$|/)', // some-module
13364
+ '(?:^|/)(?:local-file|your-file|my-file|module-name|package-name|your-package|example-package)(?:$|/)',
13365
+ '(?:^|/)path/to/', // ./path/to/x
13366
+ ].join('|'), 'i');
13408
13367
 
13409
- /** Default file-existence check: resolve a referenced path against cwd. */
13410
- function defaultFileExists(cwd, ref) {
13411
- const clean = ref.replace(/^\.\//, '');
13412
- for (const c of [path.resolve(cwd, clean), path.resolve(cwd, ref)]) {
13368
+ /**
13369
+ * Build the set of known symbol identifiers from the SigMap signature index,
13370
+ * plus `{ name, file, line }` candidates (for closest-match suggestions).
13371
+ */
13372
+ function buildSymbolSet(cwd) {
13373
+ const set = new Set();
13374
+ let fileKeys = [];
13375
+ let symbolCandidates = [];
13413
13376
  try {
13414
- if (fs.existsSync(c)) return true;
13377
+ const { buildSigIndex } = __require('./src/retrieval/ranker');
13378
+ const idx = buildSigIndex(cwd);
13379
+ fileKeys = [...idx.keys()];
13380
+ for (const sigs of idx.values()) {
13381
+ for (const sig of sigs) {
13382
+ const cleaned = String(sig).replace(/\s*:\d+(?:-\d+)?\s*$/, '');
13383
+ const ids = cleaned.match(/[A-Za-z_$][\w$]*/g) || [];
13384
+ for (const id of ids) set.add(id);
13385
+ }
13386
+ }
13387
+ symbolCandidates = buildSymbolCandidates(idx);
13415
13388
  } catch (_) {}
13389
+ return { set, fileKeys, symbolCandidates };
13416
13390
  }
13417
- return false;
13418
- }
13419
13391
 
13420
- /** Default relative-import resolver: fs candidates + basename match in index. */
13421
- function defaultRelativeResolvable(cwd, mod, fileBasenames) {
13422
- const base = path.resolve(cwd, mod);
13423
- for (const e of REL_EXTS) {
13392
+ /** Load declared dependency names from package.json. */
13393
+ function loadDeps(cwd) {
13394
+ const deps = new Set();
13395
+ let hasPkg = false;
13424
13396
  try {
13425
- if (fs.existsSync(base + e)) return true;
13397
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
13398
+ hasPkg = true;
13399
+ for (const k of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
13400
+ if (pkg[k] && typeof pkg[k] === 'object') {
13401
+ for (const name of Object.keys(pkg[k])) deps.add(name);
13402
+ }
13403
+ }
13426
13404
  } catch (_) {}
13405
+ return { deps, hasPkg };
13427
13406
  }
13428
- for (const idx of REL_INDEX) {
13407
+
13408
+ /** Load the set of npm script names declared in package.json. */
13409
+ function loadScripts(cwd) {
13410
+ const scripts = new Set();
13429
13411
  try {
13430
- if (fs.existsSync(path.join(base, idx))) return true;
13412
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
13413
+ if (pkg.scripts && typeof pkg.scripts === 'object') {
13414
+ for (const name of Object.keys(pkg.scripts)) scripts.add(name);
13415
+ }
13431
13416
  } catch (_) {}
13417
+ return scripts;
13432
13418
  }
13433
- // Fall back to basename match against the indexed file set (the answer's
13434
- // import is relative to a file we cannot know, so a name match is enough
13435
- // to avoid false positives).
13436
- const wantBase = path.basename(mod).replace(/\.[^.]+$/, '').toLowerCase();
13437
- return fileBasenames.has(wantBase);
13438
- }
13439
-
13440
- /**
13441
- * Verify an AI answer against the repository.
13442
- *
13443
- * Each issue has the shape:
13444
- * { type, value, line, location, message, confidence, suggestion }
13445
- * where `confidence` is the *detection* certainty ('high' for path/dep/script
13446
- * checks, 'medium' for symbol checks) and `suggestion` is a heuristic
13447
- * closest-match hint (or null).
13448
- *
13449
- * @param {string} answerText
13450
- * @param {string} cwd
13451
- * @param {object} [opts]
13452
- * @param {Set<string>} [opts.symbolSet] override known symbols
13453
- * @param {Array} [opts.symbolCandidates] override { name, file, line } list
13454
- * @param {Array<string>} [opts.fileCandidates] override repo file paths (suggestions)
13455
- * @param {Set<string>} [opts.deps] override package deps
13456
- * @param {Set<string>} [opts.scripts] override package.json script names
13457
- * @param {boolean} [opts.hasPkg] whether a package.json exists
13458
- * @param {(ref: string) => boolean} [opts.fileExists] override file check
13459
- * @param {(mod: string) => boolean} [opts.relativeResolvable] override rel-import check
13460
- * @returns {{ issues: object[], summary: object }}
13461
- */
13462
- function verify(answerText, cwd, opts = {}) {
13463
- let symbolSet = opts.symbolSet;
13464
- let fileBasenames = opts.fileBasenames;
13465
- let symbolCandidates = opts.symbolCandidates || [];
13466
- let fileCandidates = opts.fileCandidates || [];
13467
- if (!symbolSet) {
13468
- const built = buildSymbolSet(cwd);
13469
- symbolSet = built.set;
13470
- fileBasenames = new Set(built.fileKeys.map(
13471
- (k) => path.basename(k).replace(/\.[^.]+$/, '').toLowerCase()
13472
- ));
13473
- symbolCandidates = built.symbolCandidates;
13474
- fileCandidates = built.fileKeys;
13475
- }
13476
- if (!fileBasenames) fileBasenames = new Set();
13477
-
13478
- let deps = opts.deps;
13479
- let hasPkg = opts.hasPkg;
13480
- if (!deps) {
13481
- const loaded = loadDeps(cwd);
13482
- deps = loaded.deps;
13483
- if (hasPkg === undefined) hasPkg = loaded.hasPkg;
13484
- }
13485
- const scripts = opts.scripts || (hasPkg ? loadScripts(cwd) : new Set());
13486
-
13487
- const fileExists = opts.fileExists || ((ref) => defaultFileExists(cwd, ref));
13488
- const relativeResolvable = opts.relativeResolvable
13489
- || ((mod) => defaultRelativeResolvable(cwd, mod, fileBasenames));
13490
-
13491
- // Pre-derive basename candidates for file suggestions (compare on basename so
13492
- // a wrong directory still surfaces the right file).
13493
- const fileBasenameCandidates = fileCandidates.map((f) => ({ name: path.basename(f), file: f }));
13494
-
13495
- const issues = [];
13496
- const dedupe = new Set();
13497
- const add = (issue) => {
13498
- const key = `${issue.type}::${issue.value}`;
13499
- if (dedupe.has(key)) return;
13500
- dedupe.add(key);
13501
- if (!('suggestion' in issue)) issue.suggestion = null;
13502
- issue.location = `L${issue.line}`;
13503
- issues.push(issue);
13504
- };
13505
13419
 
13506
- // 1. fake-file / fake-test-file
13507
- for (const { path: p, line } of parsers.extractFilePaths(answerText)) {
13508
- if (fileExists(p)) continue;
13509
- const isTest = isTestPath(p);
13510
- const match = closestMatch(path.basename(p), fileBasenameCandidates, { minLen: 4 });
13511
- add({
13512
- type: isTest ? 'fake-test-file' : 'fake-file',
13513
- value: p,
13514
- line,
13515
- message: `${isTest ? 'Test file' : 'File'} not found on disk: ${p}`,
13516
- confidence: 'high',
13517
- suggestion: match ? formatSuggestion(match, false) : null,
13518
- });
13420
+ /** Default file-existence check: resolve a referenced path against cwd. */
13421
+ function defaultFileExists(cwd, ref) {
13422
+ const clean = ref.replace(/^\.\//, '');
13423
+ for (const c of [path.resolve(cwd, clean), path.resolve(cwd, ref)]) {
13424
+ try {
13425
+ if (fs.existsSync(c)) return true;
13426
+ } catch (_) {}
13427
+ }
13428
+ return false;
13519
13429
  }
13520
13430
 
13521
- // 2. fake-import
13522
- for (const imp of parsers.extractImports(answerText)) {
13523
- if (imp.relative) {
13524
- if (!relativeResolvable(imp.module)) {
13525
- add({ type: 'fake-import', value: imp.module, line: imp.line, message: `Import does not resolve: ${imp.module}`, confidence: 'high' });
13526
- }
13527
- continue;
13431
+ /** Default relative-import resolver: fs candidates + basename match in index. */
13432
+ function defaultRelativeResolvable(cwd, mod, fileBasenames) {
13433
+ const base = path.resolve(cwd, mod);
13434
+ for (const e of REL_EXTS) {
13435
+ try {
13436
+ if (fs.existsSync(base + e)) return true;
13437
+ } catch (_) {}
13528
13438
  }
13529
- // Bare module — only verifiable for JS when a package.json exists.
13530
- const top = imp.module.split('/')[0];
13531
- if (imp.kind === 'js') {
13532
- if (!hasPkg) continue;
13533
- if (NODE_BUILTINS.has(imp.module) || NODE_BUILTINS.has(top)) continue;
13534
- if (top.startsWith('@')) {
13535
- const scoped = imp.module.split('/').slice(0, 2).join('/');
13536
- if (deps.has(scoped) || deps.has(imp.module)) continue;
13537
- } else if (deps.has(top) || deps.has(imp.module)) {
13538
- continue;
13539
- }
13540
- const match = closestMatch(top, [...deps], { minLen: 3 });
13541
- add({
13542
- type: 'fake-import',
13543
- value: imp.module,
13544
- line: imp.line,
13545
- message: `Package not in dependencies: ${imp.module}`,
13546
- confidence: 'high',
13547
- suggestion: match ? formatSuggestion({ name: match.name }, false) : null,
13548
- });
13439
+ for (const idx of REL_INDEX) {
13440
+ try {
13441
+ if (fs.existsSync(path.join(base, idx))) return true;
13442
+ } catch (_) {}
13549
13443
  }
13550
- // Python bare imports: stdlib is unbounded offline skip to keep precision.
13444
+ // Fall back to basename match against the indexed file set (the answer's
13445
+ // import is relative to a file we cannot know, so a name match is enough
13446
+ // to avoid false positives).
13447
+ const wantBase = path.basename(mod).replace(/\.[^.]+$/, '').toLowerCase();
13448
+ return fileBasenames.has(wantBase);
13551
13449
  }
13552
13450
 
13553
- // 3. fake-symbol
13554
- if (symbolSet.size > 0) {
13555
- for (const { name, line } of parsers.extractSymbols(answerText)) {
13556
- if (symbolSet.has(name)) continue;
13557
- if (LANG_GLOBALS.has(name) || NODE_BUILTINS.has(name) || PY_BUILTINS.has(name)) continue;
13558
- const match = closestMatch(name, symbolCandidates, { minLen: 4 });
13559
- add({
13560
- type: 'fake-symbol',
13561
- value: name,
13562
- line,
13563
- message: `Symbol not found in repo index: ${name}()`,
13564
- confidence: 'medium',
13565
- suggestion: match ? formatSuggestion(match, true) : null,
13566
- });
13451
+ /**
13452
+ * Verify an AI answer against the repository.
13453
+ *
13454
+ * Each issue has the shape:
13455
+ * { type, value, line, location, message, confidence, suggestion }
13456
+ * where `confidence` is the *detection* certainty ('high' for path/dep/script
13457
+ * checks, 'medium' for symbol checks) and `suggestion` is a heuristic
13458
+ * closest-match hint (or null).
13459
+ *
13460
+ * @param {string} answerText
13461
+ * @param {string} cwd
13462
+ * @param {object} [opts]
13463
+ * @param {Set<string>} [opts.symbolSet] override known symbols
13464
+ * @param {Array} [opts.symbolCandidates] override { name, file, line } list
13465
+ * @param {Array<string>} [opts.fileCandidates] override repo file paths (suggestions)
13466
+ * @param {Set<string>} [opts.deps] override package deps
13467
+ * @param {Set<string>} [opts.scripts] override package.json script names
13468
+ * @param {boolean} [opts.hasPkg] whether a package.json exists
13469
+ * @param {(ref: string) => boolean} [opts.fileExists] override file check
13470
+ * @param {(mod: string) => boolean} [opts.relativeResolvable] override rel-import check
13471
+ * @returns {{ issues: object[], summary: object }}
13472
+ */
13473
+ function verify(answerText, cwd, opts = {}) {
13474
+ let symbolSet = opts.symbolSet;
13475
+ let fileBasenames = opts.fileBasenames;
13476
+ let symbolCandidates = opts.symbolCandidates || [];
13477
+ let fileCandidates = opts.fileCandidates || [];
13478
+ if (!symbolSet) {
13479
+ const built = buildSymbolSet(cwd);
13480
+ symbolSet = built.set;
13481
+ fileBasenames = new Set(built.fileKeys.map(
13482
+ (k) => path.basename(k).replace(/\.[^.]+$/, '').toLowerCase()
13483
+ ));
13484
+ symbolCandidates = built.symbolCandidates;
13485
+ fileCandidates = built.fileKeys;
13567
13486
  }
13568
- }
13487
+ if (!fileBasenames) fileBasenames = new Set();
13569
13488
 
13570
- // 4. fake-npm-script
13571
- if (hasPkg && scripts.size > 0) {
13572
- for (const { name, line } of parsers.extractNpmScripts(answerText)) {
13573
- if (scripts.has(name)) continue;
13574
- const match = closestMatch(name, [...scripts], { minLen: 2 });
13489
+ let deps = opts.deps;
13490
+ let hasPkg = opts.hasPkg;
13491
+ if (!deps) {
13492
+ const loaded = loadDeps(cwd);
13493
+ deps = loaded.deps;
13494
+ if (hasPkg === undefined) hasPkg = loaded.hasPkg;
13495
+ }
13496
+ const scripts = opts.scripts || (hasPkg ? loadScripts(cwd) : new Set());
13497
+
13498
+ const fileExists = opts.fileExists || ((ref) => defaultFileExists(cwd, ref));
13499
+ const relativeResolvable = opts.relativeResolvable
13500
+ || ((mod) => defaultRelativeResolvable(cwd, mod, fileBasenames));
13501
+
13502
+ // Pre-derive basename candidates for file suggestions (compare on basename so
13503
+ // a wrong directory still surfaces the right file).
13504
+ const fileBasenameCandidates = fileCandidates.map((f) => ({ name: path.basename(f), file: f }));
13505
+
13506
+ const issues = [];
13507
+ const dedupe = new Set();
13508
+ const add = (issue) => {
13509
+ const key = `${issue.type}::${issue.value}`;
13510
+ if (dedupe.has(key)) return;
13511
+ dedupe.add(key);
13512
+ if (!('suggestion' in issue)) issue.suggestion = null;
13513
+ issue.location = `L${issue.line}`;
13514
+ issues.push(issue);
13515
+ };
13516
+
13517
+ // 1. fake-file / fake-test-file
13518
+ for (const { path: p, line } of parsers.extractFilePaths(answerText)) {
13519
+ if (fileExists(p)) continue;
13520
+ const isTest = isTestPath(p);
13521
+ const match = closestMatch(path.basename(p), fileBasenameCandidates, { minLen: 4 });
13575
13522
  add({
13576
- type: 'fake-npm-script',
13577
- value: name,
13523
+ type: isTest ? 'fake-test-file' : 'fake-file',
13524
+ value: p,
13578
13525
  line,
13579
- message: `npm script not in package.json: ${name}`,
13526
+ message: `${isTest ? 'Test file' : 'File'} not found on disk: ${p}`,
13580
13527
  confidence: 'high',
13581
- suggestion: match ? formatSuggestion({ name: match.name }, false) : null,
13528
+ suggestion: match ? formatSuggestion(match, false) : null,
13582
13529
  });
13583
13530
  }
13584
- }
13585
13531
 
13586
- issues.sort((a, b) => a.line - b.line);
13532
+ // 2. fake-import
13533
+ for (const imp of parsers.extractImports(answerText)) {
13534
+ if (PLACEHOLDER_IMPORT_RE.test(imp.module)) continue;
13535
+ if (imp.relative) {
13536
+ if (!relativeResolvable(imp.module)) {
13537
+ add({ type: 'fake-import', value: imp.module, line: imp.line, message: `Import does not resolve: ${imp.module}`, confidence: 'high' });
13538
+ }
13539
+ continue;
13540
+ }
13541
+ // Bare module — only verifiable for JS when a package.json exists.
13542
+ const top = imp.module.split('/')[0];
13543
+ if (imp.kind === 'js') {
13544
+ if (!hasPkg) continue;
13545
+ if (NODE_BUILTINS.has(imp.module) || NODE_BUILTINS.has(top)) continue;
13546
+ if (top.startsWith('@')) {
13547
+ const scoped = imp.module.split('/').slice(0, 2).join('/');
13548
+ if (deps.has(scoped) || deps.has(imp.module)) continue;
13549
+ } else if (deps.has(top) || deps.has(imp.module)) {
13550
+ continue;
13551
+ }
13552
+ const match = closestMatch(top, [...deps], { minLen: 3 });
13553
+ add({
13554
+ type: 'fake-import',
13555
+ value: imp.module,
13556
+ line: imp.line,
13557
+ message: `Package not in dependencies: ${imp.module}`,
13558
+ confidence: 'high',
13559
+ suggestion: match ? formatSuggestion({ name: match.name }, false) : null,
13560
+ });
13561
+ }
13562
+ // Python bare imports: stdlib is unbounded offline — skip to keep precision.
13563
+ }
13564
+
13565
+ // 3. fake-symbol
13566
+ if (symbolSet.size > 0) {
13567
+ for (const { name, line } of parsers.extractSymbols(answerText)) {
13568
+ if (symbolSet.has(name)) continue;
13569
+ if (LANG_GLOBALS.has(name) || NODE_BUILTINS.has(name) || PY_BUILTINS.has(name)) continue;
13570
+ const match = closestMatch(name, symbolCandidates, { minLen: 4 });
13571
+ add({
13572
+ type: 'fake-symbol',
13573
+ value: name,
13574
+ line,
13575
+ message: `Symbol not found in repo index: ${name}()`,
13576
+ confidence: 'medium',
13577
+ suggestion: match ? formatSuggestion(match, true) : null,
13578
+ });
13579
+ }
13580
+ }
13587
13581
 
13588
- const byType = {
13589
- 'fake-file': 0, 'fake-test-file': 0, 'fake-import': 0,
13590
- 'fake-symbol': 0, 'fake-npm-script': 0,
13591
- };
13592
- for (const i of issues) byType[i.type] = (byType[i.type] || 0) + 1;
13593
-
13594
- const summary = {
13595
- total: issues.length,
13596
- byType,
13597
- clean: issues.length === 0,
13598
- symbolsIndexed: symbolSet.size,
13599
- withSuggestion: issues.filter((i) => i.suggestion).length,
13600
- };
13582
+ // 4. fake-npm-script
13583
+ if (hasPkg && scripts.size > 0) {
13584
+ for (const { name, line } of parsers.extractNpmScripts(answerText)) {
13585
+ if (scripts.has(name)) continue;
13586
+ const match = closestMatch(name, [...scripts], { minLen: 2 });
13587
+ add({
13588
+ type: 'fake-npm-script',
13589
+ value: name,
13590
+ line,
13591
+ message: `npm script not in package.json: ${name}`,
13592
+ confidence: 'high',
13593
+ suggestion: match ? formatSuggestion({ name: match.name }, false) : null,
13594
+ });
13595
+ }
13596
+ }
13601
13597
 
13602
- return { issues, summary };
13603
- }
13598
+ issues.sort((a, b) => a.line - b.line);
13604
13599
 
13605
- module.exports = { verify, buildSymbolSet, loadDeps, loadScripts, isTestPath };
13600
+ const byType = {
13601
+ 'fake-file': 0, 'fake-test-file': 0, 'fake-import': 0,
13602
+ 'fake-symbol': 0, 'fake-npm-script': 0,
13603
+ };
13604
+ for (const i of issues) byType[i.type] = (byType[i.type] || 0) + 1;
13605
+
13606
+ const summary = {
13607
+ total: issues.length,
13608
+ byType,
13609
+ clean: issues.length === 0,
13610
+ symbolsIndexed: symbolSet.size,
13611
+ withSuggestion: issues.filter((i) => i.suggestion).length,
13612
+ };
13606
13613
 
13614
+ return { issues, summary };
13615
+ }
13616
+
13617
+ module.exports = { verify, buildSymbolSet, loadDeps, loadScripts, isTestPath };
13618
+
13607
13619
  };
13608
13620
 
13609
13621
  const fs = require('fs');
@@ -13622,7 +13634,7 @@ function __tryGit(args, opts = {}) {
13622
13634
  catch (_) { return ''; }
13623
13635
  }
13624
13636
 
13625
- const VERSION = '7.22.1';
13637
+ const VERSION = '7.22.2';
13626
13638
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
13627
13639
 
13628
13640
  function requireSourceOrBundled(key) {
package/llms-full.txt CHANGED
@@ -9,13 +9,13 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
9
9
  grounded. Deterministic, offline, no embeddings or vector database. Works with
10
10
  Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
11
11
 
12
- # Version: 7.22.1 | Benchmark: sigmap-v7.0-main (2026-06-18)
12
+ # Version: 7.22.2 | Benchmark: sigmap-v7.0-main (2026-06-19)
13
13
  # Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
14
14
  # Regenerate: npm run generate:llms | Validate: npm run validate:llms
15
15
 
16
16
  ---
17
17
 
18
- ## Core metrics (benchmark: sigmap-v7.0-main, 2026-06-18)
18
+ ## Core metrics (benchmark: sigmap-v7.0-main, 2026-06-19)
19
19
 
20
20
  | Metric | Without SigMap | With SigMap |
21
21
  |--------|----------------|-------------|
package/llms.txt CHANGED
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
9
9
  grounded. Deterministic, offline, no embeddings or vector database. Works with
10
10
  Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
11
11
 
12
- # Version: 7.22.1 | Benchmark: sigmap-v7.0-main (2026-06-18)
12
+ # Version: 7.22.2 | Benchmark: sigmap-v7.0-main (2026-06-19)
13
13
  # Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
14
14
  # Regenerate: npm run generate:llms | Validate: npm run validate:llms
15
15
 
@@ -21,7 +21,7 @@ Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
21
21
  - No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
22
22
  - Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
23
23
 
24
- ## Core metrics (benchmark: sigmap-v7.0-main, 2026-06-18)
24
+ ## Core metrics (benchmark: sigmap-v7.0-main, 2026-06-19)
25
25
 
26
26
  - hit@5 retrieval: 75.6% vs 13.6% random baseline (5.6× lift)
27
27
  - Token reduction: 97.0% average across benchmark repos
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "7.22.1",
3
+ "version": "7.22.2",
4
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.",
5
5
  "main": "packages/core/index.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "7.22.1",
3
+ "version": "7.22.2",
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": "7.22.1",
3
+ "version": "7.22.2",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
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: '7.22.1',
21
+ version: '7.22.2',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -59,6 +59,15 @@ const LANG_GLOBALS = new Set([
59
59
  const REL_EXTS = ['', '.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs', '.json', '.py', '.r', '.R', '.vue'];
60
60
  const REL_INDEX = ['index.js', 'index.ts', 'index.tsx', 'index.jsx', '__init__.py'];
61
61
 
62
+ // Obvious documentation-placeholder imports the model writes in illustrative
63
+ // snippets — not real dependency claims. e.g. @scope/utils, some-module, ./local-file.
64
+ const PLACEHOLDER_IMPORT_RE = new RegExp([
65
+ '^@(?:scope|org|your-org|my-org|company|example)(?:/|$)', // @scope/utils
66
+ '(?:^|/)(?:some|your|my)-(?:module|package|lib|component|file|dep)(?:$|/)', // some-module
67
+ '(?:^|/)(?:local-file|your-file|my-file|module-name|package-name|your-package|example-package)(?:$|/)',
68
+ '(?:^|/)path/to/', // ./path/to/x
69
+ ].join('|'), 'i');
70
+
62
71
  /**
63
72
  * Build the set of known symbol identifiers from the SigMap signature index,
64
73
  * plus `{ name, file, line }` candidates (for closest-match suggestions).
@@ -225,6 +234,7 @@ function verify(answerText, cwd, opts = {}) {
225
234
 
226
235
  // 2. fake-import
227
236
  for (const imp of parsers.extractImports(answerText)) {
237
+ if (PLACEHOLDER_IMPORT_RE.test(imp.module)) continue;
228
238
  if (imp.relative) {
229
239
  if (!relativeResolvable(imp.module)) {
230
240
  add({ type: 'fake-import', value: imp.module, line: imp.line, message: `Import does not resolve: ${imp.module}`, confidence: 'high' });
@@ -30,7 +30,10 @@ const LIBRARY_TOKENS = new Set([
30
30
 
31
31
  // Illustrative placeholder names the model writes in prose, not repo claims:
32
32
  // e.g. example.js, minimal-example.js, sample.ts, demo.js, placeholder.js.
33
- const PLACEHOLDER_RE = /(?:^|[-_.])(?:example|sample|demo|placeholder)(?:[-_.]|$)/i;
33
+ const PLACEHOLDER_RE = /(?:^|[-_.])(?:example|sample|demo|placeholder)(?:[-_.]|s?$)/i;
34
+ // camelCase / Pascal placeholders: myExample.js, exampleConfig.js, fooSample.ts.
35
+ // Requires a case boundary so ordinary words (resample.js) are NOT suppressed.
36
+ const PLACEHOLDER_CAMEL_RE = /(?:^|[a-z])(?:Example|Sample|Demo|Placeholder)|(?:^|[-_.])(?:example|sample|demo|placeholder)(?=[A-Z])/;
34
37
 
35
38
  /**
36
39
  * Extract fenced code blocks.
@@ -86,7 +89,7 @@ function extractFilePaths(text) {
86
89
  if (!hasSlash && !KNOWN_CODE_EXT.has(ext)) continue;
87
90
  if (LIBRARY_TOKENS.has(p.toLowerCase())) continue;
88
91
  const base = p.split('/').pop();
89
- if (PLACEHOLDER_RE.test(base)) continue;
92
+ if (PLACEHOLDER_RE.test(base) || PLACEHOLDER_CAMEL_RE.test(base)) continue;
90
93
  if (!seen.has(p)) seen.set(p, i + 1);
91
94
  }
92
95
  }