sigmap 3.5.1 → 4.0.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 +142 -105
- package/README.md +5 -1
- package/gen-context.js +232 -25
- package/package.json +1 -1
- package/packages/adapters/claude.js +12 -1
- package/packages/adapters/copilot.js +12 -1
- package/packages/adapters/cursor.js +12 -1
- package/packages/adapters/gemini.js +11 -0
- package/packages/adapters/openai.js +11 -0
- package/packages/adapters/windsurf.js +12 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/analysis/coverage-score.js +85 -0
- package/src/eval/analyzer.js +5 -0
- package/src/mcp/server.js +1 -1
package/AGENTS.md
CHANGED
|
@@ -12,49 +12,40 @@ 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
|
|
15
|
+
Below are the code signatures extracted by SigMap v4.0.0 on 2026-04-15T05:40:32.012Z.
|
|
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
|
|
21
|
+
<!-- Generated by SigMap gen-context.js v4.0.0 -->
|
|
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 —
|
|
26
|
+
## changes (last 5 commits — 6 hours ago)
|
|
27
27
|
```
|
|
28
|
-
src/
|
|
29
|
-
src/
|
|
30
|
-
src/
|
|
31
|
-
|
|
32
|
-
src/extractors/properties.js +extract
|
|
33
|
-
src/extractors/python_dataclass.js +extract +definitions
|
|
34
|
-
src/extractors/toml.js +extract
|
|
35
|
-
src/extractors/typescript_react.js +extract +declarations
|
|
36
|
-
src/extractors/vue_sfc.js +extract
|
|
37
|
-
src/extractors/xml.js +attributes +extract
|
|
28
|
+
src/extractors/generic.js +extract
|
|
29
|
+
src/format/llm-txt.js +outputPath +format
|
|
30
|
+
src/format/llms-txt.js +outputPath +getShortCommit +detectVersion +format
|
|
31
|
+
packages/adapters/llm-full.js +outputPath +format +write
|
|
38
32
|
```
|
|
39
33
|
|
|
40
34
|
## packages
|
|
41
35
|
|
|
42
|
-
### packages/
|
|
36
|
+
### packages/adapters/llm-full.js
|
|
43
37
|
```
|
|
44
|
-
module.exports = {
|
|
45
|
-
function
|
|
46
|
-
function
|
|
47
|
-
function
|
|
48
|
-
function buildSigIndex(cwd) → Map<string, string[]>
|
|
49
|
-
function scan(sigs, filePath) → { safe: string[], redacte
|
|
50
|
-
function score(cwd) → { * score: number, * grad
|
|
51
|
-
function adapt(context, adapterName, opts = {}) → string
|
|
38
|
+
module.exports = { name: 'llm-full', format, outputPath, write }
|
|
39
|
+
function outputPath(cwd)
|
|
40
|
+
function format(context, opts)
|
|
41
|
+
function write(context, cwd, opts)
|
|
52
42
|
```
|
|
53
43
|
|
|
54
44
|
### packages/adapters/claude.js
|
|
55
45
|
```
|
|
56
46
|
module.exports = { name, format, outputPath, write }
|
|
57
47
|
function format(context, opts = {}) → string
|
|
48
|
+
function _confidenceMeta(opts)
|
|
58
49
|
function outputPath(cwd) → string
|
|
59
50
|
function write(context, cwd, opts = {})
|
|
60
51
|
```
|
|
@@ -71,6 +62,7 @@ function write(context, cwd, opts = {})
|
|
|
71
62
|
```
|
|
72
63
|
module.exports = { name, format, outputPath, write }
|
|
73
64
|
function format(context, opts = {}) → string
|
|
65
|
+
function _confidenceMeta(opts)
|
|
74
66
|
function outputPath(cwd) → string
|
|
75
67
|
function write(context, cwd, opts = {})
|
|
76
68
|
```
|
|
@@ -79,6 +71,7 @@ function write(context, cwd, opts = {})
|
|
|
79
71
|
```
|
|
80
72
|
module.exports = { name, format, outputPath }
|
|
81
73
|
function format(context, opts = {}) → string
|
|
74
|
+
function _confidenceMeta(opts)
|
|
82
75
|
function outputPath(cwd) → string
|
|
83
76
|
```
|
|
84
77
|
|
|
@@ -88,6 +81,7 @@ module.exports = { name, format, outputPath, write }
|
|
|
88
81
|
function format(context, opts = {}) → string
|
|
89
82
|
function outputPath(cwd) → string
|
|
90
83
|
function write(context, cwd, opts = {})
|
|
84
|
+
function _confidenceMeta(opts)
|
|
91
85
|
```
|
|
92
86
|
|
|
93
87
|
### packages/adapters/index.js
|
|
@@ -104,12 +98,14 @@ function outputsToAdapters(outputs) → string[]
|
|
|
104
98
|
module.exports = { name, format, outputPath }
|
|
105
99
|
function format(context, opts = {}) → string
|
|
106
100
|
function outputPath(cwd) → string
|
|
101
|
+
function _confidenceMeta(opts)
|
|
107
102
|
```
|
|
108
103
|
|
|
109
104
|
### packages/adapters/windsurf.js
|
|
110
105
|
```
|
|
111
106
|
module.exports = { name, format, outputPath }
|
|
112
107
|
function format(context, opts = {}) → string
|
|
108
|
+
function _confidenceMeta(opts)
|
|
113
109
|
function outputPath(cwd) → string
|
|
114
110
|
```
|
|
115
111
|
|
|
@@ -139,8 +135,49 @@ code-fence js
|
|
|
139
135
|
code-fence ---
|
|
140
136
|
```
|
|
141
137
|
|
|
138
|
+
### packages/core/index.js
|
|
139
|
+
```
|
|
140
|
+
module.exports = { extract, rank, buildSigIndex, scan, score, adapt }
|
|
141
|
+
function _resolveExtractor(language)
|
|
142
|
+
function extract(src, language) → string[]
|
|
143
|
+
function rank(query, sigIndex, opts) → { file: string, score: nu
|
|
144
|
+
function buildSigIndex(cwd) → Map<string, string[]>
|
|
145
|
+
function scan(sigs, filePath) → { safe: string[], redacte
|
|
146
|
+
function score(cwd) → { * score: number, * grad
|
|
147
|
+
function adapt(context, adapterName, opts = {}) → string
|
|
148
|
+
```
|
|
149
|
+
|
|
142
150
|
## src
|
|
143
151
|
|
|
152
|
+
### src/extractors/generic.js
|
|
153
|
+
```
|
|
154
|
+
module.exports = { extract }
|
|
155
|
+
function extract(src)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### src/format/llm-txt.js
|
|
159
|
+
```
|
|
160
|
+
module.exports = { format, outputPath }
|
|
161
|
+
function outputPath(cwd)
|
|
162
|
+
function format(context, cwd, version)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### src/format/llms-txt.js
|
|
166
|
+
```
|
|
167
|
+
module.exports = { format, outputPath }
|
|
168
|
+
function outputPath(cwd)
|
|
169
|
+
function getShortCommit(cwd)
|
|
170
|
+
function detectVersion(cwd)
|
|
171
|
+
function format(context, cwd, writtenFiles, sigmapVersion)
|
|
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)
|
|
179
|
+
```
|
|
180
|
+
|
|
144
181
|
### src/config/defaults.js
|
|
145
182
|
```
|
|
146
183
|
module.exports = { DEFAULTS }
|
|
@@ -167,89 +204,6 @@ function formatAnalysisTable(stats, showSlow) → string
|
|
|
167
204
|
function formatAnalysisJSON(stats) → object
|
|
168
205
|
```
|
|
169
206
|
|
|
170
|
-
### src/extractors/graphql.js
|
|
171
|
-
```
|
|
172
|
-
module.exports = { extract }
|
|
173
|
-
function extract(src) → string[]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### src/extractors/markdown.js
|
|
177
|
-
```
|
|
178
|
-
module.exports = { extract }
|
|
179
|
-
function extract(src) → string[]
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### src/extractors/patterns.js
|
|
183
|
-
```
|
|
184
|
-
module.exports = { extract }
|
|
185
|
-
function extract(src) → string[]
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### src/extractors/properties.js
|
|
189
|
-
```
|
|
190
|
-
module.exports = { extract }
|
|
191
|
-
function extract(src) → string[]
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### src/extractors/protobuf.js
|
|
195
|
-
```
|
|
196
|
-
module.exports = { extract }
|
|
197
|
-
function extract(src) → string[]
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### src/extractors/python_dataclass.js
|
|
201
|
-
```
|
|
202
|
-
module.exports = { extract }
|
|
203
|
-
function extract(src) → string[]
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### src/extractors/sql.js
|
|
207
|
-
```
|
|
208
|
-
module.exports = { extract }
|
|
209
|
-
function extract(src) → string[]
|
|
210
|
-
function _cleanName(raw)
|
|
211
|
-
function _normalizeParams(raw)
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### src/extractors/terraform.js
|
|
215
|
-
```
|
|
216
|
-
module.exports = { extract }
|
|
217
|
-
function extract(src) → string[]
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### src/extractors/toml.js
|
|
221
|
-
```
|
|
222
|
-
module.exports = { extract }
|
|
223
|
-
function extract(src) → string[]
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### src/extractors/typescript_react.js
|
|
227
|
-
```
|
|
228
|
-
module.exports = { extract }
|
|
229
|
-
function extract(src) → string[]
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### src/extractors/vue_sfc.js
|
|
233
|
-
```
|
|
234
|
-
module.exports = { extract }
|
|
235
|
-
function extract(src) → string[]
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### src/extractors/xml.js
|
|
239
|
-
```
|
|
240
|
-
module.exports = { extract }
|
|
241
|
-
function extract(src) → string[]
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### src/mcp/server.js
|
|
245
|
-
```
|
|
246
|
-
module.exports = { start }
|
|
247
|
-
function respond(id, result)
|
|
248
|
-
function respondError(id, code, message)
|
|
249
|
-
function dispatch(msg, cwd)
|
|
250
|
-
function start(cwd)
|
|
251
|
-
```
|
|
252
|
-
|
|
253
207
|
### src/eval/runner.js
|
|
254
208
|
```
|
|
255
209
|
module.exports = { run, rank, loadTasks, buildSigIndex, formatTable, formatMetrics, tokenize }
|
|
@@ -342,6 +296,12 @@ function extractInterfaceMethods(block)
|
|
|
342
296
|
function normalizeParams(params)
|
|
343
297
|
```
|
|
344
298
|
|
|
299
|
+
### src/extractors/graphql.js
|
|
300
|
+
```
|
|
301
|
+
module.exports = { extract }
|
|
302
|
+
function extract(src) → string[]
|
|
303
|
+
```
|
|
304
|
+
|
|
345
305
|
### src/extractors/html.js
|
|
346
306
|
```
|
|
347
307
|
module.exports = { extract }
|
|
@@ -379,6 +339,18 @@ function extractMembers(block)
|
|
|
379
339
|
function normalizeParams(params)
|
|
380
340
|
```
|
|
381
341
|
|
|
342
|
+
### src/extractors/markdown.js
|
|
343
|
+
```
|
|
344
|
+
module.exports = { extract }
|
|
345
|
+
function extract(src) → string[]
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### src/extractors/patterns.js
|
|
349
|
+
```
|
|
350
|
+
module.exports = { extract }
|
|
351
|
+
function extract(src) → string[]
|
|
352
|
+
```
|
|
353
|
+
|
|
382
354
|
### src/extractors/php.js
|
|
383
355
|
```
|
|
384
356
|
module.exports = { extract }
|
|
@@ -396,6 +368,18 @@ function diffSignatures(baseSigs, currentSigs) → {added:string[], removed:
|
|
|
396
368
|
function extractName(sig)
|
|
397
369
|
```
|
|
398
370
|
|
|
371
|
+
### src/extractors/properties.js
|
|
372
|
+
```
|
|
373
|
+
module.exports = { extract }
|
|
374
|
+
function extract(src) → string[]
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### src/extractors/protobuf.js
|
|
378
|
+
```
|
|
379
|
+
module.exports = { extract }
|
|
380
|
+
function extract(src) → string[]
|
|
381
|
+
```
|
|
382
|
+
|
|
399
383
|
### src/extractors/python.js
|
|
400
384
|
```
|
|
401
385
|
module.exports = { extract }
|
|
@@ -409,6 +393,12 @@ function normalizeParams(params)
|
|
|
409
393
|
function extractDocHint(src, fnName, fnSigLine)
|
|
410
394
|
```
|
|
411
395
|
|
|
396
|
+
### src/extractors/python_dataclass.js
|
|
397
|
+
```
|
|
398
|
+
module.exports = { extract }
|
|
399
|
+
function extract(src) → string[]
|
|
400
|
+
```
|
|
401
|
+
|
|
412
402
|
### src/extractors/ruby.js
|
|
413
403
|
```
|
|
414
404
|
module.exports = { extract }
|
|
@@ -443,6 +433,14 @@ module.exports = { extract }
|
|
|
443
433
|
function extract(src) → string[]
|
|
444
434
|
```
|
|
445
435
|
|
|
436
|
+
### src/extractors/sql.js
|
|
437
|
+
```
|
|
438
|
+
module.exports = { extract }
|
|
439
|
+
function extract(src) → string[]
|
|
440
|
+
function _cleanName(raw)
|
|
441
|
+
function _normalizeParams(raw)
|
|
442
|
+
```
|
|
443
|
+
|
|
446
444
|
### src/extractors/svelte.js
|
|
447
445
|
```
|
|
448
446
|
module.exports = { extract }
|
|
@@ -461,12 +459,24 @@ function normalizeParams(params)
|
|
|
461
459
|
function extractArrowType(str)
|
|
462
460
|
```
|
|
463
461
|
|
|
462
|
+
### src/extractors/terraform.js
|
|
463
|
+
```
|
|
464
|
+
module.exports = { extract }
|
|
465
|
+
function extract(src) → string[]
|
|
466
|
+
```
|
|
467
|
+
|
|
464
468
|
### src/extractors/todos.js
|
|
465
469
|
```
|
|
466
470
|
module.exports = { extractTodos }
|
|
467
471
|
function extractTodos(src) → {line:number, tag:string,
|
|
468
472
|
```
|
|
469
473
|
|
|
474
|
+
### src/extractors/toml.js
|
|
475
|
+
```
|
|
476
|
+
module.exports = { extract }
|
|
477
|
+
function extract(src) → string[]
|
|
478
|
+
```
|
|
479
|
+
|
|
470
480
|
### src/extractors/typescript.js
|
|
471
481
|
```
|
|
472
482
|
module.exports = { extract }
|
|
@@ -477,6 +487,12 @@ function extractClassMembers(block)
|
|
|
477
487
|
function normalizeParams(params)
|
|
478
488
|
```
|
|
479
489
|
|
|
490
|
+
### src/extractors/typescript_react.js
|
|
491
|
+
```
|
|
492
|
+
module.exports = { extract }
|
|
493
|
+
function extract(src) → string[]
|
|
494
|
+
```
|
|
495
|
+
|
|
480
496
|
### src/extractors/vue.js
|
|
481
497
|
```
|
|
482
498
|
module.exports = { extract }
|
|
@@ -485,6 +501,18 @@ function normalizeParams(params)
|
|
|
485
501
|
function normalizeType(type)
|
|
486
502
|
```
|
|
487
503
|
|
|
504
|
+
### src/extractors/vue_sfc.js
|
|
505
|
+
```
|
|
506
|
+
module.exports = { extract }
|
|
507
|
+
function extract(src) → string[]
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### src/extractors/xml.js
|
|
511
|
+
```
|
|
512
|
+
module.exports = { extract }
|
|
513
|
+
function extract(src) → string[]
|
|
514
|
+
```
|
|
515
|
+
|
|
488
516
|
### src/extractors/yaml.js
|
|
489
517
|
```
|
|
490
518
|
module.exports = { extract }
|
|
@@ -581,6 +609,15 @@ function queryContext(args, cwd)
|
|
|
581
609
|
function getImpact(args, cwd)
|
|
582
610
|
```
|
|
583
611
|
|
|
612
|
+
### src/mcp/server.js
|
|
613
|
+
```
|
|
614
|
+
module.exports = { start }
|
|
615
|
+
function respond(id, result)
|
|
616
|
+
function respondError(id, code, message)
|
|
617
|
+
function dispatch(msg, cwd)
|
|
618
|
+
function start(cwd)
|
|
619
|
+
```
|
|
620
|
+
|
|
584
621
|
### src/mcp/tools.js
|
|
585
622
|
```
|
|
586
623
|
module.exports = { TOOLS }
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="
|
|
3
|
+
<img src="assets/logo.png" alt="SigMap logo" width="80" height="80" />
|
|
4
4
|
|
|
5
5
|
<h1>⚡ SigMap</h1>
|
|
6
6
|
|
|
@@ -456,6 +456,10 @@ Activates on startup (`onStartupFinished`) — loads within 3 s, never blocks ed
|
|
|
456
456
|
|
|
457
457
|
The official SigMap JetBrains plugin brings the same features to IntelliJ-based IDEs. Install it from the JetBrains Marketplace and it works identically to the VS Code extension.
|
|
458
458
|
|
|
459
|
+
<div align="center">
|
|
460
|
+
<img src="assets/intelij.gif" alt="SigMap JetBrains plugin — status bar health grade, regenerate action, and context auto-refresh in IntelliJ IDEA" width="700" />
|
|
461
|
+
</div>
|
|
462
|
+
|
|
459
463
|
| Feature | Detail |
|
|
460
464
|
|---|---|
|
|
461
465
|
| **Status bar widget** | Shows health grade (`A`-`F`) + time since last regen; updates every 60 s |
|
package/gen-context.js
CHANGED
|
@@ -4478,7 +4478,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
4478
4478
|
|
|
4479
4479
|
const SERVER_INFO = {
|
|
4480
4480
|
name: 'sigmap',
|
|
4481
|
-
version: '
|
|
4481
|
+
version: '4.0.0',
|
|
4482
4482
|
description: 'SigMap MCP server — code signatures on demand',
|
|
4483
4483
|
};
|
|
4484
4484
|
|
|
@@ -6040,7 +6040,7 @@ const path = require('path');
|
|
|
6040
6040
|
const os = require('os');
|
|
6041
6041
|
const { execSync } = require('child_process');
|
|
6042
6042
|
|
|
6043
|
-
const VERSION = '
|
|
6043
|
+
const VERSION = '4.0.0';
|
|
6044
6044
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
6045
6045
|
|
|
6046
6046
|
function requireSourceOrBundled(key) {
|
|
@@ -6270,35 +6270,47 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
6270
6270
|
// Sort by drop priority (drop first = index 0)
|
|
6271
6271
|
const withPriority = fileEntries.map((e) => {
|
|
6272
6272
|
let priority = 0;
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
else if (
|
|
6276
|
-
else if (
|
|
6273
|
+
let dropReason = 'budget: low recency';
|
|
6274
|
+
if (isGeneratedFile(e.filePath)) { priority = 10; dropReason = 'budget: generated file'; }
|
|
6275
|
+
else if (isMockFile(e.filePath)) { priority = 9; dropReason = 'budget: mock file'; }
|
|
6276
|
+
else if (isTestFile(e.filePath)) { priority = 8; dropReason = 'budget: test file'; }
|
|
6277
|
+
else if (isConfigFile(e.filePath)) { priority = 6; dropReason = 'budget: config file'; }
|
|
6277
6278
|
else priority = 4;
|
|
6278
|
-
|
|
6279
|
+
// v4.0: signal quality = sigs per line-of-code (higher = more informative)
|
|
6280
|
+
const loc = e.content ? e.content.split('\n').length : 1;
|
|
6281
|
+
const signalQuality = loc > 0 ? (e.sigs ? e.sigs.length : 0) / loc : 0;
|
|
6282
|
+
return { ...e, priority, dropReason, signalQuality };
|
|
6279
6283
|
});
|
|
6280
6284
|
|
|
6281
|
-
// Within same priority
|
|
6285
|
+
// Within same priority: sort by mtime ascending (oldest first), then signalQuality ascending (least informative first)
|
|
6282
6286
|
withPriority.sort((a, b) => {
|
|
6283
6287
|
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
6284
|
-
return (a.mtime || 0) - (b.mtime || 0);
|
|
6288
|
+
if ((a.mtime || 0) !== (b.mtime || 0)) return (a.mtime || 0) - (b.mtime || 0);
|
|
6289
|
+
return (a.signalQuality || 0) - (b.signalQuality || 0);
|
|
6285
6290
|
});
|
|
6286
6291
|
|
|
6287
6292
|
const kept = [];
|
|
6288
|
-
|
|
6293
|
+
const verboseDropped = [];
|
|
6289
6294
|
// Iterate forward: highest drop-priority files (generated=10, mock=9, test=8) are at index 0
|
|
6290
6295
|
// Drop those first until we're under budget, then keep everything else
|
|
6291
6296
|
for (const entry of withPriority) {
|
|
6292
6297
|
const entryTokens = estimateTokens(entry.sigs.join('\n'));
|
|
6293
6298
|
if (total > effectiveBudget) {
|
|
6294
6299
|
total -= entryTokens;
|
|
6295
|
-
|
|
6300
|
+
verboseDropped.push({ filePath: entry.filePath, reason: entry.dropReason });
|
|
6296
6301
|
} else {
|
|
6297
6302
|
kept.push(entry);
|
|
6298
6303
|
}
|
|
6299
6304
|
}
|
|
6300
|
-
if (
|
|
6301
|
-
console.warn(`[sigmap] budget: dropped ${
|
|
6305
|
+
if (verboseDropped.length > 0) {
|
|
6306
|
+
console.warn(`[sigmap] budget: dropped ${verboseDropped.length} files to stay under ${maxTokens} tokens`);
|
|
6307
|
+
// Feature 7: --verbose — print per-file drop reason
|
|
6308
|
+
if (process.argv.includes('--verbose')) {
|
|
6309
|
+
for (const { filePath, reason } of verboseDropped) {
|
|
6310
|
+
console.warn(`[sigmap] dropped: ${path.relative(process.cwd(), filePath)} — ${reason}`);
|
|
6311
|
+
}
|
|
6312
|
+
console.warn(`[sigmap] included: ${kept.length} files, dropped: ${verboseDropped.length}`);
|
|
6313
|
+
}
|
|
6302
6314
|
}
|
|
6303
6315
|
return kept;
|
|
6304
6316
|
}
|
|
@@ -6708,11 +6720,17 @@ function writeClaude(content, cwd) {
|
|
|
6708
6720
|
// ---------------------------------------------------------------------------
|
|
6709
6721
|
// Report
|
|
6710
6722
|
// ---------------------------------------------------------------------------
|
|
6711
|
-
function
|
|
6723
|
+
function _coverageBar(pct, width) {
|
|
6724
|
+
width = width || 16;
|
|
6725
|
+
const filled = Math.round(pct / 100 * width);
|
|
6726
|
+
return '\u2588'.repeat(filled) + '\u2591'.repeat(width - filled);
|
|
6727
|
+
}
|
|
6728
|
+
|
|
6729
|
+
function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson, budgetLimit, coverageResult) {
|
|
6712
6730
|
const reduction = inputTokens > 0 ? (100 - (finalTokens / inputTokens) * 100).toFixed(1) : 0;
|
|
6713
6731
|
const overBudget = finalTokens > (budgetLimit || 6000);
|
|
6714
6732
|
if (asJson) {
|
|
6715
|
-
|
|
6733
|
+
const payload = {
|
|
6716
6734
|
version: VERSION,
|
|
6717
6735
|
timestamp: new Date().toISOString(),
|
|
6718
6736
|
rawTokens: inputTokens,
|
|
@@ -6723,7 +6741,22 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
6723
6741
|
reductionPct: parseFloat(reduction),
|
|
6724
6742
|
overBudget,
|
|
6725
6743
|
budgetLimit: budgetLimit || 6000,
|
|
6726
|
-
}
|
|
6744
|
+
};
|
|
6745
|
+
if (coverageResult) {
|
|
6746
|
+
payload.coverage = {
|
|
6747
|
+
score: coverageResult.score,
|
|
6748
|
+
grade: coverageResult.grade,
|
|
6749
|
+
confidence: coverageResult.confidence,
|
|
6750
|
+
totalFiles: coverageResult.total,
|
|
6751
|
+
includedFiles: coverageResult.included,
|
|
6752
|
+
droppedFiles: coverageResult.dropped,
|
|
6753
|
+
perModule: Object.fromEntries(
|
|
6754
|
+
Array.from(coverageResult.perModule.entries())
|
|
6755
|
+
.map(([k, v]) => [k, v])
|
|
6756
|
+
),
|
|
6757
|
+
};
|
|
6758
|
+
}
|
|
6759
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
6727
6760
|
// Exit 1 in CI if over budget — lets pipelines fail fast
|
|
6728
6761
|
if (overBudget) process.exitCode = 1;
|
|
6729
6762
|
} else {
|
|
@@ -6735,6 +6768,20 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
6735
6768
|
console.log(` output tokens : ~${finalTokens}`);
|
|
6736
6769
|
console.log(` budget limit : ${budgetLimit || 6000}`);
|
|
6737
6770
|
console.log(` reduction : ${reduction}%`);
|
|
6771
|
+
if (coverageResult) {
|
|
6772
|
+
console.log(` coverage : ${coverageResult.grade} (${coverageResult.score}%) — ${coverageResult.included} of ${coverageResult.total} source files included`);
|
|
6773
|
+
console.log(` confidence : ${coverageResult.confidence}`);
|
|
6774
|
+
if (coverageResult.perModule && coverageResult.perModule.size > 0) {
|
|
6775
|
+
console.log('');
|
|
6776
|
+
console.log(' Module Coverage:');
|
|
6777
|
+
for (const [dir, mod] of coverageResult.perModule) {
|
|
6778
|
+
if (mod.total === 0) continue;
|
|
6779
|
+
const bar = _coverageBar(mod.pct);
|
|
6780
|
+
const attention = mod.pct < 50 ? ' \u2190 attention needed' : '';
|
|
6781
|
+
console.log(` ${dir.padEnd(18)} ${bar} ${String(mod.pct).padStart(3)}% (${mod.included}/${mod.total} files)${attention}`);
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6738
6785
|
if (overBudget) console.warn(`[sigmap] WARNING: output (${finalTokens} tokens) exceeds budget (${budgetLimit || 6000})`);
|
|
6739
6786
|
}
|
|
6740
6787
|
}
|
|
@@ -7024,6 +7071,41 @@ function runDiff(cwd, config, stagedOnly, baseRef) {
|
|
|
7024
7071
|
const scope = baseRef ? `diff-vs-${baseRef}` : (stagedOnly ? 'staged' : 'diff');
|
|
7025
7072
|
console.warn(`[sigmap] ${scope} files: ${fileEntries.length}, diff tokens: ~${finalTokens}`);
|
|
7026
7073
|
|
|
7074
|
+
// v4.0: risk score per changed file
|
|
7075
|
+
try {
|
|
7076
|
+
const { buildFromCwd } = requireSourceOrBundled('./src/graph/builder');
|
|
7077
|
+
const graph = buildFromCwd(cwd, { silent: true });
|
|
7078
|
+
const reverseGraph = graph.reverse || new Map();
|
|
7079
|
+
|
|
7080
|
+
function _isRouteFile(f) {
|
|
7081
|
+
return /\/(routes?|pages?|controllers?|handlers?|api)\//i.test(f)
|
|
7082
|
+
|| /\.(route|page|controller|handler)\.\w+$/.test(f);
|
|
7083
|
+
}
|
|
7084
|
+
function _riskScore(filePath, sigs, revGraph) {
|
|
7085
|
+
let s = 0;
|
|
7086
|
+
if (sigs.some(sig => sig.includes('export') || sig.includes('module.exports'))) s += 2;
|
|
7087
|
+
const deps = revGraph.get(filePath) || new Set();
|
|
7088
|
+
if (deps.size > 3) s += 2;
|
|
7089
|
+
if (_isRouteFile(filePath)) s += 1;
|
|
7090
|
+
if (/\.(config|env|settings)\.\w+$/.test(filePath)) s += 1;
|
|
7091
|
+
return s >= 4 ? 'HIGH' : s >= 2 ? 'MEDIUM' : 'LOW';
|
|
7092
|
+
}
|
|
7093
|
+
|
|
7094
|
+
console.warn(`[sigmap] Risk: Changed files (${fileEntries.length}):`);
|
|
7095
|
+
for (const fe of fileEntries) {
|
|
7096
|
+
const level = _riskScore(fe.filePath, fe.sigs, reverseGraph);
|
|
7097
|
+
const rel = path.relative(cwd, fe.filePath).replace(/\\/g, '/');
|
|
7098
|
+
const deps = reverseGraph.get(fe.filePath) || new Set();
|
|
7099
|
+
const reasons = [];
|
|
7100
|
+
if (fe.sigs.some(s => s.includes('export') || s.includes('module.exports'))) reasons.push('exports public API');
|
|
7101
|
+
if (deps.size > 0) reasons.push(`${deps.size} downstream dependent${deps.size === 1 ? '' : 's'}`);
|
|
7102
|
+
if (_isRouteFile(fe.filePath)) reasons.push('route file');
|
|
7103
|
+
if (/\.(config|env|settings)\.\w+$/.test(fe.filePath)) reasons.push('config file');
|
|
7104
|
+
const label = level === 'HIGH' ? '[HIGH] ' : level === 'MEDIUM' ? '[MEDIUM]' : '[LOW] ';
|
|
7105
|
+
console.warn(` ${rel.padEnd(40)} ${label}${reasons.length ? ' — ' + reasons.join(', ') : ''}`);
|
|
7106
|
+
}
|
|
7107
|
+
} catch (_) {}
|
|
7108
|
+
|
|
7027
7109
|
if (process.argv.includes('--report')) {
|
|
7028
7110
|
// Also show what the full run would cost for comparison
|
|
7029
7111
|
const fullResult = runGenerate(cwd, config, true);
|
|
@@ -7168,11 +7250,17 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7168
7250
|
const droppedCount = beforeCount - budgeted.length;
|
|
7169
7251
|
const content = formatOutput(budgeted, cwd, false, config, null);
|
|
7170
7252
|
const finalTokens = estimateTokens(content);
|
|
7171
|
-
|
|
7253
|
+
// v4.0: compute coverage score for --report heatmap
|
|
7254
|
+
let coverageResult = null;
|
|
7255
|
+
try {
|
|
7256
|
+
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7257
|
+
coverageResult = coverageScore(cwd, budgeted, config);
|
|
7258
|
+
} catch (_) {}
|
|
7259
|
+
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount, coverageResult };
|
|
7172
7260
|
}
|
|
7173
7261
|
|
|
7174
7262
|
if (reportMode || process.argv.includes('--report')) {
|
|
7175
|
-
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, config.maxTokens);
|
|
7263
|
+
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, config.maxTokens, result.coverageResult);
|
|
7176
7264
|
}
|
|
7177
7265
|
|
|
7178
7266
|
// Usage tracking (v0.9) — optional append-only NDJSON log
|
|
@@ -7204,17 +7292,26 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7204
7292
|
const pct = result.inputTokenTotal > 0
|
|
7205
7293
|
? Math.round((1 - result.finalTokens / result.inputTokenTotal) * 100)
|
|
7206
7294
|
: 0;
|
|
7207
|
-
|
|
7295
|
+
// v4.0: coverage score in post-run summary
|
|
7296
|
+
let coverageLine = '';
|
|
7297
|
+
try {
|
|
7298
|
+
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7299
|
+
const cov = coverageScore(cwd, fileEntries, config);
|
|
7300
|
+
coverageLine = ` Coverage : ${cov.grade} (${cov.score}%) \u2014 ${cov.included} of ${cov.total} source files included`;
|
|
7301
|
+
} catch (_) {}
|
|
7302
|
+
const lines = [
|
|
7208
7303
|
bar,
|
|
7209
7304
|
` SigMap v${VERSION}`,
|
|
7210
7305
|
` Files scanned : ${result.fileCount}`,
|
|
7211
7306
|
` Symbols found : ${syms.toLocaleString()}`,
|
|
7212
7307
|
` Token reduction: ${pct}% (${result.inputTokenTotal.toLocaleString()} \u2192 ${result.finalTokens.toLocaleString()})`,
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7308
|
+
];
|
|
7309
|
+
if (coverageLine) lines.push(coverageLine);
|
|
7310
|
+
lines.push(` Output : .github/copilot-instructions.md`);
|
|
7311
|
+
lines.push(bar);
|
|
7312
|
+
lines.push(` Try: "explain the architecture" \u00b7 "find the auth module"`);
|
|
7313
|
+
lines.push(bar, '');
|
|
7314
|
+
process.stderr.write(lines.join('\n'));
|
|
7218
7315
|
}
|
|
7219
7316
|
|
|
7220
7317
|
return result;
|
|
@@ -7628,6 +7725,87 @@ function main() {
|
|
|
7628
7725
|
process.exit(0);
|
|
7629
7726
|
}
|
|
7630
7727
|
|
|
7728
|
+
// Feature 1: `sigmap explain <file>` — why a file is included or excluded
|
|
7729
|
+
if (args[0] === 'explain' || args.includes('--explain')) {
|
|
7730
|
+
const target = args[0] === 'explain'
|
|
7731
|
+
? args[1]
|
|
7732
|
+
: args[args.indexOf('--explain') + 1];
|
|
7733
|
+
|
|
7734
|
+
if (!target || target.startsWith('--')) {
|
|
7735
|
+
console.error('[sigmap] Usage: sigmap explain <file>');
|
|
7736
|
+
process.exit(1);
|
|
7737
|
+
}
|
|
7738
|
+
|
|
7739
|
+
const absPath = path.resolve(cwd, target);
|
|
7740
|
+
const rel = path.relative(cwd, absPath);
|
|
7741
|
+
const jsonOut = args.includes('--json');
|
|
7742
|
+
|
|
7743
|
+
// 1. Check .contextignore
|
|
7744
|
+
const ignorePatterns = loadIgnorePatterns(cwd);
|
|
7745
|
+
const ignored = matchesIgnore(rel.replace(/\\/g, '/'), ignorePatterns);
|
|
7746
|
+
if (ignored) {
|
|
7747
|
+
if (jsonOut) {
|
|
7748
|
+
process.stdout.write(JSON.stringify({ status: 'excluded', reason: '.contextignore', fix: `remove the pattern or add '!${rel}' as an exception` }) + '\n');
|
|
7749
|
+
} else {
|
|
7750
|
+
console.log(`[sigmap] ${rel} — EXCLUDED`);
|
|
7751
|
+
console.log(` Reason: matched .contextignore`);
|
|
7752
|
+
console.log(` Fix: remove the pattern or add '!${rel}' as an exception`);
|
|
7753
|
+
}
|
|
7754
|
+
process.exit(0);
|
|
7755
|
+
}
|
|
7756
|
+
|
|
7757
|
+
// 2. Check srcDirs membership
|
|
7758
|
+
const inSrcDirs = (config.srcDirs || []).some(d => absPath.startsWith(path.resolve(cwd, d)));
|
|
7759
|
+
if (!inSrcDirs) {
|
|
7760
|
+
if (jsonOut) {
|
|
7761
|
+
process.stdout.write(JSON.stringify({ status: 'excluded', reason: 'not in srcDirs', srcDirs: config.srcDirs, fix: 'add the containing directory to srcDirs in gen-context.config.json' }) + '\n');
|
|
7762
|
+
} else {
|
|
7763
|
+
console.log(`[sigmap] ${rel} — EXCLUDED`);
|
|
7764
|
+
console.log(` Reason: not under any srcDir (${(config.srcDirs || []).join(', ')})`);
|
|
7765
|
+
console.log(` Fix: add the containing directory to srcDirs in gen-context.config.json`);
|
|
7766
|
+
}
|
|
7767
|
+
process.exit(0);
|
|
7768
|
+
}
|
|
7769
|
+
|
|
7770
|
+
// 3. Detect extractor and extract sigs
|
|
7771
|
+
const base = path.basename(absPath);
|
|
7772
|
+
const ext = path.extname(base).toLowerCase();
|
|
7773
|
+
let extractorName = EXT_MAP[ext] || null;
|
|
7774
|
+
if (!extractorName && isDockerfile(base)) extractorName = 'dockerfile';
|
|
7775
|
+
if (!extractorName) extractorName = 'generic';
|
|
7776
|
+
|
|
7777
|
+
let sigs = [];
|
|
7778
|
+
if (fs.existsSync(absPath)) {
|
|
7779
|
+
try {
|
|
7780
|
+
const content = fs.readFileSync(absPath, 'utf8');
|
|
7781
|
+
const extractor = getExtractor(extractorName);
|
|
7782
|
+
if (extractor) sigs = extractor.extract(content).slice(0, config.maxSigsPerFile || 25);
|
|
7783
|
+
} catch (_) {}
|
|
7784
|
+
}
|
|
7785
|
+
|
|
7786
|
+
if (!sigs.length) {
|
|
7787
|
+
if (jsonOut) {
|
|
7788
|
+
process.stdout.write(JSON.stringify({ status: 'excluded', reason: 'no signatures', extractor: extractorName, fix: 'check that the file contains function/class definitions' }) + '\n');
|
|
7789
|
+
} else {
|
|
7790
|
+
console.log(`[sigmap] ${rel} — EXCLUDED`);
|
|
7791
|
+
console.log(` Reason: no extractable signatures (extractor: ${extractorName})`);
|
|
7792
|
+
console.log(` Fix: check that the file contains function/class definitions`);
|
|
7793
|
+
}
|
|
7794
|
+
process.exit(0);
|
|
7795
|
+
}
|
|
7796
|
+
|
|
7797
|
+
// 4. Included
|
|
7798
|
+
if (jsonOut) {
|
|
7799
|
+
process.stdout.write(JSON.stringify({ status: 'included', extractor: extractorName, signatures: sigs.length, preview: sigs.slice(0, 3), sigs }) + '\n');
|
|
7800
|
+
} else {
|
|
7801
|
+
console.log(`[sigmap] ${rel} — INCLUDED`);
|
|
7802
|
+
console.log(` Extractor : ${extractorName}`);
|
|
7803
|
+
console.log(` Signatures: ${sigs.length}`);
|
|
7804
|
+
console.log(` Preview : ${sigs.slice(0, 3).join(' · ')}`);
|
|
7805
|
+
}
|
|
7806
|
+
process.exit(0);
|
|
7807
|
+
}
|
|
7808
|
+
|
|
7631
7809
|
if (args.includes('--init')) {
|
|
7632
7810
|
writeInitConfig(cwd);
|
|
7633
7811
|
process.exit(0);
|
|
@@ -7636,11 +7814,40 @@ function main() {
|
|
|
7636
7814
|
if (args.includes('--health')) {
|
|
7637
7815
|
const { score } = __require('./src/health/scorer');
|
|
7638
7816
|
const result = score(cwd);
|
|
7817
|
+
// v4.0: compute live coverage score to include in health output
|
|
7818
|
+
let coverageResult = null;
|
|
7819
|
+
try {
|
|
7820
|
+
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7821
|
+
const { loadConfig: lc } = requireSourceOrBundled('./src/config/loader');
|
|
7822
|
+
const cfg = lc(cwd);
|
|
7823
|
+
// Use all files from srcDirs as proxies for "included" (no budget applied in health mode)
|
|
7824
|
+
const allFiles = buildFileList(cwd, cfg);
|
|
7825
|
+
const fakeEntries = allFiles.map(f => ({ filePath: f }));
|
|
7826
|
+
coverageResult = coverageScore(cwd, fakeEntries, cfg);
|
|
7827
|
+
} catch (_) {}
|
|
7639
7828
|
if (args.includes('--json')) {
|
|
7640
|
-
|
|
7829
|
+
// Feature 3 (VS Code) + Feature 5 (JetBrains): emit tokens + reduction for plugins
|
|
7830
|
+
const ctxPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
7831
|
+
let tokens = 0;
|
|
7832
|
+
let reduction = result.tokenReductionPct || 0;
|
|
7833
|
+
if (fs.existsSync(ctxPath)) {
|
|
7834
|
+
try { tokens = estimateTokens(fs.readFileSync(ctxPath, 'utf8')); } catch (_) {}
|
|
7835
|
+
}
|
|
7836
|
+
const payload = { ...result, tokens, reduction };
|
|
7837
|
+
if (coverageResult) {
|
|
7838
|
+
payload.coverage = coverageResult.score;
|
|
7839
|
+
payload.coverageGrade = coverageResult.grade;
|
|
7840
|
+
payload.coverageConfidence = coverageResult.confidence;
|
|
7841
|
+
payload.coverageTotalFiles = coverageResult.total;
|
|
7842
|
+
payload.coverageIncludedFiles = coverageResult.included;
|
|
7843
|
+
}
|
|
7844
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
7641
7845
|
} else {
|
|
7642
7846
|
console.log('[sigmap] health:');
|
|
7643
7847
|
console.log(` score : ${result.score}/100 (grade ${result.grade})`);
|
|
7848
|
+
if (coverageResult) {
|
|
7849
|
+
console.log(` coverage : ${coverageResult.grade} (${coverageResult.score}%) — ${coverageResult.included} of ${coverageResult.total} source files`);
|
|
7850
|
+
}
|
|
7644
7851
|
console.log(` strategy : ${result.strategy}`);
|
|
7645
7852
|
console.log(` token reduction : ${result.tokenReductionPct !== null ? result.tokenReductionPct + '%' : 'no history'}`);
|
|
7646
7853
|
console.log(` days since regen: ${result.daysSinceRegen !== null ? result.daysSinceRegen : 'context file not found'}`);
|
package/package.json
CHANGED
|
@@ -26,15 +26,26 @@ const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js
|
|
|
26
26
|
*/
|
|
27
27
|
function format(context, opts = {}) {
|
|
28
28
|
if (!context || typeof context !== 'string') return '';
|
|
29
|
-
const version
|
|
29
|
+
const version = opts.version || 'unknown';
|
|
30
30
|
const timestamp = new Date().toISOString();
|
|
31
|
+
const meta = _confidenceMeta(opts);
|
|
31
32
|
return [
|
|
32
33
|
`<!-- Generated by SigMap v${version} — ${timestamp} -->`,
|
|
34
|
+
meta,
|
|
33
35
|
'',
|
|
34
36
|
context,
|
|
35
37
|
].join('\n');
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
function _confidenceMeta(opts) {
|
|
41
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
42
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
43
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
44
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
45
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
46
|
+
return `<!-- sigmap: ${parts.join(' ')} -->`;
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
/**
|
|
39
50
|
* Return the output file path for this adapter.
|
|
40
51
|
* @param {string} cwd - Project root
|
|
@@ -24,11 +24,13 @@ const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js
|
|
|
24
24
|
*/
|
|
25
25
|
function format(context, opts = {}) {
|
|
26
26
|
if (!context || typeof context !== 'string') return '';
|
|
27
|
-
const version
|
|
27
|
+
const version = opts.version || 'unknown';
|
|
28
28
|
const timestamp = new Date().toISOString();
|
|
29
|
+
const meta = _confidenceMeta(opts);
|
|
29
30
|
const header = [
|
|
30
31
|
`<!-- Generated by SigMap gen-context.js v${version} -->`,
|
|
31
32
|
`<!-- Updated: ${timestamp} -->`,
|
|
33
|
+
meta,
|
|
32
34
|
`<!-- Do not edit below — regenerate with: node gen-context.js -->`,
|
|
33
35
|
'',
|
|
34
36
|
'# Code signatures',
|
|
@@ -37,6 +39,15 @@ function format(context, opts = {}) {
|
|
|
37
39
|
return header + context;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
function _confidenceMeta(opts) {
|
|
43
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
44
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
45
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
46
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
47
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
48
|
+
return `<!-- sigmap: ${parts.join(' ')} -->`;
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
/**
|
|
41
52
|
* Return the output file path for this adapter.
|
|
42
53
|
* @param {string} cwd - Project root
|
|
@@ -22,17 +22,28 @@ const name = 'cursor';
|
|
|
22
22
|
*/
|
|
23
23
|
function format(context, opts = {}) {
|
|
24
24
|
if (!context || typeof context !== 'string') return '';
|
|
25
|
-
const version
|
|
25
|
+
const version = opts.version || 'unknown';
|
|
26
26
|
const timestamp = new Date().toISOString();
|
|
27
|
+
const meta = _confidenceMeta(opts);
|
|
27
28
|
const header = [
|
|
28
29
|
`# Code signatures — generated by SigMap v${version}`,
|
|
29
30
|
`# Updated: ${timestamp}`,
|
|
31
|
+
`# ${meta}`,
|
|
30
32
|
`# Regenerate: node gen-context.js`,
|
|
31
33
|
'',
|
|
32
34
|
].join('\n');
|
|
33
35
|
return header + context;
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
function _confidenceMeta(opts) {
|
|
39
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
40
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
41
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
42
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
43
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
44
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
/**
|
|
37
48
|
* Return the output file path for this adapter.
|
|
38
49
|
* @param {string} cwd - Project root
|
|
@@ -36,9 +36,11 @@ function format(context, opts = {}) {
|
|
|
36
36
|
? `Project: ${opts.projectName}\n`
|
|
37
37
|
: '';
|
|
38
38
|
|
|
39
|
+
const meta = _confidenceMeta(opts);
|
|
39
40
|
return [
|
|
40
41
|
`You are a coding assistant with complete knowledge of this codebase.`,
|
|
41
42
|
`The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
|
|
43
|
+
`<!-- ${meta} -->`,
|
|
42
44
|
projectLine,
|
|
43
45
|
`These signatures represent every public function, class, and type in the project.`,
|
|
44
46
|
`Refer to them when answering questions about code structure, APIs, and implementation.`,
|
|
@@ -91,4 +93,13 @@ function write(context, cwd, opts = {}) {
|
|
|
91
93
|
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
function _confidenceMeta(opts) {
|
|
97
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
98
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
99
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
100
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
101
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
102
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
module.exports = { name, format, outputPath, write };
|
|
@@ -34,9 +34,11 @@ function format(context, opts = {}) {
|
|
|
34
34
|
? `Project: ${opts.projectName}\n`
|
|
35
35
|
: '';
|
|
36
36
|
|
|
37
|
+
const meta = _confidenceMeta(opts);
|
|
37
38
|
return [
|
|
38
39
|
`You are a coding assistant with full knowledge of this codebase.`,
|
|
39
40
|
`Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
|
|
41
|
+
`<!-- ${meta} -->`,
|
|
40
42
|
projectLine,
|
|
41
43
|
`Use these signatures to answer questions about the code accurately.`,
|
|
42
44
|
`When the user asks about a specific file or function, refer to the signatures below.`,
|
|
@@ -57,4 +59,13 @@ function outputPath(cwd) {
|
|
|
57
59
|
return path.join(cwd, '.github', 'openai-context.md');
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
function _confidenceMeta(opts) {
|
|
63
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
64
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
65
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
66
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
67
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
68
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
module.exports = { name, format, outputPath };
|
|
@@ -22,17 +22,28 @@ const name = 'windsurf';
|
|
|
22
22
|
*/
|
|
23
23
|
function format(context, opts = {}) {
|
|
24
24
|
if (!context || typeof context !== 'string') return '';
|
|
25
|
-
const version
|
|
25
|
+
const version = opts.version || 'unknown';
|
|
26
26
|
const timestamp = new Date().toISOString();
|
|
27
|
+
const meta = _confidenceMeta(opts);
|
|
27
28
|
const header = [
|
|
28
29
|
`# Code signatures — generated by SigMap v${version}`,
|
|
29
30
|
`# Updated: ${timestamp}`,
|
|
31
|
+
`# ${meta}`,
|
|
30
32
|
`# Regenerate: node gen-context.js`,
|
|
31
33
|
'',
|
|
32
34
|
].join('\n');
|
|
33
35
|
return header + context;
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
function _confidenceMeta(opts) {
|
|
39
|
+
const parts = [`version=${opts.version || 'unknown'}`];
|
|
40
|
+
if (opts.confidence) parts.push(`confidence=${opts.confidence}`);
|
|
41
|
+
if (opts.coverage != null) parts.push(`coverage=${opts.coverage}%`);
|
|
42
|
+
if (opts.dropped != null) parts.push(`dropped=${opts.dropped}`);
|
|
43
|
+
if (opts.commit) parts.push(`commit=${opts.commit}`);
|
|
44
|
+
return `sigmap: ${parts.join(' ')}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
/**
|
|
37
48
|
* Return the output file path for this adapter.
|
|
38
49
|
* @param {string} cwd - Project root
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SigMap coverage scorer — v4.0.0
|
|
5
|
+
*
|
|
6
|
+
* Measures what fraction of source files made it into the context output
|
|
7
|
+
* after token-budget application. This is complementary to the health score:
|
|
8
|
+
* - Health score = context freshness / reduction quality / budget compliance
|
|
9
|
+
* - Coverage score = how much of the codebase is represented in context
|
|
10
|
+
*
|
|
11
|
+
* Grade scale: A ≥ 90% | B ≥ 75% | C ≥ 50% | D < 50%
|
|
12
|
+
*
|
|
13
|
+
* @param {string} cwd
|
|
14
|
+
* @param {Array<{filePath:string}>} fileEntries - files that made it into output
|
|
15
|
+
* @param {{srcDirs:string[], exclude:string[]}} config
|
|
16
|
+
* @returns {{
|
|
17
|
+
* score: number,
|
|
18
|
+
* grade: 'A'|'B'|'C'|'D',
|
|
19
|
+
* total: number,
|
|
20
|
+
* included: number,
|
|
21
|
+
* dropped: number,
|
|
22
|
+
* confidence: 'HIGH'|'MEDIUM'|'LOW',
|
|
23
|
+
* perModule: Map<string, {total:number, included:number, pct:number}>,
|
|
24
|
+
* }}
|
|
25
|
+
*/
|
|
26
|
+
function coverageScore(cwd, fileEntries, config) {
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
|
|
30
|
+
const srcDirs = (config && Array.isArray(config.srcDirs) && config.srcDirs.length > 0)
|
|
31
|
+
? config.srcDirs
|
|
32
|
+
: ['src', 'app', 'lib'];
|
|
33
|
+
|
|
34
|
+
const excludeSet = new Set([
|
|
35
|
+
'node_modules', '.git', 'dist', 'build', 'out', '__pycache__',
|
|
36
|
+
'.next', 'coverage', 'target', 'vendor', '.context',
|
|
37
|
+
]);
|
|
38
|
+
if (config && Array.isArray(config.exclude)) {
|
|
39
|
+
for (const x of config.exclude) excludeSet.add(String(x));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const includedSet = new Set((fileEntries || []).map(f => f.filePath));
|
|
43
|
+
|
|
44
|
+
// Walk all source files from srcDirs
|
|
45
|
+
const allSource = [];
|
|
46
|
+
for (const relDir of srcDirs) {
|
|
47
|
+
const absDir = path.resolve(cwd, relDir);
|
|
48
|
+
if (fs.existsSync(absDir)) _walk(absDir, excludeSet, allSource);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const total = allSource.length;
|
|
52
|
+
const included = allSource.filter(f => includedSet.has(f)).length;
|
|
53
|
+
const dropped = total - included;
|
|
54
|
+
const pct = total > 0 ? Math.round((included / total) * 100) : 100;
|
|
55
|
+
|
|
56
|
+
const grade = pct >= 90 ? 'A' : pct >= 75 ? 'B' : pct >= 50 ? 'C' : 'D';
|
|
57
|
+
const confidence = pct >= 90 ? 'HIGH' : pct >= 70 ? 'MEDIUM' : 'LOW';
|
|
58
|
+
|
|
59
|
+
// Per-module breakdown (one entry per srcDir)
|
|
60
|
+
const perModule = new Map();
|
|
61
|
+
for (const relDir of srcDirs) {
|
|
62
|
+
const absDir = path.resolve(cwd, relDir);
|
|
63
|
+
const modFiles = allSource.filter(f => f.startsWith(absDir + path.sep) || f === absDir);
|
|
64
|
+
const modIncl = modFiles.filter(f => includedSet.has(f)).length;
|
|
65
|
+
const modPct = modFiles.length > 0 ? Math.round((modIncl / modFiles.length) * 100) : 100;
|
|
66
|
+
perModule.set(relDir, { total: modFiles.length, included: modIncl, pct: modPct });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { score: pct, grade, total, included, dropped, confidence, perModule };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function _walk(dir, excludeSet, out) {
|
|
73
|
+
const fs = require('fs');
|
|
74
|
+
const path = require('path');
|
|
75
|
+
let entries;
|
|
76
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
77
|
+
for (const e of entries) {
|
|
78
|
+
if (excludeSet.has(e.name)) continue;
|
|
79
|
+
const full = path.join(dir, e.name);
|
|
80
|
+
if (e.isDirectory()) { _walk(full, excludeSet, out); }
|
|
81
|
+
else if (e.isFile()) { out.push(full); }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { coverageScore };
|
package/src/eval/analyzer.js
CHANGED
|
@@ -147,6 +147,9 @@ function analyzeFiles(files, cwd, opts) {
|
|
|
147
147
|
const tokens = tokenCount(sigs);
|
|
148
148
|
const covered = hasCoverage(filePath, cwd);
|
|
149
149
|
const isSlow = slow && elapsedMs > slowMs;
|
|
150
|
+
// v4.0: signal quality = sigs per line-of-code (higher = more informative to LLMs)
|
|
151
|
+
const linesOfCode = content.split('\n').length;
|
|
152
|
+
const signalQuality = linesOfCode > 0 ? parseFloat((sigs.length / linesOfCode).toFixed(4)) : 0;
|
|
150
153
|
|
|
151
154
|
stats.push({
|
|
152
155
|
file: rel,
|
|
@@ -154,6 +157,8 @@ function analyzeFiles(files, cwd, opts) {
|
|
|
154
157
|
sigs: sigs.length,
|
|
155
158
|
tokens,
|
|
156
159
|
covered,
|
|
160
|
+
linesOfCode,
|
|
161
|
+
signalQuality,
|
|
157
162
|
elapsedMs: slow ? elapsedMs : undefined,
|
|
158
163
|
slow: slow ? isSlow : undefined,
|
|
159
164
|
});
|
package/src/mcp/server.js
CHANGED