sigmap 8.4.0 → 8.6.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 +18 -0
- package/README.md +29 -7
- package/gen-context.js +136 -10
- package/llms-full.txt +9 -7
- package/llms.txt +4 -4
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/config/defaults.js +3 -0
- package/src/mcp/server.js +1 -1
- package/src/retrieval/bm25.js +76 -3
- package/src/verify/lib-index.js +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,24 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [8.6.0] — 2026-07-05
|
|
14
|
+
|
|
15
|
+
Minor release — **Phase 1 "bank the A": the grounding moat's supporting surface.** Three master-plan items land together: a self-contained, third-party-runnable benchmark harness (G1), installed dependency version pins in the generated context header (D8), and `verify` promoted to a documented flagship command (G2). All zero-dependency, deterministic, and in-boundary.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Public benchmark harness (#425, PR #426):** new `public-benchmarks/` — a self-contained, third-party-runnable retrieval harness. `repos.csv` (18 real repos pinned to exact commits), `queries.json` (90 natural-language query → expected-file tasks), `run.sh` (shallow-clones the pinned repos, then scores), `score.mjs` (maps each repo with `gen-context.js`, ranks with the **shipped** identifier-aware BM25 ranker `src/retrieval/bm25.js`, reports hit@1/hit@5/MRR), and a methodology `README.md`. Deterministic — pinned commits + a byte-stable map + rank-only math → the same numbers on any machine; no LLM, no API keys, no external deps. Dev-only (excluded from the npm package). Turns the published retrieval numbers from a claim into a third-party-verifiable fact (v9.0 G1).
|
|
19
|
+
- **Version pins in the context header (#425, PR #426):** the generated header now carries a `## versions (installed direct deps)` block of sorted `name@version` pins for installed JS + Python direct dependencies, so agents reading `CLAUDE.md`/`AGENTS.md` ground against what is **actually installed here** (compounds with the G5/D5 verify moat). New `collectVersionPins(cwd)` in `src/verify/lib-index.js` (versions only — no symbol parsing, cheap enough to run per-build); gated by a new `versionPins` config key (default on). Byte-stable given a fixed installed tree (D8).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **`sigmap verify` — the grounding flagship (#425, PR #426):** `verify` is now a first-class alias of `verify-ai-output` (identical flags, output, and exit codes), with a dedicated flagship section near the top of the README and a `--help` entry. Positions the deterministic grounding guard — the one capability no agentic-grep loop or competitor offers — as the headline (v9.0 G2).
|
|
23
|
+
|
|
24
|
+
## [8.5.0] — 2026-07-05
|
|
25
|
+
|
|
26
|
+
Minor release — **deterministic query expansion (a vocabulary-mismatch recall aid).** The BM25 ranker now bridges common code-domain synonyms/abbreviations so a query for `authentication` can still surface a file whose signatures only say `auth`. Zero-dependency, deterministic. **Honest framing:** measured on the retrieval benchmark, this is **benchmark-neutral** (hit@5 unchanged within the harness's 86.7–87.8% noise band at the shipped weight) — not a hit@5 improvement. The benefit is for real users whose query vocabulary differs from the code, a case the curated benchmark doesn't exercise.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **Query expansion (#421, PR #422):** `src/retrieval/bm25.js` gains a curated, high-precision synonym/abbreviation table (`auth`↔`authentication`/`login`, `db`↔`database`, `ctx`↔`context`, `config`↔`configuration`, `req`/`res`, `init`, `impl`, …). `expandQuery()` adds synonyms to the query tokens at a **discount weight (0.15)** so an exact-term match always outranks a synonym-only match; documents are unchanged. Wired through the ranker, so `sigmap ask`, `--query`, and MCP `query_context` all benefit. A weight sweep confirmed higher weights regress retrieval, so 0.15 (benchmark-neutral) is the shipped setting.
|
|
30
|
+
|
|
13
31
|
## [8.4.0] — 2026-07-05
|
|
14
32
|
|
|
15
33
|
Minor release — **PR Evidence Report (v9.0 G3): a branded, deterministic review artifact.** SigMap already had the pieces — `review-pr` findings and `get_diff_context` — but no single Markdown comment an agent or CI could post on a PR. This adds it: one report that answers *"what changed, what it touches, and what to test"*, with no LLM.
|
package/README.md
CHANGED
|
@@ -52,15 +52,15 @@ That map is exactly what agentic grep is worst at: reproducible, auditable conte
|
|
|
52
52
|
|
|
53
53
|
**Deterministic and verifiable — the two things an agentic-grep loop can't give you:**
|
|
54
54
|
- **Deterministic** — no LLM calls, no agent loop; the same repo always produces a byte-identical map you can diff, cache, and gate in CI.
|
|
55
|
-
- **Auditable & grounded** — every file and symbol traces to a real line anchor; `sigmap verify
|
|
55
|
+
- **Auditable & grounded** — every file and symbol traces to a real line anchor; `sigmap verify` flags any AI claim that isn't.
|
|
56
56
|
- **Zero dependencies** — `npx sigmap` on any machine; no embeddings, no vector DB, no hosted service, fully offline.
|
|
57
57
|
|
|
58
58
|
**Proof it pays off** (full benchmark below):
|
|
59
59
|
<!--SM:whyMetrics-->
|
|
60
60
|
- **86.7% hit@5** — right file found in top 5 results (vs 13.6% baseline)
|
|
61
61
|
- **97.0% token reduction** — average across 21 real repos
|
|
62
|
-
- **
|
|
63
|
-
- **1.
|
|
62
|
+
- **66.7% task success rate** — up from 10% without context
|
|
63
|
+
- **1.47 prompts per task** — down from 2.84 (48.4% fewer retries)
|
|
64
64
|
<!--/SM:whyMetrics-->
|
|
65
65
|
- **<!--SM:languages-->33<!--/SM:languages--> languages supported** — TypeScript, Python, Go, Rust, Java, R, and more
|
|
66
66
|
- **No vendor lock-in** — works with any AI assistant or local LLM
|
|
@@ -69,6 +69,28 @@ That map is exactly what agentic grep is worst at: reproducible, auditable conte
|
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
72
|
+
## 🔒 `sigmap verify` — the grounding flagship
|
|
73
|
+
|
|
74
|
+
The one thing no agentic-grep loop, and no competitor, gives you: **prove an AI answer is anchored to real signatures and line numbers before you trust it.** Deterministic, offline, no LLM — SigMap indexes your repo *plus the libraries actually installed here* and flags every fabricated file, import, symbol, test, or npm script.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
sigmap verify answer.md # ✓ grounded, or a line-by-line list of fabrications
|
|
78
|
+
sigmap verify answer.md --json # machine-readable report; exits 1 if any issue (CI gate)
|
|
79
|
+
sigmap verify answer.md --report # standalone red/amber/green HTML report
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
[sigmap] ✗ answer.md — 2 issues found
|
|
84
|
+
fake-file: 1 fake-test-file: 0 fake-import: 0 fake-symbol: 1 fake-npm-script: 0
|
|
85
|
+
|
|
86
|
+
L12 [Fake file] src/auth/session-store.js does not exist
|
|
87
|
+
L27 [Fake symbol] authorize() — did you mean authenticate()?
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`verify` is the flagship; `verify-ai-output` remains as the full command name. Pair it with `sigmap verify-plan` (check a plan before execution) and the `verify_suggestion` MCP tool (verify AI code against repo + private + installed-library symbols mid-session).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
72
94
|
## Replace this with SigMap
|
|
73
95
|
|
|
74
96
|
| Without SigMap | With SigMap |
|
|
@@ -98,13 +120,13 @@ Ask → Rank → Context → Validate → Judge → Learn
|
|
|
98
120
|
|
|
99
121
|
<!--SM:benchmarkBlock-->
|
|
100
122
|
```
|
|
101
|
-
Benchmark : sigmap-v8.
|
|
102
|
-
Date : 2026-07-
|
|
123
|
+
Benchmark : sigmap-v8.6-main (21 repositories, including R language)
|
|
124
|
+
Date : 2026-07-05
|
|
103
125
|
|
|
104
126
|
Hit@5 : 86.7% (baseline 13.6% — 6.4× lift)
|
|
105
127
|
Token reduction: 97.0% (across 21 repos)
|
|
106
|
-
Prompt reduction : 48.
|
|
107
|
-
Task success :
|
|
128
|
+
Prompt reduction : 48.4% (2.84 → 1.47 prompts per task)
|
|
129
|
+
Task success : 66.7% (baseline 10%)
|
|
108
130
|
Repos tested : 21 (JavaScript, Python, Go, Rust, Java, R, C++, C#, Dart, Swift, Ruby, PHP, Scala, Kotlin, and more)
|
|
109
131
|
```
|
|
110
132
|
<!--/SM:benchmarkBlock-->
|
package/gen-context.js
CHANGED
|
@@ -1498,6 +1498,9 @@ __factories["./src/config/defaults"] = function(module, exports) {
|
|
|
1498
1498
|
// Include a compact import dependency map at top of output
|
|
1499
1499
|
depMap: true,
|
|
1500
1500
|
|
|
1501
|
+
// Include a compact `name@version` list of installed direct deps (D8)
|
|
1502
|
+
versionPins: true,
|
|
1503
|
+
|
|
1501
1504
|
// Include TODO/FIXME/HACK/XXX comments as compact section
|
|
1502
1505
|
todos: true,
|
|
1503
1506
|
|
|
@@ -13108,7 +13111,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
13108
13111
|
|
|
13109
13112
|
const SERVER_INFO = {
|
|
13110
13113
|
name: 'sigmap',
|
|
13111
|
-
version: '8.
|
|
13114
|
+
version: '8.6.0',
|
|
13112
13115
|
description: 'SigMap MCP server — code signatures on demand',
|
|
13113
13116
|
};
|
|
13114
13117
|
|
|
@@ -13923,6 +13926,78 @@ __factories["./src/retrieval/bm25"] = function(module, exports) {
|
|
|
13923
13926
|
// are counted PATH_BOOST times when building the document term-frequency map.
|
|
13924
13927
|
const PATH_BOOST = 3;
|
|
13925
13928
|
|
|
13929
|
+
// Curated, high-precision code-domain synonym / abbreviation expansions. A query
|
|
13930
|
+
// for `authentication` should still surface a file whose signatures only say
|
|
13931
|
+
// `auth`. Kept deliberately tight — over-broad synonyms hurt precision. Groups
|
|
13932
|
+
// are expanded bidirectionally (every member maps to the others). Values are
|
|
13933
|
+
// tokenized+stemmed at load, so entries are written in natural form.
|
|
13934
|
+
const EXPANSION_GROUPS = [
|
|
13935
|
+
['auth', 'authenticate', 'authentication', 'login', 'signin', 'credential'],
|
|
13936
|
+
['authorize', 'authorization', 'permission', 'access'],
|
|
13937
|
+
['config', 'configuration', 'settings', 'options'],
|
|
13938
|
+
['db', 'database'],
|
|
13939
|
+
['ctx', 'context'],
|
|
13940
|
+
['req', 'request'],
|
|
13941
|
+
['res', 'response'],
|
|
13942
|
+
['err', 'error'],
|
|
13943
|
+
['msg', 'message'],
|
|
13944
|
+
['init', 'initialize', 'initialization', 'setup'],
|
|
13945
|
+
['async', 'asynchronous'],
|
|
13946
|
+
['sync', 'synchronize', 'synchronous'],
|
|
13947
|
+
['repo', 'repository'],
|
|
13948
|
+
['impl', 'implementation'],
|
|
13949
|
+
['util', 'utility', 'helper'],
|
|
13950
|
+
['param', 'parameter', 'argument'],
|
|
13951
|
+
['fn', 'func', 'function'],
|
|
13952
|
+
['btn', 'button'],
|
|
13953
|
+
['calc', 'calculate', 'calculation'],
|
|
13954
|
+
['gen', 'generate', 'generator'],
|
|
13955
|
+
['val', 'validate', 'validation'],
|
|
13956
|
+
['del', 'delete', 'remove'],
|
|
13957
|
+
['dir', 'directory', 'folder'],
|
|
13958
|
+
['env', 'environment'],
|
|
13959
|
+
['doc', 'document', 'documentation'],
|
|
13960
|
+
['id', 'identifier'],
|
|
13961
|
+
['num', 'number'],
|
|
13962
|
+
['str', 'string'],
|
|
13963
|
+
];
|
|
13964
|
+
|
|
13965
|
+
// The weight applied to an expanded (synonym) query term, so an exact match on
|
|
13966
|
+
// the literal query token always outranks a synonym-only match.
|
|
13967
|
+
const EXPANSION_WEIGHT = 0.15;
|
|
13968
|
+
|
|
13969
|
+
// Build a stemmed lookup: stem(member) → Set of the group's other stemmed members.
|
|
13970
|
+
const EXPANSIONS = (() => {
|
|
13971
|
+
const map = new Map();
|
|
13972
|
+
for (const group of EXPANSION_GROUPS) {
|
|
13973
|
+
const stemmed = [...new Set(group.map((w) => tokenize(w).join('')).filter(Boolean))];
|
|
13974
|
+
for (const s of stemmed) {
|
|
13975
|
+
if (!map.has(s)) map.set(s, new Set());
|
|
13976
|
+
for (const other of stemmed) if (other !== s) map.get(s).add(other);
|
|
13977
|
+
}
|
|
13978
|
+
}
|
|
13979
|
+
return map;
|
|
13980
|
+
})();
|
|
13981
|
+
|
|
13982
|
+
/**
|
|
13983
|
+
* Expand stemmed query tokens with curated synonyms. Returns a Map of
|
|
13984
|
+
* token → weight (1 for the original query tokens, EXPANSION_WEIGHT for
|
|
13985
|
+
* synonyms). Original tokens always keep full weight even if also a synonym.
|
|
13986
|
+
*
|
|
13987
|
+
* @param {string[]} qToks stemmed, de-duplicated query tokens
|
|
13988
|
+
* @returns {Map<string, number>}
|
|
13989
|
+
*/
|
|
13990
|
+
function expandQuery(qToks) {
|
|
13991
|
+
const weights = new Map();
|
|
13992
|
+
for (const t of qToks) weights.set(t, 1);
|
|
13993
|
+
for (const t of qToks) {
|
|
13994
|
+
const syns = EXPANSIONS.get(t);
|
|
13995
|
+
if (!syns) continue;
|
|
13996
|
+
for (const s of syns) if (!weights.has(s)) weights.set(s, EXPANSION_WEIGHT);
|
|
13997
|
+
}
|
|
13998
|
+
return weights;
|
|
13999
|
+
}
|
|
14000
|
+
|
|
13926
14001
|
/**
|
|
13927
14002
|
* BM25 re-rank of candidates against a query. Each candidate is
|
|
13928
14003
|
* `{ file, sigs }`; the returned objects preserve all original candidate
|
|
@@ -13958,23 +14033,24 @@ __factories["./src/retrieval/bm25"] = function(module, exports) {
|
|
|
13958
14033
|
}
|
|
13959
14034
|
|
|
13960
14035
|
const qToks = [...new Set(tokenize(query))];
|
|
14036
|
+
const qWeights = expandQuery(qToks); // token → weight (1 exact, <1 synonym)
|
|
13961
14037
|
|
|
13962
14038
|
return docs
|
|
13963
14039
|
.map((d) => {
|
|
13964
14040
|
let score = 0;
|
|
13965
|
-
for (const t of
|
|
14041
|
+
for (const [t, w] of qWeights) {
|
|
13966
14042
|
const f = d.tf.get(t);
|
|
13967
14043
|
if (!f) continue;
|
|
13968
14044
|
const dfT = df.get(t);
|
|
13969
14045
|
const idf = Math.log(1 + (N - dfT + 0.5) / (dfT + 0.5));
|
|
13970
|
-
score += (idf * (f * (k1 + 1))) / (f + k1 * (1 - b + (b * d.len) / avgdl));
|
|
14046
|
+
score += w * ((idf * (f * (k1 + 1))) / (f + k1 * (1 - b + (b * d.len) / avgdl)));
|
|
13971
14047
|
}
|
|
13972
14048
|
return Object.assign({}, d.cand, { score });
|
|
13973
14049
|
})
|
|
13974
14050
|
.sort((a, c) => c.score - a.score || String(a.file).localeCompare(String(c.file)));
|
|
13975
14051
|
}
|
|
13976
14052
|
|
|
13977
|
-
module.exports = { tokenize, stem, bm25rank, PATH_BOOST, STOP };
|
|
14053
|
+
module.exports = { tokenize, stem, bm25rank, PATH_BOOST, STOP, expandQuery, EXPANSIONS, EXPANSION_WEIGHT };
|
|
13978
14054
|
|
|
13979
14055
|
};
|
|
13980
14056
|
|
|
@@ -17238,8 +17314,37 @@ __factories["./src/verify/lib-index"] = function(module, exports) {
|
|
|
17238
17314
|
.map((l) => `${l.name}@${l.version}`);
|
|
17239
17315
|
}
|
|
17240
17316
|
|
|
17317
|
+
/**
|
|
17318
|
+
* D8: collect `name@version` pins for direct dependencies — versions only, no
|
|
17319
|
+
* symbol extraction, no cache. Cheap enough to run on every context build so
|
|
17320
|
+
* the generated header can ground against what is actually installed (JS +
|
|
17321
|
+
* Python). Deterministic: sorted, then capped.
|
|
17322
|
+
* @param {string} cwd
|
|
17323
|
+
* @param {object} [opts]
|
|
17324
|
+
* @param {number} [opts.limit=40] max pins returned (after sort)
|
|
17325
|
+
* @returns {{ pins: string[], total: number }} pins capped to limit; total = all resolved
|
|
17326
|
+
*/
|
|
17327
|
+
function collectVersionPins(cwd, opts = {}) {
|
|
17328
|
+
const limit = Number.isInteger(opts.limit) && opts.limit >= 0 ? opts.limit : 40;
|
|
17329
|
+
const pins = [];
|
|
17330
|
+
for (const dep of directDeps(cwd).slice(0, MAX_DEPS)) {
|
|
17331
|
+
const r = resolveEntry(cwd, dep);
|
|
17332
|
+
if (r && r.version) pins.push(`${dep}@${r.version}`);
|
|
17333
|
+
}
|
|
17334
|
+
const sitePkgs = findSitePackages(cwd);
|
|
17335
|
+
if (sitePkgs.length) {
|
|
17336
|
+
for (const dep of pythonDirectDeps(cwd).slice(0, MAX_DEPS)) {
|
|
17337
|
+
const r = resolvePyEntry(sitePkgs, dep);
|
|
17338
|
+
if (r && r.version) pins.push(`${dep}@${r.version}`);
|
|
17339
|
+
}
|
|
17340
|
+
}
|
|
17341
|
+
pins.sort();
|
|
17342
|
+
return { pins: limit ? pins.slice(0, limit) : pins, total: pins.length };
|
|
17343
|
+
}
|
|
17344
|
+
|
|
17241
17345
|
module.exports = {
|
|
17242
17346
|
buildLibraryIndex, extractDtsExports, directDeps, resolveEntry, formatVersionPins,
|
|
17347
|
+
collectVersionPins,
|
|
17243
17348
|
extractPyExports, pythonDirectDeps, findSitePackages, resolvePyEntry,
|
|
17244
17349
|
};
|
|
17245
17350
|
|
|
@@ -17574,7 +17679,7 @@ function __tryGit(args, opts = {}) {
|
|
|
17574
17679
|
catch (_) { return ''; }
|
|
17575
17680
|
}
|
|
17576
17681
|
|
|
17577
|
-
const VERSION = '8.
|
|
17682
|
+
const VERSION = '8.6.0';
|
|
17578
17683
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
17579
17684
|
|
|
17580
17685
|
function requireSourceOrBundled(key) {
|
|
@@ -18177,6 +18282,23 @@ function formatOutput(fileEntries, cwd, routingEnabled, config, extras) {
|
|
|
18177
18282
|
lines.push('');
|
|
18178
18283
|
}
|
|
18179
18284
|
|
|
18285
|
+
// D8: installed direct-dependency version pins — grounds agents against what
|
|
18286
|
+
// is actually installed here (byte-stable given a fixed installed tree).
|
|
18287
|
+
if (!config || config.versionPins !== false) {
|
|
18288
|
+
try {
|
|
18289
|
+
const { collectVersionPins } = requireSourceOrBundled('./src/verify/lib-index');
|
|
18290
|
+
const { pins, total } = collectVersionPins(cwd);
|
|
18291
|
+
if (pins.length) {
|
|
18292
|
+
lines.push('## versions (installed direct deps)');
|
|
18293
|
+
lines.push('```');
|
|
18294
|
+
lines.push(...pins);
|
|
18295
|
+
if (total > pins.length) lines.push(`… +${total - pins.length} more`);
|
|
18296
|
+
lines.push('```');
|
|
18297
|
+
lines.push('');
|
|
18298
|
+
}
|
|
18299
|
+
} catch (_) { /* no/uninstalled deps → skip */ }
|
|
18300
|
+
}
|
|
18301
|
+
|
|
18180
18302
|
const todoLines = buildTodoSection(fileEntries, cwd, config || {});
|
|
18181
18303
|
if (todoLines.length) {
|
|
18182
18304
|
lines.push('## todos');
|
|
@@ -19369,9 +19491,10 @@ Usage:
|
|
|
19369
19491
|
${cmd} --impact <file> Show every file impacted by changing <file>
|
|
19370
19492
|
${cmd} --impact <file> --json Impact as JSON {changed, direct, transitive, tests, routes}
|
|
19371
19493
|
${cmd} --impact <file> --depth <n> BFS depth limit (default 3, 0=unlimited)
|
|
19372
|
-
${cmd} verify
|
|
19373
|
-
${cmd} verify
|
|
19374
|
-
${cmd} verify
|
|
19494
|
+
${cmd} verify <answer.md> Flagship grounding guard — flag fake files/tests/imports/symbols/npm-scripts in an AI answer (alias of verify-ai-output)
|
|
19495
|
+
${cmd} verify <answer.md> --json Grounding report as JSON (exits 1 if issues)
|
|
19496
|
+
${cmd} verify <answer.md> --report Write a standalone HTML report (red/amber/green)
|
|
19497
|
+
${cmd} verify-ai-output <answer.md> Full command name for ${cmd} verify
|
|
19375
19498
|
${cmd} conventions Extract repo file-naming/export/test conventions (--conflicts, --inject, --report, --fix)
|
|
19376
19499
|
${cmd} scaffold "<name>" Propose a convention-matched file/dir scaffold (--ext, --threshold, --force, --json)
|
|
19377
19500
|
${cmd} verify-plan <plan.md|-> Check a plan vs the live index — files/symbols exist, blast radius, scope (--json)
|
|
@@ -21231,7 +21354,10 @@ function main() {
|
|
|
21231
21354
|
process.exit(s.ok ? 0 : 1);
|
|
21232
21355
|
}
|
|
21233
21356
|
|
|
21234
|
-
|
|
21357
|
+
// `sigmap verify` is the flagship alias for `verify-ai-output` — the grounding
|
|
21358
|
+
// guard that proves an AI answer is anchored to real signatures + line anchors.
|
|
21359
|
+
if (args[0] === 'verify-ai-output' || args[0] === 'verify') {
|
|
21360
|
+
const cmdName = args[0];
|
|
21235
21361
|
const target = args[1];
|
|
21236
21362
|
const jsonOut = args.includes('--json');
|
|
21237
21363
|
const reportIdx = args.indexOf('--report');
|
|
@@ -21239,7 +21365,7 @@ function main() {
|
|
|
21239
21365
|
? (args[reportIdx + 1] && !args[reportIdx + 1].startsWith('--') ? args[reportIdx + 1] : 'sigmap-verify-report.html')
|
|
21240
21366
|
: null;
|
|
21241
21367
|
if (!target || target.startsWith('--')) {
|
|
21242
|
-
console.error(
|
|
21368
|
+
console.error(`[sigmap] Usage: sigmap ${cmdName} <answer.md> [--json] [--report [out.html]]`);
|
|
21243
21369
|
process.exit(1);
|
|
21244
21370
|
}
|
|
21245
21371
|
const absTarget = path.resolve(cwd, target);
|
package/llms-full.txt
CHANGED
|
@@ -11,20 +11,20 @@ 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.6.0 | Benchmark: sigmap-v8.6-main (2026-07-05)
|
|
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.6-main, 2026-07-05)
|
|
21
21
|
|
|
22
22
|
| Metric | Without SigMap | With SigMap |
|
|
23
23
|
|--------|----------------|-------------|
|
|
24
24
|
| Retrieval hit@5 | 13.6% (random) | 86.7% (6.4× lift) |
|
|
25
25
|
| Token reduction | — | 97.0% average |
|
|
26
|
-
| Task success proxy | 10% |
|
|
27
|
-
| Prompts per task | 2.84 | 1.
|
|
26
|
+
| Task success proxy | 10% | 66.7% |
|
|
27
|
+
| Prompts per task | 2.84 | 1.47 (48.4% fewer) |
|
|
28
28
|
| Supported languages | — | 33 |
|
|
29
29
|
| MCP tools | — | 18 |
|
|
30
30
|
| npm runtime dependencies | — | 0 |
|
|
@@ -99,9 +99,10 @@ sigmap weights --json Learned weights as JSON
|
|
|
99
99
|
sigmap --impact <file> Show every file impacted by changing <file>
|
|
100
100
|
sigmap --impact <file> --json Impact as JSON {changed, direct, transitive, tests, routes}
|
|
101
101
|
sigmap --impact <file> --depth <n> BFS depth limit (default 3, 0=unlimited)
|
|
102
|
-
sigmap verify
|
|
103
|
-
sigmap verify
|
|
104
|
-
sigmap verify
|
|
102
|
+
sigmap verify <answer.md> Flagship grounding guard — flag fake files/tests/imports/symbols/npm-scripts in an AI answer (alias of verify-ai-output)
|
|
103
|
+
sigmap verify <answer.md> --json Grounding report as JSON (exits 1 if issues)
|
|
104
|
+
sigmap verify <answer.md> --report Write a standalone HTML report (red/amber/green)
|
|
105
|
+
sigmap verify-ai-output <answer.md> Full command name for gen-context verify
|
|
105
106
|
sigmap conventions Extract repo file-naming/export/test conventions (--conflicts, --inject, --report, --fix)
|
|
106
107
|
sigmap scaffold "<name>" Propose a convention-matched file/dir scaffold (--ext, --threshold, --force, --json)
|
|
107
108
|
sigmap verify-plan <plan.md|-> Check a plan vs the live index — files/symbols exist, blast radius, scope (--json)
|
|
@@ -310,6 +311,7 @@ format = default
|
|
|
310
311
|
tracking = false
|
|
311
312
|
mcp = {"autoRegister":true}
|
|
312
313
|
depMap = true
|
|
314
|
+
versionPins = true
|
|
313
315
|
todos = true
|
|
314
316
|
changes = true
|
|
315
317
|
changesCommits = 10
|
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.6.0 | Benchmark: sigmap-v8.6-main (2026-07-05)
|
|
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,12 +23,12 @@ 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.6-main, 2026-07-05)
|
|
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
|
-
- Task success:
|
|
31
|
-
- Prompts per task: 1.
|
|
30
|
+
- Task success: 66.7% vs 10% without SigMap
|
|
31
|
+
- Prompts per task: 1.47 vs 2.84 baseline (48.4% fewer)
|
|
32
32
|
- Languages: 33 supported · MCP tools: 18
|
|
33
33
|
- Dependencies: zero npm runtime dependencies · fully offline
|
|
34
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.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/config/defaults.js
CHANGED
|
@@ -114,6 +114,9 @@ const DEFAULTS = {
|
|
|
114
114
|
// Include a compact import dependency map at top of output
|
|
115
115
|
depMap: true,
|
|
116
116
|
|
|
117
|
+
// Include a compact `name@version` list of installed direct deps (D8)
|
|
118
|
+
versionPins: true,
|
|
119
|
+
|
|
117
120
|
// Include TODO/FIXME/HACK/XXX comments as compact section
|
|
118
121
|
todos: true,
|
|
119
122
|
|
package/src/mcp/server.js
CHANGED
package/src/retrieval/bm25.js
CHANGED
|
@@ -68,6 +68,78 @@ function tokenize(text) {
|
|
|
68
68
|
// are counted PATH_BOOST times when building the document term-frequency map.
|
|
69
69
|
const PATH_BOOST = 3;
|
|
70
70
|
|
|
71
|
+
// Curated, high-precision code-domain synonym / abbreviation expansions. A query
|
|
72
|
+
// for `authentication` should still surface a file whose signatures only say
|
|
73
|
+
// `auth`. Kept deliberately tight — over-broad synonyms hurt precision. Groups
|
|
74
|
+
// are expanded bidirectionally (every member maps to the others). Values are
|
|
75
|
+
// tokenized+stemmed at load, so entries are written in natural form.
|
|
76
|
+
const EXPANSION_GROUPS = [
|
|
77
|
+
['auth', 'authenticate', 'authentication', 'login', 'signin', 'credential'],
|
|
78
|
+
['authorize', 'authorization', 'permission', 'access'],
|
|
79
|
+
['config', 'configuration', 'settings', 'options'],
|
|
80
|
+
['db', 'database'],
|
|
81
|
+
['ctx', 'context'],
|
|
82
|
+
['req', 'request'],
|
|
83
|
+
['res', 'response'],
|
|
84
|
+
['err', 'error'],
|
|
85
|
+
['msg', 'message'],
|
|
86
|
+
['init', 'initialize', 'initialization', 'setup'],
|
|
87
|
+
['async', 'asynchronous'],
|
|
88
|
+
['sync', 'synchronize', 'synchronous'],
|
|
89
|
+
['repo', 'repository'],
|
|
90
|
+
['impl', 'implementation'],
|
|
91
|
+
['util', 'utility', 'helper'],
|
|
92
|
+
['param', 'parameter', 'argument'],
|
|
93
|
+
['fn', 'func', 'function'],
|
|
94
|
+
['btn', 'button'],
|
|
95
|
+
['calc', 'calculate', 'calculation'],
|
|
96
|
+
['gen', 'generate', 'generator'],
|
|
97
|
+
['val', 'validate', 'validation'],
|
|
98
|
+
['del', 'delete', 'remove'],
|
|
99
|
+
['dir', 'directory', 'folder'],
|
|
100
|
+
['env', 'environment'],
|
|
101
|
+
['doc', 'document', 'documentation'],
|
|
102
|
+
['id', 'identifier'],
|
|
103
|
+
['num', 'number'],
|
|
104
|
+
['str', 'string'],
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
// The weight applied to an expanded (synonym) query term, so an exact match on
|
|
108
|
+
// the literal query token always outranks a synonym-only match.
|
|
109
|
+
const EXPANSION_WEIGHT = 0.15;
|
|
110
|
+
|
|
111
|
+
// Build a stemmed lookup: stem(member) → Set of the group's other stemmed members.
|
|
112
|
+
const EXPANSIONS = (() => {
|
|
113
|
+
const map = new Map();
|
|
114
|
+
for (const group of EXPANSION_GROUPS) {
|
|
115
|
+
const stemmed = [...new Set(group.map((w) => tokenize(w).join('')).filter(Boolean))];
|
|
116
|
+
for (const s of stemmed) {
|
|
117
|
+
if (!map.has(s)) map.set(s, new Set());
|
|
118
|
+
for (const other of stemmed) if (other !== s) map.get(s).add(other);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return map;
|
|
122
|
+
})();
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Expand stemmed query tokens with curated synonyms. Returns a Map of
|
|
126
|
+
* token → weight (1 for the original query tokens, EXPANSION_WEIGHT for
|
|
127
|
+
* synonyms). Original tokens always keep full weight even if also a synonym.
|
|
128
|
+
*
|
|
129
|
+
* @param {string[]} qToks stemmed, de-duplicated query tokens
|
|
130
|
+
* @returns {Map<string, number>}
|
|
131
|
+
*/
|
|
132
|
+
function expandQuery(qToks) {
|
|
133
|
+
const weights = new Map();
|
|
134
|
+
for (const t of qToks) weights.set(t, 1);
|
|
135
|
+
for (const t of qToks) {
|
|
136
|
+
const syns = EXPANSIONS.get(t);
|
|
137
|
+
if (!syns) continue;
|
|
138
|
+
for (const s of syns) if (!weights.has(s)) weights.set(s, EXPANSION_WEIGHT);
|
|
139
|
+
}
|
|
140
|
+
return weights;
|
|
141
|
+
}
|
|
142
|
+
|
|
71
143
|
/**
|
|
72
144
|
* BM25 re-rank of candidates against a query. Each candidate is
|
|
73
145
|
* `{ file, sigs }`; the returned objects preserve all original candidate
|
|
@@ -103,20 +175,21 @@ function bm25rank(query, candidates) {
|
|
|
103
175
|
}
|
|
104
176
|
|
|
105
177
|
const qToks = [...new Set(tokenize(query))];
|
|
178
|
+
const qWeights = expandQuery(qToks); // token → weight (1 exact, <1 synonym)
|
|
106
179
|
|
|
107
180
|
return docs
|
|
108
181
|
.map((d) => {
|
|
109
182
|
let score = 0;
|
|
110
|
-
for (const t of
|
|
183
|
+
for (const [t, w] of qWeights) {
|
|
111
184
|
const f = d.tf.get(t);
|
|
112
185
|
if (!f) continue;
|
|
113
186
|
const dfT = df.get(t);
|
|
114
187
|
const idf = Math.log(1 + (N - dfT + 0.5) / (dfT + 0.5));
|
|
115
|
-
score += (idf * (f * (k1 + 1))) / (f + k1 * (1 - b + (b * d.len) / avgdl));
|
|
188
|
+
score += w * ((idf * (f * (k1 + 1))) / (f + k1 * (1 - b + (b * d.len) / avgdl)));
|
|
116
189
|
}
|
|
117
190
|
return Object.assign({}, d.cand, { score });
|
|
118
191
|
})
|
|
119
192
|
.sort((a, c) => c.score - a.score || String(a.file).localeCompare(String(c.file)));
|
|
120
193
|
}
|
|
121
194
|
|
|
122
|
-
module.exports = { tokenize, stem, bm25rank, PATH_BOOST, STOP };
|
|
195
|
+
module.exports = { tokenize, stem, bm25rank, PATH_BOOST, STOP, expandQuery, EXPANSIONS, EXPANSION_WEIGHT };
|
package/src/verify/lib-index.js
CHANGED
|
@@ -298,7 +298,36 @@ function formatVersionPins(libraries) {
|
|
|
298
298
|
.map((l) => `${l.name}@${l.version}`);
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/**
|
|
302
|
+
* D8: collect `name@version` pins for direct dependencies — versions only, no
|
|
303
|
+
* symbol extraction, no cache. Cheap enough to run on every context build so
|
|
304
|
+
* the generated header can ground against what is actually installed (JS +
|
|
305
|
+
* Python). Deterministic: sorted, then capped.
|
|
306
|
+
* @param {string} cwd
|
|
307
|
+
* @param {object} [opts]
|
|
308
|
+
* @param {number} [opts.limit=40] max pins returned (after sort)
|
|
309
|
+
* @returns {{ pins: string[], total: number }} pins capped to limit; total = all resolved
|
|
310
|
+
*/
|
|
311
|
+
function collectVersionPins(cwd, opts = {}) {
|
|
312
|
+
const limit = Number.isInteger(opts.limit) && opts.limit >= 0 ? opts.limit : 40;
|
|
313
|
+
const pins = [];
|
|
314
|
+
for (const dep of directDeps(cwd).slice(0, MAX_DEPS)) {
|
|
315
|
+
const r = resolveEntry(cwd, dep);
|
|
316
|
+
if (r && r.version) pins.push(`${dep}@${r.version}`);
|
|
317
|
+
}
|
|
318
|
+
const sitePkgs = findSitePackages(cwd);
|
|
319
|
+
if (sitePkgs.length) {
|
|
320
|
+
for (const dep of pythonDirectDeps(cwd).slice(0, MAX_DEPS)) {
|
|
321
|
+
const r = resolvePyEntry(sitePkgs, dep);
|
|
322
|
+
if (r && r.version) pins.push(`${dep}@${r.version}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
pins.sort();
|
|
326
|
+
return { pins: limit ? pins.slice(0, limit) : pins, total: pins.length };
|
|
327
|
+
}
|
|
328
|
+
|
|
301
329
|
module.exports = {
|
|
302
330
|
buildLibraryIndex, extractDtsExports, directDeps, resolveEntry, formatVersionPins,
|
|
331
|
+
collectVersionPins,
|
|
303
332
|
extractPyExports, pythonDirectDeps, findSitePackages, resolvePyEntry,
|
|
304
333
|
};
|