sigmap 5.8.0 → 6.0.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 +39 -0
- package/README.md +132 -993
- package/gen-context.js +70 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/cache/sig-cache.js +105 -0
- package/src/mcp/handlers.js +5 -1
- package/src/mcp/server.js +1 -1
- package/src/retrieval/ranker.js +28 -0
package/gen-context.js
CHANGED
|
@@ -5291,7 +5291,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
5291
5291
|
|
|
5292
5292
|
const SERVER_INFO = {
|
|
5293
5293
|
name: 'sigmap',
|
|
5294
|
-
version: '
|
|
5294
|
+
version: '6.0.0',
|
|
5295
5295
|
description: 'SigMap MCP server — code signatures on demand',
|
|
5296
5296
|
};
|
|
5297
5297
|
|
|
@@ -7009,7 +7009,7 @@ const path = require('path');
|
|
|
7009
7009
|
const os = require('os');
|
|
7010
7010
|
const { execSync } = require('child_process');
|
|
7011
7011
|
|
|
7012
|
-
const VERSION = '
|
|
7012
|
+
const VERSION = '6.0.0';
|
|
7013
7013
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
7014
7014
|
|
|
7015
7015
|
function requireSourceOrBundled(key) {
|
|
@@ -9081,6 +9081,74 @@ function main() {
|
|
|
9081
9081
|
process.exit(0);
|
|
9082
9082
|
}
|
|
9083
9083
|
|
|
9084
|
+
// v5.9: `sigmap bench --submit` — format local benchmark history as a shareable community block
|
|
9085
|
+
if (args[0] === 'bench' && args.includes('--submit')) {
|
|
9086
|
+
const versionMeta = (() => {
|
|
9087
|
+
try { return JSON.parse(fs.readFileSync(path.join(__dirname, 'version.json'), 'utf8')); }
|
|
9088
|
+
catch (_) { return {}; }
|
|
9089
|
+
})();
|
|
9090
|
+
|
|
9091
|
+
const histPath = path.join(cwd, '.context', 'benchmark-history.ndjson');
|
|
9092
|
+
let localMetrics = null;
|
|
9093
|
+
if (fs.existsSync(histPath)) {
|
|
9094
|
+
try {
|
|
9095
|
+
const entries = fs.readFileSync(histPath, 'utf8').trim().split('\n')
|
|
9096
|
+
.map((l) => { try { return JSON.parse(l); } catch (_) { return null; } }).filter(Boolean);
|
|
9097
|
+
const ret = [...entries].reverse().find((e) => e.type === 'retrieval');
|
|
9098
|
+
const tok = [...entries].reverse().find((e) => e.type === 'token-reduction');
|
|
9099
|
+
if (ret || tok) {
|
|
9100
|
+
localMetrics = {
|
|
9101
|
+
hitAt5Pct: ret ? (ret.hitAt5Pct || Math.round((ret.hitAt5 || 0) * 100)) : null,
|
|
9102
|
+
reductionPct: tok ? (tok.reduction || tok.avgReductionPct || null) : null,
|
|
9103
|
+
runDate: (ret || tok).ts ? new Date((ret || tok).ts).toISOString().slice(0, 10) : null,
|
|
9104
|
+
};
|
|
9105
|
+
}
|
|
9106
|
+
} catch (_) {}
|
|
9107
|
+
}
|
|
9108
|
+
|
|
9109
|
+
const canonical = versionMeta.metrics || {};
|
|
9110
|
+
const submission = {
|
|
9111
|
+
sigmapVersion: versionMeta.version || 'unknown',
|
|
9112
|
+
benchmarkId: versionMeta.benchmark_id || 'unknown',
|
|
9113
|
+
canonicalHitAt5: canonical.hit_at_5 != null ? Math.round(canonical.hit_at_5 * 1000) / 10 : null,
|
|
9114
|
+
canonicalReduction: canonical.overall_token_reduction_pct || null,
|
|
9115
|
+
local: localMetrics,
|
|
9116
|
+
submittedAt: new Date().toISOString().slice(0, 10),
|
|
9117
|
+
};
|
|
9118
|
+
|
|
9119
|
+
if (args.includes('--json')) {
|
|
9120
|
+
process.stdout.write(JSON.stringify(submission, null, 2) + '\n');
|
|
9121
|
+
} else {
|
|
9122
|
+
const bar = '─'.repeat(56);
|
|
9123
|
+
const lines = [
|
|
9124
|
+
bar,
|
|
9125
|
+
' SigMap Community Benchmark Submission',
|
|
9126
|
+
bar,
|
|
9127
|
+
` SigMap version : ${submission.sigmapVersion}`,
|
|
9128
|
+
` Benchmark ID : ${submission.benchmarkId}`,
|
|
9129
|
+
` Submitted : ${submission.submittedAt}`,
|
|
9130
|
+
bar,
|
|
9131
|
+
' Canonical metrics (official release):',
|
|
9132
|
+
` hit@5 : ${submission.canonicalHitAt5 != null ? submission.canonicalHitAt5 + '%' : 'n/a'}`,
|
|
9133
|
+
` token reduction: ${submission.canonicalReduction != null ? submission.canonicalReduction + '%' : 'n/a'}`,
|
|
9134
|
+
];
|
|
9135
|
+
if (localMetrics) {
|
|
9136
|
+
lines.push(bar, ' Local run metrics (this repo):');
|
|
9137
|
+
if (localMetrics.hitAt5Pct != null) lines.push(` hit@5 : ${localMetrics.hitAt5Pct}%`);
|
|
9138
|
+
if (localMetrics.reductionPct != null) lines.push(` token reduction: ${localMetrics.reductionPct}%`);
|
|
9139
|
+
if (localMetrics.runDate) lines.push(` run date : ${localMetrics.runDate}`);
|
|
9140
|
+
} else {
|
|
9141
|
+
lines.push(bar, ' Local run metrics: none yet — run node scripts/run-retrieval-benchmark.mjs');
|
|
9142
|
+
}
|
|
9143
|
+
lines.push(bar);
|
|
9144
|
+
lines.push(' Paste the block above into a GitHub Discussion to share your results.');
|
|
9145
|
+
lines.push(' https://github.com/manojmallick/sigmap/discussions');
|
|
9146
|
+
lines.push(bar);
|
|
9147
|
+
console.log(lines.join('\n'));
|
|
9148
|
+
}
|
|
9149
|
+
process.exit(0);
|
|
9150
|
+
}
|
|
9151
|
+
|
|
9084
9152
|
// v5.2: `sigmap learn` — manual learning controls for ranking
|
|
9085
9153
|
if (args[0] === 'learn') {
|
|
9086
9154
|
const doReset = args.includes('--reset');
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Incremental extraction cache (v6.1).
|
|
5
|
+
*
|
|
6
|
+
* Stores extracted signatures keyed by file path + mtime so only
|
|
7
|
+
* modified files are re-extracted on subsequent runs.
|
|
8
|
+
*
|
|
9
|
+
* Cache file: .sigmap-cache.json in cwd (gitignored).
|
|
10
|
+
* Format:
|
|
11
|
+
* { sigmapVersion: string, entries: { [absPath]: { mtime: number, sigs: string[] } } }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const CACHE_FILE = '.sigmap-cache.json';
|
|
18
|
+
|
|
19
|
+
function cachePath(cwd) {
|
|
20
|
+
return path.join(cwd, CACHE_FILE);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Load the cache from disk.
|
|
25
|
+
* Returns a Map<absPath, { mtime: number, sigs: string[] }>.
|
|
26
|
+
* Returns an empty Map if cache is absent, corrupt, or from a different version.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} cwd
|
|
29
|
+
* @param {string} currentVersion - sigmap VERSION constant
|
|
30
|
+
* @returns {Map<string, { mtime: number, sigs: string[] }>}
|
|
31
|
+
*/
|
|
32
|
+
function loadCache(cwd, currentVersion) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = fs.readFileSync(cachePath(cwd), 'utf8');
|
|
35
|
+
const data = JSON.parse(raw);
|
|
36
|
+
// Bust cache on version change to avoid stale sig formats
|
|
37
|
+
if (data.sigmapVersion !== currentVersion) return new Map();
|
|
38
|
+
return new Map(Object.entries(data.entries || {}));
|
|
39
|
+
} catch (_) {
|
|
40
|
+
return new Map();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Persist the cache to disk.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} cwd
|
|
48
|
+
* @param {string} currentVersion
|
|
49
|
+
* @param {Map<string, { mtime: number, sigs: string[] }>} cache
|
|
50
|
+
*/
|
|
51
|
+
function saveCache(cwd, currentVersion, cache) {
|
|
52
|
+
try {
|
|
53
|
+
const data = {
|
|
54
|
+
sigmapVersion: currentVersion,
|
|
55
|
+
entries: Object.fromEntries(cache),
|
|
56
|
+
};
|
|
57
|
+
fs.writeFileSync(cachePath(cwd), JSON.stringify(data), 'utf8');
|
|
58
|
+
} catch (_) {
|
|
59
|
+
// Non-fatal: cache save failure just means a full re-extract next run
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Given a list of absolute file paths, return only those whose mtime
|
|
65
|
+
* differs from the cached value (or that are not cached at all).
|
|
66
|
+
*
|
|
67
|
+
* @param {string[]} files - absolute paths
|
|
68
|
+
* @param {Map<string, { mtime: number, sigs: string[] }>} cache
|
|
69
|
+
* @returns {{ changed: string[], unchanged: string[] }}
|
|
70
|
+
*/
|
|
71
|
+
function getChangedFiles(files, cache) {
|
|
72
|
+
const changed = [];
|
|
73
|
+
const unchanged = [];
|
|
74
|
+
for (const f of files) {
|
|
75
|
+
try {
|
|
76
|
+
const mtime = fs.statSync(f).mtimeMs;
|
|
77
|
+
const cached = cache.get(f);
|
|
78
|
+
if (!cached || cached.mtime !== mtime) {
|
|
79
|
+
changed.push(f);
|
|
80
|
+
} else {
|
|
81
|
+
unchanged.push(f);
|
|
82
|
+
}
|
|
83
|
+
} catch (_) {
|
|
84
|
+
changed.push(f); // file unreadable → treat as changed
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { changed, unchanged };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Update cache entries for a batch of files after fresh extraction.
|
|
92
|
+
*
|
|
93
|
+
* @param {Map<string, { mtime: number, sigs: string[] }>} cache
|
|
94
|
+
* @param {{ file: string, sigs: string[] }[]} extracted - freshly extracted results
|
|
95
|
+
*/
|
|
96
|
+
function updateCacheEntries(cache, extracted) {
|
|
97
|
+
for (const { file, sigs } of extracted) {
|
|
98
|
+
try {
|
|
99
|
+
const mtime = fs.statSync(file).mtimeMs;
|
|
100
|
+
cache.set(file, { mtime, sigs });
|
|
101
|
+
} catch (_) {}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { loadCache, saveCache, getChangedFiles, updateCacheEntries };
|
package/src/mcp/handlers.js
CHANGED
|
@@ -446,11 +446,15 @@ function queryContext(args, cwd) {
|
|
|
446
446
|
|
|
447
447
|
try {
|
|
448
448
|
const { rank, buildSigIndex, formatRankTable } = require('../retrieval/ranker');
|
|
449
|
+
const { buildFromCwd } = require('../graph/builder');
|
|
449
450
|
const index = buildSigIndex(cwd);
|
|
450
451
|
if (index.size === 0) return 'No signatures indexed. Run: node gen-context.js';
|
|
451
452
|
|
|
452
453
|
const topK = Math.min(Math.max(1, parseInt(args.topK, 10) || 10), 25);
|
|
453
|
-
|
|
454
|
+
// Build dependency graph for neighbor boost — non-fatal if it fails
|
|
455
|
+
let graph = null;
|
|
456
|
+
try { graph = buildFromCwd(cwd); } catch (_) {}
|
|
457
|
+
const results = rank(args.query, index, { topK, cwd, graph });
|
|
454
458
|
return formatRankTable(results, args.query);
|
|
455
459
|
} catch (err) {
|
|
456
460
|
return `_query_context failed: ${err.message}_`;
|
package/src/mcp/server.js
CHANGED
package/src/retrieval/ranker.js
CHANGED
|
@@ -29,6 +29,7 @@ const DEFAULT_WEIGHTS = {
|
|
|
29
29
|
prefixMatch: 0.3, // partial prefix hit (query token ≥ 4 chars)
|
|
30
30
|
pathMatch: 0.8, // query token appears in the file path
|
|
31
31
|
recencyBoost: 1.5, // multiplier applied when file is in recencySet
|
|
32
|
+
graphBoost: 0.4, // additive bonus for 1-hop import neighbors of matching files
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -99,6 +100,7 @@ function scoreFile(filePath, sigs, queryTokens, weights) {
|
|
|
99
100
|
* @param {Set<string>} [opts.recencySet] - set of recently-changed file paths
|
|
100
101
|
* @param {object} [opts.weights] - override scoring weights
|
|
101
102
|
* @param {string} [opts.cwd] - project root for learned ranking weights
|
|
103
|
+
* @param {{ forward: Map<string,string[]> }} [opts.graph] - dependency graph for neighbor boost
|
|
102
104
|
* @returns {{ file: string, score: number, sigs: string[], tokens: number }[]}
|
|
103
105
|
*/
|
|
104
106
|
function rank(query, sigIndex, opts) {
|
|
@@ -110,6 +112,8 @@ function rank(query, sigIndex, opts) {
|
|
|
110
112
|
const recencySet = (opts && opts.recencySet) || null;
|
|
111
113
|
const weights = (opts && opts.weights) ? Object.assign({}, DEFAULT_WEIGHTS, opts.weights) : DEFAULT_WEIGHTS;
|
|
112
114
|
const learnedWeights = opts && opts.cwd ? loadWeights(opts.cwd) : null;
|
|
115
|
+
const graph = (opts && opts.graph && opts.graph.forward instanceof Map) ? opts.graph : null;
|
|
116
|
+
const cwd = (opts && opts.cwd) || null;
|
|
113
117
|
|
|
114
118
|
const queryTokens = tokenize(query);
|
|
115
119
|
if (queryTokens.length === 0) {
|
|
@@ -143,6 +147,30 @@ function rank(query, sigIndex, opts) {
|
|
|
143
147
|
});
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
// Graph neighbor boost: for each file with score > 0, add graphBoost to 1-hop forward
|
|
151
|
+
// neighbors that are also in the index. sigIndex uses relative paths; graph uses absolute.
|
|
152
|
+
if (graph && cwd) {
|
|
153
|
+
const path = require('path');
|
|
154
|
+
// Build a map: relative path → index position in scored array for O(1) lookup
|
|
155
|
+
const relToIdx = new Map();
|
|
156
|
+
for (let i = 0; i < scored.length; i++) {
|
|
157
|
+
relToIdx.set(scored[i].file, i);
|
|
158
|
+
}
|
|
159
|
+
for (const entry of scored) {
|
|
160
|
+
if (entry.score <= 0) continue;
|
|
161
|
+
// Resolve relative path to absolute for graph lookup
|
|
162
|
+
const abs = path.resolve(cwd, entry.file);
|
|
163
|
+
const neighbors = graph.forward.get(abs) || [];
|
|
164
|
+
for (const neighborAbs of neighbors) {
|
|
165
|
+
const neighborRel = path.relative(cwd, neighborAbs).replace(/\\/g, '/');
|
|
166
|
+
const idx = relToIdx.get(neighborRel);
|
|
167
|
+
if (idx !== undefined) {
|
|
168
|
+
scored[idx].score += weights.graphBoost;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
146
174
|
scored.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
|
|
147
175
|
return scored.slice(0, topK);
|
|
148
176
|
}
|