sigmap 4.1.1 → 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 +54 -0
- package/README.md +24 -0
- package/gen-context.js +375 -17
- 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 +40 -9
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,60 @@ 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
|
+
|
|
27
|
+
## [4.1.2] — 2026-04-16 — Feat: --output <file> flag for custom context path
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`--output <file>` flag** — write signatures to any custom path, not just
|
|
32
|
+
an adapter's fixed location:
|
|
33
|
+
```bash
|
|
34
|
+
sigmap --output .context/ai-context.md # default generation
|
|
35
|
+
sigmap --adapter claude --output shared/sigs.md # adapter + custom path
|
|
36
|
+
```
|
|
37
|
+
The custom file is written **in addition to** the adapter's default output so
|
|
38
|
+
existing tooling is unaffected.
|
|
39
|
+
|
|
40
|
+
- **Automatic discovery for `--query`** — the resolved path is persisted to
|
|
41
|
+
`gen-context.config.json` as `customOutput` so subsequent `--query` runs
|
|
42
|
+
find it automatically without needing to pass `--output` again:
|
|
43
|
+
```bash
|
|
44
|
+
sigmap --output .context/ai-context.md # generates + persists path
|
|
45
|
+
sigmap --query "add a new extractor" # auto-finds .context/ai-context.md
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- **Priority order for `--query` context resolution** (most specific first):
|
|
49
|
+
1. `--output <file>` flag — explicit path
|
|
50
|
+
2. `--adapter <name>` flag — adapter's fixed output path
|
|
51
|
+
3. `customOutput` in `gen-context.config.json` — persisted from last `--output` run
|
|
52
|
+
4. Probe all known adapter output paths — existing fallback behaviour
|
|
53
|
+
|
|
54
|
+
- **Nested directories created automatically** — `--output a/b/c/file.md`
|
|
55
|
+
creates any missing parent directories.
|
|
56
|
+
|
|
57
|
+
### Tests
|
|
58
|
+
|
|
59
|
+
- Added `test/integration/output-flag.test.js` (13 tests) covering: custom
|
|
60
|
+
file creation, parseable headers, config persistence, nested dirs, missing
|
|
61
|
+
arg error, `--adapter` + `--output` combo, explicit `--query` with `--output`,
|
|
62
|
+
auto-discovery via persisted config, missing-file error, `--output` overrides
|
|
63
|
+
`--adapter` during `--query`.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
13
67
|
## [4.1.1] — 2026-04-16 — Fix: --query works with any adapter output
|
|
14
68
|
|
|
15
69
|
### Fixed
|
package/README.md
CHANGED
|
@@ -353,6 +353,24 @@ Configure multiple adapters at once in `gen-context.config.json`:
|
|
|
353
353
|
|
|
354
354
|
Use SigMap as a Node.js library without spawning a subprocess. See the [full API reference](#-programmatic-api) below.
|
|
355
355
|
|
|
356
|
+
### Custom output path
|
|
357
|
+
|
|
358
|
+
Write signatures to any file location — useful for shared docs folders, monorepos,
|
|
359
|
+
or tooling that expects context at a non-standard path:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
sigmap --output .context/ai-context.md # write to custom path
|
|
363
|
+
sigmap --adapter claude --output shared/sigs.md # adapter + custom path
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The path is persisted to `gen-context.config.json`, so `--query` finds it
|
|
367
|
+
automatically on subsequent runs — no need to pass `--output` again:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
sigmap --output .context/ai-context.md # generates and saves the path
|
|
371
|
+
sigmap --query "add an extractor" # auto-discovers .context/ai-context.md
|
|
372
|
+
```
|
|
373
|
+
|
|
356
374
|
### Query-aware retrieval
|
|
357
375
|
|
|
358
376
|
Find the most relevant files for any task without reading the whole codebase:
|
|
@@ -361,6 +379,7 @@ Find the most relevant files for any task without reading the whole codebase:
|
|
|
361
379
|
sigmap --query "authentication middleware" # ranked file list
|
|
362
380
|
sigmap --query "auth" --json # machine-readable output
|
|
363
381
|
sigmap --query "auth" --top 5 # top 5 results only
|
|
382
|
+
sigmap --query "auth" --adapter claude # query against CLAUDE.md specifically
|
|
364
383
|
```
|
|
365
384
|
|
|
366
385
|
### Diagnostic and evaluation tools
|
|
@@ -616,9 +635,14 @@ sigmap --diff Generate context for git-changed f
|
|
|
616
635
|
sigmap --diff --staged Staged files only (pre-commit check)
|
|
617
636
|
sigmap --mcp Start MCP server on stdio
|
|
618
637
|
|
|
638
|
+
sigmap --output <file> Write signatures to a custom path (persists for --query)
|
|
639
|
+
sigmap --output <file> --adapter <name> Adapter output + custom copy
|
|
640
|
+
|
|
619
641
|
sigmap --query "<text>" Rank files by relevance to a query
|
|
620
642
|
sigmap --query "<text>" --json Ranked results as JSON
|
|
621
643
|
sigmap --query "<text>" --top <n> Limit results to top N files (default 10)
|
|
644
|
+
sigmap --query "<text>" --adapter <name> Query against a specific adapter's output file
|
|
645
|
+
sigmap --query "<text>" --output <file> Query against a specific custom file
|
|
622
646
|
|
|
623
647
|
sigmap --analyze Per-file breakdown (sigs / tokens / extractor / coverage)
|
|
624
648
|
sigmap --analyze --json Analysis as JSON
|
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
|
|
|
@@ -5479,8 +5479,19 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
5479
5479
|
return index;
|
|
5480
5480
|
}
|
|
5481
5481
|
function buildSigIndex(cwd, opts) {
|
|
5482
|
-
const path = require('path');
|
|
5482
|
+
const fs = require('fs'); const path = require('path');
|
|
5483
5483
|
if (opts && opts.contextPath) return _parseContextFile(opts.contextPath);
|
|
5484
|
+
// Check gen-context.config.json for a persisted customOutput path.
|
|
5485
|
+
try {
|
|
5486
|
+
const cfgPath = path.join(cwd, 'gen-context.config.json');
|
|
5487
|
+
if (fs.existsSync(cfgPath)) {
|
|
5488
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
5489
|
+
if (cfg.customOutput) {
|
|
5490
|
+
const idx = _parseContextFile(path.resolve(cwd, cfg.customOutput));
|
|
5491
|
+
if (idx.size > 0) return idx;
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
} catch (_) {}
|
|
5484
5495
|
for (const parts of ADAPTER_OUTPUT_PATHS) {
|
|
5485
5496
|
const contextPath = path.join(cwd, ...parts);
|
|
5486
5497
|
const index = _parseContextFile(contextPath);
|
|
@@ -5504,7 +5515,20 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
5504
5515
|
function formatRankJSON(results, query) {
|
|
5505
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 };
|
|
5506
5517
|
}
|
|
5507
|
-
|
|
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 };
|
|
5508
5532
|
};
|
|
5509
5533
|
|
|
5510
5534
|
// ── ./src/eval/scorer ──
|
|
@@ -6238,7 +6262,7 @@ const path = require('path');
|
|
|
6238
6262
|
const os = require('os');
|
|
6239
6263
|
const { execSync } = require('child_process');
|
|
6240
6264
|
|
|
6241
|
-
const VERSION = '4.
|
|
6265
|
+
const VERSION = '4.2.0';
|
|
6242
6266
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
6243
6267
|
|
|
6244
6268
|
function requireSourceOrBundled(key) {
|
|
@@ -6926,6 +6950,19 @@ function writeOutputs(content, targets, cwd, config) {
|
|
|
6926
6950
|
if (ADAPTER_TARGETS.has(target)) {
|
|
6927
6951
|
try {
|
|
6928
6952
|
const adapterMod = __require('./packages/adapters/' + target);
|
|
6953
|
+
// copilot: honour config.output custom path (redirects away from default .github/copilot-instructions.md)
|
|
6954
|
+
if (target === 'copilot') {
|
|
6955
|
+
const outPath = resolveAdapterPath('copilot', cwd, config);
|
|
6956
|
+
const defaultPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
6957
|
+
if (outPath !== defaultPath) {
|
|
6958
|
+
// custom path: format and write directly (no append logic)
|
|
6959
|
+
const formatted = adapterMod.format(content, { version: VERSION });
|
|
6960
|
+
ensureDir(outPath);
|
|
6961
|
+
fs.writeFileSync(outPath, formatted, 'utf8');
|
|
6962
|
+
console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
|
|
6963
|
+
continue;
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6929
6966
|
if (typeof adapterMod.write === 'function') {
|
|
6930
6967
|
adapterMod.write(content, cwd, { version: VERSION });
|
|
6931
6968
|
const outPath = adapterMod.outputPath(cwd);
|
|
@@ -7507,6 +7544,25 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7507
7544
|
writeOutputs(content, config.outputs, cwd, config);
|
|
7508
7545
|
}
|
|
7509
7546
|
if (formatValue === 'cache') writeCacheOutput(content, cwd);
|
|
7547
|
+
|
|
7548
|
+
// --output <file>: write a copy of the formatted context to the custom path.
|
|
7549
|
+
if (config.customOutput) {
|
|
7550
|
+
try {
|
|
7551
|
+
const absCustom = path.resolve(cwd, config.customOutput);
|
|
7552
|
+
const SIGMAP_HEADER = [
|
|
7553
|
+
`<!-- Generated by SigMap v${VERSION} -->`,
|
|
7554
|
+
`<!-- Updated: ${new Date().toISOString()} -->`,
|
|
7555
|
+
`<!-- Regenerate: node gen-context.js --output ${config.customOutput} -->`,
|
|
7556
|
+
'',
|
|
7557
|
+
].join('\n');
|
|
7558
|
+
fs.mkdirSync(path.dirname(absCustom), { recursive: true });
|
|
7559
|
+
fs.writeFileSync(absCustom, SIGMAP_HEADER + content, 'utf8');
|
|
7560
|
+
console.warn(`[sigmap] wrote ${path.relative(cwd, absCustom)} (custom output)`);
|
|
7561
|
+
} catch (err) {
|
|
7562
|
+
console.warn(`[sigmap] --output write failed: ${err.message}`);
|
|
7563
|
+
}
|
|
7564
|
+
}
|
|
7565
|
+
|
|
7510
7566
|
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
|
|
7511
7567
|
}
|
|
7512
7568
|
} else {
|
|
@@ -7896,6 +7952,58 @@ function registerMcp(cwd, scriptPath) {
|
|
|
7896
7952
|
console.warn(JSON.stringify({ mcpServers: { 'sigmap': serverEntry } }, null, 2));
|
|
7897
7953
|
}
|
|
7898
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
|
+
|
|
7899
8007
|
function main() {
|
|
7900
8008
|
const args = process.argv.slice(2);
|
|
7901
8009
|
|
|
@@ -7931,6 +8039,39 @@ function main() {
|
|
|
7931
8039
|
|
|
7932
8040
|
const config = loadConfig(cwd);
|
|
7933
8041
|
|
|
8042
|
+
// ── --output <file> — parse early so every subsequent block can use it ─────
|
|
8043
|
+
// Resolves the custom output path and merges it into config.customOutput.
|
|
8044
|
+
// Also persists the resolved relative path to gen-context.config.json so
|
|
8045
|
+
// future --query calls (without --output) find the file automatically.
|
|
8046
|
+
(function resolveOutputFlag() {
|
|
8047
|
+
const outIdx = args.indexOf('--output');
|
|
8048
|
+
if (outIdx < 0) return;
|
|
8049
|
+
const raw = (args[outIdx + 1] || '').trim();
|
|
8050
|
+
if (!raw || raw.startsWith('--')) {
|
|
8051
|
+
console.error('[sigmap] --output requires a file path');
|
|
8052
|
+
console.error(' Example: node gen-context.js --output .context/ai-context.md');
|
|
8053
|
+
process.exit(1);
|
|
8054
|
+
}
|
|
8055
|
+
const abs = path.resolve(cwd, raw);
|
|
8056
|
+
const rel = path.relative(cwd, abs);
|
|
8057
|
+
config.customOutput = rel; // consumed by runGenerate
|
|
8058
|
+
|
|
8059
|
+
// Persist to gen-context.config.json for future --query calls
|
|
8060
|
+
const cfgPath = path.join(cwd, 'gen-context.config.json');
|
|
8061
|
+
try {
|
|
8062
|
+
let savedCfg = {};
|
|
8063
|
+
if (fs.existsSync(cfgPath)) {
|
|
8064
|
+
try { savedCfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); } catch (_) {}
|
|
8065
|
+
}
|
|
8066
|
+
if (savedCfg.customOutput !== rel) {
|
|
8067
|
+
savedCfg.customOutput = rel;
|
|
8068
|
+
fs.writeFileSync(cfgPath, JSON.stringify(savedCfg, null, 2) + '\n', 'utf8');
|
|
8069
|
+
}
|
|
8070
|
+
} catch (err) {
|
|
8071
|
+
console.warn(`[sigmap] could not persist customOutput to config: ${err.message}`);
|
|
8072
|
+
}
|
|
8073
|
+
})();
|
|
8074
|
+
|
|
7934
8075
|
// Feature 2: `--mode fast|full|both`
|
|
7935
8076
|
const modeIdx = args.indexOf('--mode');
|
|
7936
8077
|
const mode = modeIdx !== -1
|
|
@@ -7942,6 +8083,170 @@ function main() {
|
|
|
7942
8083
|
process.exit(1);
|
|
7943
8084
|
}
|
|
7944
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
|
+
|
|
7945
8250
|
// Feature 6: `sigmap sync` — write all outputs + llms.txt + print compact diff
|
|
7946
8251
|
if (args[0] === 'sync') {
|
|
7947
8252
|
try {
|
|
@@ -8355,19 +8660,28 @@ function main() {
|
|
|
8355
8660
|
}
|
|
8356
8661
|
const { rank, buildSigIndex, formatRankTable, formatRankJSON } = requireSourceOrBundled('./src/retrieval/ranker');
|
|
8357
8662
|
|
|
8358
|
-
// Resolve
|
|
8359
|
-
//
|
|
8360
|
-
//
|
|
8663
|
+
// Resolve the context file path to query against.
|
|
8664
|
+
// Priority: --output flag > --adapter flag > buildSigIndex probe order
|
|
8665
|
+
// (customOutput from config is handled inside buildSigIndex itself)
|
|
8361
8666
|
let queryOpts;
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8667
|
+
|
|
8668
|
+
// 1. --output <file> pins to an explicit path
|
|
8669
|
+
if (config.customOutput) {
|
|
8670
|
+
queryOpts = { contextPath: path.resolve(cwd, config.customOutput) };
|
|
8671
|
+
}
|
|
8672
|
+
|
|
8673
|
+
// 2. --adapter <name> pins to that adapter's output path (if --output not given)
|
|
8674
|
+
if (!queryOpts) {
|
|
8675
|
+
const adpIdx = args.indexOf('--adapter');
|
|
8676
|
+
if (adpIdx >= 0) {
|
|
8677
|
+
const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
|
|
8678
|
+
const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
|
|
8679
|
+
if (VALID_ADAPTERS.includes(adapterName)) {
|
|
8680
|
+
try {
|
|
8681
|
+
const adapterMod = __require('./packages/adapters/' + adapterName);
|
|
8682
|
+
queryOpts = { contextPath: adapterMod.outputPath(cwd) };
|
|
8683
|
+
} catch (_) {}
|
|
8684
|
+
}
|
|
8371
8685
|
}
|
|
8372
8686
|
}
|
|
8373
8687
|
|
|
@@ -8384,7 +8698,13 @@ function main() {
|
|
|
8384
8698
|
: ((config && config.retrieval && config.retrieval.topK) || 10);
|
|
8385
8699
|
const recencyBoost = (config && config.retrieval && config.retrieval.recencyBoost) || 1.5;
|
|
8386
8700
|
const results = rank(query, index, { topK, recencyBoost });
|
|
8387
|
-
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')) {
|
|
8388
8708
|
process.stdout.write(JSON.stringify(formatRankJSON(results, query)) + '\n');
|
|
8389
8709
|
} else {
|
|
8390
8710
|
process.stdout.write(formatRankTable(results, query));
|
|
@@ -8554,6 +8874,44 @@ function main() {
|
|
|
8554
8874
|
process.exit(0);
|
|
8555
8875
|
}
|
|
8556
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
|
+
|
|
8557
8915
|
// Default: generate once
|
|
8558
8916
|
runGenerate(cwd, config, false);
|
|
8559
8917
|
}
|
package/package.json
CHANGED
package/src/mcp/server.js
CHANGED
package/src/retrieval/ranker.js
CHANGED
|
@@ -206,26 +206,39 @@ function _parseContextFile(contextPath) {
|
|
|
206
206
|
* Returns Map<filePath, string[]> where filePath is the relative path
|
|
207
207
|
* as it appears in the ### headers of the context file.
|
|
208
208
|
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* first file that produces a non-empty index is returned.
|
|
209
|
+
* Resolution priority:
|
|
210
|
+
* 1. `opts.contextPath` — explicit path from --output or --adapter flag
|
|
211
|
+
* 2. `customOutput` key in gen-context.config.json — persisted from a
|
|
212
|
+
* previous `--output <file>` generation run
|
|
213
|
+
* 3. All known adapter output paths probed in order (first non-empty wins)
|
|
215
214
|
*
|
|
216
215
|
* @param {string} cwd
|
|
217
216
|
* @param {{ contextPath?: string }} [opts]
|
|
218
217
|
* @returns {Map<string, string[]>}
|
|
219
218
|
*/
|
|
220
219
|
function buildSigIndex(cwd, opts) {
|
|
220
|
+
const fs = require('fs');
|
|
221
221
|
const path = require('path');
|
|
222
222
|
|
|
223
|
-
// Caller supplied an explicit path — use it directly.
|
|
223
|
+
// 1. Caller supplied an explicit path — use it directly.
|
|
224
224
|
if (opts && opts.contextPath) {
|
|
225
225
|
return _parseContextFile(opts.contextPath);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
//
|
|
228
|
+
// 2. Check gen-context.config.json for a persisted customOutput path.
|
|
229
|
+
try {
|
|
230
|
+
const cfgPath = path.join(cwd, 'gen-context.config.json');
|
|
231
|
+
if (fs.existsSync(cfgPath)) {
|
|
232
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
233
|
+
if (cfg.customOutput) {
|
|
234
|
+
const customPath = path.resolve(cwd, cfg.customOutput);
|
|
235
|
+
const index = _parseContextFile(customPath);
|
|
236
|
+
if (index.size > 0) return index;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch (_) {}
|
|
240
|
+
|
|
241
|
+
// 3. Probe all known adapter output paths; return first non-empty index.
|
|
229
242
|
for (const parts of ADAPTER_OUTPUT_PATHS) {
|
|
230
243
|
const contextPath = path.join(cwd, ...parts);
|
|
231
244
|
const index = _parseContextFile(contextPath);
|
|
@@ -294,4 +307,22 @@ function formatRankJSON(results, query) {
|
|
|
294
307
|
};
|
|
295
308
|
}
|
|
296
309
|
|
|
297
|
-
|
|
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 };
|