sigmap 6.10.10 → 6.10.12

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/AGENTS.md CHANGED
@@ -61,14 +61,10 @@ Always run `sigmap ask` or `sigmap --query` before searching for files relevant
61
61
  src/extractors/python_ast.py ← ast
62
62
  ```
63
63
 
64
- ## changes (last 5 commits — 23 minutes ago)
64
+ ## changes (last 5 commits — 19 minutes ago)
65
65
  ```
66
- src/discovery/r-manifest.js +readDescription +splitDeps +readNamespace +splitArgs
67
- src/extractors/deps.js +extractRDeps ~extractTSDeps ~buildReverseDepMap
68
- src/extractors/r.js +members +regex +definitions +body
69
- src/graph/builder.js +normalizePath +escapeRegex +resolveRPath +extractFileDeps
70
- src/graph/impact.js +normalizePath ~isRouteFile ~getImpact
71
- src/retrieval/ranker.js ~_computeHubs ~_isHub
66
+ src/mcp/handlers.js +_readContextFiles ~flushBlock ~readContext ~explainFile
67
+ src/retrieval/ranker.js +_mergeSigIndex +_buildSigIndexFromCache +_enrichSigIndexFromStrategy ~_parseContextFile
72
68
  ```
73
69
 
74
70
  ## packages
@@ -194,12 +190,6 @@ function adapt(context, adapterName, opts = {}) → string
194
190
 
195
191
  ## src
196
192
 
197
- ### src/extractors/python_dataclass.js
198
- ```
199
- module.exports = { extract }
200
- function extract(src) → string[]
201
- ```
202
-
203
193
  ### src/extractors/typescript_react.js
204
194
  ```
205
195
  module.exports = { extract }
@@ -307,20 +297,6 @@ function getChangedFiles(files, cache) → { changed: string[], unch
307
297
  function updateCacheEntries(cache, extracted)
308
298
  ```
309
299
 
310
- ### src/mcp/handlers.js
311
- ```
312
- module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact }
313
- function readContext(args, cwd)
314
- function searchSignatures(args, cwd)
315
- function getMap(args, cwd)
316
- function createCheckpoint(args, cwd)
317
- function getRouting(args, cwd)
318
- function explainFile(args, cwd)
319
- function listModules(args, cwd)
320
- function queryContext(args, cwd)
321
- function getImpact(args, cwd)
322
- ```
323
-
324
300
  ### src/tracking/logger.js
325
301
  ```
326
302
  module.exports = { logRun, readLog, summarize }
@@ -593,6 +569,21 @@ function dispatch(msg, cwd)
593
569
  function start(cwd)
594
570
  ```
595
571
 
572
+ ### src/mcp/handlers.js
573
+ ```
574
+ module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact }
575
+ function _readContextFiles(cwd)
576
+ function readContext(args, cwd)
577
+ function searchSignatures(args, cwd)
578
+ function getMap(args, cwd)
579
+ function createCheckpoint(args, cwd)
580
+ function getRouting(args, cwd)
581
+ function explainFile(args, cwd)
582
+ function listModules(args, cwd)
583
+ function queryContext(args, cwd)
584
+ function getImpact(args, cwd)
585
+ ```
586
+
596
587
  ### src/retrieval/ranker.js
597
588
  ```
598
589
  module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, GRAPH_BOOST_AMOUNTS, detectIntent }
@@ -602,6 +593,9 @@ function _isHub(filePath)
602
593
  function scoreFile(filePath, sigs, queryTokens, weights) → { score: number, signals:
603
594
  function rank(query, sigIndex, opts) → { file: string, score: nu
604
595
  function _parseContextFile(contextPath) → Map<string, string[]>
596
+ function _mergeSigIndex(target, source)
597
+ function _buildSigIndexFromCache(cwd) → Map<string, string[]>
598
+ function _enrichSigIndexFromStrategy(cwd, index) → Map<string, string[]>
605
599
  function buildSigIndex(cwd, opts) → Map<string, string[]>
606
600
  function formatRankTable(results, query) → string
607
601
  function formatRankJSON(results, query) → object
package/CHANGELOG.md CHANGED
@@ -10,7 +10,23 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
- ## [6.10.10] — 2026-05-12
13
+ ## [6.10.12] — 2026-05-27
14
+
15
+ ### Added
16
+
17
+ - **Portable `.mcp.json` support** — MCP server registration now detects and prioritizes `.mcp.json` at the project root, making MCP configuration portable across multiple agentic harnesses (Claude, Cursor, Windsurf, etc.). Falls back to `.claude/settings.json` if `.mcp.json` doesn't exist (closes #209).
18
+
19
+ ---
20
+
21
+ ## [6.10.11] — 2026-05-22
22
+
23
+ ### Fixed
24
+
25
+ - **Test assertions** — Updated integration tests to verify correct benchmark date (2026-05-22) and language count (31 with R + GDScript support). Tests now validate version.json metrics consistency across all documentation files.
26
+
27
+ ---
28
+
29
+ ## [6.10.10] — 2026-05-22
14
30
 
15
31
  ### Added
16
32
 
@@ -25,6 +41,8 @@ Format: [Semantic Versioning](https://semver.org/)
25
41
 
26
42
  ### Fixed
27
43
 
44
+ - **MCP handler improvements** — Merged hot-cold cache and context-cold support into MCP index. MCP tools (`read_context`, `search_signatures`, `get_map`) now correctly serve signatures from multiple sources: primary context file (copilot-instructions.md), cold storage (context-cold.md), and sig-cache index. Fixes issue where MCP clients received partial results when using hot-cold or per-module output strategies.
45
+ - **Ranker hot-cold support** — Extended `buildSigIndex()` to merge signature indexes from multiple sources (primary file + context-cold.md + sig-cache). Added internal helper functions `_mergeSigIndex()`, `_buildSigIndexFromCache()`, and `_enrichSigIndexFromStrategy()` to support hot-cold and memory-efficient strategies without API breakage. Allows monorepo and per-module output strategies to serve complete signatures to rank and MCP handlers.
28
46
  - **Windows path normalization in get_impact** — Implement case-insensitive path lookups in dependency graph for Windows compatibility. All paths in forward/reverse maps now normalized to lowercase, enabling `get_impact` to work correctly when file paths have different case (e.g., `src/Ledger/equity_ledger.py` vs `src/ledger/equity_ledger.py`). Applied normalization uniformly across JS, Python, Go, Rust, JVM, Ruby, and R import detection (closes #193).
29
47
 
30
48
  ---
package/README.md CHANGED
@@ -47,10 +47,11 @@ SigMap extracts function and class signatures from your codebase and feeds the r
47
47
 
48
48
  ## Why SigMap?
49
49
 
50
- - **80.0% hit@5** — right file found in top 5 results (vs 13.6% baseline)
51
- - **40–98% token reduction** — 2K–4K tokens instead of 80K+
50
+ - **78.9% hit@5** — right file found in top 5 results (vs 13.6% baseline)
51
+ - **97.9% token reduction** — 278K instead of 13.5M tokens across 21 repos
52
52
  - **52.2% task success rate** — up from 10% without context
53
- - **1.68 prompts per task** — down from 2.84
53
+ - **1.66 prompts per task** — down from 2.84 (40.6% fewer retries)
54
+ - **31 languages supported** — TypeScript, Python, Go, Rust, Java, R, and 25 others
54
55
  - **No vendor lock-in** — works with any AI assistant or local LLM
55
56
  - **No API costs** — use local models (Ollama, llama.cpp, vLLM) with zero token fees
56
57
  - **Full privacy** — keep your code and context on your machine
@@ -86,14 +87,14 @@ Ask → Rank → Context → Validate → Judge → Learn
86
87
  ## Benchmark
87
88
 
88
89
  ```
89
- Benchmark : sigmap-v6.10-main
90
- Date : 2026-05-12
91
-
92
- Hit@5 : 80.0% (baseline 13.6% — 5.9× lift)
93
- Prompt reduction : 41.4%
94
- Task success : 52.2% (baseline 10%)
95
- Prompts / task : 1.68 (baseline 2.84)
96
- Token reduction: 40–98% (avg 96.8% across 18 real repos)
90
+ Benchmark : sigmap-v6.10-main (21 repositories, including R language)
91
+ Date : 2026-05-22
92
+
93
+ Hit@5 : 80% (baseline 13.6% — 5.9× lift)
94
+ Token reduction: 96.5% (across 21 repos)
95
+ Prompt reduction : 41.4% (2.84 → 1.67 prompts per task)
96
+ Task success : 53.3% (baseline 10%)
97
+ Repos tested : 21 (JavaScript, Python, Go, Rust, Java, R, C++, C#, Dart, Swift, Ruby, PHP, Scala, Kotlin, and more)
97
98
  ```
98
99
 
99
100
  Measured on 90 coding tasks across 18 real public repos. No LLM API — fully reproducible.
@@ -232,7 +233,7 @@ sigmap --health
232
233
  | Benchmark methodology | [benchmark.html](https://manojmallick.github.io/sigmap/guide/benchmark.html) |
233
234
  | Config reference | [config.html](https://manojmallick.github.io/sigmap/guide/config.html) |
234
235
  | Roadmap | [roadmap.html](https://manojmallick.github.io/sigmap/guide/roadmap.html) |
235
- | 29 languages | [generalization.html](https://manojmallick.github.io/sigmap/guide/generalization.html) |
236
+ | 31 languages | [generalization.html](https://manojmallick.github.io/sigmap/guide/generalization.html) |
236
237
 
237
238
  ---
238
239
 
@@ -277,9 +278,9 @@ See [.github/PULL_REQUEST_TEMPLATE.md](.github/PULL_REQUEST_TEMPLATE.md) for the
277
278
 
278
279
  ---
279
280
 
280
- ## 29 languages
281
+ ## 31 languages
281
282
 
282
- TypeScript · JavaScript · Python · Java · Kotlin · Go · Rust · C# · C/C++ · Ruby · PHP · Swift · Dart · Scala · Vue · Svelte · HTML · CSS/SCSS · YAML · Shell · SQL · GraphQL · Terraform · Protobuf · Dockerfile · TOML · XML · Properties · Markdown
283
+ TypeScript · JavaScript · Python · Java · Kotlin · Go · Rust · C# · C/C++ · Ruby · PHP · Swift · Dart · Scala · Vue · Svelte · HTML · CSS/SCSS · YAML · Shell · SQL · GraphQL · Terraform · Protobuf · Dockerfile · TOML · XML · Properties · Markdown · R · GDScript
283
284
 
284
285
  All implemented with zero external dependencies.
285
286
 
package/gen-context.js CHANGED
@@ -5901,7 +5901,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
5901
5901
 
5902
5902
  const SERVER_INFO = {
5903
5903
  name: 'sigmap',
5904
- version: '6.10.10',
5904
+ version: '6.10.12',
5905
5905
  description: 'SigMap MCP server — code signatures on demand',
5906
5906
  };
5907
5907
 
@@ -8443,6 +8443,117 @@ __factories["./src/discovery/source-root-resolver"] = function(module, exports)
8443
8443
  module.exports = { resolveSourceRoots };
8444
8444
  };
8445
8445
 
8446
+ // ── ./src/discovery/r-manifest ──
8447
+ __factories["./src/discovery/r-manifest"] = function(module, exports) {
8448
+ 'use strict';
8449
+ const fs = require('fs');
8450
+ const path = require('path');
8451
+ module.exports = { readDescription, readNamespace, collectLocalDefs };
8452
+ function readDescription(cwd) {
8453
+ const p = path.join(cwd, 'DESCRIPTION');
8454
+ if (!fs.existsSync(p)) return null;
8455
+ let raw;
8456
+ try { raw = fs.readFileSync(p, 'utf8'); } catch (_) { return null; }
8457
+ const fields = {};
8458
+ let currentKey = null;
8459
+ for (const rawLine of raw.split('\n')) {
8460
+ if (/^\s/.test(rawLine) && currentKey) {
8461
+ fields[currentKey] += ' ' + rawLine.trim();
8462
+ continue;
8463
+ }
8464
+ const m = rawLine.match(/^([A-Za-z][\w.]*)\s*:\s*(.*)$/);
8465
+ if (m) {
8466
+ currentKey = m[1];
8467
+ fields[currentKey] = m[2].trim();
8468
+ } else {
8469
+ currentKey = null;
8470
+ }
8471
+ }
8472
+ return {
8473
+ package: fields.Package || null,
8474
+ version: fields.Version || null,
8475
+ imports: splitDeps(fields.Imports),
8476
+ depends: splitDeps(fields.Depends),
8477
+ suggests: splitDeps(fields.Suggests),
8478
+ linkingTo: splitDeps(fields.LinkingTo),
8479
+ };
8480
+ }
8481
+ function splitDeps(value) {
8482
+ if (!value) return [];
8483
+ return value.split(',')
8484
+ .map((s) => s.trim().replace(/\s*\([^)]*\)\s*$/, '').trim())
8485
+ .filter((s) => s && s !== 'R');
8486
+ }
8487
+ function readNamespace(cwd) {
8488
+ const p = path.join(cwd, 'NAMESPACE');
8489
+ if (!fs.existsSync(p)) return null;
8490
+ let raw;
8491
+ try { raw = fs.readFileSync(p, 'utf8'); } catch (_) { return null; }
8492
+ const text = raw.replace(/#.*$/gm, '');
8493
+ const exports = new Set();
8494
+ const exportPatterns = [];
8495
+ const s3methods = [];
8496
+ const importFrom = new Map();
8497
+ for (const m of text.matchAll(/\bexport\s*\(\s*([^)]+)\)/g)) {
8498
+ for (const name of splitArgs(m[1])) {
8499
+ const clean = stripQuotes(name);
8500
+ if (clean) exports.add(clean);
8501
+ }
8502
+ }
8503
+ for (const m of text.matchAll(/\bexportMethods\s*\(\s*([^)]+)\)/g)) {
8504
+ for (const name of splitArgs(m[1])) {
8505
+ const clean = stripQuotes(name);
8506
+ if (clean) exports.add(clean);
8507
+ }
8508
+ }
8509
+ for (const m of text.matchAll(/\bexportPattern\s*\(\s*["']([^"']+)["']\s*\)/g)) {
8510
+ try { exportPatterns.push(new RegExp(m[1])); } catch (_) {}
8511
+ }
8512
+ for (const m of text.matchAll(/\bS3method\s*\(\s*([\w.]+)\s*,\s*([\w.]+)\s*\)/g)) {
8513
+ s3methods.push({ generic: m[1], class: m[2] });
8514
+ exports.add(m[1]);
8515
+ }
8516
+ for (const m of text.matchAll(/\bimportFrom\s*\(\s*([\w.]+)\s*,\s*([^)]+)\)/g)) {
8517
+ const pkg = m[1];
8518
+ if (!importFrom.has(pkg)) importFrom.set(pkg, new Set());
8519
+ for (const name of splitArgs(m[2])) {
8520
+ const clean = stripQuotes(name);
8521
+ if (clean) importFrom.get(pkg).add(clean);
8522
+ }
8523
+ }
8524
+ return { exports, exportPatterns, s3methods, importFrom };
8525
+ }
8526
+ function splitArgs(raw) {
8527
+ return raw.split(',').map((s) => s.trim()).filter(Boolean);
8528
+ }
8529
+ function stripQuotes(s) {
8530
+ return s.replace(/^["']|["']$/g, '').trim();
8531
+ }
8532
+ function collectLocalDefs(rFiles) {
8533
+ const defs = new Map();
8534
+ const reAssign = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*(?:(?:R6::)?R6Class|(?:S7::)?new_class|function)\b/gm;
8535
+ const reS4Generic = /^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm;
8536
+ const reS4Class = /^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm;
8537
+ for (const filePath of rFiles) {
8538
+ let content;
8539
+ try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
8540
+ const stripped = content.replace(/#.*$/gm, '');
8541
+ let m;
8542
+ while ((m = reAssign.exec(stripped)) !== null) {
8543
+ if (m[1].startsWith('.')) continue;
8544
+ if (!defs.has(m[1])) defs.set(m[1], filePath);
8545
+ }
8546
+ while ((m = reS4Generic.exec(stripped)) !== null) {
8547
+ if (!defs.has(m[1])) defs.set(m[1], filePath);
8548
+ }
8549
+ while ((m = reS4Class.exec(stripped)) !== null) {
8550
+ if (!defs.has(m[1])) defs.set(m[1], filePath);
8551
+ }
8552
+ }
8553
+ return defs;
8554
+ }
8555
+ };
8556
+
8446
8557
  // ── ./src/workspace/detector ──
8447
8558
  __factories["./src/workspace/detector"] = function(module, exports) {
8448
8559
  'use strict';
@@ -8544,7 +8655,7 @@ const path = require('path');
8544
8655
  const os = require('os');
8545
8656
  const { execSync } = require('child_process');
8546
8657
 
8547
- const VERSION = '6.10.10';
8658
+ const VERSION = '6.10.12';
8548
8659
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
8549
8660
 
8550
8661
  function requireSourceOrBundled(key) {
@@ -10344,9 +10455,11 @@ function registerMcp(cwd, scriptPath) {
10344
10455
  args: [path.resolve(scriptPath), '--mcp'],
10345
10456
  };
10346
10457
 
10347
- // JSON mcpServers targets: Claude, Cursor, Windsurf project, Windsurf global,
10348
- // VS Code (GitHub Copilot 1.99+), OpenCode project, OpenCode global, Gemini CLI
10458
+ // JSON mcpServers targets: portable .mcp.json (priority), Claude, Cursor,
10459
+ // Windsurf project, Windsurf global, VS Code (GitHub Copilot 1.99+),
10460
+ // OpenCode project, OpenCode global, Gemini CLI
10349
10461
  const jsonTargets = [
10462
+ path.join(cwd, '.mcp.json'),
10350
10463
  path.join(cwd, '.claude', 'settings.json'),
10351
10464
  path.join(cwd, '.cursor', 'mcp.json'),
10352
10465
  path.join(cwd, '.windsurf', 'mcp.json'),
@@ -10385,6 +10498,8 @@ function registerMcp(cwd, scriptPath) {
10385
10498
 
10386
10499
  // Print manual snippets for all targets
10387
10500
  console.warn('[sigmap] MCP / context server config snippets:');
10501
+ console.warn(' .mcp.json (portable, recommended):');
10502
+ console.warn(JSON.stringify({ mcpServers: { sigmap: serverEntry } }, null, 2));
10388
10503
  console.warn(' Claude / Cursor / Windsurf / VS Code / OpenCode / Gemini CLI:');
10389
10504
  console.warn(JSON.stringify({ mcpServers: { sigmap: serverEntry } }, null, 2));
10390
10505
  console.warn(' Zed (~/.config/zed/settings.json):');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "6.10.10",
3
+ "version": "6.10.12",
4
4
  "description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
5
5
  "main": "gen-context.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "6.10.10",
3
+ "version": "6.10.12",
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": "6.10.10",
3
+ "version": "6.10.12",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -5,6 +5,16 @@ const path = require('path');
5
5
  const { execSync } = require('child_process');
6
6
 
7
7
  const CONTEXT_FILE = path.join('.github', 'copilot-instructions.md');
8
+ const CONTEXT_COLD_FILE = path.join('.github', 'context-cold.md');
9
+
10
+ function _readContextFiles(cwd) {
11
+ const paths = [path.join(cwd, CONTEXT_FILE), path.join(cwd, CONTEXT_COLD_FILE)];
12
+ const chunks = [];
13
+ for (const p of paths) {
14
+ if (fs.existsSync(p)) chunks.push(fs.readFileSync(p, 'utf8'));
15
+ }
16
+ return chunks.join('\n');
17
+ }
8
18
 
9
19
  // Section header keywords in PROJECT_MAP.md
10
20
  const MAP_SECTIONS = {
@@ -20,13 +30,11 @@ const MAP_SECTIONS = {
20
30
  * contain the given module substring.
21
31
  */
22
32
  function readContext(args, cwd) {
23
- const contextPath = path.join(cwd, CONTEXT_FILE);
24
- if (!fs.existsSync(contextPath)) {
33
+ const content = _readContextFiles(cwd);
34
+ if (!content) {
25
35
  return 'No context file found. Run: node gen-context.js';
26
36
  }
27
37
 
28
- const content = fs.readFileSync(contextPath, 'utf8');
29
-
30
38
  if (!args || !args.module) return content;
31
39
 
32
40
  const mod = args.module.replace(/\\/g, '/').replace(/\/$/, '');
@@ -62,41 +70,28 @@ function readContext(args, cwd) {
62
70
  function searchSignatures(args, cwd) {
63
71
  if (!args || !args.query) return 'Missing required argument: query';
64
72
 
65
- const contextPath = path.join(cwd, CONTEXT_FILE);
66
- if (!fs.existsSync(contextPath)) {
67
- return 'No context file found. Run: node gen-context.js';
68
- }
69
-
70
- const content = fs.readFileSync(contextPath, 'utf8');
71
73
  const query = args.query.toLowerCase();
72
- const lines = content.split('\n');
73
-
74
- const result = [];
75
- let currentFile = '';
76
- let fileHeaderAdded = false;
77
-
78
- for (const line of lines) {
79
- if (line.startsWith('### ')) {
80
- currentFile = line.slice(4).trim();
81
- fileHeaderAdded = false;
82
- continue;
83
- }
84
- // Skip markdown fences and top-level headers
85
- if (line.startsWith('```') || line.startsWith('## ') || line.startsWith('# ') || line.startsWith('<!--')) {
86
- continue;
74
+ try {
75
+ const { buildSigIndex } = require('../retrieval/ranker');
76
+ const index = buildSigIndex(cwd);
77
+ if (index.size === 0) {
78
+ return 'No context file found. Run: node gen-context.js';
87
79
  }
88
- if (line.toLowerCase().includes(query)) {
89
- if (currentFile && !fileHeaderAdded) {
90
- if (result.length > 0) result.push('');
91
- result.push(`### ${currentFile}`);
92
- fileHeaderAdded = true;
93
- }
94
- result.push(line);
80
+
81
+ const result = [];
82
+ for (const [file, sigs] of index.entries()) {
83
+ const hits = sigs.filter((s) => s.toLowerCase().includes(query));
84
+ if (hits.length === 0) continue;
85
+ if (result.length > 0) result.push('');
86
+ result.push(`### ${file}`);
87
+ result.push(...hits);
95
88
  }
96
- }
97
89
 
98
- if (result.length === 0) return `No signatures found matching: ${args.query}`;
99
- return result.join('\n');
90
+ if (result.length === 0) return `No signatures found matching: ${args.query}`;
91
+ return result.join('\n');
92
+ } catch (err) {
93
+ return `_search_signatures failed: ${err.message}_`;
94
+ }
100
95
  }
101
96
 
102
97
  /**
@@ -280,39 +275,29 @@ function explainFile(args, cwd) {
280
275
 
281
276
  const lines = ['# explain_file: ' + targetRel, ''];
282
277
 
283
- // ── Signatures (from context file) ─────────────────────────────────────
278
+ // ── Signatures (hot + cold + cache via buildSigIndex) ───────────────────
284
279
  lines.push('## Signatures');
285
280
  let indexedFiles = [];
286
281
 
287
- if (fs.existsSync(contextPath)) {
288
- const ctxContent = fs.readFileSync(contextPath, 'utf8');
289
- const ctxLines = ctxContent.split('\n');
290
- let capturing = false;
291
- const sigLines = [];
292
-
293
- for (const line of ctxLines) {
294
- if (line.startsWith('### ')) {
295
- if (capturing) break; // already collected our block
296
- const rel = line.slice(4).trim().replace(/\\/g, '/');
297
- capturing = rel === targetRel || rel.endsWith('/' + targetRel) || targetRel.endsWith('/' + rel);
298
- if (capturing) continue;
299
- } else if (capturing) {
300
- sigLines.push(line);
282
+ try {
283
+ const { buildSigIndex } = require('../retrieval/ranker');
284
+ const index = buildSigIndex(cwd);
285
+ let sigs = index.get(targetRel);
286
+ if (!sigs) {
287
+ for (const [file, fileSigs] of index.entries()) {
288
+ if (file === targetRel || file.endsWith('/' + targetRel) || targetRel.endsWith('/' + file)) {
289
+ sigs = fileSigs;
290
+ break;
291
+ }
301
292
  }
302
293
  }
303
-
304
- const sigs = sigLines.filter((l) => l !== '```' && l.trim() !== '');
305
- if (sigs.length > 0) {
294
+ if (sigs && sigs.length > 0) {
306
295
  lines.push(...sigs);
307
296
  } else {
308
297
  lines.push('_No signatures indexed for this file. Run: node gen-context.js_');
309
298
  }
310
-
311
- indexedFiles = ctxContent
312
- .split('\n')
313
- .filter((l) => l.startsWith('### '))
314
- .map((l) => path.resolve(cwd, l.slice(4).trim()));
315
- } else {
299
+ indexedFiles = [...index.keys()].map((rel) => path.resolve(cwd, rel));
300
+ } catch (_) {
316
301
  lines.push('_No context file found. Run: node gen-context.js_');
317
302
  }
318
303
 
@@ -377,37 +362,21 @@ function explainFile(args, cwd) {
377
362
  * descending. Helps agents decide which module to query with read_context.
378
363
  */
379
364
  function listModules(args, cwd) {
380
- const contextPath = path.join(cwd, CONTEXT_FILE);
381
- if (!fs.existsSync(contextPath)) {
382
- return 'No context file found. Run: node gen-context.js';
383
- }
384
-
385
- const content = fs.readFileSync(contextPath, 'utf8');
386
- const ctxLines = content.split('\n');
387
-
388
- const groups = {}; // key: top-level dir, value: { fileCount, tokenCount }
389
- let currentGroup = null;
390
- let blockBuf = [];
391
-
392
- function flushBlock() {
393
- if (currentGroup === null || blockBuf.length === 0) return;
394
- if (!groups[currentGroup]) groups[currentGroup] = { fileCount: 0, tokenCount: 0 };
395
- groups[currentGroup].fileCount++;
396
- groups[currentGroup].tokenCount += Math.ceil(blockBuf.join('\n').length / 4);
397
- blockBuf = [];
398
- }
365
+ try {
366
+ const { buildSigIndex } = require('../retrieval/ranker');
367
+ const index = buildSigIndex(cwd);
368
+ if (index.size === 0) {
369
+ return 'No context file found. Run: node gen-context.js';
370
+ }
399
371
 
400
- for (const line of ctxLines) {
401
- if (line.startsWith('### ')) {
402
- flushBlock();
403
- const rel = line.slice(4).trim().replace(/\\/g, '/');
404
- const parts = rel.split('/');
405
- currentGroup = parts.length > 1 ? parts[0] : '.';
406
- } else if (currentGroup !== null) {
407
- blockBuf.push(line);
372
+ const groups = {};
373
+ for (const [rel, sigs] of index.entries()) {
374
+ const parts = rel.replace(/\\/g, '/').split('/');
375
+ const mod = parts.length > 1 ? parts[0] : '.';
376
+ if (!groups[mod]) groups[mod] = { fileCount: 0, tokenCount: 0 };
377
+ groups[mod].fileCount++;
378
+ groups[mod].tokenCount += Math.ceil(sigs.join('\n').length / 4);
408
379
  }
409
- }
410
- flushBlock();
411
380
 
412
381
  const sorted = Object.entries(groups)
413
382
  .map(([mod, data]) => ({ module: mod, fileCount: data.fileCount, tokenCount: data.tokenCount }))
@@ -428,6 +397,9 @@ function listModules(args, cwd) {
428
397
  '',
429
398
  '_Use `read_context({ module: "name" })` to get signatures for a specific module._',
430
399
  ].join('\n');
400
+ } catch (err) {
401
+ return `_list_modules failed: ${err.message}_`;
402
+ }
431
403
  }
432
404
 
433
405
  /**
@@ -439,11 +411,6 @@ function listModules(args, cwd) {
439
411
  function queryContext(args, cwd) {
440
412
  if (!args || !args.query) return 'Missing required argument: query';
441
413
 
442
- const contextPath = path.join(cwd, CONTEXT_FILE);
443
- if (!fs.existsSync(contextPath)) {
444
- return 'No context file found. Run: node gen-context.js';
445
- }
446
-
447
414
  try {
448
415
  const { rank, buildSigIndex, formatRankTable } = require('../retrieval/ranker');
449
416
  const { buildFromCwd } = require('../graph/builder');
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: '6.10.10',
21
+ version: '6.10.12',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -361,6 +361,58 @@ function _parseContextFile(contextPath) {
361
361
  return index;
362
362
  }
363
363
 
364
+ /** Merge source index into target; prefer non-empty sig lists. */
365
+ function _mergeSigIndex(target, source) {
366
+ for (const [file, sigs] of source.entries()) {
367
+ if (!sigs || sigs.length === 0) continue;
368
+ if (!target.has(file) || target.get(file).length < sigs.length) {
369
+ target.set(file, sigs);
370
+ }
371
+ }
372
+ return target;
373
+ }
374
+
375
+ /**
376
+ * Load signatures from .sigmap-cache.json (absolute paths → repo-relative keys).
377
+ * @param {string} cwd
378
+ * @returns {Map<string, string[]>}
379
+ */
380
+ function _buildSigIndexFromCache(cwd) {
381
+ const fs = require('fs');
382
+ const path = require('path');
383
+ const index = new Map();
384
+ try {
385
+ const { loadCache } = require('../cache/sig-cache');
386
+ const pkgPath = path.join(cwd, 'package.json');
387
+ let version = '0.0.0';
388
+ if (fs.existsSync(pkgPath)) {
389
+ version = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version || version;
390
+ }
391
+ const cache = loadCache(cwd, version);
392
+ for (const [absPath, entry] of cache.entries()) {
393
+ if (!entry || !entry.sigs || entry.sigs.length === 0) continue;
394
+ const rel = path.relative(cwd, absPath).replace(/\\/g, '/');
395
+ if (!rel || rel.startsWith('..')) continue;
396
+ index.set(rel, entry.sigs);
397
+ }
398
+ } catch (_) {}
399
+ return index;
400
+ }
401
+
402
+ /**
403
+ * Hot-cold and per-module strategies store most signatures outside the primary
404
+ * copilot-instructions.md file. MCP tools must merge all sources.
405
+ * @param {string} cwd
406
+ * @returns {Map<string, string[]>}
407
+ */
408
+ function _enrichSigIndexFromStrategy(cwd, index) {
409
+ const path = require('path');
410
+ const coldPath = path.join(cwd, '.github', 'context-cold.md');
411
+ _mergeSigIndex(index, _parseContextFile(coldPath));
412
+ _mergeSigIndex(index, _buildSigIndexFromCache(cwd));
413
+ return index;
414
+ }
415
+
364
416
  /**
365
417
  * Build a signature index from the generated context file.
366
418
  * Returns Map<filePath, string[]> where filePath is the relative path
@@ -382,7 +434,8 @@ function buildSigIndex(cwd, opts) {
382
434
 
383
435
  // 1. Caller supplied an explicit path — use it directly.
384
436
  if (opts && opts.contextPath) {
385
- return _parseContextFile(opts.contextPath);
437
+ const index = _parseContextFile(opts.contextPath);
438
+ return _enrichSigIndexFromStrategy(cwd, index);
386
439
  }
387
440
 
388
441
  // 2. Check gen-context.config.json for a persisted customOutput path.
@@ -393,7 +446,7 @@ function buildSigIndex(cwd, opts) {
393
446
  if (cfg.customOutput) {
394
447
  const customPath = path.resolve(cwd, cfg.customOutput);
395
448
  const index = _parseContextFile(customPath);
396
- if (index.size > 0) return index;
449
+ if (index.size > 0) return _enrichSigIndexFromStrategy(cwd, index);
397
450
  }
398
451
  }
399
452
  } catch (_) {}
@@ -402,10 +455,12 @@ function buildSigIndex(cwd, opts) {
402
455
  for (const parts of ADAPTER_OUTPUT_PATHS) {
403
456
  const contextPath = path.join(cwd, ...parts);
404
457
  const index = _parseContextFile(contextPath);
405
- if (index.size > 0) return index;
458
+ if (index.size > 0) return _enrichSigIndexFromStrategy(cwd, index);
406
459
  }
407
460
 
408
- return new Map();
461
+ // 4. Primary file empty/missing (hot-cold) — still serve cold + cache.
462
+ const fallback = new Map();
463
+ return _enrichSigIndexFromStrategy(cwd, fallback);
409
464
  }
410
465
 
411
466
  /**