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 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.0 on 2026-04-15T08:05:43.080Z.
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.0 -->
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/copilot.js
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/cursor.js
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 _confidenceMeta(opts)
56
+ function write(context, cwd, opts = {})
69
57
  ```
70
58
 
71
- ### packages/adapters/windsurf.js
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/codex.js
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/analysis/coverage-score.js
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 = { analyzeFiles, formatAnalysisTable, formatAnalysisJSON }
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/config/defaults.js
163
+ ### src/retrieval/ranker.js
174
164
  ```
175
- module.exports = { DEFAULTS }
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.1.0',
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
- module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS };
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.1.2';
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('--json')) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
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": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
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: '4.1.0',
21
+ version: '4.2.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -307,4 +307,22 @@ function formatRankJSON(results, query) {
307
307
  };
308
308
  }
309
309
 
310
- module.exports = { rank, buildSigIndex, scoreFile, formatRankTable, formatRankJSON, DEFAULT_WEIGHTS };
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 };