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 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-ai-output` flags any AI claim that isn't.
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
- - **67.8% task success rate** — up from 10% without context
63
- - **1.46 prompts per task** — down from 2.84 (48.8% fewer retries)
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.4-main (21 repositories, including R language)
102
- Date : 2026-07-04
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.8% (2.84 → 1.46 prompts per task)
107
- Task success : 67.8% (baseline 10%)
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.4.0',
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 qToks) {
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.4.0';
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-ai-output <answer.md> Flag fake files/tests/imports/symbols/npm-scripts in an AI answer
19373
- ${cmd} verify-ai-output <answer.md> --json Hallucination report as JSON (exits 1 if issues)
19374
- ${cmd} verify-ai-output <answer.md> --report Write a standalone HTML report (red/amber/green)
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
- if (args[0] === 'verify-ai-output') {
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('[sigmap] Usage: sigmap verify-ai-output <answer.md> [--json] [--report [out.html]]');
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.4.0 | Benchmark: sigmap-v8.4-main (2026-07-04)
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.4-main, 2026-07-04)
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% | 67.8% |
27
- | Prompts per task | 2.84 | 1.46 (48.8% fewer) |
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-ai-output <answer.md> Flag fake files/tests/imports/symbols/npm-scripts in an AI answer
103
- sigmap verify-ai-output <answer.md> --json Hallucination report as JSON (exits 1 if issues)
104
- sigmap verify-ai-output <answer.md> --report Write a standalone HTML report (red/amber/green)
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.4.0 | Benchmark: sigmap-v8.4-main (2026-07-04)
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.4-main, 2026-07-04)
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: 67.8% vs 10% without SigMap
31
- - Prompts per task: 1.46 vs 2.84 baseline (48.8% fewer)
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.4.0",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.4.0",
3
+ "version": "8.6.0",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "8.4.0",
3
+ "version": "8.6.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '8.4.0',
21
+ version: '8.6.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -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 qToks) {
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 };
@@ -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
  };