sigmap 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/README.md +24 -0
- package/gen-context.js +140 -7
- package/package.json +1 -1
- package/src/retrieval/ranker.js +78 -10
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,91 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [4.1.2] — 2026-04-16 — Feat: --output <file> flag for custom context path
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`--output <file>` flag** — write signatures to any custom path, not just
|
|
18
|
+
an adapter's fixed location:
|
|
19
|
+
```bash
|
|
20
|
+
sigmap --output .context/ai-context.md # default generation
|
|
21
|
+
sigmap --adapter claude --output shared/sigs.md # adapter + custom path
|
|
22
|
+
```
|
|
23
|
+
The custom file is written **in addition to** the adapter's default output so
|
|
24
|
+
existing tooling is unaffected.
|
|
25
|
+
|
|
26
|
+
- **Automatic discovery for `--query`** — the resolved path is persisted to
|
|
27
|
+
`gen-context.config.json` as `customOutput` so subsequent `--query` runs
|
|
28
|
+
find it automatically without needing to pass `--output` again:
|
|
29
|
+
```bash
|
|
30
|
+
sigmap --output .context/ai-context.md # generates + persists path
|
|
31
|
+
sigmap --query "add a new extractor" # auto-finds .context/ai-context.md
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- **Priority order for `--query` context resolution** (most specific first):
|
|
35
|
+
1. `--output <file>` flag — explicit path
|
|
36
|
+
2. `--adapter <name>` flag — adapter's fixed output path
|
|
37
|
+
3. `customOutput` in `gen-context.config.json` — persisted from last `--output` run
|
|
38
|
+
4. Probe all known adapter output paths — existing fallback behaviour
|
|
39
|
+
|
|
40
|
+
- **Nested directories created automatically** — `--output a/b/c/file.md`
|
|
41
|
+
creates any missing parent directories.
|
|
42
|
+
|
|
43
|
+
### Tests
|
|
44
|
+
|
|
45
|
+
- Added `test/integration/output-flag.test.js` (13 tests) covering: custom
|
|
46
|
+
file creation, parseable headers, config persistence, nested dirs, missing
|
|
47
|
+
arg error, `--adapter` + `--output` combo, explicit `--query` with `--output`,
|
|
48
|
+
auto-discovery via persisted config, missing-file error, `--output` overrides
|
|
49
|
+
`--adapter` during `--query`.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## [4.1.1] — 2026-04-16 — Fix: --query works with any adapter output
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- **`--query` fails after `--adapter` generation** (`[sigmap] no context file found`):
|
|
58
|
+
`buildSigIndex` hardcoded `.github/copilot-instructions.md` as the only
|
|
59
|
+
context file path, so `--query` always failed when any adapter other than
|
|
60
|
+
`copilot` wrote to a different location (`CLAUDE.md`, `AGENTS.md`,
|
|
61
|
+
`.cursorrules`, `.windsurfrules`, etc.).
|
|
62
|
+
|
|
63
|
+
`buildSigIndex` now probes all nine known adapter output paths in priority
|
|
64
|
+
order and returns the first non-empty index:
|
|
65
|
+
```
|
|
66
|
+
copilot → claude → codex → cursor → windsurf → openai → gemini → llm-full → llm
|
|
67
|
+
```
|
|
68
|
+
Human-written preamble before the `## Auto-generated signatures` marker
|
|
69
|
+
(e.g. custom content in `CLAUDE.md`) is skipped so those `###` sections
|
|
70
|
+
don't pollute the signature index.
|
|
71
|
+
|
|
72
|
+
- **`--adapter <name> --query "..."` combination ignored the adapter flag**:
|
|
73
|
+
The `--query` handler now detects a co-present `--adapter` flag, resolves
|
|
74
|
+
that adapter's output path, and reads from it directly — so both forms work:
|
|
75
|
+
```bash
|
|
76
|
+
# generate with claude adapter, then query without re-specifying adapter
|
|
77
|
+
node gen-context.js --adapter claude
|
|
78
|
+
node gen-context.js --query "add a new extractor"
|
|
79
|
+
|
|
80
|
+
# or pin explicitly in one command
|
|
81
|
+
node gen-context.js --adapter claude --query "add a new extractor"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- **`--analyze --json` output truncated at ~8 KB on macOS**:
|
|
85
|
+
Calling `process.exit(0)` immediately after `process.stdout.write(largeJson)`
|
|
86
|
+
truncated output because the underlying pipe write is asynchronous even
|
|
87
|
+
when `write()` returns `true`. Fixed by using the write callback so the
|
|
88
|
+
process exits only after the OS has accepted all bytes.
|
|
89
|
+
|
|
90
|
+
### Tests
|
|
91
|
+
|
|
92
|
+
- Added `test/integration/query-adapter.test.js` (17 tests) covering every
|
|
93
|
+
adapter output path (unit + CLI), probe order, marker-skipping, explicit
|
|
94
|
+
`opts.contextPath` override, and empty-project fallback.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
13
98
|
## [4.1.0] — 2026-04-15 — Smart Budget: auto-scaling token budget
|
|
14
99
|
|
|
15
100
|
### Added
|
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
|
@@ -5449,12 +5449,24 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
5449
5449
|
scored.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
|
|
5450
5450
|
return scored.slice(0, topK);
|
|
5451
5451
|
}
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5452
|
+
const ADAPTER_OUTPUT_PATHS = [
|
|
5453
|
+
['.github', 'copilot-instructions.md'],
|
|
5454
|
+
['CLAUDE.md'],
|
|
5455
|
+
['AGENTS.md'],
|
|
5456
|
+
['.cursorrules'],
|
|
5457
|
+
['.windsurfrules'],
|
|
5458
|
+
['.github', 'openai-context.md'],
|
|
5459
|
+
['.github', 'gemini-context.md'],
|
|
5460
|
+
['llm-full.txt'],
|
|
5461
|
+
['llm.txt'],
|
|
5462
|
+
];
|
|
5463
|
+
function _parseContextFile(contextPath) {
|
|
5464
|
+
const fs = require('fs');
|
|
5455
5465
|
const index = new Map();
|
|
5456
5466
|
if (!fs.existsSync(contextPath)) return index;
|
|
5457
|
-
|
|
5467
|
+
let content = fs.readFileSync(contextPath, 'utf8');
|
|
5468
|
+
const markerIdx = content.indexOf('## Auto-generated signatures');
|
|
5469
|
+
if (markerIdx !== -1) content = content.slice(markerIdx);
|
|
5458
5470
|
const lines = content.split('\n');
|
|
5459
5471
|
let currentFile = null; let inBlock = false; let sigs = [];
|
|
5460
5472
|
for (const line of lines) {
|
|
@@ -5466,6 +5478,27 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
|
|
|
5466
5478
|
if (currentFile !== null) index.set(currentFile, sigs);
|
|
5467
5479
|
return index;
|
|
5468
5480
|
}
|
|
5481
|
+
function buildSigIndex(cwd, opts) {
|
|
5482
|
+
const fs = require('fs'); const path = require('path');
|
|
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 (_) {}
|
|
5495
|
+
for (const parts of ADAPTER_OUTPUT_PATHS) {
|
|
5496
|
+
const contextPath = path.join(cwd, ...parts);
|
|
5497
|
+
const index = _parseContextFile(contextPath);
|
|
5498
|
+
if (index.size > 0) return index;
|
|
5499
|
+
}
|
|
5500
|
+
return new Map();
|
|
5501
|
+
}
|
|
5469
5502
|
function formatRankTable(results, query) {
|
|
5470
5503
|
if (!results || results.length === 0) return `No matching files found for query: "${query}"\n`;
|
|
5471
5504
|
const lines = [`## Query: ${query}`, '', '| Rank | File | Score | Sigs | Tokens |', '|------|------|-------|------|--------|',
|
|
@@ -6216,7 +6249,7 @@ const path = require('path');
|
|
|
6216
6249
|
const os = require('os');
|
|
6217
6250
|
const { execSync } = require('child_process');
|
|
6218
6251
|
|
|
6219
|
-
const VERSION = '4.1.
|
|
6252
|
+
const VERSION = '4.1.2';
|
|
6220
6253
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
6221
6254
|
|
|
6222
6255
|
function requireSourceOrBundled(key) {
|
|
@@ -6904,6 +6937,19 @@ function writeOutputs(content, targets, cwd, config) {
|
|
|
6904
6937
|
if (ADAPTER_TARGETS.has(target)) {
|
|
6905
6938
|
try {
|
|
6906
6939
|
const adapterMod = __require('./packages/adapters/' + target);
|
|
6940
|
+
// copilot: honour config.output custom path (redirects away from default .github/copilot-instructions.md)
|
|
6941
|
+
if (target === 'copilot') {
|
|
6942
|
+
const outPath = resolveAdapterPath('copilot', cwd, config);
|
|
6943
|
+
const defaultPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
6944
|
+
if (outPath !== defaultPath) {
|
|
6945
|
+
// custom path: format and write directly (no append logic)
|
|
6946
|
+
const formatted = adapterMod.format(content, { version: VERSION });
|
|
6947
|
+
ensureDir(outPath);
|
|
6948
|
+
fs.writeFileSync(outPath, formatted, 'utf8');
|
|
6949
|
+
console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
|
|
6950
|
+
continue;
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6907
6953
|
if (typeof adapterMod.write === 'function') {
|
|
6908
6954
|
adapterMod.write(content, cwd, { version: VERSION });
|
|
6909
6955
|
const outPath = adapterMod.outputPath(cwd);
|
|
@@ -7485,6 +7531,25 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7485
7531
|
writeOutputs(content, config.outputs, cwd, config);
|
|
7486
7532
|
}
|
|
7487
7533
|
if (formatValue === 'cache') writeCacheOutput(content, cwd);
|
|
7534
|
+
|
|
7535
|
+
// --output <file>: write a copy of the formatted context to the custom path.
|
|
7536
|
+
if (config.customOutput) {
|
|
7537
|
+
try {
|
|
7538
|
+
const absCustom = path.resolve(cwd, config.customOutput);
|
|
7539
|
+
const SIGMAP_HEADER = [
|
|
7540
|
+
`<!-- Generated by SigMap v${VERSION} -->`,
|
|
7541
|
+
`<!-- Updated: ${new Date().toISOString()} -->`,
|
|
7542
|
+
`<!-- Regenerate: node gen-context.js --output ${config.customOutput} -->`,
|
|
7543
|
+
'',
|
|
7544
|
+
].join('\n');
|
|
7545
|
+
fs.mkdirSync(path.dirname(absCustom), { recursive: true });
|
|
7546
|
+
fs.writeFileSync(absCustom, SIGMAP_HEADER + content, 'utf8');
|
|
7547
|
+
console.warn(`[sigmap] wrote ${path.relative(cwd, absCustom)} (custom output)`);
|
|
7548
|
+
} catch (err) {
|
|
7549
|
+
console.warn(`[sigmap] --output write failed: ${err.message}`);
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7488
7553
|
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
|
|
7489
7554
|
}
|
|
7490
7555
|
} else {
|
|
@@ -7909,6 +7974,39 @@ function main() {
|
|
|
7909
7974
|
|
|
7910
7975
|
const config = loadConfig(cwd);
|
|
7911
7976
|
|
|
7977
|
+
// ── --output <file> — parse early so every subsequent block can use it ─────
|
|
7978
|
+
// Resolves the custom output path and merges it into config.customOutput.
|
|
7979
|
+
// Also persists the resolved relative path to gen-context.config.json so
|
|
7980
|
+
// future --query calls (without --output) find the file automatically.
|
|
7981
|
+
(function resolveOutputFlag() {
|
|
7982
|
+
const outIdx = args.indexOf('--output');
|
|
7983
|
+
if (outIdx < 0) return;
|
|
7984
|
+
const raw = (args[outIdx + 1] || '').trim();
|
|
7985
|
+
if (!raw || raw.startsWith('--')) {
|
|
7986
|
+
console.error('[sigmap] --output requires a file path');
|
|
7987
|
+
console.error(' Example: node gen-context.js --output .context/ai-context.md');
|
|
7988
|
+
process.exit(1);
|
|
7989
|
+
}
|
|
7990
|
+
const abs = path.resolve(cwd, raw);
|
|
7991
|
+
const rel = path.relative(cwd, abs);
|
|
7992
|
+
config.customOutput = rel; // consumed by runGenerate
|
|
7993
|
+
|
|
7994
|
+
// Persist to gen-context.config.json for future --query calls
|
|
7995
|
+
const cfgPath = path.join(cwd, 'gen-context.config.json');
|
|
7996
|
+
try {
|
|
7997
|
+
let savedCfg = {};
|
|
7998
|
+
if (fs.existsSync(cfgPath)) {
|
|
7999
|
+
try { savedCfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); } catch (_) {}
|
|
8000
|
+
}
|
|
8001
|
+
if (savedCfg.customOutput !== rel) {
|
|
8002
|
+
savedCfg.customOutput = rel;
|
|
8003
|
+
fs.writeFileSync(cfgPath, JSON.stringify(savedCfg, null, 2) + '\n', 'utf8');
|
|
8004
|
+
}
|
|
8005
|
+
} catch (err) {
|
|
8006
|
+
console.warn(`[sigmap] could not persist customOutput to config: ${err.message}`);
|
|
8007
|
+
}
|
|
8008
|
+
})();
|
|
8009
|
+
|
|
7912
8010
|
// Feature 2: `--mode fast|full|both`
|
|
7913
8011
|
const modeIdx = args.indexOf('--mode');
|
|
7914
8012
|
const mode = modeIdx !== -1
|
|
@@ -8220,7 +8318,13 @@ function main() {
|
|
|
8220
8318
|
const stats = analyzeFiles(allFiles, cwd, { slow, maxSigs: cfg.maxSigsPerFile || 25 });
|
|
8221
8319
|
|
|
8222
8320
|
if (args.includes('--json')) {
|
|
8223
|
-
|
|
8321
|
+
const out = JSON.stringify(formatAnalysisJSON(stats)) + '\n';
|
|
8322
|
+
// Use the write callback to exit only after the OS has accepted all
|
|
8323
|
+
// bytes. Calling process.exit(0) synchronously after write() truncates
|
|
8324
|
+
// large outputs because the underlying pipe write is asynchronous even
|
|
8325
|
+
// when write() returns true.
|
|
8326
|
+
process.stdout.write(out, 'utf8', () => process.exit(0));
|
|
8327
|
+
return; // exit is handled by the callback above
|
|
8224
8328
|
} else {
|
|
8225
8329
|
const table = formatAnalysisTable(stats, slow);
|
|
8226
8330
|
process.stdout.write(table);
|
|
@@ -8326,9 +8430,38 @@ function main() {
|
|
|
8326
8430
|
process.exit(1);
|
|
8327
8431
|
}
|
|
8328
8432
|
const { rank, buildSigIndex, formatRankTable, formatRankJSON } = requireSourceOrBundled('./src/retrieval/ranker');
|
|
8329
|
-
|
|
8433
|
+
|
|
8434
|
+
// Resolve the context file path to query against.
|
|
8435
|
+
// Priority: --output flag > --adapter flag > buildSigIndex probe order
|
|
8436
|
+
// (customOutput from config is handled inside buildSigIndex itself)
|
|
8437
|
+
let queryOpts;
|
|
8438
|
+
|
|
8439
|
+
// 1. --output <file> pins to an explicit path
|
|
8440
|
+
if (config.customOutput) {
|
|
8441
|
+
queryOpts = { contextPath: path.resolve(cwd, config.customOutput) };
|
|
8442
|
+
}
|
|
8443
|
+
|
|
8444
|
+
// 2. --adapter <name> pins to that adapter's output path (if --output not given)
|
|
8445
|
+
if (!queryOpts) {
|
|
8446
|
+
const adpIdx = args.indexOf('--adapter');
|
|
8447
|
+
if (adpIdx >= 0) {
|
|
8448
|
+
const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
|
|
8449
|
+
const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
|
|
8450
|
+
if (VALID_ADAPTERS.includes(adapterName)) {
|
|
8451
|
+
try {
|
|
8452
|
+
const adapterMod = __require('./packages/adapters/' + adapterName);
|
|
8453
|
+
queryOpts = { contextPath: adapterMod.outputPath(cwd) };
|
|
8454
|
+
} catch (_) {}
|
|
8455
|
+
}
|
|
8456
|
+
}
|
|
8457
|
+
}
|
|
8458
|
+
|
|
8459
|
+
const index = buildSigIndex(cwd, queryOpts);
|
|
8330
8460
|
if (index.size === 0) {
|
|
8331
8461
|
console.error('[sigmap] no context file found. Run: node gen-context.js');
|
|
8462
|
+
if (adpIdx >= 0) {
|
|
8463
|
+
console.error(' (tried the path for --adapter ' + (args[adpIdx + 1] || '') + ')');
|
|
8464
|
+
}
|
|
8332
8465
|
process.exit(1);
|
|
8333
8466
|
}
|
|
8334
8467
|
const topIdx = args.indexOf('--top');
|
package/package.json
CHANGED
package/src/retrieval/ranker.js
CHANGED
|
@@ -141,24 +141,45 @@ function rank(query, sigIndex, opts) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
144
|
+
* All paths where sigmap adapters write their context files, in probe order.
|
|
145
|
+
* The first existing file with a non-empty index wins when no explicit path
|
|
146
|
+
* is supplied.
|
|
147
|
+
*/
|
|
148
|
+
const ADAPTER_OUTPUT_PATHS = [
|
|
149
|
+
['.github', 'copilot-instructions.md'], // copilot (default)
|
|
150
|
+
['CLAUDE.md'], // claude
|
|
151
|
+
['AGENTS.md'], // codex
|
|
152
|
+
['.cursorrules'], // cursor
|
|
153
|
+
['.windsurfrules'], // windsurf
|
|
154
|
+
['.github', 'openai-context.md'], // openai
|
|
155
|
+
['.github', 'gemini-context.md'], // gemini
|
|
156
|
+
['llm-full.txt'], // llm-full
|
|
157
|
+
['llm.txt'], // llm
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Parse a single context file into a Map<filePath, string[]>.
|
|
147
162
|
*
|
|
148
|
-
*
|
|
163
|
+
* Files that contain human-written content before an
|
|
164
|
+
* "## Auto-generated signatures" marker (e.g. CLAUDE.md) are handled
|
|
165
|
+
* by skipping everything above the marker before scanning for ### headers.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} contextPath - absolute path to the context file
|
|
149
168
|
* @returns {Map<string, string[]>}
|
|
150
169
|
*/
|
|
151
|
-
function
|
|
152
|
-
const fs
|
|
153
|
-
const path = require('path');
|
|
154
|
-
const contextPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
170
|
+
function _parseContextFile(contextPath) {
|
|
171
|
+
const fs = require('fs');
|
|
155
172
|
const index = new Map();
|
|
156
173
|
|
|
157
174
|
if (!fs.existsSync(contextPath)) return index;
|
|
158
175
|
|
|
159
|
-
|
|
160
|
-
const lines = content.split('\n');
|
|
176
|
+
let content = fs.readFileSync(contextPath, 'utf8');
|
|
161
177
|
|
|
178
|
+
// Skip any human-written preamble that sits above the auto-generated block.
|
|
179
|
+
const markerIdx = content.indexOf('## Auto-generated signatures');
|
|
180
|
+
if (markerIdx !== -1) content = content.slice(markerIdx);
|
|
181
|
+
|
|
182
|
+
const lines = content.split('\n');
|
|
162
183
|
let currentFile = null;
|
|
163
184
|
let inBlock = false;
|
|
164
185
|
let sigs = [];
|
|
@@ -180,6 +201,53 @@ function buildSigIndex(cwd) {
|
|
|
180
201
|
return index;
|
|
181
202
|
}
|
|
182
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Build a signature index from the generated context file.
|
|
206
|
+
* Returns Map<filePath, string[]> where filePath is the relative path
|
|
207
|
+
* as it appears in the ### headers of the context file.
|
|
208
|
+
*
|
|
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)
|
|
214
|
+
*
|
|
215
|
+
* @param {string} cwd
|
|
216
|
+
* @param {{ contextPath?: string }} [opts]
|
|
217
|
+
* @returns {Map<string, string[]>}
|
|
218
|
+
*/
|
|
219
|
+
function buildSigIndex(cwd, opts) {
|
|
220
|
+
const fs = require('fs');
|
|
221
|
+
const path = require('path');
|
|
222
|
+
|
|
223
|
+
// 1. Caller supplied an explicit path — use it directly.
|
|
224
|
+
if (opts && opts.contextPath) {
|
|
225
|
+
return _parseContextFile(opts.contextPath);
|
|
226
|
+
}
|
|
227
|
+
|
|
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.
|
|
242
|
+
for (const parts of ADAPTER_OUTPUT_PATHS) {
|
|
243
|
+
const contextPath = path.join(cwd, ...parts);
|
|
244
|
+
const index = _parseContextFile(contextPath);
|
|
245
|
+
if (index.size > 0) return index;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return new Map();
|
|
249
|
+
}
|
|
250
|
+
|
|
183
251
|
/**
|
|
184
252
|
* Format ranked results as a markdown table string.
|
|
185
253
|
*
|