sigmap 8.21.0 → 8.22.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,19 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [8.22.0] — 2026-07-28
14
+
15
+ Minor release — **"Hard Corpus" (v8.22)**: the benchmark corpus gains a no-leakage hard split with a deterministic leakage gate, and per-repo-size buckets stop tiny repos from flattering the average. The headline retrieval number gets harder — and honest.
16
+
17
+ ### Added
18
+ - **Hard-split corpus + leakage gate + size buckets (#505, PR #506):** new `src/eval/corpus.js` — a task "leaks" when its BM25-tokenized query shares a stemmed token with the tokenized basenames of its expected files; `validateTasks` flags leaky `split: "hard"` tasks as violations, and `sizeBucket` groups repos at 200/1000 scanned files (tertiles of the 43-repo corpus). New CI gate `scripts/validate-task-corpus.mjs` (exit 1 on hard-split leakage). `loadTasks` carries the optional `split` field (default `easy`). `benchmark:honest` now reports hit@5/MRR per split and per size bucket — buckets use files scanned on disk, not the budget-capped context index. 15 hand-authored hard tasks (express, flask, axios, fastify, gin), all leak-free. 8 new integration tests (128 test files).
19
+ - **MiniMax LLM-ablation provider (PR #504)** — thanks @octo-patch: `MINIMAX_API_KEY` support in `scripts/run-llm-ablation.mjs` (OpenAI-compatible endpoint, default model MiniMax-M3, `MINIMAX_BASE_URL` override) plus a pricing entry and tests.
20
+
21
+ ### Changed
22
+ - **Headline honesty, again:** the leakage gate measured that **90 of 110 pre-existing easy tasks leak filename tokens**, and the new hard split scores **33.3% hit@5 vs the grep baseline's 53.3%** — with leakage removed, grep currently wins; that measured vocabulary-mismatch ceiling is what B2 (repo-mined expansion, v9.0) exists to attack. Overall corpus (125 tasks): 72.8% hit@5, honest lift 1.63× (+28pt).
23
+
24
+ ---
25
+
13
26
  ## [8.21.0] — 2026-07-19
14
27
 
15
28
  Minor release — **"Semantic Bridge II" (v8.21)**: doc-comment hints reach Go, Rust, and Java, and the import graph gains a principled centrality prior for ranking — flag-gated and measured.
package/README.md CHANGED
@@ -59,10 +59,10 @@ That map is exactly what agentic grep is worst at: reproducible, auditable conte
59
59
 
60
60
  **Proof it pays off** (full benchmark below):
61
61
  <!--SM:whyMetrics-->
62
- - **85.6% hit@5** — right file in top 5 results (vs 42.7% single-shot grep baseline — 2.00× lift)
62
+ - **82.2% hit@5** — right file in top 5 results (vs 44.8% single-shot grep baseline — 1.59× lift)
63
63
  - **96.8% token reduction** — average across 21 real repos
64
- - **66.7% task-success proxy** — modeled from retrieval tiers, not measured LLM sessions
65
- - **1.48 prompts per task** — down from 2.84 (48.0% fewer retries, modeled)
64
+ - **64.8% task-success proxy** — modeled from retrieval tiers, not measured LLM sessions
65
+ - **1.53 prompts per task** — down from 2.84 (46.1% fewer retries, modeled)
66
66
  <!--/SM:whyMetrics-->
67
67
  - **<!--SM:languages-->33<!--/SM:languages--> languages supported** — TypeScript, Python, Go, Rust, Java, R, and more
68
68
  - **No vendor lock-in** — works with any AI assistant or local LLM
@@ -98,7 +98,7 @@ sigmap verify answer.md --report # standalone red/amber/green HTML report
98
98
  | Without SigMap | With SigMap |
99
99
  |---|---|
100
100
  | ❌ Non-reproducible agent guesses | ✅ Deterministic map — same input, same output, every time |
101
- | ❌ "Trust me" AI answers | ✅ Grounded — right file in context <!--SM:hitWhole-->86%<!--/SM:hitWhole--> of the time, every symbol on a real line anchor |
101
+ | ❌ "Trust me" AI answers | ✅ Grounded — right file in context <!--SM:hitWhole-->82%<!--/SM:hitWhole--> of the time, every symbol on a real line anchor |
102
102
  | ❌ Embeddings / vector DB required | ✅ Zero deps, no infra, fully offline |
103
103
 
104
104
  ---
@@ -122,13 +122,13 @@ Ask → Rank → Context → Validate → Judge → Learn
122
122
 
123
123
  <!--SM:benchmarkBlock-->
124
124
  ```
125
- Benchmark : sigmap-v8.21-main (21 repositories, including R language)
126
- Date : 2026-07-19
125
+ Benchmark : sigmap-v8.22-main (21 repositories, including R language)
126
+ Date : 2026-07-27
127
127
 
128
- Hit@5 : 85.6% (grep-agent baseline 42.7% — 2.00× lift)
128
+ Hit@5 : 82.2% (grep-agent baseline 44.8% — 1.59× lift)
129
129
  Token reduction: 96.8% (across 21 repos)
130
- Prompt reduction : 48.0% (2.84 → 1.48 prompts per task, modeled)
131
- Task success : 66.7% (proxy — modeled from retrieval tiers)
130
+ Prompt reduction : 46.1% (2.84 → 1.53 prompts per task, modeled)
131
+ Task success : 64.8% (proxy — modeled from retrieval tiers)
132
132
  Repos tested : 21 (JavaScript, Python, Go, Rust, Java, R, C++, C#, Dart, Swift, Ruby, PHP, Scala, Kotlin, and more)
133
133
  ```
134
134
  <!--/SM:benchmarkBlock-->
package/gen-context.js CHANGED
@@ -4101,6 +4101,93 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
4101
4101
 
4102
4102
  };
4103
4103
 
4104
+ // ── ./src/eval/corpus ──
4105
+ __factories["./src/eval/corpus"] = function(module, exports) {
4106
+
4107
+ /**
4108
+ * Task-corpus hygiene (A3, v8.22 "Hard Corpus").
4109
+ *
4110
+ * A benchmark query "leaks" when it shares a token with the basenames of its
4111
+ * expected files — hit@5 then partly measures filename matching, not
4112
+ * retrieval. The criterion is deterministic and reuses the production
4113
+ * tokenizer (identifier splitting + stemming from src/retrieval/bm25.js), so
4114
+ * "payments" leaks against payment.js and "InterceptorManager" leaks against
4115
+ * "interceptor manager" the same way the ranker would see them.
4116
+ *
4117
+ * Tasks carry an optional `split` field: 'hard' tasks MUST be leak-free
4118
+ * (validateTasks reports them as violations); 'easy' tasks (the default) may
4119
+ * leak — that is what makes them easy.
4120
+ *
4121
+ * Size buckets group repos by indexed file count so large repos stop being
4122
+ * averaged away by tiny ones. Thresholds are the rough tertiles of the
4123
+ * current benchmarks/repos corpus (43 repos, 27–3450 source files).
4124
+ */
4125
+
4126
+ const { tokenize } = __require('./src/retrieval/bm25');
4127
+
4128
+ const BUCKET_LIMITS = { small: 200, medium: 1000 }; // files; large = above medium
4129
+
4130
+ /**
4131
+ * Stemmed tokens of a file path's basename (extension stripped).
4132
+ * @param {string} filePath
4133
+ * @returns {string[]}
4134
+ */
4135
+ function basenameTokens(filePath) {
4136
+ const base = String(filePath).split('/').pop() || '';
4137
+ return tokenize(base.replace(/\.[^.]*$/, ''));
4138
+ }
4139
+
4140
+ /**
4141
+ * Leaked tokens between a query and its expected files' basenames.
4142
+ * @param {string} query
4143
+ * @param {string[]} expectedFiles
4144
+ * @returns {{ leaked: string[], clean: boolean }}
4145
+ */
4146
+ function queryLeakage(query, expectedFiles) {
4147
+ const qToks = new Set(tokenize(query));
4148
+ const leaked = new Set();
4149
+ for (const f of expectedFiles || []) {
4150
+ for (const t of basenameTokens(f)) {
4151
+ if (qToks.has(t)) leaked.add(t);
4152
+ }
4153
+ }
4154
+ return { leaked: [...leaked].sort(), clean: leaked.size === 0 };
4155
+ }
4156
+
4157
+ /**
4158
+ * Validate a task list: every task gets a leakage result; hard-split tasks
4159
+ * that leak are violations.
4160
+ * @param {Array<{id?:string, query:string, expected_files?:string[], split?:string}>} tasks
4161
+ * @returns {{ results: object[], hardViolations: object[] }}
4162
+ */
4163
+ function validateTasks(tasks) {
4164
+ const results = [];
4165
+ const hardViolations = [];
4166
+ for (const t of tasks || []) {
4167
+ const split = t.split === 'hard' ? 'hard' : 'easy';
4168
+ const { leaked, clean } = queryLeakage(t.query, t.expected_files);
4169
+ const row = { id: t.id || '?', split, leaked, clean };
4170
+ results.push(row);
4171
+ if (split === 'hard' && !clean) hardViolations.push(row);
4172
+ }
4173
+ return { results, hardViolations };
4174
+ }
4175
+
4176
+ /**
4177
+ * Size bucket for a repo by indexed file count.
4178
+ * @param {number} fileCount
4179
+ * @returns {'small'|'medium'|'large'}
4180
+ */
4181
+ function sizeBucket(fileCount) {
4182
+ if (fileCount < BUCKET_LIMITS.small) return 'small';
4183
+ if (fileCount <= BUCKET_LIMITS.medium) return 'medium';
4184
+ return 'large';
4185
+ }
4186
+
4187
+ module.exports = { basenameTokens, queryLeakage, validateTasks, sizeBucket, BUCKET_LIMITS };
4188
+
4189
+ };
4190
+
4104
4191
  // ── ./src/eval/llm-ablation ──
4105
4192
  __factories["./src/eval/llm-ablation"] = function(module, exports) {
4106
4193
 
@@ -4400,7 +4487,7 @@ __factories["./src/eval/runner"] = function(module, exports) {
4400
4487
 
4401
4488
  /**
4402
4489
  * Load tasks from a JSONL file.
4403
- * Each line: { id, query, expected_files, repo }
4490
+ * Each line: { id, query, expected_files, repo, split? ('easy'|'hard') }
4404
4491
  * Invalid or blank lines are silently skipped.
4405
4492
  * @param {string} tasksFile - absolute or relative path
4406
4493
  * @returns {Array<{id:string, query:string, expected:string[], repo:string}>}
@@ -4420,6 +4507,7 @@ __factories["./src/eval/runner"] = function(module, exports) {
4420
4507
  query: obj.query,
4421
4508
  expected: obj.expected_files,
4422
4509
  repo: obj.repo || '.',
4510
+ split: obj.split === 'hard' ? 'hard' : 'easy',
4423
4511
  });
4424
4512
  }
4425
4513
  } catch {
@@ -14850,7 +14938,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
14850
14938
 
14851
14939
  const SERVER_INFO = {
14852
14940
  name: 'sigmap',
14853
- version: '8.21.0',
14941
+ version: '8.22.0',
14854
14942
  description: 'SigMap MCP server — code signatures on demand',
14855
14943
  };
14856
14944
 
@@ -18544,6 +18632,8 @@ __factories["./src/tracking/pricing"] = function(module, exports) {
18544
18632
  'gpt-4o-mini': 0.15,
18545
18633
  'gemini-1.5-pro': 1.25,
18546
18634
  'gemini-1.5-flash': 0.075,
18635
+ 'minimax-m3': 0.6,
18636
+ 'minimax-m2.7': 0.3,
18547
18637
  };
18548
18638
 
18549
18639
  const DEFAULT_MODEL = 'claude-sonnet';
@@ -20068,7 +20158,7 @@ function __tryGit(args, opts = {}) {
20068
20158
  catch (_) { return ''; }
20069
20159
  }
20070
20160
 
20071
- const VERSION = '8.21.0';
20161
+ const VERSION = '8.22.0';
20072
20162
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
20073
20163
 
20074
20164
  function requireSourceOrBundled(key) {
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.21.0 | Benchmark: sigmap-v8.21-main (2026-07-19)
14
+ # Version: 8.22.0 | Benchmark: sigmap-v8.22-main (2026-07-27)
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.21-main, 2026-07-19)
20
+ ## Core metrics (benchmark: sigmap-v8.22-main, 2026-07-27)
21
21
 
22
22
  | Metric | Without SigMap | With SigMap |
23
23
  |--------|----------------|-------------|
24
- | Retrieval hit@5 | 42.7% (single-shot grep) | 85.6% (2.00× lift) |
24
+ | Retrieval hit@5 | 44.8% (single-shot grep) | 82.2% (1.59× lift) |
25
25
  | Token reduction | — | 96.8% average |
26
- | Task-success proxy (modeled) | — | 66.7% |
27
- | Prompts per task | 2.84 | 1.48 (48.0% fewer) |
26
+ | Task-success proxy (modeled) | — | 64.8% |
27
+ | Prompts per task | 2.84 | 1.53 (46.1% fewer) |
28
28
  | Supported languages | — | 33 |
29
29
  | MCP tools | — | 20 |
30
30
  | npm runtime dependencies | — | 0 |
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.21.0 | Benchmark: sigmap-v8.21-main (2026-07-19)
14
+ # Version: 8.22.0 | Benchmark: sigmap-v8.22-main (2026-07-27)
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.21-main, 2026-07-19)
26
+ ## Core metrics (benchmark: sigmap-v8.22-main, 2026-07-27)
27
27
 
28
- - hit@5 retrieval: 85.6% vs 42.7% single-shot grep baseline (2.00× lift)
28
+ - hit@5 retrieval: 82.2% vs 44.8% single-shot grep baseline (1.59× lift)
29
29
  - Token reduction: 96.8% average across benchmark repos
30
- - Task-success proxy: 66.7% (modeled from retrieval tiers, not measured LLM sessions)
31
- - Prompts per task: 1.48 vs 2.84 baseline (48.0% fewer, modeled)
30
+ - Task-success proxy: 64.8% (modeled from retrieval tiers, not measured LLM sessions)
31
+ - Prompts per task: 1.53 vs 2.84 baseline (46.1% fewer, modeled)
32
32
  - Languages: 33 supported · MCP tools: 20
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.21.0",
3
+ "version": "8.22.0",
4
4
  "description": "The deterministic, verifiable grounding layer for AI code work — a zero-dependency signature-and-evidence map that grounds Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP agents against your real code (repo + installed libraries) so they stop hallucinating files, imports & APIs. Runs offline via npx; byte-stable output; ~97% token reduction as proof.",
5
5
  "main": "packages/core/index.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.21.0",
3
+ "version": "8.22.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.21.0",
3
+ "version": "8.22.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Task-corpus hygiene (A3, v8.22 "Hard Corpus").
5
+ *
6
+ * A benchmark query "leaks" when it shares a token with the basenames of its
7
+ * expected files — hit@5 then partly measures filename matching, not
8
+ * retrieval. The criterion is deterministic and reuses the production
9
+ * tokenizer (identifier splitting + stemming from src/retrieval/bm25.js), so
10
+ * "payments" leaks against payment.js and "InterceptorManager" leaks against
11
+ * "interceptor manager" the same way the ranker would see them.
12
+ *
13
+ * Tasks carry an optional `split` field: 'hard' tasks MUST be leak-free
14
+ * (validateTasks reports them as violations); 'easy' tasks (the default) may
15
+ * leak — that is what makes them easy.
16
+ *
17
+ * Size buckets group repos by indexed file count so large repos stop being
18
+ * averaged away by tiny ones. Thresholds are the rough tertiles of the
19
+ * current benchmarks/repos corpus (43 repos, 27–3450 source files).
20
+ */
21
+
22
+ const { tokenize } = require('../retrieval/bm25');
23
+
24
+ const BUCKET_LIMITS = { small: 200, medium: 1000 }; // files; large = above medium
25
+
26
+ /**
27
+ * Stemmed tokens of a file path's basename (extension stripped).
28
+ * @param {string} filePath
29
+ * @returns {string[]}
30
+ */
31
+ function basenameTokens(filePath) {
32
+ const base = String(filePath).split('/').pop() || '';
33
+ return tokenize(base.replace(/\.[^.]*$/, ''));
34
+ }
35
+
36
+ /**
37
+ * Leaked tokens between a query and its expected files' basenames.
38
+ * @param {string} query
39
+ * @param {string[]} expectedFiles
40
+ * @returns {{ leaked: string[], clean: boolean }}
41
+ */
42
+ function queryLeakage(query, expectedFiles) {
43
+ const qToks = new Set(tokenize(query));
44
+ const leaked = new Set();
45
+ for (const f of expectedFiles || []) {
46
+ for (const t of basenameTokens(f)) {
47
+ if (qToks.has(t)) leaked.add(t);
48
+ }
49
+ }
50
+ return { leaked: [...leaked].sort(), clean: leaked.size === 0 };
51
+ }
52
+
53
+ /**
54
+ * Validate a task list: every task gets a leakage result; hard-split tasks
55
+ * that leak are violations.
56
+ * @param {Array<{id?:string, query:string, expected_files?:string[], split?:string}>} tasks
57
+ * @returns {{ results: object[], hardViolations: object[] }}
58
+ */
59
+ function validateTasks(tasks) {
60
+ const results = [];
61
+ const hardViolations = [];
62
+ for (const t of tasks || []) {
63
+ const split = t.split === 'hard' ? 'hard' : 'easy';
64
+ const { leaked, clean } = queryLeakage(t.query, t.expected_files);
65
+ const row = { id: t.id || '?', split, leaked, clean };
66
+ results.push(row);
67
+ if (split === 'hard' && !clean) hardViolations.push(row);
68
+ }
69
+ return { results, hardViolations };
70
+ }
71
+
72
+ /**
73
+ * Size bucket for a repo by indexed file count.
74
+ * @param {number} fileCount
75
+ * @returns {'small'|'medium'|'large'}
76
+ */
77
+ function sizeBucket(fileCount) {
78
+ if (fileCount < BUCKET_LIMITS.small) return 'small';
79
+ if (fileCount <= BUCKET_LIMITS.medium) return 'medium';
80
+ return 'large';
81
+ }
82
+
83
+ module.exports = { basenameTokens, queryLeakage, validateTasks, sizeBucket, BUCKET_LIMITS };
@@ -124,7 +124,7 @@ function estimateTokens(sigs) {
124
124
 
125
125
  /**
126
126
  * Load tasks from a JSONL file.
127
- * Each line: { id, query, expected_files, repo }
127
+ * Each line: { id, query, expected_files, repo, split? ('easy'|'hard') }
128
128
  * Invalid or blank lines are silently skipped.
129
129
  * @param {string} tasksFile - absolute or relative path
130
130
  * @returns {Array<{id:string, query:string, expected:string[], repo:string}>}
@@ -144,6 +144,7 @@ function loadTasks(tasksFile) {
144
144
  query: obj.query,
145
145
  expected: obj.expected_files,
146
146
  repo: obj.repo || '.',
147
+ split: obj.split === 'hard' ? 'hard' : 'easy',
147
148
  });
148
149
  }
149
150
  } catch {
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.21.0',
21
+ version: '8.22.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -20,6 +20,8 @@ const PRICES = {
20
20
  'gpt-4o-mini': 0.15,
21
21
  'gemini-1.5-pro': 1.25,
22
22
  'gemini-1.5-flash': 0.075,
23
+ 'minimax-m3': 0.6,
24
+ 'minimax-m2.7': 0.3,
23
25
  };
24
26
 
25
27
  const DEFAULT_MODEL = 'claude-sonnet';