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 +9 -0
- package/README.md +1 -1
- package/gen-context.js +292 -280
- package/llms-full.txt +2 -2
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/server.js +1 -1
- package/src/verify/hallucination-guard.js +10 -0
- package/src/verify/parsers.js +5 -2
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-
|
|
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.
|
|
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)(?:[-_.]
|
|
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
|
-
|
|
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
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
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
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
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
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
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
|
-
|
|
13382
|
-
|
|
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
|
-
|
|
13398
|
-
|
|
13399
|
-
const
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
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
|
-
/**
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
13421
|
-
function
|
|
13422
|
-
|
|
13423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
const
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
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
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
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
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
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
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
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-
|
|
13577
|
-
value:
|
|
13523
|
+
type: isTest ? 'fake-test-file' : 'fake-file',
|
|
13524
|
+
value: p,
|
|
13578
13525
|
line,
|
|
13579
|
-
message:
|
|
13526
|
+
message: `${isTest ? 'Test file' : 'File'} not found on disk: ${p}`,
|
|
13580
13527
|
confidence: 'high',
|
|
13581
|
-
suggestion: match ? formatSuggestion(
|
|
13528
|
+
suggestion: match ? formatSuggestion(match, false) : null,
|
|
13582
13529
|
});
|
|
13583
13530
|
}
|
|
13584
|
-
}
|
|
13585
13531
|
|
|
13586
|
-
|
|
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
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
|
|
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
|
-
|
|
13603
|
-
}
|
|
13598
|
+
issues.sort((a, b) => a.line - b.line);
|
|
13604
13599
|
|
|
13605
|
-
|
|
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.
|
|
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.
|
|
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
|
+
## 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.
|
|
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-
|
|
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.
|
|
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": {
|
package/src/mcp/server.js
CHANGED
|
@@ -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' });
|
package/src/verify/parsers.js
CHANGED
|
@@ -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)(?:[-_.]
|
|
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
|
}
|