sigmap 6.10.10 → 6.10.11
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 +21 -27
- package/CHANGELOG.md +11 -1
- package/README.md +15 -14
- package/gen-context.js +113 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/handlers.js +60 -93
- package/src/mcp/server.js +1 -1
- package/src/retrieval/ranker.js +59 -4
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 —
|
|
64
|
+
## changes (last 5 commits — 19 minutes ago)
|
|
65
65
|
```
|
|
66
|
-
src/
|
|
67
|
-
src/
|
|
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,15 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## [6.10.
|
|
13
|
+
## [6.10.11] — 2026-05-22
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **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.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [6.10.10] — 2026-05-22
|
|
14
22
|
|
|
15
23
|
### Added
|
|
16
24
|
|
|
@@ -25,6 +33,8 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
25
33
|
|
|
26
34
|
### Fixed
|
|
27
35
|
|
|
36
|
+
- **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.
|
|
37
|
+
- **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
38
|
- **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
39
|
|
|
30
40
|
---
|
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
|
-
- **
|
|
51
|
-
- **
|
|
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.
|
|
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-
|
|
91
|
-
|
|
92
|
-
Hit@5 : 80
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
|
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
|
-
##
|
|
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.
|
|
5904
|
+
version: '6.10.11',
|
|
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.
|
|
8658
|
+
const VERSION = '6.10.11';
|
|
8548
8659
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
8549
8660
|
|
|
8550
8661
|
function requireSourceOrBundled(key) {
|
package/package.json
CHANGED
package/src/mcp/handlers.js
CHANGED
|
@@ -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
|
|
24
|
-
if (!
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
result.push(
|
|
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
|
-
|
|
99
|
-
|
|
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 (
|
|
278
|
+
// ── Signatures (hot + cold + cache via buildSigIndex) ───────────────────
|
|
284
279
|
lines.push('## Signatures');
|
|
285
280
|
let indexedFiles = [];
|
|
286
281
|
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
let
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
package/src/retrieval/ranker.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
/**
|