sigmap 8.0.0 → 8.2.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/CHANGELOG.md +15 -0
- package/README.md +2 -2
- package/gen-context.js +264 -5
- package/llms-full.txt +12 -4
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/handlers.js +46 -1
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +20 -0
- package/src/verify/hallucination-guard.js +25 -0
- package/src/verify/lib-index.js +164 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,21 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [8.2.0] — 2026-07-04
|
|
14
|
+
|
|
15
|
+
Minor release — **`verify_suggestion` MCP tool: the grounding moat, made consumable by agents.** v8.1.0 built local-library grounding inside the `verify-ai-output` CLI; this exposes it as the **18th MCP tool**, so a coding agent can verify its own generated code against the repo **and the libraries actually installed** in `node_modules` — *before it writes* — and get back the flagged issues plus the pinned versions it verified against (D8).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`verify_suggestion` MCP tool (#409, PR #410):** ground an AI code suggestion before writing it. `verify_suggestion({ code })` runs the Hallucination Guard against the repo signature index **and** the installed-library symbol index (the G5/D5 moat), returning a clean/✗ verdict, one line per issue (fake file / import / symbol / npm-script, with closest-match suggestions), and a **D8** line listing the installed libraries it verified against with pinned versions (`name@version`). Deterministic, offline, zero-dependency. Reuses the shipped `verify()` core; graceful on missing/empty `code`. MCP surface **17 → 18 tools**.
|
|
19
|
+
|
|
20
|
+
## [8.1.0] — 2026-07-04
|
|
21
|
+
|
|
22
|
+
Minor release — **v9.0 G5/D5: the local-library signature index (the private-API grounding moat, v1).** SigMap's hallucination guard can now verify AI suggestions against the libraries **actually installed** in `node_modules`, not just declared dependency *names*. This is a capability no competitor offers — Context7 knows only *public* library docs; SigMap grounds against the real installed tree. Local, zero-dependency, deterministic (byte-stable given a fixed installed tree).
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **Local-library signature index (#405, PR #406):** new `src/verify/lib-index.js` — `buildLibraryIndex(cwd)` resolves the **direct** dependencies declared in `package.json`, locates each under `node_modules/<dep>`, reads its version (**D8 version pinning**) and TypeScript declaration entry (`types`/`typings`, else `index.d.ts`), and deterministically extracts the exported symbol names. Bounded (per-file + dep caps), cached via `src/cache/sig-cache.js`, and graceful on missing/untyped/malformed packages.
|
|
26
|
+
- **Installed-library grounding in `verify-ai-output`:** `verify()` now unions installed-library symbols into its known-symbol universe, so genuine library calls (e.g. `Router()`, `debounce()`) stop being false-flagged as `fake-symbol`. The result summary gains `librariesIndexed` and `libraries` (`name@version`, D8). Auto-runs from the project's `node_modules`; opt-out via `libIndex:false`. Scope v1 is JS/TS `.d.ts`; Python site-packages and a standalone `verify_suggestion` MCP tool are deferred to a follow-up.
|
|
27
|
+
|
|
13
28
|
## [8.0.0] — 2026-07-04
|
|
14
29
|
|
|
15
30
|
Major release — **v8.5 "Repo-Context Coverage & Test Discovery" (C1 + C2 + C3).** Marks the v8 milestone: the signature map now reaches beyond functions/classes/routes into the repo's operational surface, impl→test discovery is measured rather than best-effort, and every Evidence Pack file carries a risk label from a richer, precedence-ordered set. All zero-dependency, deterministic, and in-boundary with the North-Star constraints. **No breaking API changes** — the `8.0.0` bump aligns the published version with the roadmap's v8 framing; existing `riskLabel`/`relatedTests` consumers keep working.
|
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ Ask → Rank → Context → Validate → Judge → Learn
|
|
|
98
98
|
|
|
99
99
|
<!--SM:benchmarkBlock-->
|
|
100
100
|
```
|
|
101
|
-
Benchmark : sigmap-v8.
|
|
101
|
+
Benchmark : sigmap-v8.2-main (21 repositories, including R language)
|
|
102
102
|
Date : 2026-07-04
|
|
103
103
|
|
|
104
104
|
Hit@5 : 86.7% (baseline 13.6% — 6.4× lift)
|
|
@@ -198,7 +198,7 @@ Use SigMap with open-source tools and fully self-hosted setups:
|
|
|
198
198
|
| **JetBrains** | [Marketplace](https://plugins.jetbrains.com/plugin/31109-sigmap--ai-context-engine/) | [github.com/manojmallick/sigmap-jetbrains](https://github.com/manojmallick/sigmap-jetbrains) | IntelliJ IDEA, WebStorm, PyCharm, GoLand — tool window + actions |
|
|
199
199
|
| **Neovim** | lazy.nvim / packer / vim-plug | [github.com/manojmallick/sigmap.nvim](https://github.com/manojmallick/sigmap.nvim) | `:SigMap`, `:SigMapQuery` float window, statusline widget |
|
|
200
200
|
|
|
201
|
-
**MCP server** —
|
|
201
|
+
**MCP server** — 18 on-demand tools for Claude Code and Cursor:
|
|
202
202
|
|
|
203
203
|
```bash
|
|
204
204
|
sigmap --mcp
|
package/gen-context.js
CHANGED
|
@@ -12892,7 +12892,52 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
|
|
|
12892
12892
|
}
|
|
12893
12893
|
}
|
|
12894
12894
|
|
|
12895
|
-
|
|
12895
|
+
/**
|
|
12896
|
+
* verify_suggestion({ code }) → string
|
|
12897
|
+
*
|
|
12898
|
+
* Ground an AI code suggestion before it is written: run the Hallucination
|
|
12899
|
+
* Guard against the repo AND the installed-library symbol index (the moat), and
|
|
12900
|
+
* render a verdict + issues + the installed libraries verified against (pinned
|
|
12901
|
+
* versions, D8). Deterministic, offline.
|
|
12902
|
+
*/
|
|
12903
|
+
function verifySuggestion(args, cwd) {
|
|
12904
|
+
const code = args && typeof args.code === 'string' ? args.code : '';
|
|
12905
|
+
if (!code.trim()) {
|
|
12906
|
+
return 'Usage: verify_suggestion({ code: "<AI-suggested code or answer>" }) — provide the snippet to verify against the repo + installed libraries.';
|
|
12907
|
+
}
|
|
12908
|
+
|
|
12909
|
+
let result;
|
|
12910
|
+
try {
|
|
12911
|
+
const { verify } = __require('./src/verify/hallucination-guard');
|
|
12912
|
+
result = verify(code, cwd);
|
|
12913
|
+
} catch (err) {
|
|
12914
|
+
return `_verify_suggestion failed: ${err.message}_`;
|
|
12915
|
+
}
|
|
12916
|
+
|
|
12917
|
+
const { issues, summary } = result;
|
|
12918
|
+
const out = [];
|
|
12919
|
+
if (summary.clean) {
|
|
12920
|
+
out.push('✓ Grounded — no fake files, imports, symbols, or scripts detected.');
|
|
12921
|
+
} else {
|
|
12922
|
+
out.push(`✗ ${summary.total} issue(s) found:`);
|
|
12923
|
+
for (const i of issues) {
|
|
12924
|
+
out.push(` L${i.line} [${i.type}] ${i.message}`);
|
|
12925
|
+
if (i.suggestion) out.push(` ↳ ${i.suggestion}`);
|
|
12926
|
+
}
|
|
12927
|
+
}
|
|
12928
|
+
|
|
12929
|
+
// D8: report the installed libraries the suggestion was verified against.
|
|
12930
|
+
const pins = (summary.libraries || []).filter((l) => l.version).map((l) => `${l.name}@${l.version}`);
|
|
12931
|
+
const n = summary.librariesIndexed || 0;
|
|
12932
|
+
out.push('');
|
|
12933
|
+
out.push(
|
|
12934
|
+
`Grounded against ${summary.symbolsIndexed} repo + library symbol(s)` +
|
|
12935
|
+
(n ? ` · ${n} installed librar${n === 1 ? 'y' : 'ies'}${pins.length ? ': ' + pins.join(', ') : ''}` : '')
|
|
12936
|
+
);
|
|
12937
|
+
return out.join('\n');
|
|
12938
|
+
}
|
|
12939
|
+
|
|
12940
|
+
module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion };
|
|
12896
12941
|
|
|
12897
12942
|
};
|
|
12898
12943
|
|
|
@@ -13053,17 +13098,17 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
13053
13098
|
*
|
|
13054
13099
|
* Supported methods:
|
|
13055
13100
|
* initialize → serverInfo + capabilities
|
|
13056
|
-
* tools/list →
|
|
13101
|
+
* tools/list → 18 tool definitions
|
|
13057
13102
|
* tools/call → dispatch to handler, return result
|
|
13058
13103
|
*/
|
|
13059
13104
|
|
|
13060
13105
|
const readline = require('readline');
|
|
13061
13106
|
const { TOOLS } = __require('./src/mcp/tools');
|
|
13062
|
-
const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview } = __require('./src/mcp/handlers');
|
|
13107
|
+
const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion } = __require('./src/mcp/handlers');
|
|
13063
13108
|
|
|
13064
13109
|
const SERVER_INFO = {
|
|
13065
13110
|
name: 'sigmap',
|
|
13066
|
-
version: '8.
|
|
13111
|
+
version: '8.2.0',
|
|
13067
13112
|
description: 'SigMap MCP server — code signatures on demand',
|
|
13068
13113
|
};
|
|
13069
13114
|
|
|
@@ -13128,6 +13173,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
13128
13173
|
else if (name === 'sigmap_notify_file_deleted') text = notifyFileDeleted(args, cwd);
|
|
13129
13174
|
else if (name === 'get_diff_context') text = getDiffContext(args, cwd);
|
|
13130
13175
|
else if (name === 'get_architecture_overview') text = getArchitectureOverview(args, cwd);
|
|
13176
|
+
else if (name === 'verify_suggestion') text = verifySuggestion(args, cwd);
|
|
13131
13177
|
else {
|
|
13132
13178
|
respondError(id, -32601, `Unknown tool: ${name}`);
|
|
13133
13179
|
return;
|
|
@@ -13503,6 +13549,26 @@ __factories["./src/mcp/tools"] = function(module, exports) {
|
|
|
13503
13549
|
required: [],
|
|
13504
13550
|
},
|
|
13505
13551
|
},
|
|
13552
|
+
{
|
|
13553
|
+
name: 'verify_suggestion',
|
|
13554
|
+
description:
|
|
13555
|
+
'Ground an AI code suggestion before writing it: verify a snippet or answer against the ' +
|
|
13556
|
+
'repository AND the libraries actually installed in node_modules (the grounding moat). ' +
|
|
13557
|
+
'Flags fake file paths, unresolvable imports, symbols absent from both the repo index and ' +
|
|
13558
|
+
'the installed libraries, and non-existent npm scripts — deterministic, offline, no LLM. ' +
|
|
13559
|
+
'Reports the installed libraries it verified against with pinned versions.',
|
|
13560
|
+
inputSchema: {
|
|
13561
|
+
type: 'object',
|
|
13562
|
+
properties: {
|
|
13563
|
+
code: {
|
|
13564
|
+
type: 'string',
|
|
13565
|
+
description:
|
|
13566
|
+
'The AI-suggested code snippet or answer text to verify against the repo + installed libraries.',
|
|
13567
|
+
},
|
|
13568
|
+
},
|
|
13569
|
+
required: ['code'],
|
|
13570
|
+
},
|
|
13571
|
+
},
|
|
13506
13572
|
];
|
|
13507
13573
|
|
|
13508
13574
|
module.exports = { TOOLS };
|
|
@@ -16403,6 +16469,7 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
16403
16469
|
const path = require('path');
|
|
16404
16470
|
const parsers = __require('./src/verify/parsers');
|
|
16405
16471
|
const { closestMatch, buildSymbolCandidates, formatSuggestion } = __require('./src/verify/closest-match');
|
|
16472
|
+
const { buildLibraryIndex } = __require('./src/verify/lib-index');
|
|
16406
16473
|
|
|
16407
16474
|
// A path that looks like a test file (JS/TS spec/test, Python test_/_test, or
|
|
16408
16475
|
// a tests/__tests__ directory). Used to flag fake-test-file separately.
|
|
@@ -16571,6 +16638,28 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
16571
16638
|
}
|
|
16572
16639
|
if (!fileBasenames) fileBasenames = new Set();
|
|
16573
16640
|
|
|
16641
|
+
// Installed-library grounding (G5/D5, the moat): union the exported symbols of
|
|
16642
|
+
// the libraries actually installed in node_modules, so genuine library calls
|
|
16643
|
+
// stop false-flagging as fake-symbol and the summary can pin the versions the
|
|
16644
|
+
// answer was verified against. Auto-runs only when the caller did not override
|
|
16645
|
+
// the symbol set (keeps hermetic callers unchanged); disable with libIndex:false.
|
|
16646
|
+
let libraries = opts.libraries || [];
|
|
16647
|
+
{
|
|
16648
|
+
let libSyms = opts.libSymbols;
|
|
16649
|
+
if (!libSyms && opts.libIndex !== false && !opts.symbolSet) {
|
|
16650
|
+
try {
|
|
16651
|
+
const li = buildLibraryIndex(cwd, { version: opts.version });
|
|
16652
|
+
libSyms = li.symbols;
|
|
16653
|
+
if (!opts.libraries) libraries = li.libraries;
|
|
16654
|
+
} catch (_) { libSyms = null; }
|
|
16655
|
+
}
|
|
16656
|
+
if (libSyms && libSyms.size) {
|
|
16657
|
+
const merged = new Set(symbolSet);
|
|
16658
|
+
for (const s of libSyms) merged.add(s);
|
|
16659
|
+
symbolSet = merged;
|
|
16660
|
+
}
|
|
16661
|
+
}
|
|
16662
|
+
|
|
16574
16663
|
let deps = opts.deps;
|
|
16575
16664
|
let hasPkg = opts.hasPkg;
|
|
16576
16665
|
if (!deps) {
|
|
@@ -16694,6 +16783,8 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
16694
16783
|
clean: issues.length === 0,
|
|
16695
16784
|
symbolsIndexed: symbolSet.size,
|
|
16696
16785
|
withSuggestion: issues.filter((i) => i.suggestion).length,
|
|
16786
|
+
librariesIndexed: libraries.length,
|
|
16787
|
+
libraries: libraries.map((l) => ({ name: l.name, version: l.version, symbols: l.symbols, typed: l.typed })),
|
|
16697
16788
|
};
|
|
16698
16789
|
|
|
16699
16790
|
return { issues, summary };
|
|
@@ -16703,6 +16794,174 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
16703
16794
|
|
|
16704
16795
|
};
|
|
16705
16796
|
|
|
16797
|
+
// ── ./src/verify/lib-index ──
|
|
16798
|
+
__factories["./src/verify/lib-index"] = function(module, exports) {
|
|
16799
|
+
|
|
16800
|
+
/**
|
|
16801
|
+
* Local-library signature index (v9.0 G5/D5 — the private-API grounding moat).
|
|
16802
|
+
*
|
|
16803
|
+
* Context7 knows only *public* library docs. SigMap can do something no
|
|
16804
|
+
* competitor can: index the signatures of the libraries **actually installed**
|
|
16805
|
+
* in `node_modules` and verify AI suggestions against repo + private +
|
|
16806
|
+
* installed-lib symbols. This module builds the installed-lib half.
|
|
16807
|
+
*
|
|
16808
|
+
* For each **direct** dependency declared in `package.json`, it locates the
|
|
16809
|
+
* package under `node_modules/<dep>`, reads its version (D8 version pinning),
|
|
16810
|
+
* and extracts the exported symbol names from its TypeScript declaration entry
|
|
16811
|
+
* (`types`/`typings`, else `index.d.ts`). Pure, zero-dependency, deterministic:
|
|
16812
|
+
* byte-stable given a fixed installed tree. Bounded (per-file read cap + dep
|
|
16813
|
+
* cap) and cached via `src/cache/sig-cache.js` so repeat builds are near-free.
|
|
16814
|
+
*/
|
|
16815
|
+
|
|
16816
|
+
const fs = require('fs');
|
|
16817
|
+
const path = require('path');
|
|
16818
|
+
const { loadCache, saveCache, getChangedFiles, updateCacheEntries } = __require('./src/cache/sig-cache');
|
|
16819
|
+
|
|
16820
|
+
const MAX_DTS_BYTES = 512 * 1024; // per-file read cap
|
|
16821
|
+
const MAX_DEPS = 1000; // dep count cap
|
|
16822
|
+
const DEP_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
16823
|
+
|
|
16824
|
+
/**
|
|
16825
|
+
* Extract exported symbol names from a `.d.ts` declaration file. Deterministic,
|
|
16826
|
+
* regex-based (declaration files are already normalized, so this is robust
|
|
16827
|
+
* without a full TS parser and stays zero-dependency).
|
|
16828
|
+
* @param {string} src
|
|
16829
|
+
* @returns {string[]} sorted unique exported names
|
|
16830
|
+
*/
|
|
16831
|
+
function extractDtsExports(src) {
|
|
16832
|
+
const names = new Set();
|
|
16833
|
+
if (!src) return [];
|
|
16834
|
+
|
|
16835
|
+
// export [declare] [default] function|const|let|var|class|interface|type|enum|namespace Name
|
|
16836
|
+
const declRe = /\bexport\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:function|const|let|var|class|interface|type|enum|namespace|module)\s+([A-Za-z_$][\w$]*)/g;
|
|
16837
|
+
let m;
|
|
16838
|
+
while ((m = declRe.exec(src)) !== null) names.add(m[1]);
|
|
16839
|
+
|
|
16840
|
+
// export { a, b as c, default as d }
|
|
16841
|
+
const listRe = /\bexport\s*(?:type\s*)?\{([^}]*)\}/g;
|
|
16842
|
+
while ((m = listRe.exec(src)) !== null) {
|
|
16843
|
+
for (const part of m[1].split(',')) {
|
|
16844
|
+
const name = part.trim().split(/\s+as\s+/).pop().trim();
|
|
16845
|
+
if (/^[A-Za-z_$][\w$]*$/.test(name) && name !== 'default') names.add(name);
|
|
16846
|
+
}
|
|
16847
|
+
}
|
|
16848
|
+
|
|
16849
|
+
// export as namespace Name / export = Name
|
|
16850
|
+
const nsRe = /\bexport\s+as\s+namespace\s+([A-Za-z_$][\w$]*)/g;
|
|
16851
|
+
while ((m = nsRe.exec(src)) !== null) names.add(m[1]);
|
|
16852
|
+
const assignRe = /\bexport\s*=\s*([A-Za-z_$][\w$]*)/g;
|
|
16853
|
+
while ((m = assignRe.exec(src)) !== null) names.add(m[1]);
|
|
16854
|
+
|
|
16855
|
+
return [...names].sort();
|
|
16856
|
+
}
|
|
16857
|
+
|
|
16858
|
+
/** Read direct dependency names declared in the project's package.json. */
|
|
16859
|
+
function directDeps(cwd) {
|
|
16860
|
+
const names = new Set();
|
|
16861
|
+
try {
|
|
16862
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
16863
|
+
for (const k of DEP_KEYS) {
|
|
16864
|
+
if (pkg[k] && typeof pkg[k] === 'object') {
|
|
16865
|
+
for (const n of Object.keys(pkg[k])) names.add(n);
|
|
16866
|
+
}
|
|
16867
|
+
}
|
|
16868
|
+
} catch (_) { /* no/invalid package.json → no deps */ }
|
|
16869
|
+
return [...names].sort();
|
|
16870
|
+
}
|
|
16871
|
+
|
|
16872
|
+
/**
|
|
16873
|
+
* Resolve an installed dependency's version + entry `.d.ts` path.
|
|
16874
|
+
* @returns {{ version: string|null, dtsPath: string|null }|null} null if not installed
|
|
16875
|
+
*/
|
|
16876
|
+
function resolveEntry(cwd, dep) {
|
|
16877
|
+
const pkgDir = path.join(cwd, 'node_modules', dep);
|
|
16878
|
+
let pkg;
|
|
16879
|
+
try { pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')); } catch (_) { return null; }
|
|
16880
|
+
const version = typeof pkg.version === 'string' ? pkg.version : null;
|
|
16881
|
+
|
|
16882
|
+
const candidates = [];
|
|
16883
|
+
const typesField = pkg.types || pkg.typings;
|
|
16884
|
+
if (typeof typesField === 'string') {
|
|
16885
|
+
candidates.push(typesField);
|
|
16886
|
+
candidates.push(path.join(typesField, 'index.d.ts')); // typesField may be a dir
|
|
16887
|
+
}
|
|
16888
|
+
candidates.push('index.d.ts');
|
|
16889
|
+
if (typeof pkg.main === 'string') candidates.push(pkg.main.replace(/\.(js|cjs|mjs)$/, '.d.ts'));
|
|
16890
|
+
|
|
16891
|
+
for (const c of candidates) {
|
|
16892
|
+
const p = path.join(pkgDir, c);
|
|
16893
|
+
try { if (fs.statSync(p).isFile()) return { version, dtsPath: p }; } catch (_) { /* next */ }
|
|
16894
|
+
}
|
|
16895
|
+
return { version, dtsPath: null }; // installed but untyped
|
|
16896
|
+
}
|
|
16897
|
+
|
|
16898
|
+
/**
|
|
16899
|
+
* Build the installed-library signature index for `cwd`.
|
|
16900
|
+
*
|
|
16901
|
+
* @param {string} cwd
|
|
16902
|
+
* @param {object} [opts]
|
|
16903
|
+
* @param {string} [opts.version='0'] sigmap version, for cache busting
|
|
16904
|
+
* @param {boolean} [opts.cache=true] use the on-disk sig-cache
|
|
16905
|
+
* @returns {{ symbols: Set<string>, libraries: Array<{name,version,symbols,typed}>, count: number }}
|
|
16906
|
+
*/
|
|
16907
|
+
function buildLibraryIndex(cwd, opts = {}) {
|
|
16908
|
+
const version = opts.version || '0';
|
|
16909
|
+
const useCache = opts.cache !== false;
|
|
16910
|
+
const deps = directDeps(cwd).slice(0, MAX_DEPS);
|
|
16911
|
+
|
|
16912
|
+
const entries = [];
|
|
16913
|
+
for (const dep of deps) {
|
|
16914
|
+
const r = resolveEntry(cwd, dep);
|
|
16915
|
+
if (r) entries.push({ dep, version: r.version, dtsPath: r.dtsPath });
|
|
16916
|
+
}
|
|
16917
|
+
|
|
16918
|
+
const cache = useCache ? loadCache(cwd, version) : new Map();
|
|
16919
|
+
const dtsFiles = entries.filter((e) => e.dtsPath).map((e) => e.dtsPath);
|
|
16920
|
+
const { unchanged } = getChangedFiles(dtsFiles, cache);
|
|
16921
|
+
const unchangedSet = new Set(unchanged);
|
|
16922
|
+
|
|
16923
|
+
const symbols = new Set();
|
|
16924
|
+
const libraries = [];
|
|
16925
|
+
const fresh = [];
|
|
16926
|
+
|
|
16927
|
+
for (const e of entries) {
|
|
16928
|
+
let names;
|
|
16929
|
+
if (!e.dtsPath) {
|
|
16930
|
+
names = [];
|
|
16931
|
+
} else if (unchangedSet.has(e.dtsPath) && cache.get(e.dtsPath)) {
|
|
16932
|
+
names = cache.get(e.dtsPath).sigs || [];
|
|
16933
|
+
} else {
|
|
16934
|
+
let src = '';
|
|
16935
|
+
try {
|
|
16936
|
+
if (fs.statSync(e.dtsPath).size <= MAX_DTS_BYTES) src = fs.readFileSync(e.dtsPath, 'utf8');
|
|
16937
|
+
} catch (_) { /* unreadable → empty */ }
|
|
16938
|
+
names = extractDtsExports(src);
|
|
16939
|
+
fresh.push({ file: e.dtsPath, sigs: names });
|
|
16940
|
+
}
|
|
16941
|
+
for (const n of names) symbols.add(n);
|
|
16942
|
+
libraries.push({ name: e.dep, version: e.version, symbols: names.length, typed: !!e.dtsPath });
|
|
16943
|
+
}
|
|
16944
|
+
|
|
16945
|
+
if (useCache && fresh.length) {
|
|
16946
|
+
updateCacheEntries(cache, fresh);
|
|
16947
|
+
saveCache(cwd, version, cache);
|
|
16948
|
+
}
|
|
16949
|
+
|
|
16950
|
+
libraries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
16951
|
+
return { symbols, libraries, count: symbols.size };
|
|
16952
|
+
}
|
|
16953
|
+
|
|
16954
|
+
/** D8: render `name@version` pins for the typed/installed libraries. */
|
|
16955
|
+
function formatVersionPins(libraries) {
|
|
16956
|
+
return (libraries || [])
|
|
16957
|
+
.filter((l) => l.version)
|
|
16958
|
+
.map((l) => `${l.name}@${l.version}`);
|
|
16959
|
+
}
|
|
16960
|
+
|
|
16961
|
+
module.exports = { buildLibraryIndex, extractDtsExports, directDeps, resolveEntry, formatVersionPins };
|
|
16962
|
+
|
|
16963
|
+
};
|
|
16964
|
+
|
|
16706
16965
|
// ── ./src/verify/parsers ──
|
|
16707
16966
|
__factories["./src/verify/parsers"] = function(module, exports) {
|
|
16708
16967
|
|
|
@@ -17032,7 +17291,7 @@ function __tryGit(args, opts = {}) {
|
|
|
17032
17291
|
catch (_) { return ''; }
|
|
17033
17292
|
}
|
|
17034
17293
|
|
|
17035
|
-
const VERSION = '8.
|
|
17294
|
+
const VERSION = '8.2.0';
|
|
17036
17295
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
17037
17296
|
|
|
17038
17297
|
function requireSourceOrBundled(key) {
|
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.
|
|
14
|
+
# Version: 8.2.0 | Benchmark: sigmap-v8.2-main (2026-07-04)
|
|
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.
|
|
20
|
+
## Core metrics (benchmark: sigmap-v8.2-main, 2026-07-04)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
|
@@ -26,7 +26,7 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
26
26
|
| Task success proxy | 10% | 67.8% |
|
|
27
27
|
| Prompts per task | 2.84 | 1.46 (48.8% fewer) |
|
|
28
28
|
| Supported languages | — | 33 |
|
|
29
|
-
| MCP tools | — |
|
|
29
|
+
| MCP tools | — | 18 |
|
|
30
30
|
| npm runtime dependencies | — | 0 |
|
|
31
31
|
|
|
32
32
|
---
|
|
@@ -127,7 +127,7 @@ sigmap --version Show version
|
|
|
127
127
|
|
|
128
128
|
---
|
|
129
129
|
|
|
130
|
-
## MCP server —
|
|
130
|
+
## MCP server — 18 tools
|
|
131
131
|
|
|
132
132
|
Start with `sigmap --mcp` (stdio JSON-RPC). Configure once:
|
|
133
133
|
|
|
@@ -271,6 +271,14 @@ A high-level map of the codebase in one call: module breakdown (files/tokens), t
|
|
|
271
271
|
Input: { } (no arguments)
|
|
272
272
|
```
|
|
273
273
|
|
|
274
|
+
### verify_suggestion
|
|
275
|
+
|
|
276
|
+
Ground an AI code suggestion before writing it: verify a snippet or answer against the repository AND the libraries actually installed in node_modules (the grounding moat). Flags fake file paths, unresolvable imports, symbols absent from both the repo index and the installed libraries, and non-existent npm scripts — deterministic, offline, no LLM. Reports the installed libraries it verified against with pinned versions.
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
Input: { code: string }
|
|
280
|
+
```
|
|
281
|
+
|
|
274
282
|
---
|
|
275
283
|
|
|
276
284
|
## Configuration (gen-context.config.json)
|
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.
|
|
14
|
+
# Version: 8.2.0 | Benchmark: sigmap-v8.2-main (2026-07-04)
|
|
15
15
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
16
16
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
17
17
|
|
|
@@ -23,13 +23,13 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
23
23
|
- No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
|
|
24
24
|
- Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
|
|
25
25
|
|
|
26
|
-
## Core metrics (benchmark: sigmap-v8.
|
|
26
|
+
## Core metrics (benchmark: sigmap-v8.2-main, 2026-07-04)
|
|
27
27
|
|
|
28
28
|
- hit@5 retrieval: 86.7% vs 13.6% random baseline (6.4× lift)
|
|
29
29
|
- Token reduction: 97.0% average across benchmark repos
|
|
30
30
|
- Task success: 67.8% vs 10% without SigMap
|
|
31
31
|
- Prompts per task: 1.46 vs 2.84 baseline (48.8% fewer)
|
|
32
|
-
- Languages: 33 supported · MCP tools:
|
|
32
|
+
- Languages: 33 supported · MCP tools: 18
|
|
33
33
|
- Dependencies: zero npm runtime dependencies · fully offline
|
|
34
34
|
|
|
35
35
|
## Quick start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0",
|
|
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/handlers.js
CHANGED
|
@@ -853,4 +853,49 @@ function getArchitectureOverview(args, cwd) {
|
|
|
853
853
|
}
|
|
854
854
|
}
|
|
855
855
|
|
|
856
|
-
|
|
856
|
+
/**
|
|
857
|
+
* verify_suggestion({ code }) → string
|
|
858
|
+
*
|
|
859
|
+
* Ground an AI code suggestion before it is written: run the Hallucination
|
|
860
|
+
* Guard against the repo AND the installed-library symbol index (the moat), and
|
|
861
|
+
* render a verdict + issues + the installed libraries verified against (pinned
|
|
862
|
+
* versions, D8). Deterministic, offline.
|
|
863
|
+
*/
|
|
864
|
+
function verifySuggestion(args, cwd) {
|
|
865
|
+
const code = args && typeof args.code === 'string' ? args.code : '';
|
|
866
|
+
if (!code.trim()) {
|
|
867
|
+
return 'Usage: verify_suggestion({ code: "<AI-suggested code or answer>" }) — provide the snippet to verify against the repo + installed libraries.';
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
let result;
|
|
871
|
+
try {
|
|
872
|
+
const { verify } = require('../verify/hallucination-guard');
|
|
873
|
+
result = verify(code, cwd);
|
|
874
|
+
} catch (err) {
|
|
875
|
+
return `_verify_suggestion failed: ${err.message}_`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const { issues, summary } = result;
|
|
879
|
+
const out = [];
|
|
880
|
+
if (summary.clean) {
|
|
881
|
+
out.push('✓ Grounded — no fake files, imports, symbols, or scripts detected.');
|
|
882
|
+
} else {
|
|
883
|
+
out.push(`✗ ${summary.total} issue(s) found:`);
|
|
884
|
+
for (const i of issues) {
|
|
885
|
+
out.push(` L${i.line} [${i.type}] ${i.message}`);
|
|
886
|
+
if (i.suggestion) out.push(` ↳ ${i.suggestion}`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// D8: report the installed libraries the suggestion was verified against.
|
|
891
|
+
const pins = (summary.libraries || []).filter((l) => l.version).map((l) => `${l.name}@${l.version}`);
|
|
892
|
+
const n = summary.librariesIndexed || 0;
|
|
893
|
+
out.push('');
|
|
894
|
+
out.push(
|
|
895
|
+
`Grounded against ${summary.symbolsIndexed} repo + library symbol(s)` +
|
|
896
|
+
(n ? ` · ${n} installed librar${n === 1 ? 'y' : 'ies'}${pins.length ? ': ' + pins.join(', ') : ''}` : '')
|
|
897
|
+
);
|
|
898
|
+
return out.join('\n');
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion };
|
package/src/mcp/server.js
CHANGED
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Supported methods:
|
|
10
10
|
* initialize → serverInfo + capabilities
|
|
11
|
-
* tools/list →
|
|
11
|
+
* tools/list → 18 tool definitions
|
|
12
12
|
* tools/call → dispatch to handler, return result
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const readline = require('readline');
|
|
16
16
|
const { TOOLS } = require('./tools');
|
|
17
|
-
const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview } = require('./handlers');
|
|
17
|
+
const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview, verifySuggestion } = require('./handlers');
|
|
18
18
|
|
|
19
19
|
const SERVER_INFO = {
|
|
20
20
|
name: 'sigmap',
|
|
21
|
-
version: '8.
|
|
21
|
+
version: '8.2.0',
|
|
22
22
|
description: 'SigMap MCP server — code signatures on demand',
|
|
23
23
|
};
|
|
24
24
|
|
|
@@ -83,6 +83,7 @@ function dispatch(msg, cwd) {
|
|
|
83
83
|
else if (name === 'sigmap_notify_file_deleted') text = notifyFileDeleted(args, cwd);
|
|
84
84
|
else if (name === 'get_diff_context') text = getDiffContext(args, cwd);
|
|
85
85
|
else if (name === 'get_architecture_overview') text = getArchitectureOverview(args, cwd);
|
|
86
|
+
else if (name === 'verify_suggestion') text = verifySuggestion(args, cwd);
|
|
86
87
|
else {
|
|
87
88
|
respondError(id, -32601, `Unknown tool: ${name}`);
|
|
88
89
|
return;
|
package/src/mcp/tools.js
CHANGED
|
@@ -316,6 +316,26 @@ const TOOLS = [
|
|
|
316
316
|
required: [],
|
|
317
317
|
},
|
|
318
318
|
},
|
|
319
|
+
{
|
|
320
|
+
name: 'verify_suggestion',
|
|
321
|
+
description:
|
|
322
|
+
'Ground an AI code suggestion before writing it: verify a snippet or answer against the ' +
|
|
323
|
+
'repository AND the libraries actually installed in node_modules (the grounding moat). ' +
|
|
324
|
+
'Flags fake file paths, unresolvable imports, symbols absent from both the repo index and ' +
|
|
325
|
+
'the installed libraries, and non-existent npm scripts — deterministic, offline, no LLM. ' +
|
|
326
|
+
'Reports the installed libraries it verified against with pinned versions.',
|
|
327
|
+
inputSchema: {
|
|
328
|
+
type: 'object',
|
|
329
|
+
properties: {
|
|
330
|
+
code: {
|
|
331
|
+
type: 'string',
|
|
332
|
+
description:
|
|
333
|
+
'The AI-suggested code snippet or answer text to verify against the repo + installed libraries.',
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
required: ['code'],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
319
339
|
];
|
|
320
340
|
|
|
321
341
|
module.exports = { TOOLS };
|
|
@@ -21,6 +21,7 @@ const fs = require('fs');
|
|
|
21
21
|
const path = require('path');
|
|
22
22
|
const parsers = require('./parsers');
|
|
23
23
|
const { closestMatch, buildSymbolCandidates, formatSuggestion } = require('./closest-match');
|
|
24
|
+
const { buildLibraryIndex } = require('./lib-index');
|
|
24
25
|
|
|
25
26
|
// A path that looks like a test file (JS/TS spec/test, Python test_/_test, or
|
|
26
27
|
// a tests/__tests__ directory). Used to flag fake-test-file separately.
|
|
@@ -189,6 +190,28 @@ function verify(answerText, cwd, opts = {}) {
|
|
|
189
190
|
}
|
|
190
191
|
if (!fileBasenames) fileBasenames = new Set();
|
|
191
192
|
|
|
193
|
+
// Installed-library grounding (G5/D5, the moat): union the exported symbols of
|
|
194
|
+
// the libraries actually installed in node_modules, so genuine library calls
|
|
195
|
+
// stop false-flagging as fake-symbol and the summary can pin the versions the
|
|
196
|
+
// answer was verified against. Auto-runs only when the caller did not override
|
|
197
|
+
// the symbol set (keeps hermetic callers unchanged); disable with libIndex:false.
|
|
198
|
+
let libraries = opts.libraries || [];
|
|
199
|
+
{
|
|
200
|
+
let libSyms = opts.libSymbols;
|
|
201
|
+
if (!libSyms && opts.libIndex !== false && !opts.symbolSet) {
|
|
202
|
+
try {
|
|
203
|
+
const li = buildLibraryIndex(cwd, { version: opts.version });
|
|
204
|
+
libSyms = li.symbols;
|
|
205
|
+
if (!opts.libraries) libraries = li.libraries;
|
|
206
|
+
} catch (_) { libSyms = null; }
|
|
207
|
+
}
|
|
208
|
+
if (libSyms && libSyms.size) {
|
|
209
|
+
const merged = new Set(symbolSet);
|
|
210
|
+
for (const s of libSyms) merged.add(s);
|
|
211
|
+
symbolSet = merged;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
192
215
|
let deps = opts.deps;
|
|
193
216
|
let hasPkg = opts.hasPkg;
|
|
194
217
|
if (!deps) {
|
|
@@ -312,6 +335,8 @@ function verify(answerText, cwd, opts = {}) {
|
|
|
312
335
|
clean: issues.length === 0,
|
|
313
336
|
symbolsIndexed: symbolSet.size,
|
|
314
337
|
withSuggestion: issues.filter((i) => i.suggestion).length,
|
|
338
|
+
librariesIndexed: libraries.length,
|
|
339
|
+
libraries: libraries.map((l) => ({ name: l.name, version: l.version, symbols: l.symbols, typed: l.typed })),
|
|
315
340
|
};
|
|
316
341
|
|
|
317
342
|
return { issues, summary };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local-library signature index (v9.0 G5/D5 — the private-API grounding moat).
|
|
5
|
+
*
|
|
6
|
+
* Context7 knows only *public* library docs. SigMap can do something no
|
|
7
|
+
* competitor can: index the signatures of the libraries **actually installed**
|
|
8
|
+
* in `node_modules` and verify AI suggestions against repo + private +
|
|
9
|
+
* installed-lib symbols. This module builds the installed-lib half.
|
|
10
|
+
*
|
|
11
|
+
* For each **direct** dependency declared in `package.json`, it locates the
|
|
12
|
+
* package under `node_modules/<dep>`, reads its version (D8 version pinning),
|
|
13
|
+
* and extracts the exported symbol names from its TypeScript declaration entry
|
|
14
|
+
* (`types`/`typings`, else `index.d.ts`). Pure, zero-dependency, deterministic:
|
|
15
|
+
* byte-stable given a fixed installed tree. Bounded (per-file read cap + dep
|
|
16
|
+
* cap) and cached via `src/cache/sig-cache.js` so repeat builds are near-free.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { loadCache, saveCache, getChangedFiles, updateCacheEntries } = require('../cache/sig-cache');
|
|
22
|
+
|
|
23
|
+
const MAX_DTS_BYTES = 512 * 1024; // per-file read cap
|
|
24
|
+
const MAX_DEPS = 1000; // dep count cap
|
|
25
|
+
const DEP_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract exported symbol names from a `.d.ts` declaration file. Deterministic,
|
|
29
|
+
* regex-based (declaration files are already normalized, so this is robust
|
|
30
|
+
* without a full TS parser and stays zero-dependency).
|
|
31
|
+
* @param {string} src
|
|
32
|
+
* @returns {string[]} sorted unique exported names
|
|
33
|
+
*/
|
|
34
|
+
function extractDtsExports(src) {
|
|
35
|
+
const names = new Set();
|
|
36
|
+
if (!src) return [];
|
|
37
|
+
|
|
38
|
+
// export [declare] [default] function|const|let|var|class|interface|type|enum|namespace Name
|
|
39
|
+
const declRe = /\bexport\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:function|const|let|var|class|interface|type|enum|namespace|module)\s+([A-Za-z_$][\w$]*)/g;
|
|
40
|
+
let m;
|
|
41
|
+
while ((m = declRe.exec(src)) !== null) names.add(m[1]);
|
|
42
|
+
|
|
43
|
+
// export { a, b as c, default as d }
|
|
44
|
+
const listRe = /\bexport\s*(?:type\s*)?\{([^}]*)\}/g;
|
|
45
|
+
while ((m = listRe.exec(src)) !== null) {
|
|
46
|
+
for (const part of m[1].split(',')) {
|
|
47
|
+
const name = part.trim().split(/\s+as\s+/).pop().trim();
|
|
48
|
+
if (/^[A-Za-z_$][\w$]*$/.test(name) && name !== 'default') names.add(name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// export as namespace Name / export = Name
|
|
53
|
+
const nsRe = /\bexport\s+as\s+namespace\s+([A-Za-z_$][\w$]*)/g;
|
|
54
|
+
while ((m = nsRe.exec(src)) !== null) names.add(m[1]);
|
|
55
|
+
const assignRe = /\bexport\s*=\s*([A-Za-z_$][\w$]*)/g;
|
|
56
|
+
while ((m = assignRe.exec(src)) !== null) names.add(m[1]);
|
|
57
|
+
|
|
58
|
+
return [...names].sort();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Read direct dependency names declared in the project's package.json. */
|
|
62
|
+
function directDeps(cwd) {
|
|
63
|
+
const names = new Set();
|
|
64
|
+
try {
|
|
65
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
66
|
+
for (const k of DEP_KEYS) {
|
|
67
|
+
if (pkg[k] && typeof pkg[k] === 'object') {
|
|
68
|
+
for (const n of Object.keys(pkg[k])) names.add(n);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (_) { /* no/invalid package.json → no deps */ }
|
|
72
|
+
return [...names].sort();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolve an installed dependency's version + entry `.d.ts` path.
|
|
77
|
+
* @returns {{ version: string|null, dtsPath: string|null }|null} null if not installed
|
|
78
|
+
*/
|
|
79
|
+
function resolveEntry(cwd, dep) {
|
|
80
|
+
const pkgDir = path.join(cwd, 'node_modules', dep);
|
|
81
|
+
let pkg;
|
|
82
|
+
try { pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')); } catch (_) { return null; }
|
|
83
|
+
const version = typeof pkg.version === 'string' ? pkg.version : null;
|
|
84
|
+
|
|
85
|
+
const candidates = [];
|
|
86
|
+
const typesField = pkg.types || pkg.typings;
|
|
87
|
+
if (typeof typesField === 'string') {
|
|
88
|
+
candidates.push(typesField);
|
|
89
|
+
candidates.push(path.join(typesField, 'index.d.ts')); // typesField may be a dir
|
|
90
|
+
}
|
|
91
|
+
candidates.push('index.d.ts');
|
|
92
|
+
if (typeof pkg.main === 'string') candidates.push(pkg.main.replace(/\.(js|cjs|mjs)$/, '.d.ts'));
|
|
93
|
+
|
|
94
|
+
for (const c of candidates) {
|
|
95
|
+
const p = path.join(pkgDir, c);
|
|
96
|
+
try { if (fs.statSync(p).isFile()) return { version, dtsPath: p }; } catch (_) { /* next */ }
|
|
97
|
+
}
|
|
98
|
+
return { version, dtsPath: null }; // installed but untyped
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build the installed-library signature index for `cwd`.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} cwd
|
|
105
|
+
* @param {object} [opts]
|
|
106
|
+
* @param {string} [opts.version='0'] sigmap version, for cache busting
|
|
107
|
+
* @param {boolean} [opts.cache=true] use the on-disk sig-cache
|
|
108
|
+
* @returns {{ symbols: Set<string>, libraries: Array<{name,version,symbols,typed}>, count: number }}
|
|
109
|
+
*/
|
|
110
|
+
function buildLibraryIndex(cwd, opts = {}) {
|
|
111
|
+
const version = opts.version || '0';
|
|
112
|
+
const useCache = opts.cache !== false;
|
|
113
|
+
const deps = directDeps(cwd).slice(0, MAX_DEPS);
|
|
114
|
+
|
|
115
|
+
const entries = [];
|
|
116
|
+
for (const dep of deps) {
|
|
117
|
+
const r = resolveEntry(cwd, dep);
|
|
118
|
+
if (r) entries.push({ dep, version: r.version, dtsPath: r.dtsPath });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const cache = useCache ? loadCache(cwd, version) : new Map();
|
|
122
|
+
const dtsFiles = entries.filter((e) => e.dtsPath).map((e) => e.dtsPath);
|
|
123
|
+
const { unchanged } = getChangedFiles(dtsFiles, cache);
|
|
124
|
+
const unchangedSet = new Set(unchanged);
|
|
125
|
+
|
|
126
|
+
const symbols = new Set();
|
|
127
|
+
const libraries = [];
|
|
128
|
+
const fresh = [];
|
|
129
|
+
|
|
130
|
+
for (const e of entries) {
|
|
131
|
+
let names;
|
|
132
|
+
if (!e.dtsPath) {
|
|
133
|
+
names = [];
|
|
134
|
+
} else if (unchangedSet.has(e.dtsPath) && cache.get(e.dtsPath)) {
|
|
135
|
+
names = cache.get(e.dtsPath).sigs || [];
|
|
136
|
+
} else {
|
|
137
|
+
let src = '';
|
|
138
|
+
try {
|
|
139
|
+
if (fs.statSync(e.dtsPath).size <= MAX_DTS_BYTES) src = fs.readFileSync(e.dtsPath, 'utf8');
|
|
140
|
+
} catch (_) { /* unreadable → empty */ }
|
|
141
|
+
names = extractDtsExports(src);
|
|
142
|
+
fresh.push({ file: e.dtsPath, sigs: names });
|
|
143
|
+
}
|
|
144
|
+
for (const n of names) symbols.add(n);
|
|
145
|
+
libraries.push({ name: e.dep, version: e.version, symbols: names.length, typed: !!e.dtsPath });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (useCache && fresh.length) {
|
|
149
|
+
updateCacheEntries(cache, fresh);
|
|
150
|
+
saveCache(cwd, version, cache);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
libraries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
154
|
+
return { symbols, libraries, count: symbols.size };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** D8: render `name@version` pins for the typed/installed libraries. */
|
|
158
|
+
function formatVersionPins(libraries) {
|
|
159
|
+
return (libraries || [])
|
|
160
|
+
.filter((l) => l.version)
|
|
161
|
+
.map((l) => `${l.name}@${l.version}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { buildLibraryIndex, extractDtsExports, directDeps, resolveEntry, formatVersionPins };
|