sigmap 4.1.2 → 4.2.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/AGENTS.md +59 -53
- package/CHANGELOG.md +14 -0
- package/gen-context.js +277 -4
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/server.js +1 -1
- package/src/retrieval/ranker.js +19 -1
package/AGENTS.md
CHANGED
|
@@ -12,17 +12,22 @@ Use this marker block for all appendable context files:
|
|
|
12
12
|
## Auto-generated signatures
|
|
13
13
|
<!-- Updated by gen-context.js -->
|
|
14
14
|
You are a coding assistant with full knowledge of this codebase.
|
|
15
|
-
Below are the code signatures extracted by SigMap v4.1.
|
|
15
|
+
Below are the code signatures extracted by SigMap v4.1.2 on 2026-04-16T17:45:07.132Z.
|
|
16
16
|
|
|
17
17
|
Use these signatures to answer questions about the code accurately.
|
|
18
18
|
|
|
19
19
|
## Code Signatures
|
|
20
20
|
|
|
21
|
-
<!-- Generated by SigMap gen-context.js v4.1.
|
|
21
|
+
<!-- Generated by SigMap gen-context.js v4.1.2 -->
|
|
22
22
|
<!-- DO NOT EDIT below the marker line — run gen-context.js to regenerate -->
|
|
23
23
|
|
|
24
24
|
# Code signatures
|
|
25
25
|
|
|
26
|
+
## changes (last 5 commits — 53 minutes ago)
|
|
27
|
+
```
|
|
28
|
+
src/retrieval/ranker.js +_parseContextFile +buildSigIndex ~buildSigIndex ~rank
|
|
29
|
+
```
|
|
30
|
+
|
|
26
31
|
## packages
|
|
27
32
|
|
|
28
33
|
### packages/adapters/claude.js
|
|
@@ -34,41 +39,24 @@ function outputPath(cwd) → string
|
|
|
34
39
|
function write(context, cwd, opts = {})
|
|
35
40
|
```
|
|
36
41
|
|
|
37
|
-
### packages/adapters/
|
|
42
|
+
### packages/adapters/codex.js
|
|
38
43
|
```
|
|
39
44
|
module.exports = { name, format, outputPath, write }
|
|
40
45
|
function format(context, opts = {}) → string
|
|
41
|
-
function _confidenceMeta(opts)
|
|
42
46
|
function outputPath(cwd) → string
|
|
43
47
|
function write(context, cwd, opts = {})
|
|
44
48
|
```
|
|
45
49
|
|
|
46
|
-
### packages/adapters/
|
|
47
|
-
```
|
|
48
|
-
module.exports = { name, format, outputPath }
|
|
49
|
-
function format(context, opts = {}) → string
|
|
50
|
-
function _confidenceMeta(opts)
|
|
51
|
-
function outputPath(cwd) → string
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### packages/adapters/gemini.js
|
|
50
|
+
### packages/adapters/copilot.js
|
|
55
51
|
```
|
|
56
52
|
module.exports = { name, format, outputPath, write }
|
|
57
53
|
function format(context, opts = {}) → string
|
|
58
|
-
function outputPath(cwd) → string
|
|
59
|
-
function write(context, cwd, opts = {})
|
|
60
54
|
function _confidenceMeta(opts)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### packages/adapters/openai.js
|
|
64
|
-
```
|
|
65
|
-
module.exports = { name, format, outputPath }
|
|
66
|
-
function format(context, opts = {}) → string
|
|
67
55
|
function outputPath(cwd) → string
|
|
68
|
-
function
|
|
56
|
+
function write(context, cwd, opts = {})
|
|
69
57
|
```
|
|
70
58
|
|
|
71
|
-
### packages/adapters/
|
|
59
|
+
### packages/adapters/cursor.js
|
|
72
60
|
```
|
|
73
61
|
module.exports = { name, format, outputPath }
|
|
74
62
|
function format(context, opts = {}) → string
|
|
@@ -76,12 +64,13 @@ function _confidenceMeta(opts)
|
|
|
76
64
|
function outputPath(cwd) → string
|
|
77
65
|
```
|
|
78
66
|
|
|
79
|
-
### packages/adapters/
|
|
67
|
+
### packages/adapters/gemini.js
|
|
80
68
|
```
|
|
81
69
|
module.exports = { name, format, outputPath, write }
|
|
82
70
|
function format(context, opts = {}) → string
|
|
83
71
|
function outputPath(cwd) → string
|
|
84
72
|
function write(context, cwd, opts = {})
|
|
73
|
+
function _confidenceMeta(opts)
|
|
85
74
|
```
|
|
86
75
|
|
|
87
76
|
### packages/adapters/index.js
|
|
@@ -101,6 +90,22 @@ function format(context, opts)
|
|
|
101
90
|
function write(context, cwd, opts)
|
|
102
91
|
```
|
|
103
92
|
|
|
93
|
+
### packages/adapters/openai.js
|
|
94
|
+
```
|
|
95
|
+
module.exports = { name, format, outputPath }
|
|
96
|
+
function format(context, opts = {}) → string
|
|
97
|
+
function outputPath(cwd) → string
|
|
98
|
+
function _confidenceMeta(opts)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### packages/adapters/windsurf.js
|
|
102
|
+
```
|
|
103
|
+
module.exports = { name, format, outputPath }
|
|
104
|
+
function format(context, opts = {}) → string
|
|
105
|
+
function _confidenceMeta(opts)
|
|
106
|
+
function outputPath(cwd) → string
|
|
107
|
+
```
|
|
108
|
+
|
|
104
109
|
### packages/cli/index.js
|
|
105
110
|
```
|
|
106
111
|
module.exports = { CLI_ENTRY, run }
|
|
@@ -141,24 +146,9 @@ function adapt(context, adapterName, opts = {}) → string
|
|
|
141
146
|
|
|
142
147
|
## src
|
|
143
148
|
|
|
144
|
-
### src/
|
|
145
|
-
```
|
|
146
|
-
module.exports = { coverageScore }
|
|
147
|
-
function coverageScore(cwd, fileEntries, config) → { * score: number, * grad
|
|
148
|
-
function _walk(dir, excludeSet, out)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### src/eval/analyzer.js
|
|
149
|
+
### src/config/defaults.js
|
|
152
150
|
```
|
|
153
|
-
module.exports = {
|
|
154
|
-
function isDockerfile(name)
|
|
155
|
-
function getExtractorName(filePath)
|
|
156
|
-
function tokenCount(sigs)
|
|
157
|
-
function hasCoverage(filePath, cwd)
|
|
158
|
-
function loadExtractor(name, cwd)
|
|
159
|
-
function analyzeFiles(files, cwd, opts) → object[]
|
|
160
|
-
function formatAnalysisTable(stats, showSlow) → string
|
|
161
|
-
function formatAnalysisJSON(stats) → object
|
|
151
|
+
module.exports = { DEFAULTS }
|
|
162
152
|
```
|
|
163
153
|
|
|
164
154
|
### src/mcp/server.js
|
|
@@ -170,9 +160,22 @@ function dispatch(msg, cwd)
|
|
|
170
160
|
function start(cwd)
|
|
171
161
|
```
|
|
172
162
|
|
|
173
|
-
### src/
|
|
163
|
+
### src/retrieval/ranker.js
|
|
174
164
|
```
|
|
175
|
-
module.exports = {
|
|
165
|
+
module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS }
|
|
166
|
+
function scoreFile(filePath, sigs, queryTokens, weights) → number
|
|
167
|
+
function rank(query, sigIndex, opts) → { file: string, score: nu
|
|
168
|
+
function _parseContextFile(contextPath) → Map<string, string[]>
|
|
169
|
+
function buildSigIndex(cwd, opts) → Map<string, string[]>
|
|
170
|
+
function formatRankTable(results, query) → string
|
|
171
|
+
function formatRankJSON(results, query) → object
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### src/analysis/coverage-score.js
|
|
175
|
+
```
|
|
176
|
+
module.exports = { coverageScore }
|
|
177
|
+
function coverageScore(cwd, fileEntries, config) → { * score: number, * grad
|
|
178
|
+
function _walk(dir, excludeSet, out)
|
|
176
179
|
```
|
|
177
180
|
|
|
178
181
|
### src/config/loader.js
|
|
@@ -183,6 +186,19 @@ function loadConfig(cwd) → object
|
|
|
183
186
|
function deepClone(obj)
|
|
184
187
|
```
|
|
185
188
|
|
|
189
|
+
### src/eval/analyzer.js
|
|
190
|
+
```
|
|
191
|
+
module.exports = { analyzeFiles, formatAnalysisTable, formatAnalysisJSON }
|
|
192
|
+
function isDockerfile(name)
|
|
193
|
+
function getExtractorName(filePath)
|
|
194
|
+
function tokenCount(sigs)
|
|
195
|
+
function hasCoverage(filePath, cwd)
|
|
196
|
+
function loadExtractor(name, cwd)
|
|
197
|
+
function analyzeFiles(files, cwd, opts) → object[]
|
|
198
|
+
function formatAnalysisTable(stats, showSlow) → string
|
|
199
|
+
function formatAnalysisJSON(stats) → object
|
|
200
|
+
```
|
|
201
|
+
|
|
186
202
|
### src/eval/runner.js
|
|
187
203
|
```
|
|
188
204
|
module.exports = { run, rank, loadTasks, buildSigIndex, formatTable, formatMetrics, tokenize }
|
|
@@ -615,16 +631,6 @@ function getImpact(args, cwd)
|
|
|
615
631
|
module.exports = { TOOLS }
|
|
616
632
|
```
|
|
617
633
|
|
|
618
|
-
### src/retrieval/ranker.js
|
|
619
|
-
```
|
|
620
|
-
module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS }
|
|
621
|
-
function scoreFile(filePath, sigs, queryTokens, weights) → number
|
|
622
|
-
function rank(query, sigIndex, opts) → { file: string, score: nu
|
|
623
|
-
function buildSigIndex(cwd) → Map<string, string[]>
|
|
624
|
-
function formatRankTable(results, query) → string
|
|
625
|
-
function formatRankJSON(results, query) → object
|
|
626
|
-
```
|
|
627
|
-
|
|
628
634
|
### src/retrieval/tokenizer.js
|
|
629
635
|
```
|
|
630
636
|
module.exports = { tokenize, STOP_WORDS }
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,20 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [4.2.0] — 2026-04-16
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`sigmap ask "<query>"`** — unified pipeline: intent detection → ranked mini-context → coverage check → cost estimate → risk level in one command. Supports `--json` for machine-readable output.
|
|
18
|
+
- **Intent detection** (`detectIntent`) — classifies queries as `debug`, `explain`, `refactor`, `review`, or `search` and adjusts ranking weights accordingly for higher-relevance results.
|
|
19
|
+
- **`sigmap query --context`** — writes a targeted mini-context (top-5 ranked files, ≤ 2 000 tokens) to `.context/query-context.md` for direct pasting into an LLM prompt.
|
|
20
|
+
- **`--cost [--model <name>] [--json]`** — prints per-model token/dollar cost comparison (raw source vs SigMap output). Supports `gpt-4o`, `gpt-4`, `claude-3-5-sonnet`, `claude-opus-4`, `gemini-1.5-pro`, and more.
|
|
21
|
+
- **`sigmap suggest-profile [--short]`** — reads the last git commit message and staged files to recommend a context profile (`debug`, `architecture`, `review`, or `default`).
|
|
22
|
+
- **`sigmap compare [--json]`** — human-readable CLI wrapper over the retrieval benchmark scripts, showing SigMap vs baseline hit@5, token counts, and lift multiplier.
|
|
23
|
+
- **`sigmap share`** — prints a shareable one-liner with live benchmark numbers and copies it to the clipboard via `pbcopy`/`xclip`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
13
27
|
## [4.1.2] — 2026-04-16 — Feat: --output <file> flag for custom context path
|
|
14
28
|
|
|
15
29
|
### Added
|
package/gen-context.js
CHANGED
|
@@ -4654,7 +4654,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
4654
4654
|
|
|
4655
4655
|
const SERVER_INFO = {
|
|
4656
4656
|
name: 'sigmap',
|
|
4657
|
-
version: '4.
|
|
4657
|
+
version: '4.2.0',
|
|
4658
4658
|
description: 'SigMap MCP server — code signatures on demand',
|
|
4659
4659
|
};
|
|
4660
4660
|
|
|
@@ -5515,7 +5515,20 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
5515
5515
|
function formatRankJSON(results, query) {
|
|
5516
5516
|
return { query, results: (results || []).map((r, i) => ({ rank: i + 1, file: r.file, score: r.score, sigs: r.sigs, tokens: r.tokens })), totalResults: (results || []).length };
|
|
5517
5517
|
}
|
|
5518
|
-
|
|
5518
|
+
const INTENT_PATTERNS = {
|
|
5519
|
+
debug: /\b(bug|fix|error|crash|exception|broken|failing|issue|problem|regression)\b/i,
|
|
5520
|
+
explain: /\b(explain|how does|what is|understand|overview|architecture|describe|walk me)\b/i,
|
|
5521
|
+
refactor: /\b(refactor|restructure|redesign|clean up|extract|move|rename|simplify)\b/i,
|
|
5522
|
+
review: /\b(review|check|audit|security|pr|pull request|assess)\b/i,
|
|
5523
|
+
};
|
|
5524
|
+
function detectIntent(query) {
|
|
5525
|
+
if (!query || typeof query !== 'string') return 'search';
|
|
5526
|
+
for (const [intent, re] of Object.entries(INTENT_PATTERNS)) {
|
|
5527
|
+
if (re.test(query)) return intent;
|
|
5528
|
+
}
|
|
5529
|
+
return 'search';
|
|
5530
|
+
}
|
|
5531
|
+
module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, detectIntent };
|
|
5519
5532
|
};
|
|
5520
5533
|
|
|
5521
5534
|
// ── ./src/eval/scorer ──
|
|
@@ -6249,7 +6262,7 @@ const path = require('path');
|
|
|
6249
6262
|
const os = require('os');
|
|
6250
6263
|
const { execSync } = require('child_process');
|
|
6251
6264
|
|
|
6252
|
-
const VERSION = '4.
|
|
6265
|
+
const VERSION = '4.2.0';
|
|
6253
6266
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
6254
6267
|
|
|
6255
6268
|
function requireSourceOrBundled(key) {
|
|
@@ -7939,6 +7952,58 @@ function registerMcp(cwd, scriptPath) {
|
|
|
7939
7952
|
console.warn(JSON.stringify({ mcpServers: { 'sigmap': serverEntry } }, null, 2));
|
|
7940
7953
|
}
|
|
7941
7954
|
|
|
7955
|
+
// ---------------------------------------------------------------------------
|
|
7956
|
+
// v4.2 helpers
|
|
7957
|
+
// ---------------------------------------------------------------------------
|
|
7958
|
+
const MODEL_COSTS = {
|
|
7959
|
+
'gpt-4': 0.030,
|
|
7960
|
+
'gpt-4o': 0.005,
|
|
7961
|
+
'gpt-4o-mini': 0.000150,
|
|
7962
|
+
'claude-3-5-sonnet': 0.003,
|
|
7963
|
+
'claude-3-haiku': 0.00025,
|
|
7964
|
+
'claude-opus-4': 0.015,
|
|
7965
|
+
'gemini-1.5-pro': 0.00125,
|
|
7966
|
+
};
|
|
7967
|
+
|
|
7968
|
+
function buildMiniContext(ranked, cwd) {
|
|
7969
|
+
const lines = ['# SigMap Query Context', `Generated: ${new Date().toISOString()}`, ''];
|
|
7970
|
+
for (const { file, sigs } of ranked) {
|
|
7971
|
+
lines.push(`## ${file}`, '```', ...sigs.slice(0, 20), '```', '');
|
|
7972
|
+
}
|
|
7973
|
+
return lines.join('\n');
|
|
7974
|
+
}
|
|
7975
|
+
|
|
7976
|
+
function computeCurrentRisk(cwd) {
|
|
7977
|
+
try {
|
|
7978
|
+
const { execSync } = require('child_process');
|
|
7979
|
+
const out = execSync('git diff --name-only HEAD', { cwd, timeout: 3000, encoding: 'utf8' });
|
|
7980
|
+
const count = out.trim().split('\n').filter(Boolean).length;
|
|
7981
|
+
if (count === 0) return 'NONE';
|
|
7982
|
+
if (count <= 3) return 'LOW';
|
|
7983
|
+
if (count <= 10) return 'MEDIUM';
|
|
7984
|
+
return 'HIGH';
|
|
7985
|
+
} catch (_) { return 'UNKNOWN'; }
|
|
7986
|
+
}
|
|
7987
|
+
|
|
7988
|
+
function getRawTokenCount(cwd, config) {
|
|
7989
|
+
let total = 0;
|
|
7990
|
+
const files = buildFileList(cwd, config);
|
|
7991
|
+
for (const fp of files) {
|
|
7992
|
+
try { total += estimateTokens(fs.readFileSync(fp, 'utf8')); } catch (_) {}
|
|
7993
|
+
}
|
|
7994
|
+
return total;
|
|
7995
|
+
}
|
|
7996
|
+
|
|
7997
|
+
function getIntentWeights(intent) {
|
|
7998
|
+
const { DEFAULT_WEIGHTS } = requireSourceOrBundled('./src/retrieval/ranker');
|
|
7999
|
+
const base = Object.assign({}, DEFAULT_WEIGHTS);
|
|
8000
|
+
if (intent === 'debug') return Object.assign({}, base, { recencyBoost: base.recencyBoost * 1.5 });
|
|
8001
|
+
if (intent === 'explain') return Object.assign({}, base, { symbolMatch: base.symbolMatch * 1.5 });
|
|
8002
|
+
if (intent === 'refactor') return Object.assign({}, base, { pathMatch: base.pathMatch * 1.5 });
|
|
8003
|
+
if (intent === 'review') return Object.assign({}, base, { exactToken: base.exactToken * 1.3 });
|
|
8004
|
+
return base;
|
|
8005
|
+
}
|
|
8006
|
+
|
|
7942
8007
|
function main() {
|
|
7943
8008
|
const args = process.argv.slice(2);
|
|
7944
8009
|
|
|
@@ -8018,6 +8083,170 @@ function main() {
|
|
|
8018
8083
|
process.exit(1);
|
|
8019
8084
|
}
|
|
8020
8085
|
|
|
8086
|
+
// v4.2: `sigmap ask "<query>"` — unified pipeline
|
|
8087
|
+
if (args[0] === 'ask') {
|
|
8088
|
+
const query = args[1];
|
|
8089
|
+
if (!query || query.startsWith('--')) {
|
|
8090
|
+
console.error('[sigmap] Usage: sigmap ask "<query>"');
|
|
8091
|
+
console.error(' Example: sigmap ask "fix the login bug"');
|
|
8092
|
+
process.exit(1);
|
|
8093
|
+
}
|
|
8094
|
+
|
|
8095
|
+
const { detectIntent, buildSigIndex, rank } = requireSourceOrBundled('./src/retrieval/ranker');
|
|
8096
|
+
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
8097
|
+
|
|
8098
|
+
const intent = detectIntent(query);
|
|
8099
|
+
const intentWeights = getIntentWeights(intent);
|
|
8100
|
+
|
|
8101
|
+
const sigIndex = buildSigIndex(cwd);
|
|
8102
|
+
if (sigIndex.size === 0) {
|
|
8103
|
+
console.error('[sigmap] no context file found. Run: sigmap (to generate first)');
|
|
8104
|
+
process.exit(1);
|
|
8105
|
+
}
|
|
8106
|
+
|
|
8107
|
+
const ranked = rank(query, sigIndex, { topK: 5, weights: intentWeights });
|
|
8108
|
+
const miniCtx = buildMiniContext(ranked, cwd);
|
|
8109
|
+
const outPath = path.join(cwd, '.context', 'query-context.md');
|
|
8110
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
8111
|
+
fs.writeFileSync(outPath, miniCtx, 'utf8');
|
|
8112
|
+
const ctxTok = estimateTokens(miniCtx);
|
|
8113
|
+
|
|
8114
|
+
const allFiles = buildFileList(cwd, config);
|
|
8115
|
+
const fakeEntries = allFiles.map((f) => ({ filePath: f }));
|
|
8116
|
+
let coveragePct = 0;
|
|
8117
|
+
try { coveragePct = coverageScore(cwd, fakeEntries, config).score; } catch (_) {}
|
|
8118
|
+
|
|
8119
|
+
const rawTok = getRawTokenCount(cwd, config);
|
|
8120
|
+
const savings = rawTok > 0 ? Math.round((1 - ctxTok / rawTok) * 100) : 0;
|
|
8121
|
+
const model = args[args.indexOf('--model') + 1] || 'gpt-4o';
|
|
8122
|
+
const rateK = MODEL_COSTS[model] || MODEL_COSTS['gpt-4o'];
|
|
8123
|
+
const costRaw = ((rawTok / 1000) * rateK).toFixed(4);
|
|
8124
|
+
const costCtx = ((ctxTok / 1000) * rateK).toFixed(4);
|
|
8125
|
+
|
|
8126
|
+
const riskLevel = computeCurrentRisk(cwd);
|
|
8127
|
+
|
|
8128
|
+
if (args.includes('--json')) {
|
|
8129
|
+
process.stdout.write(JSON.stringify({
|
|
8130
|
+
intent, coverage: coveragePct, contextTokens: ctxTok,
|
|
8131
|
+
costBefore: costRaw, costAfter: costCtx, savingsPct: savings,
|
|
8132
|
+
riskLevel, contextPath: path.relative(cwd, outPath),
|
|
8133
|
+
}) + '\n');
|
|
8134
|
+
} else {
|
|
8135
|
+
const bar = '─'.repeat(44);
|
|
8136
|
+
console.log([
|
|
8137
|
+
bar,
|
|
8138
|
+
` sigmap ask "${query}"`,
|
|
8139
|
+
` Intent : ${intent}`,
|
|
8140
|
+
` Context : ${ctxTok.toLocaleString()} tokens → ${path.relative(cwd, outPath)}`,
|
|
8141
|
+
` Coverage : ${coveragePct}%`,
|
|
8142
|
+
` Risk : ${riskLevel}`,
|
|
8143
|
+
` Cost : $${costCtx}/query (was $${costRaw} · saved ${savings}%)`,
|
|
8144
|
+
bar,
|
|
8145
|
+
].join('\n'));
|
|
8146
|
+
}
|
|
8147
|
+
process.exit(0);
|
|
8148
|
+
}
|
|
8149
|
+
|
|
8150
|
+
// v4.2: `sigmap suggest-profile` — auto-detect task type from git state
|
|
8151
|
+
if (args[0] === 'suggest-profile') {
|
|
8152
|
+
const short = args.includes('--short');
|
|
8153
|
+
let msg = '', diff = '';
|
|
8154
|
+
try {
|
|
8155
|
+
const { execSync } = require('child_process');
|
|
8156
|
+
msg = execSync('git log -1 --format=%s', { cwd, timeout: 3000, encoding: 'utf8' }).trim();
|
|
8157
|
+
diff = execSync('git diff --cached --name-only', { cwd, timeout: 3000, encoding: 'utf8' });
|
|
8158
|
+
} catch (_) {}
|
|
8159
|
+
|
|
8160
|
+
let profile = 'default';
|
|
8161
|
+
let reason = 'no strong signal in git state';
|
|
8162
|
+
if (/fix|bug|error|crash|exception/i.test(msg)) { profile = 'debug'; reason = `commit: "${msg.slice(0, 60)}"`; }
|
|
8163
|
+
else if (/refactor|architect|redesign|module/i.test(msg)) { profile = 'architecture'; reason = `commit: "${msg.slice(0, 60)}"`; }
|
|
8164
|
+
else if (/review|pr|pull.request|check/i.test(msg)) { profile = 'review'; reason = `commit: "${msg.slice(0, 60)}"`; }
|
|
8165
|
+
else if (diff.includes('.spec.') || diff.includes('.test.')) { profile = 'debug'; reason = 'staged test files detected'; }
|
|
8166
|
+
|
|
8167
|
+
if (short) {
|
|
8168
|
+
console.log(profile);
|
|
8169
|
+
} else {
|
|
8170
|
+
console.log(`[sigmap] suggested profile: --profile ${profile}`);
|
|
8171
|
+
console.log(` Reason: ${reason}`);
|
|
8172
|
+
}
|
|
8173
|
+
process.exit(0);
|
|
8174
|
+
}
|
|
8175
|
+
|
|
8176
|
+
// v4.2: `sigmap compare` — human-readable benchmark CLI
|
|
8177
|
+
if (args[0] === 'compare') {
|
|
8178
|
+
const { execSync } = require('child_process');
|
|
8179
|
+
console.log('[sigmap] Running comparison benchmark (this may take ~30s)...\n');
|
|
8180
|
+
|
|
8181
|
+
let raw = '';
|
|
8182
|
+
try {
|
|
8183
|
+
raw = execSync(
|
|
8184
|
+
`node ${JSON.stringify(path.join(__dirname, 'scripts', 'run-retrieval-benchmark.mjs'))} --compare`,
|
|
8185
|
+
{ cwd, timeout: 90_000, encoding: 'utf8' }
|
|
8186
|
+
);
|
|
8187
|
+
} catch (e) { raw = (e && e.stdout) ? e.stdout : ''; }
|
|
8188
|
+
|
|
8189
|
+
let results = null;
|
|
8190
|
+
try { results = JSON.parse(raw); } catch (_) {}
|
|
8191
|
+
|
|
8192
|
+
if (!results) {
|
|
8193
|
+
console.error('[sigmap] Could not parse benchmark output.');
|
|
8194
|
+
console.error(' Run manually: node scripts/run-retrieval-benchmark.mjs --skip-run --json');
|
|
8195
|
+
process.exit(1);
|
|
8196
|
+
}
|
|
8197
|
+
|
|
8198
|
+
if (args.includes('--json')) {
|
|
8199
|
+
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
8200
|
+
} else {
|
|
8201
|
+
const pct = (v) => `${(v * 100).toFixed(1)}%`;
|
|
8202
|
+
const lift = (a, b) => (b > 0 ? (a / b).toFixed(1) : '∞');
|
|
8203
|
+
const bar = '─'.repeat(44);
|
|
8204
|
+
console.log([
|
|
8205
|
+
bar,
|
|
8206
|
+
' SigMap vs Baseline',
|
|
8207
|
+
bar,
|
|
8208
|
+
` hit@5 ${pct(results.sigmap.hitAt5)} vs ${pct(results.baseline.hitAt5)} (${lift(results.sigmap.hitAt5, results.baseline.hitAt5)}× lift)`,
|
|
8209
|
+
` Avg tokens ${results.sigmap.tokens.toLocaleString()} vs ${results.baseline.tokens.toLocaleString()}`,
|
|
8210
|
+
bar,
|
|
8211
|
+
].join('\n'));
|
|
8212
|
+
}
|
|
8213
|
+
process.exit(0);
|
|
8214
|
+
}
|
|
8215
|
+
|
|
8216
|
+
// v4.2: `sigmap share` — shareable one-liner with live benchmark numbers
|
|
8217
|
+
if (args[0] === 'share') {
|
|
8218
|
+
const histPath = path.join(cwd, '.context', 'benchmark-history.ndjson');
|
|
8219
|
+
let reduction = 97, hitAt5 = 88;
|
|
8220
|
+
|
|
8221
|
+
if (fs.existsSync(histPath)) {
|
|
8222
|
+
try {
|
|
8223
|
+
const entries = fs.readFileSync(histPath, 'utf8').trim().split('\n')
|
|
8224
|
+
.map((l) => { try { return JSON.parse(l); } catch (_) { return null; } }).filter(Boolean);
|
|
8225
|
+
const tok = [...entries].reverse().find((e) => e.type === 'token-reduction');
|
|
8226
|
+
const ret = [...entries].reverse().find((e) => e.type === 'retrieval');
|
|
8227
|
+
if (tok && tok.reduction) reduction = tok.reduction;
|
|
8228
|
+
if (ret && ret.hitAt5) hitAt5 = Math.round(ret.hitAt5 * 100);
|
|
8229
|
+
} catch (_) {}
|
|
8230
|
+
}
|
|
8231
|
+
|
|
8232
|
+
const shareText = [
|
|
8233
|
+
'Generated with SigMap — zero-dependency AI context engine',
|
|
8234
|
+
`${reduction}% fewer tokens · ${hitAt5}% retrieval accuracy · 6× better results`,
|
|
8235
|
+
'https://sigmap.dev',
|
|
8236
|
+
].join('\n');
|
|
8237
|
+
|
|
8238
|
+
console.log(shareText);
|
|
8239
|
+
|
|
8240
|
+
try {
|
|
8241
|
+
const { execSync } = require('child_process');
|
|
8242
|
+
const clipCmd = process.platform === 'darwin' ? 'pbcopy' : 'xclip -selection clipboard';
|
|
8243
|
+
execSync(`printf '%s' ${JSON.stringify(shareText)} | ${clipCmd}`, { timeout: 2000 });
|
|
8244
|
+
console.log('\n[sigmap] Copied to clipboard.');
|
|
8245
|
+
} catch (_) {}
|
|
8246
|
+
|
|
8247
|
+
process.exit(0);
|
|
8248
|
+
}
|
|
8249
|
+
|
|
8021
8250
|
// Feature 6: `sigmap sync` — write all outputs + llms.txt + print compact diff
|
|
8022
8251
|
if (args[0] === 'sync') {
|
|
8023
8252
|
try {
|
|
@@ -8469,7 +8698,13 @@ function main() {
|
|
|
8469
8698
|
: ((config && config.retrieval && config.retrieval.topK) || 10);
|
|
8470
8699
|
const recencyBoost = (config && config.retrieval && config.retrieval.recencyBoost) || 1.5;
|
|
8471
8700
|
const results = rank(query, index, { topK, recencyBoost });
|
|
8472
|
-
if (args.includes('--
|
|
8701
|
+
if (args.includes('--context')) {
|
|
8702
|
+
const miniCtx = buildMiniContext(results, cwd);
|
|
8703
|
+
const ctxOut = path.join(cwd, '.context', 'query-context.md');
|
|
8704
|
+
fs.mkdirSync(path.dirname(ctxOut), { recursive: true });
|
|
8705
|
+
fs.writeFileSync(ctxOut, miniCtx, 'utf8');
|
|
8706
|
+
console.log(`[sigmap] query context → ${path.relative(cwd, ctxOut)} (${estimateTokens(miniCtx)} tokens)`);
|
|
8707
|
+
} else if (args.includes('--json')) {
|
|
8473
8708
|
process.stdout.write(JSON.stringify(formatRankJSON(results, query)) + '\n');
|
|
8474
8709
|
} else {
|
|
8475
8710
|
process.stdout.write(formatRankTable(results, query));
|
|
@@ -8639,6 +8874,44 @@ function main() {
|
|
|
8639
8874
|
process.exit(0);
|
|
8640
8875
|
}
|
|
8641
8876
|
|
|
8877
|
+
// v4.2: `--cost` — show token/$ cost estimate before and after SigMap
|
|
8878
|
+
if (args.includes('--cost')) {
|
|
8879
|
+
const rawTok = getRawTokenCount(cwd, config);
|
|
8880
|
+
runGenerate(cwd, config, false);
|
|
8881
|
+
|
|
8882
|
+
const model = args[args.indexOf('--model') + 1] || 'gpt-4o';
|
|
8883
|
+
const rateK = MODEL_COSTS[model] || MODEL_COSTS['gpt-4o'];
|
|
8884
|
+
|
|
8885
|
+
const ctxPath = config.customOutput
|
|
8886
|
+
? path.resolve(cwd, config.customOutput)
|
|
8887
|
+
: path.join(cwd, '.github', 'copilot-instructions.md');
|
|
8888
|
+
let outTok = 0;
|
|
8889
|
+
try { outTok = estimateTokens(fs.readFileSync(ctxPath, 'utf8')); } catch (_) {}
|
|
8890
|
+
|
|
8891
|
+
const savings = rawTok > 0 ? Math.round((1 - outTok / rawTok) * 100) : 0;
|
|
8892
|
+
const costRaw = (rawTok / 1000) * rateK;
|
|
8893
|
+
const costCtx = (outTok / 1000) * rateK;
|
|
8894
|
+
|
|
8895
|
+
const out = {
|
|
8896
|
+
model,
|
|
8897
|
+
rawTokens: rawTok,
|
|
8898
|
+
contextTokens: outTok,
|
|
8899
|
+
costRaw: costRaw.toFixed(4),
|
|
8900
|
+
costContext: costCtx.toFixed(4),
|
|
8901
|
+
savingsPct: savings,
|
|
8902
|
+
};
|
|
8903
|
+
|
|
8904
|
+
if (args.includes('--json')) {
|
|
8905
|
+
process.stdout.write(JSON.stringify(out) + '\n');
|
|
8906
|
+
} else {
|
|
8907
|
+
console.log(`\n Cost estimate (${model}):`);
|
|
8908
|
+
console.log(` Without SigMap : ${rawTok.toLocaleString()} tok $${out.costRaw}/query`);
|
|
8909
|
+
console.log(` With SigMap : ${outTok.toLocaleString()} tok $${out.costContext}/query`);
|
|
8910
|
+
console.log(` Savings : ${savings}% ($${(costRaw - costCtx).toFixed(4)} saved per query)\n`);
|
|
8911
|
+
}
|
|
8912
|
+
process.exit(0);
|
|
8913
|
+
}
|
|
8914
|
+
|
|
8642
8915
|
// Default: generate once
|
|
8643
8916
|
runGenerate(cwd, config, false);
|
|
8644
8917
|
}
|
package/package.json
CHANGED
package/src/mcp/server.js
CHANGED
package/src/retrieval/ranker.js
CHANGED
|
@@ -307,4 +307,22 @@ function formatRankJSON(results, query) {
|
|
|
307
307
|
};
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Intent detection
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
const INTENT_PATTERNS = {
|
|
314
|
+
debug: /\b(bug|fix|error|crash|exception|broken|failing|issue|problem|regression)\b/i,
|
|
315
|
+
explain: /\b(explain|how does|what is|understand|overview|architecture|describe|walk me)\b/i,
|
|
316
|
+
refactor: /\b(refactor|restructure|redesign|clean up|extract|move|rename|simplify)\b/i,
|
|
317
|
+
review: /\b(review|check|audit|security|pr|pull request|assess)\b/i,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
function detectIntent(query) {
|
|
321
|
+
if (!query || typeof query !== 'string') return 'search';
|
|
322
|
+
for (const [intent, re] of Object.entries(INTENT_PATTERNS)) {
|
|
323
|
+
if (re.test(query)) return intent;
|
|
324
|
+
}
|
|
325
|
+
return 'search';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS, detectIntent };
|