sigmap 4.1.0 → 4.1.1
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 +45 -0
- package/gen-context.js +54 -6
- package/package.json +1 -1
- package/src/retrieval/ranker.js +65 -10
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,51 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [4.1.1] — 2026-04-16 — Fix: --query works with any adapter output
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`--query` fails after `--adapter` generation** (`[sigmap] no context file found`):
|
|
18
|
+
`buildSigIndex` hardcoded `.github/copilot-instructions.md` as the only
|
|
19
|
+
context file path, so `--query` always failed when any adapter other than
|
|
20
|
+
`copilot` wrote to a different location (`CLAUDE.md`, `AGENTS.md`,
|
|
21
|
+
`.cursorrules`, `.windsurfrules`, etc.).
|
|
22
|
+
|
|
23
|
+
`buildSigIndex` now probes all nine known adapter output paths in priority
|
|
24
|
+
order and returns the first non-empty index:
|
|
25
|
+
```
|
|
26
|
+
copilot → claude → codex → cursor → windsurf → openai → gemini → llm-full → llm
|
|
27
|
+
```
|
|
28
|
+
Human-written preamble before the `## Auto-generated signatures` marker
|
|
29
|
+
(e.g. custom content in `CLAUDE.md`) is skipped so those `###` sections
|
|
30
|
+
don't pollute the signature index.
|
|
31
|
+
|
|
32
|
+
- **`--adapter <name> --query "..."` combination ignored the adapter flag**:
|
|
33
|
+
The `--query` handler now detects a co-present `--adapter` flag, resolves
|
|
34
|
+
that adapter's output path, and reads from it directly — so both forms work:
|
|
35
|
+
```bash
|
|
36
|
+
# generate with claude adapter, then query without re-specifying adapter
|
|
37
|
+
node gen-context.js --adapter claude
|
|
38
|
+
node gen-context.js --query "add a new extractor"
|
|
39
|
+
|
|
40
|
+
# or pin explicitly in one command
|
|
41
|
+
node gen-context.js --adapter claude --query "add a new extractor"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **`--analyze --json` output truncated at ~8 KB on macOS**:
|
|
45
|
+
Calling `process.exit(0)` immediately after `process.stdout.write(largeJson)`
|
|
46
|
+
truncated output because the underlying pipe write is asynchronous even
|
|
47
|
+
when `write()` returns `true`. Fixed by using the write callback so the
|
|
48
|
+
process exits only after the OS has accepted all bytes.
|
|
49
|
+
|
|
50
|
+
### Tests
|
|
51
|
+
|
|
52
|
+
- Added `test/integration/query-adapter.test.js` (17 tests) covering every
|
|
53
|
+
adapter output path (unit + CLI), probe order, marker-skipping, explicit
|
|
54
|
+
`opts.contextPath` override, and empty-project fallback.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
13
58
|
## [4.1.0] — 2026-04-15 — Smart Budget: auto-scaling token budget
|
|
14
59
|
|
|
15
60
|
### Added
|
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,16 @@ __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 path = require('path');
|
|
5483
|
+
if (opts && opts.contextPath) return _parseContextFile(opts.contextPath);
|
|
5484
|
+
for (const parts of ADAPTER_OUTPUT_PATHS) {
|
|
5485
|
+
const contextPath = path.join(cwd, ...parts);
|
|
5486
|
+
const index = _parseContextFile(contextPath);
|
|
5487
|
+
if (index.size > 0) return index;
|
|
5488
|
+
}
|
|
5489
|
+
return new Map();
|
|
5490
|
+
}
|
|
5469
5491
|
function formatRankTable(results, query) {
|
|
5470
5492
|
if (!results || results.length === 0) return `No matching files found for query: "${query}"\n`;
|
|
5471
5493
|
const lines = [`## Query: ${query}`, '', '| Rank | File | Score | Sigs | Tokens |', '|------|------|-------|------|--------|',
|
|
@@ -8220,7 +8242,13 @@ function main() {
|
|
|
8220
8242
|
const stats = analyzeFiles(allFiles, cwd, { slow, maxSigs: cfg.maxSigsPerFile || 25 });
|
|
8221
8243
|
|
|
8222
8244
|
if (args.includes('--json')) {
|
|
8223
|
-
|
|
8245
|
+
const out = JSON.stringify(formatAnalysisJSON(stats)) + '\n';
|
|
8246
|
+
// Use the write callback to exit only after the OS has accepted all
|
|
8247
|
+
// bytes. Calling process.exit(0) synchronously after write() truncates
|
|
8248
|
+
// large outputs because the underlying pipe write is asynchronous even
|
|
8249
|
+
// when write() returns true.
|
|
8250
|
+
process.stdout.write(out, 'utf8', () => process.exit(0));
|
|
8251
|
+
return; // exit is handled by the callback above
|
|
8224
8252
|
} else {
|
|
8225
8253
|
const table = formatAnalysisTable(stats, slow);
|
|
8226
8254
|
process.stdout.write(table);
|
|
@@ -8326,9 +8354,29 @@ function main() {
|
|
|
8326
8354
|
process.exit(1);
|
|
8327
8355
|
}
|
|
8328
8356
|
const { rank, buildSigIndex, formatRankTable, formatRankJSON } = requireSourceOrBundled('./src/retrieval/ranker');
|
|
8329
|
-
|
|
8357
|
+
|
|
8358
|
+
// Resolve an explicit context file path when --adapter is present.
|
|
8359
|
+
// This lets `--adapter claude --query "..."` read CLAUDE.md instead of
|
|
8360
|
+
// falling through to the default copilot-instructions.md probe.
|
|
8361
|
+
let queryOpts;
|
|
8362
|
+
const adpIdx = args.indexOf('--adapter');
|
|
8363
|
+
if (adpIdx >= 0) {
|
|
8364
|
+
const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
|
|
8365
|
+
const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini', 'codex'];
|
|
8366
|
+
if (VALID_ADAPTERS.includes(adapterName)) {
|
|
8367
|
+
try {
|
|
8368
|
+
const adapterMod = __require('./packages/adapters/' + adapterName);
|
|
8369
|
+
queryOpts = { contextPath: adapterMod.outputPath(cwd) };
|
|
8370
|
+
} catch (_) {}
|
|
8371
|
+
}
|
|
8372
|
+
}
|
|
8373
|
+
|
|
8374
|
+
const index = buildSigIndex(cwd, queryOpts);
|
|
8330
8375
|
if (index.size === 0) {
|
|
8331
8376
|
console.error('[sigmap] no context file found. Run: node gen-context.js');
|
|
8377
|
+
if (adpIdx >= 0) {
|
|
8378
|
+
console.error(' (tried the path for --adapter ' + (args[adpIdx + 1] || '') + ')');
|
|
8379
|
+
}
|
|
8332
8380
|
process.exit(1);
|
|
8333
8381
|
}
|
|
8334
8382
|
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,40 @@ 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
|
+
* When `opts.contextPath` is provided, that specific file is used.
|
|
210
|
+
* This is the case when the caller already knows the path (e.g. via
|
|
211
|
+
* --adapter <name> or --output <file>).
|
|
212
|
+
*
|
|
213
|
+
* Otherwise all known adapter output paths are probed in order and the
|
|
214
|
+
* first file that produces a non-empty index is returned.
|
|
215
|
+
*
|
|
216
|
+
* @param {string} cwd
|
|
217
|
+
* @param {{ contextPath?: string }} [opts]
|
|
218
|
+
* @returns {Map<string, string[]>}
|
|
219
|
+
*/
|
|
220
|
+
function buildSigIndex(cwd, opts) {
|
|
221
|
+
const path = require('path');
|
|
222
|
+
|
|
223
|
+
// Caller supplied an explicit path — use it directly.
|
|
224
|
+
if (opts && opts.contextPath) {
|
|
225
|
+
return _parseContextFile(opts.contextPath);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Probe all known adapter output paths; return first non-empty index.
|
|
229
|
+
for (const parts of ADAPTER_OUTPUT_PATHS) {
|
|
230
|
+
const contextPath = path.join(cwd, ...parts);
|
|
231
|
+
const index = _parseContextFile(contextPath);
|
|
232
|
+
if (index.size > 0) return index;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return new Map();
|
|
236
|
+
}
|
|
237
|
+
|
|
183
238
|
/**
|
|
184
239
|
* Format ranked results as a markdown table string.
|
|
185
240
|
*
|