sigmap 3.6.0 → 4.0.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/AGENTS.md +128 -99
- package/CHANGELOG.md +56 -0
- package/README.md +80 -8
- package/gen-context.config.json.example +6 -3
- package/gen-context.js +237 -29
- 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,43 +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-15T06:03:22.464Z.
|
|
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
|
-
##
|
|
27
|
-
|
|
28
|
-
### packages/core/index.js
|
|
26
|
+
## changes (last 5 commits — 7 minutes ago)
|
|
29
27
|
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
src/analysis/coverage-score.js +coverageScore +_walk
|
|
29
|
+
src/eval/analyzer.js ~analyzeFiles
|
|
30
|
+
src/extractors/generic.js +extract
|
|
31
|
+
src/format/llm-txt.js +outputPath +format
|
|
32
|
+
src/format/llms-txt.js +outputPath +getShortCommit +detectVersion +format
|
|
33
|
+
packages/adapters/claude.js +_confidenceMeta ~format
|
|
34
|
+
packages/adapters/copilot.js +_confidenceMeta ~format
|
|
35
|
+
packages/adapters/cursor.js +_confidenceMeta ~format
|
|
36
|
+
packages/adapters/gemini.js +_confidenceMeta ~format ~write
|
|
37
|
+
packages/adapters/llm-full.js +outputPath +format +write
|
|
38
|
+
packages/adapters/openai.js +_confidenceMeta ~format ~outputPath
|
|
39
|
+
packages/adapters/windsurf.js +_confidenceMeta ~format
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
module.exports = { name, format, outputPath, write }
|
|
43
|
-
function format(context, opts = {}) → string
|
|
44
|
-
function outputPath(cwd) → string
|
|
45
|
-
function write(context, cwd, opts = {})
|
|
46
|
-
```
|
|
42
|
+
## packages
|
|
47
43
|
|
|
48
|
-
### packages/adapters/
|
|
44
|
+
### packages/adapters/claude.js
|
|
49
45
|
```
|
|
50
46
|
module.exports = { name, format, outputPath, write }
|
|
51
47
|
function format(context, opts = {}) → string
|
|
48
|
+
function _confidenceMeta(opts)
|
|
52
49
|
function outputPath(cwd) → string
|
|
53
50
|
function write(context, cwd, opts = {})
|
|
54
51
|
```
|
|
@@ -57,6 +54,7 @@ function write(context, cwd, opts = {})
|
|
|
57
54
|
```
|
|
58
55
|
module.exports = { name, format, outputPath, write }
|
|
59
56
|
function format(context, opts = {}) → string
|
|
57
|
+
function _confidenceMeta(opts)
|
|
60
58
|
function outputPath(cwd) → string
|
|
61
59
|
function write(context, cwd, opts = {})
|
|
62
60
|
```
|
|
@@ -65,6 +63,7 @@ function write(context, cwd, opts = {})
|
|
|
65
63
|
```
|
|
66
64
|
module.exports = { name, format, outputPath }
|
|
67
65
|
function format(context, opts = {}) → string
|
|
66
|
+
function _confidenceMeta(opts)
|
|
68
67
|
function outputPath(cwd) → string
|
|
69
68
|
```
|
|
70
69
|
|
|
@@ -74,15 +73,7 @@ module.exports = { name, format, outputPath, write }
|
|
|
74
73
|
function format(context, opts = {}) → string
|
|
75
74
|
function outputPath(cwd) → string
|
|
76
75
|
function write(context, cwd, opts = {})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
### packages/adapters/index.js
|
|
80
|
-
```
|
|
81
|
-
module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters }
|
|
82
|
-
function getAdapter(name) → { name: string, format: F
|
|
83
|
-
function listAdapters() → string[]
|
|
84
|
-
function adapt(context, adapterName, opts = {}) → string
|
|
85
|
-
function outputsToAdapters(outputs) → string[]
|
|
76
|
+
function _confidenceMeta(opts)
|
|
86
77
|
```
|
|
87
78
|
|
|
88
79
|
### packages/adapters/llm-full.js
|
|
@@ -98,15 +89,34 @@ function write(context, cwd, opts)
|
|
|
98
89
|
module.exports = { name, format, outputPath }
|
|
99
90
|
function format(context, opts = {}) → string
|
|
100
91
|
function outputPath(cwd) → string
|
|
92
|
+
function _confidenceMeta(opts)
|
|
101
93
|
```
|
|
102
94
|
|
|
103
95
|
### packages/adapters/windsurf.js
|
|
104
96
|
```
|
|
105
97
|
module.exports = { name, format, outputPath }
|
|
106
98
|
function format(context, opts = {}) → string
|
|
99
|
+
function _confidenceMeta(opts)
|
|
107
100
|
function outputPath(cwd) → string
|
|
108
101
|
```
|
|
109
102
|
|
|
103
|
+
### packages/adapters/codex.js
|
|
104
|
+
```
|
|
105
|
+
module.exports = { name, format, outputPath, write }
|
|
106
|
+
function format(context, opts = {}) → string
|
|
107
|
+
function outputPath(cwd) → string
|
|
108
|
+
function write(context, cwd, opts = {})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### packages/adapters/index.js
|
|
112
|
+
```
|
|
113
|
+
module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters }
|
|
114
|
+
function getAdapter(name) → { name: string, format: F
|
|
115
|
+
function listAdapters() → string[]
|
|
116
|
+
function adapt(context, adapterName, opts = {}) → string
|
|
117
|
+
function outputsToAdapters(outputs) → string[]
|
|
118
|
+
```
|
|
119
|
+
|
|
110
120
|
### packages/cli/index.js
|
|
111
121
|
```
|
|
112
122
|
module.exports = { CLI_ENTRY, run }
|
|
@@ -133,19 +143,25 @@ code-fence js
|
|
|
133
143
|
code-fence ---
|
|
134
144
|
```
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
### src/config/defaults.js
|
|
146
|
+
### packages/core/index.js
|
|
139
147
|
```
|
|
140
|
-
module.exports = {
|
|
148
|
+
module.exports = { extract, rank, buildSigIndex, scan, score, adapt }
|
|
149
|
+
function _resolveExtractor(language)
|
|
150
|
+
function extract(src, language) → string[]
|
|
151
|
+
function rank(query, sigIndex, opts) → { file: string, score: nu
|
|
152
|
+
function buildSigIndex(cwd) → Map<string, string[]>
|
|
153
|
+
function scan(sigs, filePath) → { safe: string[], redacte
|
|
154
|
+
function score(cwd) → { * score: number, * grad
|
|
155
|
+
function adapt(context, adapterName, opts = {}) → string
|
|
141
156
|
```
|
|
142
157
|
|
|
143
|
-
|
|
158
|
+
## src
|
|
159
|
+
|
|
160
|
+
### src/analysis/coverage-score.js
|
|
144
161
|
```
|
|
145
|
-
module.exports = {
|
|
146
|
-
function
|
|
147
|
-
function
|
|
148
|
-
function deepClone(obj)
|
|
162
|
+
module.exports = { coverageScore }
|
|
163
|
+
function coverageScore(cwd, fileEntries, config) → { * score: number, * grad
|
|
164
|
+
function _walk(dir, excludeSet, out)
|
|
149
165
|
```
|
|
150
166
|
|
|
151
167
|
### src/eval/analyzer.js
|
|
@@ -161,52 +177,26 @@ function formatAnalysisTable(stats, showSlow) → string
|
|
|
161
177
|
function formatAnalysisJSON(stats) → object
|
|
162
178
|
```
|
|
163
179
|
|
|
164
|
-
### src/extractors/
|
|
165
|
-
```
|
|
166
|
-
module.exports = { extract }
|
|
167
|
-
function extract(src) → string[]
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### src/extractors/patterns.js
|
|
171
|
-
```
|
|
172
|
-
module.exports = { extract }
|
|
173
|
-
function extract(src) → string[]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### src/extractors/properties.js
|
|
177
|
-
```
|
|
178
|
-
module.exports = { extract }
|
|
179
|
-
function extract(src) → string[]
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### src/extractors/python_dataclass.js
|
|
183
|
-
```
|
|
184
|
-
module.exports = { extract }
|
|
185
|
-
function extract(src) → string[]
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### src/extractors/toml.js
|
|
189
|
-
```
|
|
190
|
-
module.exports = { extract }
|
|
191
|
-
function extract(src) → string[]
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### src/extractors/typescript_react.js
|
|
180
|
+
### src/extractors/generic.js
|
|
195
181
|
```
|
|
196
182
|
module.exports = { extract }
|
|
197
|
-
function extract(src)
|
|
183
|
+
function extract(src)
|
|
198
184
|
```
|
|
199
185
|
|
|
200
|
-
### src/
|
|
186
|
+
### src/format/llm-txt.js
|
|
201
187
|
```
|
|
202
|
-
module.exports = {
|
|
203
|
-
function
|
|
188
|
+
module.exports = { format, outputPath }
|
|
189
|
+
function outputPath(cwd)
|
|
190
|
+
function format(context, cwd, version)
|
|
204
191
|
```
|
|
205
192
|
|
|
206
|
-
### src/
|
|
193
|
+
### src/format/llms-txt.js
|
|
207
194
|
```
|
|
208
|
-
module.exports = {
|
|
209
|
-
function
|
|
195
|
+
module.exports = { format, outputPath }
|
|
196
|
+
function outputPath(cwd)
|
|
197
|
+
function getShortCommit(cwd)
|
|
198
|
+
function detectVersion(cwd)
|
|
199
|
+
function format(context, cwd, writtenFiles, sigmapVersion)
|
|
210
200
|
```
|
|
211
201
|
|
|
212
202
|
### src/mcp/server.js
|
|
@@ -218,6 +208,19 @@ function dispatch(msg, cwd)
|
|
|
218
208
|
function start(cwd)
|
|
219
209
|
```
|
|
220
210
|
|
|
211
|
+
### src/config/defaults.js
|
|
212
|
+
```
|
|
213
|
+
module.exports = { DEFAULTS }
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### src/config/loader.js
|
|
217
|
+
```
|
|
218
|
+
module.exports = { loadConfig }
|
|
219
|
+
function detectAutoSrcDirs(cwd, excludeList) → string[]
|
|
220
|
+
function loadConfig(cwd) → object
|
|
221
|
+
function deepClone(obj)
|
|
222
|
+
```
|
|
223
|
+
|
|
221
224
|
### src/eval/runner.js
|
|
222
225
|
```
|
|
223
226
|
module.exports = { run, rank, loadTasks, buildSigIndex, formatTable, formatMetrics, tokenize }
|
|
@@ -301,12 +304,6 @@ module.exports = { extract }
|
|
|
301
304
|
function extract(src) → string[]
|
|
302
305
|
```
|
|
303
306
|
|
|
304
|
-
### src/extractors/generic.js
|
|
305
|
-
```
|
|
306
|
-
module.exports = { extract }
|
|
307
|
-
function extract(src)
|
|
308
|
-
```
|
|
309
|
-
|
|
310
307
|
### src/extractors/go.js
|
|
311
308
|
```
|
|
312
309
|
module.exports = { extract }
|
|
@@ -359,6 +356,18 @@ function extractMembers(block)
|
|
|
359
356
|
function normalizeParams(params)
|
|
360
357
|
```
|
|
361
358
|
|
|
359
|
+
### src/extractors/markdown.js
|
|
360
|
+
```
|
|
361
|
+
module.exports = { extract }
|
|
362
|
+
function extract(src) → string[]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### src/extractors/patterns.js
|
|
366
|
+
```
|
|
367
|
+
module.exports = { extract }
|
|
368
|
+
function extract(src) → string[]
|
|
369
|
+
```
|
|
370
|
+
|
|
362
371
|
### src/extractors/php.js
|
|
363
372
|
```
|
|
364
373
|
module.exports = { extract }
|
|
@@ -376,6 +385,12 @@ function diffSignatures(baseSigs, currentSigs) → {added:string[], removed:
|
|
|
376
385
|
function extractName(sig)
|
|
377
386
|
```
|
|
378
387
|
|
|
388
|
+
### src/extractors/properties.js
|
|
389
|
+
```
|
|
390
|
+
module.exports = { extract }
|
|
391
|
+
function extract(src) → string[]
|
|
392
|
+
```
|
|
393
|
+
|
|
379
394
|
### src/extractors/protobuf.js
|
|
380
395
|
```
|
|
381
396
|
module.exports = { extract }
|
|
@@ -395,6 +410,12 @@ function normalizeParams(params)
|
|
|
395
410
|
function extractDocHint(src, fnName, fnSigLine)
|
|
396
411
|
```
|
|
397
412
|
|
|
413
|
+
### src/extractors/python_dataclass.js
|
|
414
|
+
```
|
|
415
|
+
module.exports = { extract }
|
|
416
|
+
function extract(src) → string[]
|
|
417
|
+
```
|
|
418
|
+
|
|
398
419
|
### src/extractors/ruby.js
|
|
399
420
|
```
|
|
400
421
|
module.exports = { extract }
|
|
@@ -467,6 +488,12 @@ module.exports = { extractTodos }
|
|
|
467
488
|
function extractTodos(src) → {line:number, tag:string,
|
|
468
489
|
```
|
|
469
490
|
|
|
491
|
+
### src/extractors/toml.js
|
|
492
|
+
```
|
|
493
|
+
module.exports = { extract }
|
|
494
|
+
function extract(src) → string[]
|
|
495
|
+
```
|
|
496
|
+
|
|
470
497
|
### src/extractors/typescript.js
|
|
471
498
|
```
|
|
472
499
|
module.exports = { extract }
|
|
@@ -477,6 +504,12 @@ function extractClassMembers(block)
|
|
|
477
504
|
function normalizeParams(params)
|
|
478
505
|
```
|
|
479
506
|
|
|
507
|
+
### src/extractors/typescript_react.js
|
|
508
|
+
```
|
|
509
|
+
module.exports = { extract }
|
|
510
|
+
function extract(src) → string[]
|
|
511
|
+
```
|
|
512
|
+
|
|
480
513
|
### src/extractors/vue.js
|
|
481
514
|
```
|
|
482
515
|
module.exports = { extract }
|
|
@@ -485,6 +518,18 @@ function normalizeParams(params)
|
|
|
485
518
|
function normalizeType(type)
|
|
486
519
|
```
|
|
487
520
|
|
|
521
|
+
### src/extractors/vue_sfc.js
|
|
522
|
+
```
|
|
523
|
+
module.exports = { extract }
|
|
524
|
+
function extract(src) → string[]
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### src/extractors/xml.js
|
|
528
|
+
```
|
|
529
|
+
module.exports = { extract }
|
|
530
|
+
function extract(src) → string[]
|
|
531
|
+
```
|
|
532
|
+
|
|
488
533
|
### src/extractors/yaml.js
|
|
489
534
|
```
|
|
490
535
|
module.exports = { extract }
|
|
@@ -518,22 +563,6 @@ function generateDashboardHtml(cwd, health)
|
|
|
518
563
|
function renderHistoryCharts(cwd, health)
|
|
519
564
|
```
|
|
520
565
|
|
|
521
|
-
### src/format/llm-txt.js
|
|
522
|
-
```
|
|
523
|
-
module.exports = { format, outputPath }
|
|
524
|
-
function outputPath(cwd)
|
|
525
|
-
function format(context, cwd, version)
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
### src/format/llms-txt.js
|
|
529
|
-
```
|
|
530
|
-
module.exports = { format, outputPath }
|
|
531
|
-
function outputPath(cwd)
|
|
532
|
-
function getShortCommit(cwd)
|
|
533
|
-
function detectVersion(cwd)
|
|
534
|
-
function format(context, cwd, writtenFiles, sigmapVersion)
|
|
535
|
-
```
|
|
536
|
-
|
|
537
566
|
### src/graph/builder.js
|
|
538
567
|
```
|
|
539
568
|
module.exports = { build, buildFromCwd, extractFileDeps }
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,62 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [4.0.1] — 2026-04-15 — Config auto-detection fix
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Bundled `loadConfig` lacked `detectAutoSrcDirs`**: the inline `__factories["./src/config/loader"]` copy inside `gen-context.js` was a stripped-down version that returned raw `DEFAULTS` without filesystem auto-detection. After `--init` wrote a config with 6 hardcoded `srcDirs`, auto-detection was bypassed and custom project directories were missed — causing coverage to drop for any project whose source lives outside those 6 dirs. The bundled loader is now fully in sync with `src/config/loader.js`.
|
|
17
|
+
- **`--init` config hardcoded `srcDirs`**: `gen-context.config.json.example` had `"srcDirs": ["src","app","lib","packages","services","api"]` as a plain value. Any project that ran `--init` would lock into those 6 dirs and lose auto-detection. The example now omits `srcDirs` entirely and uses `_comment` keys to explain that auto-detection runs automatically. Users who need custom dirs can add `srcDirs` manually.
|
|
18
|
+
- **`gen-context.config.json` (SigMap repo)**: restored explicit `"srcDirs": ["src","packages"]` so the repo's own context generation is not affected by auto-detection picking up `docs-vp/`, `scripts/`, `test/`, and `vscode-extension/`.
|
|
19
|
+
- **Example `outputs` updated**: `gen-context.config.json.example` now lists all four standard adapters — `["copilot","codex","claude","gemini"]` — matching the recommended setup.
|
|
20
|
+
|
|
21
|
+
### Benchmarks (v4.0.1)
|
|
22
|
+
- Token reduction: **97.6% average** across 18 repos ✅
|
|
23
|
+
- Retrieval hit@5: **84.4%** (up from 83.3% in v4.0.0)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## [4.0.0] — 2026-04-15 — Intelligence Layer
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **Coverage score** (`src/analysis/coverage-score.js`): measures what fraction of source files made it into context after token-budget application.
|
|
31
|
+
- Grade scale: A ≥ 90% · B ≥ 75% · C ≥ 50% · D < 50%
|
|
32
|
+
- Confidence indicator: HIGH / MEDIUM / LOW
|
|
33
|
+
- Per-module breakdown per srcDir via `perModule` Map
|
|
34
|
+
- **Confidence indicators in all output writers**: every generated file now includes a metadata comment:
|
|
35
|
+
```
|
|
36
|
+
<!-- sigmap: version=4.0.0 confidence=HIGH coverage=94% dropped=9 commit=abc1234 -->
|
|
37
|
+
```
|
|
38
|
+
Applies to: `copilot`, `claude`, `cursor`, `windsurf`, `openai`, `gemini` adapters.
|
|
39
|
+
- **`--report` module heatmap**: ASCII bar chart per srcDir showing coverage percentage:
|
|
40
|
+
```
|
|
41
|
+
Module Coverage:
|
|
42
|
+
src ████████████████ 100% (64/64 files)
|
|
43
|
+
packages ██████████████░░ 86% (12/14 files)
|
|
44
|
+
```
|
|
45
|
+
`--report --json` gains a `coverage` object with `score`, `grade`, `confidence`, `totalFiles`, `includedFiles`, `droppedFiles`, and `perModule`.
|
|
46
|
+
- **`--diff` risk score**: each changed file is now classified LOW / MEDIUM / HIGH based on reverse-dependency BFS, public API exports, route status, and config-file status:
|
|
47
|
+
```
|
|
48
|
+
[sigmap] Risk: Changed files (3):
|
|
49
|
+
src/auth/service.ts [HIGH] — exports public API, 5 downstream dependents
|
|
50
|
+
src/config/database.ts [MEDIUM] — config file
|
|
51
|
+
src/utils/format.ts [LOW] — no dependents, internal utility
|
|
52
|
+
```
|
|
53
|
+
- **Coverage in post-run summary**: every normal run now prints a `Coverage` line:
|
|
54
|
+
```
|
|
55
|
+
Coverage : A (97%) — 76 of 78 source files included
|
|
56
|
+
```
|
|
57
|
+
- **Coverage in `--health` and `--health --json`**: coverage grade, score, and file counts are included in both text and JSON health output. `--health --json` adds `coverage`, `coverageGrade`, `coverageConfidence`, `coverageTotalFiles`, `coverageIncludedFiles`.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
- **Token budget drop order step 5**: now uses `signalQuality = sigs / linesOfCode` (least-informative files dropped first) instead of the previous "fewest sigs" heuristic.
|
|
61
|
+
- **`src/eval/analyzer.js` `analyzeFiles()` output**: each file stat now includes `linesOfCode` and `signalQuality` fields.
|
|
62
|
+
|
|
63
|
+
### Benchmark (v4.0.0)
|
|
64
|
+
- Token reduction: **97.6% average** across 18 repos (target ≥ 97.0%) ✅
|
|
65
|
+
- Retrieval hit@5: 83.3% (retrieval improvement targeted in v4.5 with adaptive query)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
13
69
|
## [3.5.0] — 2026-04-14 — Phase C/D Intelligence Expansion
|
|
14
70
|
|
|
15
71
|
### Added
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again.
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
> Latest: **
|
|
22
|
+
> Latest: **v4.0.0** — Intelligence Layer. Coverage score, confidence indicators in every output file, `--report` module heatmap, `--diff` risk scoring, and extractor quality-based drop order.
|
|
23
23
|
|
|
24
24
|
<div align="center">
|
|
25
25
|
<img src="demo.gif" alt="SigMap demo — reducing 80K tokens to 4K in under 10 seconds" width="760" />
|
|
@@ -145,7 +145,7 @@ Reproduced with `node scripts/run-benchmark.mjs` on public repos:
|
|
|
145
145
|
| fastify | JavaScript | 54.4K | 2.6K | **95.3%** |
|
|
146
146
|
| fastapi | Python | 178.4K | 5.2K | **97.1%** |
|
|
147
147
|
|
|
148
|
-
**Average: 97.
|
|
148
|
+
**Average: 97.6% reduction across 18 repos (16 languages).** See [`benchmarks/reports/token-reduction.md`](benchmarks/reports/token-reduction.md) or reproduce with `node scripts/run-benchmark.mjs`.
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
@@ -746,17 +746,89 @@ If `output` is omitted, the default `.github/copilot-instructions.md` is used.
|
|
|
746
746
|
|
|
747
747
|
## 📊 Observability
|
|
748
748
|
|
|
749
|
+
### Coverage score (v4.0)
|
|
750
|
+
|
|
751
|
+
Every run now prints a coverage line alongside token reduction:
|
|
752
|
+
|
|
753
|
+
```
|
|
754
|
+
───────────────────────────────────────────
|
|
755
|
+
SigMap v4.0.0
|
|
756
|
+
Files scanned : 76
|
|
757
|
+
Symbols found : 332
|
|
758
|
+
Token reduction: 94% (65,227 → 4,103)
|
|
759
|
+
Coverage : A (97%) — 76 of 78 source files included
|
|
760
|
+
Output : .github/copilot-instructions.md
|
|
761
|
+
───────────────────────────────────────────
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
The **coverage score** answers _how much of your codebase is represented in context_ after the token budget is applied. Grade scale: A ≥ 90% · B ≥ 75% · C ≥ 50% · D < 50%.
|
|
765
|
+
|
|
766
|
+
### Module heatmap in `--report`
|
|
767
|
+
|
|
749
768
|
```bash
|
|
750
|
-
|
|
751
|
-
|
|
769
|
+
sigmap --report
|
|
770
|
+
```
|
|
752
771
|
|
|
753
|
-
|
|
772
|
+
```
|
|
773
|
+
[sigmap] report:
|
|
774
|
+
version : 4.0.0
|
|
775
|
+
files processed : 76
|
|
776
|
+
reduction : 93.7%
|
|
777
|
+
coverage : A (97%) — 76 of 78 source files included
|
|
778
|
+
confidence : HIGH
|
|
779
|
+
|
|
780
|
+
Module Coverage:
|
|
781
|
+
src ████████████████ 100% (64/64 files)
|
|
782
|
+
packages ██████████████░░ 86% (12/14 files)
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
Machine-readable JSON (suitable for CI dashboards):
|
|
786
|
+
|
|
787
|
+
```bash
|
|
754
788
|
sigmap --report --json
|
|
755
|
-
# { "version": "
|
|
789
|
+
# { "version": "4.0.0", "finalTokens": 4103, "reductionPct": 93.7,
|
|
790
|
+
# "coverage": { "score": 97, "grade": "A", "confidence": "HIGH", ... } }
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### Composite health score
|
|
756
794
|
|
|
757
|
-
|
|
795
|
+
```bash
|
|
758
796
|
sigmap --health
|
|
759
|
-
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
```
|
|
800
|
+
[sigmap] health:
|
|
801
|
+
score : 80/100 (grade B)
|
|
802
|
+
coverage : A (97%) — 76 of 78 source files
|
|
803
|
+
strategy : full
|
|
804
|
+
...
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
```bash
|
|
808
|
+
sigmap --health --json
|
|
809
|
+
# { "score": 80, "grade": "B", "coverage": 97, "coverageGrade": "A",
|
|
810
|
+
# "tokens": 4103, "reduction": 93.7, ... }
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### Confidence indicators in generated files
|
|
814
|
+
|
|
815
|
+
Every output file now carries a metadata line so you can inspect freshness at a glance:
|
|
816
|
+
|
|
817
|
+
```
|
|
818
|
+
<!-- sigmap: version=4.0.0 confidence=HIGH coverage=97% dropped=2 commit=8540612 -->
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Diff risk score
|
|
822
|
+
|
|
823
|
+
```bash
|
|
824
|
+
sigmap --diff HEAD~3
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
```
|
|
828
|
+
[sigmap] Risk: Changed files (4):
|
|
829
|
+
src/auth/service.ts [HIGH] — exports public API, 5 downstream dependents
|
|
830
|
+
src/config/database.ts [MEDIUM] — config file
|
|
831
|
+
src/utils/format.ts [LOW] — no dependents, internal utility
|
|
760
832
|
```
|
|
761
833
|
|
|
762
834
|
### Self-healing CI
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "SigMap configuration — all keys are optional (defaults shown)",
|
|
2
|
+
"_comment": "SigMap configuration — all keys are optional (defaults shown). Copy to gen-context.config.json.",
|
|
3
3
|
|
|
4
4
|
"output": ".github/copilot-instructions.md",
|
|
5
5
|
|
|
6
|
-
"outputs": ["copilot", "codex"],
|
|
6
|
+
"outputs": ["copilot", "codex", "claude", "gemini"],
|
|
7
7
|
|
|
8
|
-
"
|
|
8
|
+
"_srcDirs_comment": "Source directories to scan. OMIT this key to use auto-detection (recommended).",
|
|
9
|
+
"_srcDirs_comment2": "Auto-detection reads package.json/go.mod/Cargo.toml and scans top-level dirs automatically.",
|
|
10
|
+
"_srcDirs_comment3": "Only set this explicitly if auto-detection misses part of your project layout.",
|
|
11
|
+
"_srcDirs_example": ["src", "app", "lib", "packages", "services", "api"],
|
|
9
12
|
|
|
10
13
|
"exclude": [
|
|
11
14
|
"node_modules", ".git", "dist", "build", "out",
|
package/gen-context.js
CHANGED
|
@@ -116,14 +116,98 @@ __factories["./src/config/defaults"] = function(module, exports) {
|
|
|
116
116
|
|
|
117
117
|
// ── ./src/config/loader ──
|
|
118
118
|
__factories["./src/config/loader"] = function(module, exports) {
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
const fs = require('fs');
|
|
121
121
|
const path = require('path');
|
|
122
122
|
const { DEFAULTS } = __require('./src/config/defaults');
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
// Keys that are valid in gen-context.config.json
|
|
125
125
|
const KNOWN_KEYS = new Set(Object.keys(DEFAULTS));
|
|
126
|
-
|
|
126
|
+
|
|
127
|
+
// Common top-level folder names that reliably hold source code
|
|
128
|
+
const COMMON_CODE_DIRS = new Set([
|
|
129
|
+
'src', 'app', 'lib', 'packages', 'services', 'api', 'core', 'cmd',
|
|
130
|
+
'internal', 'pkg', 'handlers', 'controllers', 'models', 'views',
|
|
131
|
+
'components', 'pages', 'routes', 'middleware', 'utils', 'helpers',
|
|
132
|
+
'modules', 'plugins', 'extensions', 'adapters', 'drivers',
|
|
133
|
+
'hooks', 'composables', 'stores', 'features', 'domain', 'infra',
|
|
134
|
+
'infrastructure', 'application', 'data', 'Sources', 'Tests',
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
const SUPPORTED_CODE_EXTS = new Set([
|
|
138
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
139
|
+
'.py', '.pyw', '.java', '.kt', '.kts', '.go', '.rs', '.cs',
|
|
140
|
+
'.cpp', '.c', '.h', '.hpp', '.cc', '.rb', '.rake', '.php',
|
|
141
|
+
'.swift', '.dart', '.scala', '.sc', '.vue', '.svelte',
|
|
142
|
+
'.html', '.htm', '.css', '.scss', '.sass', '.less',
|
|
143
|
+
'.yml', '.yaml', '.sh', '.bash', '.zsh', '.fish',
|
|
144
|
+
'.sql', '.graphql', '.gql', '.tf', '.tfvars', '.proto',
|
|
145
|
+
'.toml', '.properties', '.xml', '.md',
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
function detectAutoSrcDirs(cwd, excludeList) {
|
|
149
|
+
const excludeSet = new Set(excludeList || []);
|
|
150
|
+
const candidates = new Set(DEFAULTS.srcDirs);
|
|
151
|
+
|
|
152
|
+
// Manifest-based detection
|
|
153
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
154
|
+
if (fs.existsSync(pkgPath)) {
|
|
155
|
+
try {
|
|
156
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
157
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
|
|
158
|
+
if (allDeps.react || allDeps.next)
|
|
159
|
+
for (const d of ['src', 'app', 'pages', 'components', 'hooks', 'lib', 'utils']) candidates.add(d);
|
|
160
|
+
if (allDeps['@angular/core'])
|
|
161
|
+
for (const d of ['src', 'projects', 'apps', 'libs']) candidates.add(d);
|
|
162
|
+
if (allDeps['@nestjs/core'])
|
|
163
|
+
for (const d of ['src', 'libs', 'apps']) candidates.add(d);
|
|
164
|
+
if (allDeps.vue)
|
|
165
|
+
for (const d of ['src', 'components', 'views', 'stores', 'composables', 'plugins']) candidates.add(d);
|
|
166
|
+
if (allDeps.svelte || allDeps['@sveltejs/kit'])
|
|
167
|
+
for (const d of ['src', 'lib', 'routes']) candidates.add(d);
|
|
168
|
+
if (allDeps.nx || allDeps.turbo || allDeps.lerna || pkg.workspaces)
|
|
169
|
+
for (const d of ['packages', 'apps', 'libs', 'services']) candidates.add(d);
|
|
170
|
+
} catch (_) {}
|
|
171
|
+
}
|
|
172
|
+
if (fs.existsSync(path.join(cwd, 'pyproject.toml')) || fs.existsSync(path.join(cwd, 'requirements.txt')) || fs.existsSync(path.join(cwd, 'setup.py')))
|
|
173
|
+
for (const d of ['src', 'app', 'apps', 'tests', 'examples', 'instance', 'blueprints']) candidates.add(d);
|
|
174
|
+
if (fs.existsSync(path.join(cwd, 'Gemfile')))
|
|
175
|
+
for (const d of ['app', 'lib', 'config', 'db', 'spec', 'test']) candidates.add(d);
|
|
176
|
+
if (fs.existsSync(path.join(cwd, 'composer.json')))
|
|
177
|
+
for (const d of ['app', 'resources', 'routes', 'database', 'tests']) candidates.add(d);
|
|
178
|
+
if (fs.existsSync(path.join(cwd, 'go.mod')))
|
|
179
|
+
for (const d of ['cmd', 'internal', 'pkg', 'api', 'handler', 'handlers', 'middleware', 'service']) candidates.add(d);
|
|
180
|
+
if (fs.existsSync(path.join(cwd, 'Cargo.toml')))
|
|
181
|
+
for (const d of ['src', 'crates', 'examples', 'tests', 'benches']) candidates.add(d);
|
|
182
|
+
if (fs.existsSync(path.join(cwd, 'pubspec.yaml')))
|
|
183
|
+
for (const d of ['lib', 'test', 'integration_test', 'example', 'bin']) candidates.add(d);
|
|
184
|
+
if (fs.existsSync(path.join(cwd, 'Package.swift')))
|
|
185
|
+
for (const d of ['Sources', 'Tests']) candidates.add(d);
|
|
186
|
+
|
|
187
|
+
// Top-level directory scan
|
|
188
|
+
try {
|
|
189
|
+
const entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || excludeSet.has(entry.name)) continue;
|
|
192
|
+
const lname = entry.name.toLowerCase();
|
|
193
|
+
if (COMMON_CODE_DIRS.has(entry.name) || COMMON_CODE_DIRS.has(lname)) { candidates.add(entry.name); continue; }
|
|
194
|
+
const dirPath = path.join(cwd, entry.name);
|
|
195
|
+
try {
|
|
196
|
+
const subs = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
197
|
+
if (subs.some(s => s.isFile() && (SUPPORTED_CODE_EXTS.has(path.extname(s.name).toLowerCase()) || s.name === 'Dockerfile'))) {
|
|
198
|
+
candidates.add(entry.name); continue;
|
|
199
|
+
}
|
|
200
|
+
if (subs.some(s => s.isDirectory() && ['src', 'lib', 'main', 'java', 'kotlin', 'scala', 'python'].includes(s.name)))
|
|
201
|
+
candidates.add(entry.name);
|
|
202
|
+
} catch (_) {}
|
|
203
|
+
}
|
|
204
|
+
} catch (_) {}
|
|
205
|
+
|
|
206
|
+
return Array.from(candidates).filter(d => {
|
|
207
|
+
try { return fs.statSync(path.join(cwd, d)).isDirectory(); } catch (_) { return false; }
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
127
211
|
/**
|
|
128
212
|
* Load and merge configuration for a given working directory.
|
|
129
213
|
*
|
|
@@ -133,18 +217,24 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
133
217
|
function loadConfig(cwd) {
|
|
134
218
|
const configPath = path.join(cwd, 'gen-context.config.json');
|
|
135
219
|
if (!fs.existsSync(configPath)) {
|
|
136
|
-
|
|
220
|
+
const cfg = deepClone(DEFAULTS);
|
|
221
|
+
const detected = detectAutoSrcDirs(cwd, cfg.exclude);
|
|
222
|
+
if (detected.length > 0) cfg.srcDirs = detected;
|
|
223
|
+
return cfg;
|
|
137
224
|
}
|
|
138
|
-
|
|
225
|
+
|
|
139
226
|
let userConfig;
|
|
140
227
|
try {
|
|
141
228
|
const raw = fs.readFileSync(configPath, 'utf8');
|
|
142
229
|
userConfig = JSON.parse(raw);
|
|
143
230
|
} catch (err) {
|
|
144
231
|
console.warn(`[sigmap] config parse error in ${configPath}: ${err.message}`);
|
|
145
|
-
|
|
232
|
+
const cfg = deepClone(DEFAULTS);
|
|
233
|
+
const detected = detectAutoSrcDirs(cwd, cfg.exclude);
|
|
234
|
+
if (detected.length > 0) cfg.srcDirs = detected;
|
|
235
|
+
return cfg;
|
|
146
236
|
}
|
|
147
|
-
|
|
237
|
+
|
|
148
238
|
// Warn on unknown keys (helps catch typos)
|
|
149
239
|
for (const key of Object.keys(userConfig)) {
|
|
150
240
|
if (key.startsWith('_')) continue; // allow _comment etc.
|
|
@@ -152,7 +242,7 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
152
242
|
console.warn(`[sigmap] unknown config key: "${key}" (ignored)`);
|
|
153
243
|
}
|
|
154
244
|
}
|
|
155
|
-
|
|
245
|
+
|
|
156
246
|
// Deep merge: top-level known keys from user override defaults
|
|
157
247
|
// For object values (e.g. mcp), merge one level deep
|
|
158
248
|
const merged = deepClone(DEFAULTS);
|
|
@@ -167,6 +257,13 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
167
257
|
merged[key] = val;
|
|
168
258
|
}
|
|
169
259
|
}
|
|
260
|
+
|
|
261
|
+
// If user didn't specify srcDirs, auto-detect; fall back to DEFAULTS if nothing found
|
|
262
|
+
if (!Array.isArray(userConfig.srcDirs)) {
|
|
263
|
+
const detected = detectAutoSrcDirs(cwd, merged.exclude);
|
|
264
|
+
merged.srcDirs = detected.length > 0 ? detected : deepClone(DEFAULTS.srcDirs);
|
|
265
|
+
}
|
|
266
|
+
|
|
170
267
|
// Backward compat (v3.0+): if user specified 'adapters', use it as 'outputs' too.
|
|
171
268
|
// If user specified only 'outputs' (old configs), mirror to 'adapters'.
|
|
172
269
|
if (merged.adapters && !Array.isArray(merged.adapters)) merged.adapters = null;
|
|
@@ -177,13 +274,13 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
177
274
|
}
|
|
178
275
|
return merged;
|
|
179
276
|
}
|
|
180
|
-
|
|
277
|
+
|
|
181
278
|
function deepClone(obj) {
|
|
182
279
|
return JSON.parse(JSON.stringify(obj));
|
|
183
280
|
}
|
|
184
|
-
|
|
185
|
-
module.exports = { loadConfig };
|
|
186
|
-
|
|
281
|
+
|
|
282
|
+
module.exports = { loadConfig, detectAutoSrcDirs };
|
|
283
|
+
|
|
187
284
|
};
|
|
188
285
|
|
|
189
286
|
// ── ./src/extractors/cpp ──
|
|
@@ -4478,7 +4575,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
4478
4575
|
|
|
4479
4576
|
const SERVER_INFO = {
|
|
4480
4577
|
name: 'sigmap',
|
|
4481
|
-
version: '
|
|
4578
|
+
version: '4.0.1',
|
|
4482
4579
|
description: 'SigMap MCP server — code signatures on demand',
|
|
4483
4580
|
};
|
|
4484
4581
|
|
|
@@ -6040,7 +6137,7 @@ const path = require('path');
|
|
|
6040
6137
|
const os = require('os');
|
|
6041
6138
|
const { execSync } = require('child_process');
|
|
6042
6139
|
|
|
6043
|
-
const VERSION = '
|
|
6140
|
+
const VERSION = '4.0.1';
|
|
6044
6141
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
6045
6142
|
|
|
6046
6143
|
function requireSourceOrBundled(key) {
|
|
@@ -6276,13 +6373,17 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
6276
6373
|
else if (isTestFile(e.filePath)) { priority = 8; dropReason = 'budget: test file'; }
|
|
6277
6374
|
else if (isConfigFile(e.filePath)) { priority = 6; dropReason = 'budget: config file'; }
|
|
6278
6375
|
else priority = 4;
|
|
6279
|
-
|
|
6376
|
+
// v4.0: signal quality = sigs per line-of-code (higher = more informative)
|
|
6377
|
+
const loc = e.content ? e.content.split('\n').length : 1;
|
|
6378
|
+
const signalQuality = loc > 0 ? (e.sigs ? e.sigs.length : 0) / loc : 0;
|
|
6379
|
+
return { ...e, priority, dropReason, signalQuality };
|
|
6280
6380
|
});
|
|
6281
6381
|
|
|
6282
|
-
// Within same priority
|
|
6382
|
+
// Within same priority: sort by mtime ascending (oldest first), then signalQuality ascending (least informative first)
|
|
6283
6383
|
withPriority.sort((a, b) => {
|
|
6284
6384
|
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
6285
|
-
return (a.mtime || 0) - (b.mtime || 0);
|
|
6385
|
+
if ((a.mtime || 0) !== (b.mtime || 0)) return (a.mtime || 0) - (b.mtime || 0);
|
|
6386
|
+
return (a.signalQuality || 0) - (b.signalQuality || 0);
|
|
6286
6387
|
});
|
|
6287
6388
|
|
|
6288
6389
|
const kept = [];
|
|
@@ -6716,11 +6817,17 @@ function writeClaude(content, cwd) {
|
|
|
6716
6817
|
// ---------------------------------------------------------------------------
|
|
6717
6818
|
// Report
|
|
6718
6819
|
// ---------------------------------------------------------------------------
|
|
6719
|
-
function
|
|
6820
|
+
function _coverageBar(pct, width) {
|
|
6821
|
+
width = width || 16;
|
|
6822
|
+
const filled = Math.round(pct / 100 * width);
|
|
6823
|
+
return '\u2588'.repeat(filled) + '\u2591'.repeat(width - filled);
|
|
6824
|
+
}
|
|
6825
|
+
|
|
6826
|
+
function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson, budgetLimit, coverageResult) {
|
|
6720
6827
|
const reduction = inputTokens > 0 ? (100 - (finalTokens / inputTokens) * 100).toFixed(1) : 0;
|
|
6721
6828
|
const overBudget = finalTokens > (budgetLimit || 6000);
|
|
6722
6829
|
if (asJson) {
|
|
6723
|
-
|
|
6830
|
+
const payload = {
|
|
6724
6831
|
version: VERSION,
|
|
6725
6832
|
timestamp: new Date().toISOString(),
|
|
6726
6833
|
rawTokens: inputTokens,
|
|
@@ -6731,7 +6838,22 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
6731
6838
|
reductionPct: parseFloat(reduction),
|
|
6732
6839
|
overBudget,
|
|
6733
6840
|
budgetLimit: budgetLimit || 6000,
|
|
6734
|
-
}
|
|
6841
|
+
};
|
|
6842
|
+
if (coverageResult) {
|
|
6843
|
+
payload.coverage = {
|
|
6844
|
+
score: coverageResult.score,
|
|
6845
|
+
grade: coverageResult.grade,
|
|
6846
|
+
confidence: coverageResult.confidence,
|
|
6847
|
+
totalFiles: coverageResult.total,
|
|
6848
|
+
includedFiles: coverageResult.included,
|
|
6849
|
+
droppedFiles: coverageResult.dropped,
|
|
6850
|
+
perModule: Object.fromEntries(
|
|
6851
|
+
Array.from(coverageResult.perModule.entries())
|
|
6852
|
+
.map(([k, v]) => [k, v])
|
|
6853
|
+
),
|
|
6854
|
+
};
|
|
6855
|
+
}
|
|
6856
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
6735
6857
|
// Exit 1 in CI if over budget — lets pipelines fail fast
|
|
6736
6858
|
if (overBudget) process.exitCode = 1;
|
|
6737
6859
|
} else {
|
|
@@ -6743,6 +6865,20 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
6743
6865
|
console.log(` output tokens : ~${finalTokens}`);
|
|
6744
6866
|
console.log(` budget limit : ${budgetLimit || 6000}`);
|
|
6745
6867
|
console.log(` reduction : ${reduction}%`);
|
|
6868
|
+
if (coverageResult) {
|
|
6869
|
+
console.log(` coverage : ${coverageResult.grade} (${coverageResult.score}%) — ${coverageResult.included} of ${coverageResult.total} source files included`);
|
|
6870
|
+
console.log(` confidence : ${coverageResult.confidence}`);
|
|
6871
|
+
if (coverageResult.perModule && coverageResult.perModule.size > 0) {
|
|
6872
|
+
console.log('');
|
|
6873
|
+
console.log(' Module Coverage:');
|
|
6874
|
+
for (const [dir, mod] of coverageResult.perModule) {
|
|
6875
|
+
if (mod.total === 0) continue;
|
|
6876
|
+
const bar = _coverageBar(mod.pct);
|
|
6877
|
+
const attention = mod.pct < 50 ? ' \u2190 attention needed' : '';
|
|
6878
|
+
console.log(` ${dir.padEnd(18)} ${bar} ${String(mod.pct).padStart(3)}% (${mod.included}/${mod.total} files)${attention}`);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6746
6882
|
if (overBudget) console.warn(`[sigmap] WARNING: output (${finalTokens} tokens) exceeds budget (${budgetLimit || 6000})`);
|
|
6747
6883
|
}
|
|
6748
6884
|
}
|
|
@@ -7032,6 +7168,41 @@ function runDiff(cwd, config, stagedOnly, baseRef) {
|
|
|
7032
7168
|
const scope = baseRef ? `diff-vs-${baseRef}` : (stagedOnly ? 'staged' : 'diff');
|
|
7033
7169
|
console.warn(`[sigmap] ${scope} files: ${fileEntries.length}, diff tokens: ~${finalTokens}`);
|
|
7034
7170
|
|
|
7171
|
+
// v4.0: risk score per changed file
|
|
7172
|
+
try {
|
|
7173
|
+
const { buildFromCwd } = requireSourceOrBundled('./src/graph/builder');
|
|
7174
|
+
const graph = buildFromCwd(cwd, { silent: true });
|
|
7175
|
+
const reverseGraph = graph.reverse || new Map();
|
|
7176
|
+
|
|
7177
|
+
function _isRouteFile(f) {
|
|
7178
|
+
return /\/(routes?|pages?|controllers?|handlers?|api)\//i.test(f)
|
|
7179
|
+
|| /\.(route|page|controller|handler)\.\w+$/.test(f);
|
|
7180
|
+
}
|
|
7181
|
+
function _riskScore(filePath, sigs, revGraph) {
|
|
7182
|
+
let s = 0;
|
|
7183
|
+
if (sigs.some(sig => sig.includes('export') || sig.includes('module.exports'))) s += 2;
|
|
7184
|
+
const deps = revGraph.get(filePath) || new Set();
|
|
7185
|
+
if (deps.size > 3) s += 2;
|
|
7186
|
+
if (_isRouteFile(filePath)) s += 1;
|
|
7187
|
+
if (/\.(config|env|settings)\.\w+$/.test(filePath)) s += 1;
|
|
7188
|
+
return s >= 4 ? 'HIGH' : s >= 2 ? 'MEDIUM' : 'LOW';
|
|
7189
|
+
}
|
|
7190
|
+
|
|
7191
|
+
console.warn(`[sigmap] Risk: Changed files (${fileEntries.length}):`);
|
|
7192
|
+
for (const fe of fileEntries) {
|
|
7193
|
+
const level = _riskScore(fe.filePath, fe.sigs, reverseGraph);
|
|
7194
|
+
const rel = path.relative(cwd, fe.filePath).replace(/\\/g, '/');
|
|
7195
|
+
const deps = reverseGraph.get(fe.filePath) || new Set();
|
|
7196
|
+
const reasons = [];
|
|
7197
|
+
if (fe.sigs.some(s => s.includes('export') || s.includes('module.exports'))) reasons.push('exports public API');
|
|
7198
|
+
if (deps.size > 0) reasons.push(`${deps.size} downstream dependent${deps.size === 1 ? '' : 's'}`);
|
|
7199
|
+
if (_isRouteFile(fe.filePath)) reasons.push('route file');
|
|
7200
|
+
if (/\.(config|env|settings)\.\w+$/.test(fe.filePath)) reasons.push('config file');
|
|
7201
|
+
const label = level === 'HIGH' ? '[HIGH] ' : level === 'MEDIUM' ? '[MEDIUM]' : '[LOW] ';
|
|
7202
|
+
console.warn(` ${rel.padEnd(40)} ${label}${reasons.length ? ' — ' + reasons.join(', ') : ''}`);
|
|
7203
|
+
}
|
|
7204
|
+
} catch (_) {}
|
|
7205
|
+
|
|
7035
7206
|
if (process.argv.includes('--report')) {
|
|
7036
7207
|
// Also show what the full run would cost for comparison
|
|
7037
7208
|
const fullResult = runGenerate(cwd, config, true);
|
|
@@ -7176,11 +7347,17 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7176
7347
|
const droppedCount = beforeCount - budgeted.length;
|
|
7177
7348
|
const content = formatOutput(budgeted, cwd, false, config, null);
|
|
7178
7349
|
const finalTokens = estimateTokens(content);
|
|
7179
|
-
|
|
7350
|
+
// v4.0: compute coverage score for --report heatmap
|
|
7351
|
+
let coverageResult = null;
|
|
7352
|
+
try {
|
|
7353
|
+
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7354
|
+
coverageResult = coverageScore(cwd, budgeted, config);
|
|
7355
|
+
} catch (_) {}
|
|
7356
|
+
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount, coverageResult };
|
|
7180
7357
|
}
|
|
7181
7358
|
|
|
7182
7359
|
if (reportMode || process.argv.includes('--report')) {
|
|
7183
|
-
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, config.maxTokens);
|
|
7360
|
+
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, config.maxTokens, result.coverageResult);
|
|
7184
7361
|
}
|
|
7185
7362
|
|
|
7186
7363
|
// Usage tracking (v0.9) — optional append-only NDJSON log
|
|
@@ -7212,17 +7389,26 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7212
7389
|
const pct = result.inputTokenTotal > 0
|
|
7213
7390
|
? Math.round((1 - result.finalTokens / result.inputTokenTotal) * 100)
|
|
7214
7391
|
: 0;
|
|
7215
|
-
|
|
7392
|
+
// v4.0: coverage score in post-run summary
|
|
7393
|
+
let coverageLine = '';
|
|
7394
|
+
try {
|
|
7395
|
+
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7396
|
+
const cov = coverageScore(cwd, fileEntries, config);
|
|
7397
|
+
coverageLine = ` Coverage : ${cov.grade} (${cov.score}%) \u2014 ${cov.included} of ${cov.total} source files included`;
|
|
7398
|
+
} catch (_) {}
|
|
7399
|
+
const lines = [
|
|
7216
7400
|
bar,
|
|
7217
7401
|
` SigMap v${VERSION}`,
|
|
7218
7402
|
` Files scanned : ${result.fileCount}`,
|
|
7219
7403
|
` Symbols found : ${syms.toLocaleString()}`,
|
|
7220
7404
|
` Token reduction: ${pct}% (${result.inputTokenTotal.toLocaleString()} \u2192 ${result.finalTokens.toLocaleString()})`,
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7405
|
+
];
|
|
7406
|
+
if (coverageLine) lines.push(coverageLine);
|
|
7407
|
+
lines.push(` Output : .github/copilot-instructions.md`);
|
|
7408
|
+
lines.push(bar);
|
|
7409
|
+
lines.push(` Try: "explain the architecture" \u00b7 "find the auth module"`);
|
|
7410
|
+
lines.push(bar, '');
|
|
7411
|
+
process.stderr.write(lines.join('\n'));
|
|
7226
7412
|
}
|
|
7227
7413
|
|
|
7228
7414
|
return result;
|
|
@@ -7725,6 +7911,17 @@ function main() {
|
|
|
7725
7911
|
if (args.includes('--health')) {
|
|
7726
7912
|
const { score } = __require('./src/health/scorer');
|
|
7727
7913
|
const result = score(cwd);
|
|
7914
|
+
// v4.0: compute live coverage score to include in health output
|
|
7915
|
+
let coverageResult = null;
|
|
7916
|
+
try {
|
|
7917
|
+
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7918
|
+
const { loadConfig: lc } = requireSourceOrBundled('./src/config/loader');
|
|
7919
|
+
const cfg = lc(cwd);
|
|
7920
|
+
// Use all files from srcDirs as proxies for "included" (no budget applied in health mode)
|
|
7921
|
+
const allFiles = buildFileList(cwd, cfg);
|
|
7922
|
+
const fakeEntries = allFiles.map(f => ({ filePath: f }));
|
|
7923
|
+
coverageResult = coverageScore(cwd, fakeEntries, cfg);
|
|
7924
|
+
} catch (_) {}
|
|
7728
7925
|
if (args.includes('--json')) {
|
|
7729
7926
|
// Feature 3 (VS Code) + Feature 5 (JetBrains): emit tokens + reduction for plugins
|
|
7730
7927
|
const ctxPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
@@ -7733,10 +7930,21 @@ function main() {
|
|
|
7733
7930
|
if (fs.existsSync(ctxPath)) {
|
|
7734
7931
|
try { tokens = estimateTokens(fs.readFileSync(ctxPath, 'utf8')); } catch (_) {}
|
|
7735
7932
|
}
|
|
7736
|
-
|
|
7933
|
+
const payload = { ...result, tokens, reduction };
|
|
7934
|
+
if (coverageResult) {
|
|
7935
|
+
payload.coverage = coverageResult.score;
|
|
7936
|
+
payload.coverageGrade = coverageResult.grade;
|
|
7937
|
+
payload.coverageConfidence = coverageResult.confidence;
|
|
7938
|
+
payload.coverageTotalFiles = coverageResult.total;
|
|
7939
|
+
payload.coverageIncludedFiles = coverageResult.included;
|
|
7940
|
+
}
|
|
7941
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
7737
7942
|
} else {
|
|
7738
7943
|
console.log('[sigmap] health:');
|
|
7739
7944
|
console.log(` score : ${result.score}/100 (grade ${result.grade})`);
|
|
7945
|
+
if (coverageResult) {
|
|
7946
|
+
console.log(` coverage : ${coverageResult.grade} (${coverageResult.score}%) — ${coverageResult.included} of ${coverageResult.total} source files`);
|
|
7947
|
+
}
|
|
7740
7948
|
console.log(` strategy : ${result.strategy}`);
|
|
7741
7949
|
console.log(` token reduction : ${result.tokenReductionPct !== null ? result.tokenReductionPct + '%' : 'no history'}`);
|
|
7742
7950
|
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