sigmap 5.3.0 → 5.5.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/CHANGELOG.md +28 -0
- package/README.md +43 -4
- package/gen-context.js +462 -7
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/analysis/coverage-score.js +27 -7
- package/src/mcp/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,34 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [5.5.0] — 2026-04-17
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Coverage grade now accurate for mixed-content projects** — `coverageScore()` counts only code files (`.ts`, `.js`, `.py`, `.go`, etc.) in the denominator. Previously, `package.json`, `tsconfig.json`, `README.md`, and other non-code files were counted, causing inflated D-grades even when all code was covered (reported in discussion #81).
|
|
18
|
+
- **`--report` coverage label** — now reads `code files` instead of `source files`, and prints `(N non-code files skipped — json, md, config)` when non-code files were excluded.
|
|
19
|
+
- **`--report` actionable guidance** — modules marked `← attention needed` (<50% coverage) now show a tip block listing the three common causes and how to fix each.
|
|
20
|
+
- **`--health` label disambiguation** — coverage line renamed from `coverage … source files` to `file access … files accessible in srcDirs`, making it clearly distinct from the `--report` coverage metric.
|
|
21
|
+
- **`autoMaxTokens` silent-override warning** — when `autoMaxTokens` is active and overrides the user's `maxTokens` config value, `--report` now emits an explicit note explaining the override and how to disable it.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `src/analysis/coverage-score.js` exports `CODE_EXTS` (the allowlist Set) for use by other modules and tests.
|
|
26
|
+
- `coverageScore()` return object gains a `nonCodeSkipped` field (number of non-code files found in srcDirs but excluded from the denominator).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## [5.4.0] — 2026-04-17
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **Neovim plugin (`sigmap.nvim`)** — first-class Neovim integration in `neovim-plugin/`. Provides `:SigMap [args]` (async regen), `:SigMapQuery <text>` (TF-IDF retrieval in a floating window), `auto_run = true` (`BufWritePost` autocmd for source files), `require('sigmap').statusline()` for lualine/statusline widgets, and `:checkhealth sigmap` (validates Node 18+, binary presence, context file freshness).
|
|
35
|
+
- **Binary auto-detection** — plugin resolves the sigmap binary automatically: global `sigmap` → `npx sigmap` → local `gen-context.js` fallback; no manual config needed for most setups.
|
|
36
|
+
- **`release-neovim.yml` workflow** — tag `neovim-v*` to validate Lua files, run the full integration suite across Node 18/20/22, package the plugin as a `.tar.gz`, and create a GitHub Release.
|
|
37
|
+
- **CI now runs integration tests** — `ci.yml` runs both `node test/run.js` and `node test/integration/all.js` on every push and pull request.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
13
41
|
## [5.3.0] — 2026-04-17
|
|
14
42
|
|
|
15
43
|
### Added
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again
|
|
|
25
25
|
- Fewer retries (1.69 vs 2.84 prompts per task)
|
|
26
26
|
- Far smaller context (~2K–4K tokens instead of ~80K)
|
|
27
27
|
|
|
28
|
-
> Latest: **v5.
|
|
28
|
+
> Latest: **v5.5.0** — Coverage clarity + report UX plugin (`sigmap.nvim`). `:SigMap`, `:SigMapQuery`, auto-run on save, statusline widget, and `:checkhealth sigmap` for the #1 most-admired editor.
|
|
29
29
|
|
|
30
30
|
**What is new in v5.2**
|
|
31
31
|
- `sigmap ask` creates task-focused context in one step
|
|
@@ -99,6 +99,7 @@ Measured on 90 coding tasks across 18 real public repos. Full methodology and ra
|
|
|
99
99
|
| [Standalone binaries](docs/readmes/binaries.md) | macOS, Linux, Windows — no Node required |
|
|
100
100
|
| [VS Code extension](#-vs-code-extension) | Status bar, stale alerts, commands |
|
|
101
101
|
| [JetBrains plugin](#-jetbrains-plugin) | IntelliJ IDEA, WebStorm, PyCharm support |
|
|
102
|
+
| [Neovim plugin](#-neovim-plugin) | `:SigMap`, `:SigMapQuery`, statusline, health check |
|
|
102
103
|
| [Languages supported](#-languages-supported) | 29 languages |
|
|
103
104
|
| [Context strategies](#-context-strategies) | full / per-module / hot-cold |
|
|
104
105
|
| [MCP server](#-mcp-server) | 8 on-demand tools |
|
|
@@ -518,6 +519,39 @@ Compatible with **IntelliJ IDEA 2024.1+** (Community & Ultimate), **WebStorm**,
|
|
|
518
519
|
|
|
519
520
|
---
|
|
520
521
|
|
|
522
|
+
## 🖥️ Neovim plugin
|
|
523
|
+
|
|
524
|
+
<a href="https://dotfyle.com/plugins/manojmallick/sigmap.nvim">
|
|
525
|
+
<img src="https://dotfyle.com/plugins/manojmallick/sigmap.nvim/shield?style=flat" />
|
|
526
|
+
</a>
|
|
527
|
+
|
|
528
|
+
The official SigMap Neovim plugin (`sigmap.nvim`) brings first-class integration to the #1 most-admired editor (Stack Overflow 2025, 83% admiration rate). Power users who live in the terminal get context regeneration, ranked retrieval, and health checks without leaving Neovim.
|
|
529
|
+
|
|
530
|
+
| Feature | Detail |
|
|
531
|
+
|---|---|
|
|
532
|
+
| **`:SigMap [args]`** | Regenerate your AI context file asynchronously |
|
|
533
|
+
| **`:SigMapQuery <text>`** | TF-IDF ranked retrieval — results appear in a centered floating window |
|
|
534
|
+
| **Auto-run on save** | `auto_run = true` triggers regen on `BufWritePost` for `.js/ts/py/go/rs/java/rb/lua` |
|
|
535
|
+
| **Statusline widget** | `require('sigmap').statusline()` returns `sm:✓` (fresh) or `sm:⚠ Nh` (stale) |
|
|
536
|
+
| **`:checkhealth sigmap`** | Validates Node 18+, binary presence, and context file freshness |
|
|
537
|
+
| **Binary auto-detection** | Finds `sigmap` → `npx sigmap` → local `gen-context.js` automatically |
|
|
538
|
+
|
|
539
|
+
**Install (lazy.nvim):**
|
|
540
|
+
```lua
|
|
541
|
+
{ 'manojmallick/sigmap.nvim',
|
|
542
|
+
config = function()
|
|
543
|
+
require('sigmap').setup({
|
|
544
|
+
auto_run = true, -- regenerate on save
|
|
545
|
+
float_query = true, -- show query results in a floating window
|
|
546
|
+
})
|
|
547
|
+
end,
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Repo:** [manojmallick/sigmap.nvim](https://github.com/manojmallick/sigmap.nvim)
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
521
555
|
## 🌐 Languages supported
|
|
522
556
|
|
|
523
557
|
> 29 languages and formats. All implemented with zero external dependencies — pure regex + Node built-ins.
|
|
@@ -811,7 +845,7 @@ Every run now prints a coverage line alongside token reduction:
|
|
|
811
845
|
|
|
812
846
|
```
|
|
813
847
|
───────────────────────────────────────────
|
|
814
|
-
SigMap v5.
|
|
848
|
+
SigMap v5.5.0
|
|
815
849
|
Files scanned : 76
|
|
816
850
|
Symbols found : 332
|
|
817
851
|
Token reduction: 94% (65,227 → 4,103)
|
|
@@ -830,7 +864,7 @@ sigmap --report
|
|
|
830
864
|
|
|
831
865
|
```
|
|
832
866
|
[sigmap] report:
|
|
833
|
-
version : 5.
|
|
867
|
+
version : 5.5.0
|
|
834
868
|
files processed : 76
|
|
835
869
|
reduction : 93.7%
|
|
836
870
|
coverage : A (97%) — 76 of 78 source files included
|
|
@@ -874,7 +908,7 @@ sigmap --health --json
|
|
|
874
908
|
Every output file now carries a metadata line so you can inspect freshness at a glance:
|
|
875
909
|
|
|
876
910
|
```
|
|
877
|
-
<!-- sigmap: version=5.
|
|
911
|
+
<!-- sigmap: version=5.5.0 confidence=HIGH coverage=97% dropped=2 commit=8540612 -->
|
|
878
912
|
```
|
|
879
913
|
|
|
880
914
|
### Diff risk score
|
|
@@ -1009,6 +1043,11 @@ sigmap/
|
|
|
1009
1043
|
│ ├── package.json ← manifest — commands, settings, activation
|
|
1010
1044
|
│ └── src/extension.js ← status bar, stale notification, commands
|
|
1011
1045
|
│
|
|
1046
|
+
├── neovim-plugin/ ← Neovim plugin — sigmap.nvim (v5.4)
|
|
1047
|
+
│ ├── lua/sigmap/init.lua ← M.setup(), M.run(), M.query(), M.statusline()
|
|
1048
|
+
│ ├── lua/sigmap/health.lua ← :checkhealth sigmap
|
|
1049
|
+
│ └── plugin/sigmap.lua ← :SigMap and :SigMapQuery user commands
|
|
1050
|
+
│
|
|
1012
1051
|
├── test/
|
|
1013
1052
|
│ ├── fixtures/ ← one source file per language
|
|
1014
1053
|
│ ├── expected/ ← expected extractor output
|
package/gen-context.js
CHANGED
|
@@ -3034,7 +3034,445 @@ __factories["./src/format/cache"] = function(module, exports) {
|
|
|
3034
3034
|
}
|
|
3035
3035
|
|
|
3036
3036
|
module.exports = { formatCache, formatCachePayload };
|
|
3037
|
-
|
|
3037
|
+
|
|
3038
|
+
};
|
|
3039
|
+
|
|
3040
|
+
// ── ./src/format/benchmark-report ──
|
|
3041
|
+
__factories["./src/format/benchmark-report"] = function(module, exports) {
|
|
3042
|
+
'use strict';
|
|
3043
|
+
|
|
3044
|
+
const fs = require('fs');
|
|
3045
|
+
const path = require('path');
|
|
3046
|
+
|
|
3047
|
+
function escapeHtml(value) {
|
|
3048
|
+
return String(value == null ? '' : value)
|
|
3049
|
+
.replace(/&/g, '&')
|
|
3050
|
+
.replace(/</g, '<')
|
|
3051
|
+
.replace(/>/g, '>')
|
|
3052
|
+
.replace(/"/g, '"');
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
function formatInt(value) {
|
|
3056
|
+
const n = Number(value);
|
|
3057
|
+
if (!Number.isFinite(n)) return 'n/a';
|
|
3058
|
+
return Math.round(n).toLocaleString('en-US');
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
function formatCompact(value) {
|
|
3062
|
+
const n = Number(value);
|
|
3063
|
+
if (!Number.isFinite(n)) return 'n/a';
|
|
3064
|
+
if (Math.abs(n) >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
3065
|
+
if (Math.abs(n) >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
3066
|
+
return String(Math.round(n));
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
function formatPct(value, digits = 1) {
|
|
3070
|
+
const n = Number(value);
|
|
3071
|
+
if (!Number.isFinite(n)) return 'n/a';
|
|
3072
|
+
return `${n.toFixed(digits)}%`;
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function formatMaybePct(value, digits = 1) {
|
|
3076
|
+
const n = Number(value);
|
|
3077
|
+
if (!Number.isFinite(n)) return 'n/a';
|
|
3078
|
+
return `${n.toFixed(digits)}%`;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
function formatRatio(value, digits = 1) {
|
|
3082
|
+
const n = Number(value);
|
|
3083
|
+
if (!Number.isFinite(n)) return 'n/a';
|
|
3084
|
+
return `${n.toFixed(digits)}x`;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
function formatMoney(value) {
|
|
3088
|
+
const n = Number(value);
|
|
3089
|
+
if (!Number.isFinite(n)) return 'n/a';
|
|
3090
|
+
return `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
function durationLabel(ms) {
|
|
3094
|
+
const n = Number(ms);
|
|
3095
|
+
if (!Number.isFinite(n)) return 'n/a';
|
|
3096
|
+
const sec = n / 1000;
|
|
3097
|
+
if (sec < 60) return `${sec.toFixed(1)}s`;
|
|
3098
|
+
const min = Math.floor(sec / 60);
|
|
3099
|
+
const rem = sec - (min * 60);
|
|
3100
|
+
return `${min}m ${rem.toFixed(1)}s`;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
function maxOrZero(values) {
|
|
3104
|
+
if (!Array.isArray(values) || values.length === 0) return 0;
|
|
3105
|
+
return Math.max(...values.map((v) => (Number.isFinite(v) ? v : 0)));
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
function readJson(filePath) {
|
|
3109
|
+
try {
|
|
3110
|
+
if (!fs.existsSync(filePath)) return null;
|
|
3111
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
3112
|
+
} catch (_) {
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
function loadBenchmarkReports(cwd) {
|
|
3118
|
+
const reportsDir = path.join(cwd, 'benchmarks', 'reports');
|
|
3119
|
+
return {
|
|
3120
|
+
reportsDir,
|
|
3121
|
+
token: readJson(path.join(reportsDir, 'token-reduction.json')),
|
|
3122
|
+
retrieval: readJson(path.join(reportsDir, 'retrieval.json')),
|
|
3123
|
+
quality: readJson(path.join(reportsDir, 'quality.json')),
|
|
3124
|
+
task: readJson(path.join(reportsDir, 'task-benchmark.json')),
|
|
3125
|
+
matrix: readJson(path.join(reportsDir, 'benchmark-matrix.json')),
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
function buildRetrievalSummary(retrieval) {
|
|
3130
|
+
if (!retrieval || !Array.isArray(retrieval.repos) || retrieval.repos.length === 0) return null;
|
|
3131
|
+
let totalTasks = 0;
|
|
3132
|
+
let weightedHit = 0;
|
|
3133
|
+
let weightedRand = 0;
|
|
3134
|
+
let correct = 0;
|
|
3135
|
+
let partial = 0;
|
|
3136
|
+
let wrong = 0;
|
|
3137
|
+
let repoCount = 0;
|
|
3138
|
+
|
|
3139
|
+
for (const repo of retrieval.repos) {
|
|
3140
|
+
const tasks = Number(repo.tasks) || 0;
|
|
3141
|
+
repoCount++;
|
|
3142
|
+
totalTasks += tasks;
|
|
3143
|
+
weightedHit += (Number(repo.hitAt5) || 0) * tasks;
|
|
3144
|
+
weightedRand += (Number(repo.randomBaseline) || 0) * tasks;
|
|
3145
|
+
correct += Number(repo.tiers && repo.tiers.correct) || 0;
|
|
3146
|
+
partial += Number(repo.tiers && repo.tiers.partial) || 0;
|
|
3147
|
+
wrong += Number(repo.tiers && repo.tiers.wrong) || 0;
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
const hitAt5 = totalTasks > 0 ? (weightedHit / totalTasks) * 100 : null;
|
|
3151
|
+
const randomBaseline = totalTasks > 0 ? (weightedRand / totalTasks) * 100 : null;
|
|
3152
|
+
const lift = hitAt5 && randomBaseline ? hitAt5 / randomBaseline : null;
|
|
3153
|
+
|
|
3154
|
+
return { repoCount, totalTasks, hitAt5, randomBaseline, lift, correct, partial, wrong };
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
function buildBenchmarkSummary(reports, matrixSummary) {
|
|
3158
|
+
const missing = [];
|
|
3159
|
+
if (!reports.token) missing.push('token-reduction.json');
|
|
3160
|
+
if (!reports.retrieval) missing.push('retrieval.json');
|
|
3161
|
+
if (!reports.quality) missing.push('quality.json');
|
|
3162
|
+
if (!reports.task) missing.push('task-benchmark.json');
|
|
3163
|
+
|
|
3164
|
+
const retrievalSummary = buildRetrievalSummary(reports.retrieval);
|
|
3165
|
+
const qualitySummary = reports.quality && reports.quality.summary ? reports.quality.summary : null;
|
|
3166
|
+
const tokenSummary = reports.token && reports.token.summary ? reports.token.summary : null;
|
|
3167
|
+
const taskSummary = reports.task && reports.task.summary ? reports.task.summary : null;
|
|
3168
|
+
const matrix = matrixSummary || reports.matrix || null;
|
|
3169
|
+
|
|
3170
|
+
const generatedCandidates = [
|
|
3171
|
+
matrix && matrix.generated,
|
|
3172
|
+
reports.task && reports.task.generated,
|
|
3173
|
+
reports.retrieval && reports.retrieval.generated,
|
|
3174
|
+
reports.quality && reports.quality.timestamp,
|
|
3175
|
+
reports.token && reports.token.timestamp,
|
|
3176
|
+
].filter(Boolean);
|
|
3177
|
+
const generatedAt = generatedCandidates
|
|
3178
|
+
.map((value) => ({ value, time: Date.parse(value) }))
|
|
3179
|
+
.filter((item) => Number.isFinite(item.time))
|
|
3180
|
+
.sort((a, b) => b.time - a.time)[0];
|
|
3181
|
+
|
|
3182
|
+
return {
|
|
3183
|
+
generatedAt: (generatedAt && generatedAt.value) || generatedCandidates[0] || new Date().toISOString(),
|
|
3184
|
+
missing,
|
|
3185
|
+
tokenSummary,
|
|
3186
|
+
retrievalSummary,
|
|
3187
|
+
qualitySummary,
|
|
3188
|
+
taskSummary,
|
|
3189
|
+
matrix,
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
function renderCard(label, value, hint, tone) {
|
|
3194
|
+
const toneClass = tone ? ` ${tone}` : '';
|
|
3195
|
+
return [
|
|
3196
|
+
`<article class="card${toneClass}">`,
|
|
3197
|
+
`<div class="label">${escapeHtml(label)}</div>`,
|
|
3198
|
+
`<div class="value">${escapeHtml(value)}</div>`,
|
|
3199
|
+
`<div class="hint">${escapeHtml(hint || '')}</div>`,
|
|
3200
|
+
'</article>',
|
|
3201
|
+
].join('');
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
function renderProgress(label, value, max, suffix) {
|
|
3205
|
+
const safeValue = Number.isFinite(value) ? value : 0;
|
|
3206
|
+
const safeMax = Math.max(1, Number.isFinite(max) ? max : 1);
|
|
3207
|
+
const width = Math.max(2, Math.min(100, (safeValue / safeMax) * 100));
|
|
3208
|
+
return [
|
|
3209
|
+
'<div class="progress-row">',
|
|
3210
|
+
`<div class="progress-label">${escapeHtml(label)}</div>`,
|
|
3211
|
+
'<div class="progress-bar"><span style="width:',
|
|
3212
|
+
String(width.toFixed(1)),
|
|
3213
|
+
'%"></span></div>',
|
|
3214
|
+
`<div class="progress-value">${escapeHtml(`${safeValue}${suffix || ''}`)}</div>`,
|
|
3215
|
+
'</div>',
|
|
3216
|
+
].join('');
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
function renderMatrixSection(matrix) {
|
|
3220
|
+
if (!matrix || !Array.isArray(matrix.steps) || matrix.steps.length === 0) return '';
|
|
3221
|
+
const rows = matrix.steps.map((step) => {
|
|
3222
|
+
const status = step.ok ? 'ok' : 'fail';
|
|
3223
|
+
return [
|
|
3224
|
+
'<tr>',
|
|
3225
|
+
`<td>${escapeHtml(step.name)}</td>`,
|
|
3226
|
+
`<td><span class="badge ${status}">${escapeHtml(step.ok ? 'ok' : `exit ${step.status}`)}</span></td>`,
|
|
3227
|
+
`<td>${escapeHtml(durationLabel(step.durationMs))}</td>`,
|
|
3228
|
+
`<td><code>${escapeHtml(['node', step.script].concat(step.args || []).join(' '))}</code></td>`,
|
|
3229
|
+
'</tr>',
|
|
3230
|
+
].join('');
|
|
3231
|
+
}).join('');
|
|
3232
|
+
|
|
3233
|
+
return [
|
|
3234
|
+
'<section>',
|
|
3235
|
+
'<h2>Run matrix</h2>',
|
|
3236
|
+
'<p class="section-copy">This shows which benchmark jobs ran, whether they succeeded, and how long each step took.</p>',
|
|
3237
|
+
'<table>',
|
|
3238
|
+
'<thead><tr><th>Step</th><th>Status</th><th>Duration</th><th>Command</th></tr></thead>',
|
|
3239
|
+
`<tbody>${rows}</tbody>`,
|
|
3240
|
+
'</table>',
|
|
3241
|
+
'</section>',
|
|
3242
|
+
].join('');
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
function renderTokenSection(token) {
|
|
3246
|
+
if (!token || !Array.isArray(token.repos) || token.repos.length === 0) return '';
|
|
3247
|
+
const rows = token.repos
|
|
3248
|
+
.slice()
|
|
3249
|
+
.sort((a, b) => (b.reductionPct || 0) - (a.reductionPct || 0))
|
|
3250
|
+
.map((repo) => [
|
|
3251
|
+
'<tr>',
|
|
3252
|
+
`<td>${escapeHtml(repo.repo)}</td>`,
|
|
3253
|
+
`<td>${escapeHtml(repo.language || 'n/a')}</td>`,
|
|
3254
|
+
`<td>${escapeHtml(formatCompact(repo.rawTokens))}</td>`,
|
|
3255
|
+
`<td>${escapeHtml(formatCompact(repo.finalTokens))}</td>`,
|
|
3256
|
+
`<td>${escapeHtml(formatMaybePct(repo.reductionPct, 1))}</td>`,
|
|
3257
|
+
'</tr>',
|
|
3258
|
+
].join(''))
|
|
3259
|
+
.join('');
|
|
3260
|
+
|
|
3261
|
+
return [
|
|
3262
|
+
'<section>',
|
|
3263
|
+
'<h2>Token reduction</h2>',
|
|
3264
|
+
'<p class="section-copy">Raw repository tokens versus SigMap output size across the benchmark repos.</p>',
|
|
3265
|
+
'<table>',
|
|
3266
|
+
'<thead><tr><th>Repo</th><th>Language</th><th>Raw tokens</th><th>Final tokens</th><th>Reduction</th></tr></thead>',
|
|
3267
|
+
`<tbody>${rows}</tbody>`,
|
|
3268
|
+
'</table>',
|
|
3269
|
+
'</section>',
|
|
3270
|
+
].join('');
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
function renderRetrievalSection(retrieval) {
|
|
3274
|
+
if (!retrieval || !Array.isArray(retrieval.repos) || retrieval.repos.length === 0) return '';
|
|
3275
|
+
const rows = retrieval.repos.map((repo) => {
|
|
3276
|
+
const lift = repo.randomBaseline > 0 ? (repo.hitAt5 / repo.randomBaseline) : null;
|
|
3277
|
+
return [
|
|
3278
|
+
'<tr>',
|
|
3279
|
+
`<td>${escapeHtml(repo.repo)}</td>`,
|
|
3280
|
+
`<td>${escapeHtml(formatMaybePct((repo.randomBaseline || 0) * 100, 1))}</td>`,
|
|
3281
|
+
`<td>${escapeHtml(formatMaybePct((repo.hitAt5 || 0) * 100, 1))}</td>`,
|
|
3282
|
+
`<td>${escapeHtml(formatRatio(lift, 1))}</td>`,
|
|
3283
|
+
`<td>${escapeHtml(String((repo.tiers && repo.tiers.correct) || 0))}</td>`,
|
|
3284
|
+
`<td>${escapeHtml(String((repo.tiers && repo.tiers.partial) || 0))}</td>`,
|
|
3285
|
+
`<td>${escapeHtml(String((repo.tiers && repo.tiers.wrong) || 0))}</td>`,
|
|
3286
|
+
'</tr>',
|
|
3287
|
+
].join('');
|
|
3288
|
+
}).join('');
|
|
3289
|
+
|
|
3290
|
+
return [
|
|
3291
|
+
'<section>',
|
|
3292
|
+
'<h2>Retrieval quality</h2>',
|
|
3293
|
+
'<p class="section-copy">Hit@5 performance against the random baseline, plus the quality-tier mix that drives the task benchmark.</p>',
|
|
3294
|
+
'<table>',
|
|
3295
|
+
'<thead><tr><th>Repo</th><th>Random hit@5</th><th>SigMap hit@5</th><th>Lift</th><th>Correct</th><th>Partial</th><th>Wrong</th></tr></thead>',
|
|
3296
|
+
`<tbody>${rows}</tbody>`,
|
|
3297
|
+
'</table>',
|
|
3298
|
+
'</section>',
|
|
3299
|
+
].join('');
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
function renderQualitySection(quality) {
|
|
3303
|
+
if (!quality || !Array.isArray(quality.repos) || quality.repos.length === 0) return '';
|
|
3304
|
+
const rows = quality.repos.map((repo) => {
|
|
3305
|
+
const overflow = (repo.rawTokens || 0) > 128000 ? 'overflow' : 'fits';
|
|
3306
|
+
return [
|
|
3307
|
+
'<tr>',
|
|
3308
|
+
`<td>${escapeHtml(repo.repo)}</td>`,
|
|
3309
|
+
`<td>${escapeHtml(formatInt(repo.groundedSymbols))}</td>`,
|
|
3310
|
+
`<td>${escapeHtml(formatInt(repo.darkSymbols))}</td>`,
|
|
3311
|
+
`<td>${escapeHtml(formatMaybePct(repo.groundingPct, 0))}</td>`,
|
|
3312
|
+
`<td>${escapeHtml(String(repo.filesHiddenRaw || 0))}</td>`,
|
|
3313
|
+
`<td><span class="badge ${overflow === 'overflow' ? 'warn' : 'ok'}">${escapeHtml(overflow)}</span></td>`,
|
|
3314
|
+
'</tr>',
|
|
3315
|
+
].join('');
|
|
3316
|
+
}).join('');
|
|
3317
|
+
|
|
3318
|
+
return [
|
|
3319
|
+
'<section>',
|
|
3320
|
+
'<h2>Quality and hallucination surface</h2>',
|
|
3321
|
+
'<p class="section-copy">How much code stays visible to the model, plus the overflow and dark-symbol risk by repo.</p>',
|
|
3322
|
+
'<table>',
|
|
3323
|
+
'<thead><tr><th>Repo</th><th>Grounded symbols</th><th>Dark symbols</th><th>Grounding</th><th>Hidden files (raw)</th><th>GPT-4o 128K</th></tr></thead>',
|
|
3324
|
+
`<tbody>${rows}</tbody>`,
|
|
3325
|
+
'</table>',
|
|
3326
|
+
'</section>',
|
|
3327
|
+
].join('');
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
function renderTaskSection(task) {
|
|
3331
|
+
if (!task || !Array.isArray(task.repos) || task.repos.length === 0 || !task.summary) return '';
|
|
3332
|
+
const summary = task.summary;
|
|
3333
|
+
const maxReduction = maxOrZero(task.repos.map((repo) => Number(repo.reductionPct) || 0));
|
|
3334
|
+
const repoBars = task.repos
|
|
3335
|
+
.slice()
|
|
3336
|
+
.sort((a, b) => (b.reductionPct || 0) - (a.reductionPct || 0))
|
|
3337
|
+
.slice(0, 10)
|
|
3338
|
+
.map((repo) => renderProgress(repo.repo, Number(repo.reductionPct) || 0, maxReduction, '%'))
|
|
3339
|
+
.join('');
|
|
3340
|
+
|
|
3341
|
+
return [
|
|
3342
|
+
'<section>',
|
|
3343
|
+
'<h2>Task benchmark</h2>',
|
|
3344
|
+
'<p class="section-copy">A prompt-reduction proxy derived from retrieval quality tiers. Lower prompts means the right file surfaces sooner.</p>',
|
|
3345
|
+
'<div class="split">',
|
|
3346
|
+
'<div class="panel">',
|
|
3347
|
+
'<h3>Answer quality tiers</h3>',
|
|
3348
|
+
renderProgress('Correct', Number(summary.correctPct) || 0, 100, '%'),
|
|
3349
|
+
renderProgress('Partial', Number(summary.partialPct) || 0, 100, '%'),
|
|
3350
|
+
renderProgress('Wrong', Number(summary.wrongPct) || 0, 100, '%'),
|
|
3351
|
+
'</div>',
|
|
3352
|
+
'<div class="panel">',
|
|
3353
|
+
'<h3>Best prompt reduction by repo</h3>',
|
|
3354
|
+
repoBars,
|
|
3355
|
+
'</div>',
|
|
3356
|
+
'</div>',
|
|
3357
|
+
'</section>',
|
|
3358
|
+
].join('');
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
function generateBenchmarkReportHtml(reports, opts = {}) {
|
|
3362
|
+
const summary = buildBenchmarkSummary(reports, opts.matrixSummary);
|
|
3363
|
+
const cards = [];
|
|
3364
|
+
cards.push(renderCard(
|
|
3365
|
+
'Token reduction',
|
|
3366
|
+
summary.tokenSummary ? formatPct(summary.tokenSummary.overallReductionPct, 1) : 'n/a',
|
|
3367
|
+
summary.tokenSummary ? `${formatInt(summary.tokenSummary.repoCount)} repos • ${formatCompact(summary.tokenSummary.totalRawTokens)} raw -> ${formatCompact(summary.tokenSummary.totalFinalTokens)} final` : 'token-reduction.json missing',
|
|
3368
|
+
'cool'
|
|
3369
|
+
));
|
|
3370
|
+
cards.push(renderCard(
|
|
3371
|
+
'Retrieval hit@5',
|
|
3372
|
+
summary.retrievalSummary ? formatPct(summary.retrievalSummary.hitAt5, 1) : 'n/a',
|
|
3373
|
+
summary.retrievalSummary ? `${formatPct(summary.retrievalSummary.randomBaseline, 1)} random baseline • ${formatRatio(summary.retrievalSummary.lift, 1)} lift` : 'retrieval.json missing',
|
|
3374
|
+
'warm'
|
|
3375
|
+
));
|
|
3376
|
+
cards.push(renderCard(
|
|
3377
|
+
'Prompt reduction',
|
|
3378
|
+
summary.taskSummary ? formatPct(summary.taskSummary.avgReductionPct, 0) : 'n/a',
|
|
3379
|
+
summary.taskSummary ? `${summary.taskSummary.avgPromptsWithout} -> ${summary.taskSummary.avgPromptsWith} prompts • ${formatInt(summary.taskSummary.totalTasks)} tasks` : 'task-benchmark.json missing',
|
|
3380
|
+
'neutral'
|
|
3381
|
+
));
|
|
3382
|
+
cards.push(renderCard(
|
|
3383
|
+
'Overflow risk',
|
|
3384
|
+
summary.qualitySummary ? `${formatInt(summary.qualitySummary.overflowGPT4oCount)} repos` : 'n/a',
|
|
3385
|
+
summary.qualitySummary ? `${formatInt(summary.qualitySummary.totalHiddenFiles)} hidden raw files • ${formatMoney(summary.qualitySummary.gpt4oSavedPerMonth)}/month saved` : 'quality.json missing',
|
|
3386
|
+
summary.qualitySummary && summary.qualitySummary.overflowGPT4oCount > 0 ? 'warn' : 'ok'
|
|
3387
|
+
));
|
|
3388
|
+
|
|
3389
|
+
const missingHtml = summary.missing.length > 0
|
|
3390
|
+
? `<div class="notice">Missing source reports: ${escapeHtml(summary.missing.join(', '))}. The page still renders whatever data is available.</div>`
|
|
3391
|
+
: '';
|
|
3392
|
+
|
|
3393
|
+
return [
|
|
3394
|
+
'<!doctype html>',
|
|
3395
|
+
'<html lang="en">',
|
|
3396
|
+
'<head>',
|
|
3397
|
+
'<meta charset="utf-8" />',
|
|
3398
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
3399
|
+
'<title>SigMap Benchmark Report</title>',
|
|
3400
|
+
'<style>',
|
|
3401
|
+
':root { color-scheme: light; --bg:#f5f1e8; --panel:#fffaf2; --ink:#1f1b16; --muted:#6a6258; --line:#dccfbf; --gold:#c87f2a; --green:#2f6f52; --blue:#2f5f8f; --red:#9f4f43; --shadow:0 18px 40px rgba(54,38,14,.10);} ',
|
|
3402
|
+
'*{box-sizing:border-box} body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:linear-gradient(180deg,#f3ecdf 0%,#f7f3ed 100%);color:var(--ink)}',
|
|
3403
|
+
'.page{max-width:1240px;margin:0 auto;padding:28px 20px 56px}',
|
|
3404
|
+
'header{display:flex;justify-content:space-between;gap:24px;align-items:flex-end;margin-bottom:24px}',
|
|
3405
|
+
'h1{margin:0;font-size:clamp(2rem,4vw,3.6rem);line-height:1.02;letter-spacing:-.04em}',
|
|
3406
|
+
'.lede{max-width:760px;color:var(--muted);font-size:1rem;line-height:1.6;margin-top:10px}',
|
|
3407
|
+
'.stamp{font-size:.92rem;color:var(--muted);text-align:right}',
|
|
3408
|
+
'.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin:20px 0 24px}',
|
|
3409
|
+
'.card,.panel,.notice,section{background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:18px}',
|
|
3410
|
+
'.card{padding:18px 18px 16px}.card.cool{background:#f7f5ff}.card.warm{background:#fff4eb}.card.warn{background:#fff1eb}.card.ok{background:#eff8f1}',
|
|
3411
|
+
'.label{font-size:.84rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}',
|
|
3412
|
+
'.value{font-size:2rem;font-weight:700;letter-spacing:-.04em;margin-top:8px}',
|
|
3413
|
+
'.hint{font-size:.95rem;color:var(--muted);margin-top:8px;line-height:1.5}',
|
|
3414
|
+
'.notice{padding:14px 16px;margin-bottom:20px;color:var(--muted)}',
|
|
3415
|
+
'section{padding:20px;margin-top:18px}',
|
|
3416
|
+
'h2{margin:0 0 6px;font-size:1.4rem;letter-spacing:-.03em}',
|
|
3417
|
+
'h3{margin:0 0 14px;font-size:1rem}',
|
|
3418
|
+
'.section-copy{margin:0 0 16px;color:var(--muted);line-height:1.6}',
|
|
3419
|
+
'table{width:100%;border-collapse:collapse;font-size:.95rem}',
|
|
3420
|
+
'th,td{padding:10px 12px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}',
|
|
3421
|
+
'th{font-size:.82rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}',
|
|
3422
|
+
'tbody tr:hover{background:rgba(200,127,42,.06)}',
|
|
3423
|
+
'.badge{display:inline-flex;align-items:center;padding:4px 8px;border-radius:999px;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em}',
|
|
3424
|
+
'.badge.ok{background:#e6f4ea;color:#21573f}.badge.warn{background:#fff0de;color:#8a4a17}.badge.fail{background:#fde8e5;color:#8a2e23}',
|
|
3425
|
+
'.split{display:grid;grid-template-columns:1fr 1fr;gap:16px}',
|
|
3426
|
+
'.panel{padding:16px}',
|
|
3427
|
+
'.progress-row{display:grid;grid-template-columns:140px 1fr 60px;gap:12px;align-items:center;margin:10px 0}',
|
|
3428
|
+
'.progress-label,.progress-value{font-size:.92rem}',
|
|
3429
|
+
'.progress-bar{height:10px;border-radius:999px;background:#efe4d5;overflow:hidden}',
|
|
3430
|
+
'.progress-bar span{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,var(--gold),#ebbb61)}',
|
|
3431
|
+
'code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.85rem}',
|
|
3432
|
+
'@media (max-width: 1020px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.split{grid-template-columns:1fr}header{flex-direction:column;align-items:flex-start}.stamp{text-align:left}}',
|
|
3433
|
+
'@media (max-width: 640px){.grid{grid-template-columns:1fr}.progress-row{grid-template-columns:110px 1fr 52px}th:nth-child(n+5),td:nth-child(n+5){display:none}}',
|
|
3434
|
+
'</style>',
|
|
3435
|
+
'</head>',
|
|
3436
|
+
'<body>',
|
|
3437
|
+
'<div class="page">',
|
|
3438
|
+
'<header>',
|
|
3439
|
+
'<div>',
|
|
3440
|
+
'<h1>SigMap Benchmark Report</h1>',
|
|
3441
|
+
'<p class="lede">A self-contained view of token reduction, retrieval quality, hallucination surface, and task-level prompt reduction. This page reads the saved JSON benchmark artifacts so it stays easy to regenerate locally.</p>',
|
|
3442
|
+
'</div>',
|
|
3443
|
+
`<div class="stamp">Generated: ${escapeHtml(summary.generatedAt)}<br />Source directory: <code>benchmarks/reports</code></div>`,
|
|
3444
|
+
'</header>',
|
|
3445
|
+
missingHtml,
|
|
3446
|
+
`<div class="grid">${cards.join('')}</div>`,
|
|
3447
|
+
renderMatrixSection(summary.matrix),
|
|
3448
|
+
renderTokenSection(reports.token),
|
|
3449
|
+
renderRetrievalSection(reports.retrieval),
|
|
3450
|
+
renderQualitySection(reports.quality),
|
|
3451
|
+
renderTaskSection(reports.task),
|
|
3452
|
+
'</div>',
|
|
3453
|
+
'</body>',
|
|
3454
|
+
'</html>',
|
|
3455
|
+
].join('');
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
function writeBenchmarkReport(cwd, opts = {}) {
|
|
3459
|
+
const reports = loadBenchmarkReports(cwd);
|
|
3460
|
+
const html = generateBenchmarkReportHtml(reports, opts);
|
|
3461
|
+
const filePath = path.join(reports.reportsDir, opts.fileName || 'benchmark-report.html');
|
|
3462
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
3463
|
+
fs.writeFileSync(filePath, html, 'utf8');
|
|
3464
|
+
return {
|
|
3465
|
+
file: filePath,
|
|
3466
|
+
summary: buildBenchmarkSummary(reports, opts.matrixSummary),
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
module.exports = {
|
|
3471
|
+
loadBenchmarkReports,
|
|
3472
|
+
buildBenchmarkSummary,
|
|
3473
|
+
generateBenchmarkReportHtml,
|
|
3474
|
+
writeBenchmarkReport,
|
|
3475
|
+
};
|
|
3038
3476
|
};
|
|
3039
3477
|
|
|
3040
3478
|
// ── ./src/format/dashboard ──
|
|
@@ -4853,7 +5291,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
4853
5291
|
|
|
4854
5292
|
const SERVER_INFO = {
|
|
4855
5293
|
name: 'sigmap',
|
|
4856
|
-
version: '5.
|
|
5294
|
+
version: '5.5.0',
|
|
4857
5295
|
description: 'SigMap MCP server — code signatures on demand',
|
|
4858
5296
|
};
|
|
4859
5297
|
|
|
@@ -6571,7 +7009,7 @@ const path = require('path');
|
|
|
6571
7009
|
const os = require('os');
|
|
6572
7010
|
const { execSync } = require('child_process');
|
|
6573
7011
|
|
|
6574
|
-
const VERSION = '5.
|
|
7012
|
+
const VERSION = '5.5.0';
|
|
6575
7013
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
6576
7014
|
|
|
6577
7015
|
function requireSourceOrBundled(key) {
|
|
@@ -7325,7 +7763,7 @@ function _coverageBar(pct, width) {
|
|
|
7325
7763
|
return '\u2588'.repeat(filled) + '\u2591'.repeat(width - filled);
|
|
7326
7764
|
}
|
|
7327
7765
|
|
|
7328
|
-
function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson, budgetLimit, coverageResult, isAutoBudget) {
|
|
7766
|
+
function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson, budgetLimit, coverageResult, isAutoBudget, configuredMaxTokens) {
|
|
7329
7767
|
const reduction = inputTokens > 0 ? (100 - (finalTokens / inputTokens) * 100).toFixed(1) : 0;
|
|
7330
7768
|
const overBudget = finalTokens > (budgetLimit || 6000);
|
|
7331
7769
|
if (asJson) {
|
|
@@ -7363,6 +7801,10 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
7363
7801
|
const budgetLabel = isAutoBudget
|
|
7364
7802
|
? `${budgetLimit || 6000} (auto-scaled)`
|
|
7365
7803
|
: `${budgetLimit || 6000} (fixed)`;
|
|
7804
|
+
if (isAutoBudget && configuredMaxTokens && configuredMaxTokens !== budgetLimit) {
|
|
7805
|
+
console.warn(`[sigmap] note: autoMaxTokens is active — your maxTokens:${configuredMaxTokens} config was overridden by auto-scaled budget (${budgetLimit})`);
|
|
7806
|
+
console.warn(` to use your value, set "autoMaxTokens": false in gen-context.config.json`);
|
|
7807
|
+
}
|
|
7366
7808
|
console.log(`[sigmap] report:`);
|
|
7367
7809
|
console.log(` version : ${VERSION}`);
|
|
7368
7810
|
console.log(` files processed : ${fileCount}`);
|
|
@@ -7372,7 +7814,11 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
7372
7814
|
console.log(` budget limit : ${budgetLabel}`);
|
|
7373
7815
|
console.log(` reduction : ${reduction}%`);
|
|
7374
7816
|
if (coverageResult) {
|
|
7375
|
-
|
|
7817
|
+
const skipNote = coverageResult.nonCodeSkipped > 0
|
|
7818
|
+
? ` (${coverageResult.nonCodeSkipped} non-code files skipped — json, md, config)`
|
|
7819
|
+
: '';
|
|
7820
|
+
console.log(` coverage : ${coverageResult.grade} (${coverageResult.score}%) — ${coverageResult.included} of ${coverageResult.total} code files included`);
|
|
7821
|
+
if (skipNote) console.log(` ${skipNote}`);
|
|
7376
7822
|
console.log(` confidence : ${coverageResult.confidence}`);
|
|
7377
7823
|
if (coverageResult.perModule && coverageResult.perModule.size > 0) {
|
|
7378
7824
|
console.log('');
|
|
@@ -7383,6 +7829,15 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
7383
7829
|
const attention = mod.pct < 50 ? ' \u2190 attention needed' : '';
|
|
7384
7830
|
console.log(` ${dir.padEnd(18)} ${bar} ${String(mod.pct).padStart(3)}% (${mod.included}/${mod.total} files)${attention}`);
|
|
7385
7831
|
}
|
|
7832
|
+
const lowModules = [...coverageResult.perModule.entries()].filter(([, m]) => m.pct < 50 && m.total > 0);
|
|
7833
|
+
if (lowModules.length > 0) {
|
|
7834
|
+
console.log('');
|
|
7835
|
+
console.log(' tip: modules marked "\u2190 attention needed" have <50% code-file coverage.');
|
|
7836
|
+
console.log(' Common causes:');
|
|
7837
|
+
console.log(' \u2022 token budget dropped files \u2014 raise maxTokens or use strategy:"per-module"');
|
|
7838
|
+
console.log(' \u2022 srcDir path includes non-code files \u2014 check .contextignore or exclude config');
|
|
7839
|
+
console.log(' \u2022 srcDir path is wrong \u2014 verify srcDirs in gen-context.config.json');
|
|
7840
|
+
}
|
|
7386
7841
|
}
|
|
7387
7842
|
}
|
|
7388
7843
|
if (overBudget) console.warn(`[sigmap] WARNING: output (${finalTokens} tokens) exceeds budget (${budgetLimit || 6000})`);
|
|
@@ -7890,7 +8345,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7890
8345
|
}
|
|
7891
8346
|
|
|
7892
8347
|
if (reportMode || process.argv.includes('--report')) {
|
|
7893
|
-
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, effectiveMaxTokens, result.coverageResult, config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens);
|
|
8348
|
+
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, effectiveMaxTokens, result.coverageResult, config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens, config.maxTokens);
|
|
7894
8349
|
}
|
|
7895
8350
|
|
|
7896
8351
|
// Usage tracking (v0.9) — optional append-only NDJSON log
|
|
@@ -9056,7 +9511,7 @@ function main() {
|
|
|
9056
9511
|
console.log('[sigmap] health:');
|
|
9057
9512
|
console.log(` score : ${result.score}/100 (grade ${result.grade})`);
|
|
9058
9513
|
if (coverageResult) {
|
|
9059
|
-
console.log(`
|
|
9514
|
+
console.log(` file access : ${coverageResult.grade} (${coverageResult.score}%) — ${coverageResult.included} of ${coverageResult.total} files accessible in srcDirs`);
|
|
9060
9515
|
}
|
|
9061
9516
|
console.log(` strategy : ${result.strategy}`);
|
|
9062
9517
|
console.log(` token reduction : ${result.tokenReductionPct !== null ? result.tokenReductionPct + '%' : 'no history'}`);
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* SigMap coverage scorer —
|
|
4
|
+
* SigMap coverage scorer — v5.5.0
|
|
5
|
+
*
|
|
6
|
+
* Measures what fraction of *code* files made it into the context output
|
|
7
|
+
* after token-budget application. Non-code files (json, md, config) are
|
|
8
|
+
* counted separately as `nonCodeSkipped` so the grade reflects real coverage.
|
|
5
9
|
*
|
|
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
10
|
* - Health score = context freshness / reduction quality / budget compliance
|
|
9
11
|
* - Coverage score = how much of the codebase is represented in context
|
|
10
12
|
*
|
|
@@ -19,10 +21,23 @@
|
|
|
19
21
|
* total: number,
|
|
20
22
|
* included: number,
|
|
21
23
|
* dropped: number,
|
|
24
|
+
* nonCodeSkipped: number,
|
|
22
25
|
* confidence: 'HIGH'|'MEDIUM'|'LOW',
|
|
23
26
|
* perModule: Map<string, {total:number, included:number, pct:number}>,
|
|
24
27
|
* }}
|
|
25
28
|
*/
|
|
29
|
+
|
|
30
|
+
const CODE_EXTS = new Set([
|
|
31
|
+
'.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx',
|
|
32
|
+
'.py', '.rb', '.go', '.rs', '.java', '.kt',
|
|
33
|
+
'.cs', '.cpp', '.c', '.h', '.hpp',
|
|
34
|
+
'.swift', '.dart', '.scala', '.php',
|
|
35
|
+
'.vue', '.svelte', '.css', '.scss',
|
|
36
|
+
'.sql', '.graphql', '.proto', '.tf',
|
|
37
|
+
'.lua', '.r', '.jl', '.ex', '.exs',
|
|
38
|
+
'.sh', '.bash', '.zsh', '.ps1',
|
|
39
|
+
]);
|
|
40
|
+
|
|
26
41
|
function coverageScore(cwd, fileEntries, config) {
|
|
27
42
|
const fs = require('fs');
|
|
28
43
|
const path = require('path');
|
|
@@ -41,12 +56,17 @@ function coverageScore(cwd, fileEntries, config) {
|
|
|
41
56
|
|
|
42
57
|
const includedSet = new Set((fileEntries || []).map(f => f.filePath));
|
|
43
58
|
|
|
44
|
-
// Walk
|
|
59
|
+
// Walk srcDirs: separate code files from non-code files
|
|
60
|
+
const allFiles = [];
|
|
45
61
|
const allSource = [];
|
|
46
62
|
for (const relDir of srcDirs) {
|
|
47
63
|
const absDir = path.resolve(cwd, relDir);
|
|
48
|
-
if (fs.existsSync(absDir)) _walk(absDir, excludeSet,
|
|
64
|
+
if (fs.existsSync(absDir)) _walk(absDir, excludeSet, allFiles);
|
|
65
|
+
}
|
|
66
|
+
for (const f of allFiles) {
|
|
67
|
+
if (CODE_EXTS.has(path.extname(f).toLowerCase())) allSource.push(f);
|
|
49
68
|
}
|
|
69
|
+
const nonCodeSkipped = allFiles.length - allSource.length;
|
|
50
70
|
|
|
51
71
|
const total = allSource.length;
|
|
52
72
|
const included = allSource.filter(f => includedSet.has(f)).length;
|
|
@@ -66,7 +86,7 @@ function coverageScore(cwd, fileEntries, config) {
|
|
|
66
86
|
perModule.set(relDir, { total: modFiles.length, included: modIncl, pct: modPct });
|
|
67
87
|
}
|
|
68
88
|
|
|
69
|
-
return { score: pct, grade, total, included, dropped, confidence, perModule };
|
|
89
|
+
return { score: pct, grade, total, included, dropped, nonCodeSkipped, confidence, perModule };
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
function _walk(dir, excludeSet, out) {
|
|
@@ -82,4 +102,4 @@ function _walk(dir, excludeSet, out) {
|
|
|
82
102
|
}
|
|
83
103
|
}
|
|
84
104
|
|
|
85
|
-
module.exports = { coverageScore };
|
|
105
|
+
module.exports = { coverageScore, CODE_EXTS };
|
package/src/mcp/server.js
CHANGED