sigmap 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +32 -48
- package/CHANGELOG.md +50 -0
- package/README.md +26 -8
- package/gen-context.config.json.example +15 -0
- package/gen-context.js +173 -19
- package/package.json +2 -2
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/config/defaults.js +22 -1
- package/src/mcp/server.js +1 -1
package/AGENTS.md
CHANGED
|
@@ -12,33 +12,17 @@ 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 v4.
|
|
15
|
+
Below are the code signatures extracted by SigMap v4.1.0 on 2026-04-15T08:05:43.080Z.
|
|
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 v4.
|
|
21
|
+
<!-- Generated by SigMap gen-context.js v4.1.0 -->
|
|
22
22
|
<!-- DO NOT EDIT below the marker line — run gen-context.js to regenerate -->
|
|
23
23
|
|
|
24
24
|
# Code signatures
|
|
25
25
|
|
|
26
|
-
## changes (last 5 commits — 7 minutes ago)
|
|
27
|
-
```
|
|
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
|
|
40
|
-
```
|
|
41
|
-
|
|
42
26
|
## packages
|
|
43
27
|
|
|
44
28
|
### packages/adapters/claude.js
|
|
@@ -76,14 +60,6 @@ function write(context, cwd, opts = {})
|
|
|
76
60
|
function _confidenceMeta(opts)
|
|
77
61
|
```
|
|
78
62
|
|
|
79
|
-
### packages/adapters/llm-full.js
|
|
80
|
-
```
|
|
81
|
-
module.exports = { name: 'llm-full', format, outputPath, write }
|
|
82
|
-
function outputPath(cwd)
|
|
83
|
-
function format(context, opts)
|
|
84
|
-
function write(context, cwd, opts)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
63
|
### packages/adapters/openai.js
|
|
88
64
|
```
|
|
89
65
|
module.exports = { name, format, outputPath }
|
|
@@ -117,6 +93,14 @@ function adapt(context, adapterName, opts = {}) → string
|
|
|
117
93
|
function outputsToAdapters(outputs) → string[]
|
|
118
94
|
```
|
|
119
95
|
|
|
96
|
+
### packages/adapters/llm-full.js
|
|
97
|
+
```
|
|
98
|
+
module.exports = { name: 'llm-full', format, outputPath, write }
|
|
99
|
+
function outputPath(cwd)
|
|
100
|
+
function format(context, opts)
|
|
101
|
+
function write(context, cwd, opts)
|
|
102
|
+
```
|
|
103
|
+
|
|
120
104
|
### packages/cli/index.js
|
|
121
105
|
```
|
|
122
106
|
module.exports = { CLI_ENTRY, run }
|
|
@@ -177,28 +161,6 @@ function formatAnalysisTable(stats, showSlow) → string
|
|
|
177
161
|
function formatAnalysisJSON(stats) → object
|
|
178
162
|
```
|
|
179
163
|
|
|
180
|
-
### src/extractors/generic.js
|
|
181
|
-
```
|
|
182
|
-
module.exports = { extract }
|
|
183
|
-
function extract(src)
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### src/format/llm-txt.js
|
|
187
|
-
```
|
|
188
|
-
module.exports = { format, outputPath }
|
|
189
|
-
function outputPath(cwd)
|
|
190
|
-
function format(context, cwd, version)
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### src/format/llms-txt.js
|
|
194
|
-
```
|
|
195
|
-
module.exports = { format, outputPath }
|
|
196
|
-
function outputPath(cwd)
|
|
197
|
-
function getShortCommit(cwd)
|
|
198
|
-
function detectVersion(cwd)
|
|
199
|
-
function format(context, cwd, writtenFiles, sigmapVersion)
|
|
200
|
-
```
|
|
201
|
-
|
|
202
164
|
### src/mcp/server.js
|
|
203
165
|
```
|
|
204
166
|
module.exports = { start }
|
|
@@ -304,6 +266,12 @@ module.exports = { extract }
|
|
|
304
266
|
function extract(src) → string[]
|
|
305
267
|
```
|
|
306
268
|
|
|
269
|
+
### src/extractors/generic.js
|
|
270
|
+
```
|
|
271
|
+
module.exports = { extract }
|
|
272
|
+
function extract(src)
|
|
273
|
+
```
|
|
274
|
+
|
|
307
275
|
### src/extractors/go.js
|
|
308
276
|
```
|
|
309
277
|
module.exports = { extract }
|
|
@@ -563,6 +531,22 @@ function generateDashboardHtml(cwd, health)
|
|
|
563
531
|
function renderHistoryCharts(cwd, health)
|
|
564
532
|
```
|
|
565
533
|
|
|
534
|
+
### src/format/llm-txt.js
|
|
535
|
+
```
|
|
536
|
+
module.exports = { format, outputPath }
|
|
537
|
+
function outputPath(cwd)
|
|
538
|
+
function format(context, cwd, version)
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### src/format/llms-txt.js
|
|
542
|
+
```
|
|
543
|
+
module.exports = { format, outputPath }
|
|
544
|
+
function outputPath(cwd)
|
|
545
|
+
function getShortCommit(cwd)
|
|
546
|
+
function detectVersion(cwd)
|
|
547
|
+
function format(context, cwd, writtenFiles, sigmapVersion)
|
|
548
|
+
```
|
|
549
|
+
|
|
566
550
|
### src/graph/builder.js
|
|
567
551
|
```
|
|
568
552
|
module.exports = { build, buildFromCwd, extractFileDeps }
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,56 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [4.1.0] — 2026-04-15 — Smart Budget: auto-scaling token budget
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Auto-scaling token budget** (`autoMaxTokens: true`, default on):
|
|
18
|
+
Replaces the old fixed 6 000-token default with a formula that sizes the budget to your repo:
|
|
19
|
+
```
|
|
20
|
+
effective = clamp(ceil(totalSigTokens × coverageTarget), 4000, floor(modelContextLimit × maxTokensHeadroom))
|
|
21
|
+
```
|
|
22
|
+
- `coverageTarget` (default `0.80`) — target fraction of source files to include
|
|
23
|
+
- `modelContextLimit` (default `128000`) — model context window size; hard cap = `limit × headroom`
|
|
24
|
+
- `maxTokensHeadroom` (default `0.20`) — fraction of the model window reserved for SigMap output (default hard cap: **25 600 tokens**)
|
|
25
|
+
- Minimum floor: **4 000 tokens** (prevents tiny repos from being under-budgeted)
|
|
26
|
+
- When the hard cap prevents hitting the coverage target by more than 10 percentage points, SigMap warns and suggests `strategy: "per-module"`
|
|
27
|
+
|
|
28
|
+
- **Four new config keys** (all optional, documented in `gen-context.config.json.example`):
|
|
29
|
+
| Key | Default | Description |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `autoMaxTokens` | `true` | Enable auto-scaling |
|
|
32
|
+
| `coverageTarget` | `0.80` | Target fraction of source files |
|
|
33
|
+
| `modelContextLimit` | `128000` | Model context window (tokens) |
|
|
34
|
+
| `maxTokensHeadroom` | `0.20` | Fraction of context for SigMap |
|
|
35
|
+
|
|
36
|
+
- **Post-run summary annotation**: coverage line now shows `[budget: N auto-scaled]` when the formula overrode the configured `maxTokens`.
|
|
37
|
+
|
|
38
|
+
- **Per-module strategy budget fix**: each module now gets its own full effective budget instead of a proportional slice, which was the limiting factor that made `per-module` less useful than advertised.
|
|
39
|
+
|
|
40
|
+
- **Tracking log fields**: `autoBudget: true/false` and `budgetLimit: N` added to `.context/usage.ndjson` entries.
|
|
41
|
+
|
|
42
|
+
- **12 new integration tests** (`test/integration/auto-budget.test.js`): cover MIN floor, proportional scaling, hard cap, disabled auto-scaling, custom `coverageTarget`/`modelContextLimit`/`maxTokensHeadroom`, warning emission, and empty-project edge case.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `autoMaxTokens: false` + explicit `maxTokens` preserves the old fixed-budget behaviour exactly — fully backwards compatible.
|
|
47
|
+
- `printReport` now labels the budget `(auto-scaled)` vs `(fixed)` in the report line.
|
|
48
|
+
|
|
49
|
+
### Benchmarks (v4.1.0)
|
|
50
|
+
- Token reduction: **97.6% average** across 18 repos ✅
|
|
51
|
+
- Retrieval hit@5: **84.4%** ✅
|
|
52
|
+
- With auto-scaling enabled, all 18 benchmark repos now stay within a sensible budget that targets ≥ 80% file coverage rather than the old 6 K ceiling.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## [4.0.2] — 2026-04-15 — Bundle factory fix (re-release of 4.0.1)
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
- v4.0.1 was published to npm/GitHub Packages before the binary CI step ran, which meant the published package contained the incomplete bundle (missing `./src/analysis/coverage-score` factory). v4.0.2 is a clean re-release with all fixes from 4.0.1 and the correct bundle.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
13
63
|
## [4.0.1] — 2026-04-15 — Config auto-detection fix
|
|
14
64
|
|
|
15
65
|
### Fixed
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1>⚡ SigMap</h1>
|
|
6
6
|
|
|
7
7
|
<p><strong>WITHOUT SIGMAP, YOUR AI IS GUESSING.</strong><br>
|
|
8
|
-
<strong>
|
|
8
|
+
<strong>Without structured context, AI often reads the wrong file and fills the gaps with guesses.</strong></p>
|
|
9
9
|
|
|
10
10
|
<p><sub>Run one command. Force every answer to come from real code.</sub></p>
|
|
11
11
|
|
|
@@ -19,7 +19,13 @@
|
|
|
19
19
|
npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again.
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
**What you get in ~10 seconds**
|
|
23
|
+
- A compact signature map of your codebase
|
|
24
|
+
- The right file in context far more often (84.4% hit@5 vs 13.6% random)
|
|
25
|
+
- Fewer retries (1.59 vs 2.84 prompts per task)
|
|
26
|
+
- Far smaller context (~2K–4K tokens instead of ~80K)
|
|
27
|
+
|
|
28
|
+
> Latest: **v4.1.0** — Smart Budget. Token budget now auto-scales to your repo size, targeting 80% source-file coverage by default. No config change needed — it just works.
|
|
23
29
|
|
|
24
30
|
<div align="center">
|
|
25
31
|
<img src="demo.gif" alt="SigMap demo — reducing 80K tokens to 4K in under 10 seconds" width="760" />
|
|
@@ -56,11 +62,13 @@ npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again
|
|
|
56
62
|
| | Without SigMap | With SigMap |
|
|
57
63
|
|---|:---:|:---:|
|
|
58
64
|
| Task success | 10% | **59%** |
|
|
59
|
-
| Prompts per task | 2.84 | **1.
|
|
65
|
+
| Prompts per task | 2.84 | **1.59** |
|
|
60
66
|
| Tokens per session | ~80,000 | **~2,000** |
|
|
61
67
|
| Right file found | 13.6% | **84.4%** |
|
|
62
68
|
| Hallucination risk | 92% | **0%** |
|
|
63
69
|
|
|
70
|
+
Measured on 90 coding tasks across 18 real public repos. Full methodology and raw benchmark pages are linked below.
|
|
71
|
+
|
|
64
72
|
</details>
|
|
65
73
|
|
|
66
74
|
---
|
|
@@ -689,7 +697,6 @@ Copy `gen-context.config.json.example` to `gen-context.config.json`:
|
|
|
689
697
|
{
|
|
690
698
|
"output": ".github/copilot-instructions.md",
|
|
691
699
|
"srcDirs": ["src", "app", "lib"],
|
|
692
|
-
"maxTokens": 6000,
|
|
693
700
|
"outputs": ["copilot"],
|
|
694
701
|
"secretScan": true,
|
|
695
702
|
"strategy": "full",
|
|
@@ -703,10 +710,21 @@ Copy `gen-context.config.json.example` to `gen-context.config.json`:
|
|
|
703
710
|
- **`output`** — custom path for the primary markdown output file (used by `copilot` adapter). Default: `.github/copilot-instructions.md`
|
|
704
711
|
- **`outputs`** — which adapters to write to: `copilot` | `claude` | `cursor` | `windsurf`
|
|
705
712
|
- **`srcDirs`** — directories to scan (relative to project root)
|
|
706
|
-
- **`maxTokens`** — max tokens in final output before budget enforcement
|
|
707
713
|
- **`secretScan`** — redact secrets (AWS keys, tokens, etc.) from output
|
|
708
714
|
- **`strategy`** — output mode: `full` (default) | `per-module` | `hot-cold`
|
|
709
715
|
|
|
716
|
+
**Token budget (v4.1.0 — auto-scaling):**
|
|
717
|
+
|
|
718
|
+
| Key | Default | Description |
|
|
719
|
+
|---|---|---|
|
|
720
|
+
| `autoMaxTokens` | `true` | Auto-scale budget to repo size. Set `false` to pin a fixed `maxTokens`. |
|
|
721
|
+
| `coverageTarget` | `0.80` | Fraction of source files to target (0.0–1.0). |
|
|
722
|
+
| `modelContextLimit` | `128000` | Model context window size. Hard cap = `limit × maxTokensHeadroom`. |
|
|
723
|
+
| `maxTokensHeadroom` | `0.20` | Fraction of the context window reserved for SigMap output (default: 25 600 tokens). |
|
|
724
|
+
| `maxTokens` | `6000` | Used only when `autoMaxTokens: false`, or as a floor. |
|
|
725
|
+
|
|
726
|
+
The formula: `effective = clamp(ceil(totalSigTokens × coverageTarget), 4000, floor(modelContextLimit × maxTokensHeadroom))`.
|
|
727
|
+
|
|
710
728
|
Exclusions go in `.contextignore` (gitignore syntax). Also reads `.repomixignore` if present.
|
|
711
729
|
|
|
712
730
|
```
|
|
@@ -752,11 +770,11 @@ Every run now prints a coverage line alongside token reduction:
|
|
|
752
770
|
|
|
753
771
|
```
|
|
754
772
|
───────────────────────────────────────────
|
|
755
|
-
SigMap v4.
|
|
773
|
+
SigMap v4.1.0
|
|
756
774
|
Files scanned : 76
|
|
757
775
|
Symbols found : 332
|
|
758
776
|
Token reduction: 94% (65,227 → 4,103)
|
|
759
|
-
Coverage : A (97%) — 76 of 78 source files included
|
|
777
|
+
Coverage : A (97%) — 76 of 78 source files included [budget: 4000 auto-scaled]
|
|
760
778
|
Output : .github/copilot-instructions.md
|
|
761
779
|
───────────────────────────────────────────
|
|
762
780
|
```
|
|
@@ -771,7 +789,7 @@ sigmap --report
|
|
|
771
789
|
|
|
772
790
|
```
|
|
773
791
|
[sigmap] report:
|
|
774
|
-
version : 4.
|
|
792
|
+
version : 4.1.0
|
|
775
793
|
files processed : 76
|
|
776
794
|
reduction : 93.7%
|
|
777
795
|
coverage : A (97%) — 76 of 78 source files included
|
|
@@ -20,8 +20,23 @@
|
|
|
20
20
|
|
|
21
21
|
"maxSigsPerFile": 25,
|
|
22
22
|
|
|
23
|
+
"_maxTokens_comment": "Used only when autoMaxTokens is false. Override to pin a fixed budget.",
|
|
23
24
|
"maxTokens": 6000,
|
|
24
25
|
|
|
26
|
+
"_autoMaxTokens_comment": "Auto-scale budget based on repo size. Default: true.",
|
|
27
|
+
"_autoMaxTokens_formula": "effective = clamp(totalSigTokens × coverageTarget, 4000, modelContextLimit × maxTokensHeadroom)",
|
|
28
|
+
"autoMaxTokens": true,
|
|
29
|
+
|
|
30
|
+
"_coverageTarget_comment": "Fraction of source files to target for inclusion (0.0–1.0). Default: 0.80 = 80%.",
|
|
31
|
+
"coverageTarget": 0.80,
|
|
32
|
+
|
|
33
|
+
"_modelContextLimit_comment": "Model context window size (tokens). Hard cap = modelContextLimit × maxTokensHeadroom.",
|
|
34
|
+
"_modelContextLimit_examples": "128000 = GPT-4o/Claude (default) | 200000 = Claude max | 1000000 = Gemini 1M",
|
|
35
|
+
"modelContextLimit": 128000,
|
|
36
|
+
|
|
37
|
+
"_maxTokensHeadroom_comment": "Fraction of model context reserved for SigMap output. 0.20 = 25,600 token hard cap.",
|
|
38
|
+
"maxTokensHeadroom": 0.20,
|
|
39
|
+
|
|
25
40
|
"secretScan": true,
|
|
26
41
|
|
|
27
42
|
"monorepo": false,
|
package/gen-context.js
CHANGED
|
@@ -59,9 +59,22 @@ __factories["./src/config/defaults"] = function(module, exports) {
|
|
|
59
59
|
// Maximum signatures extracted per file
|
|
60
60
|
maxSigsPerFile: 25,
|
|
61
61
|
|
|
62
|
-
// Maximum tokens in final output before budget enforcement kicks in
|
|
62
|
+
// Maximum tokens in final output before budget enforcement kicks in.
|
|
63
|
+
// Used only when autoMaxTokens is false, or as a floor for auto-scaling.
|
|
63
64
|
maxTokens: 6000,
|
|
64
|
-
|
|
65
|
+
|
|
66
|
+
// Automatically scale the token budget based on repo size.
|
|
67
|
+
autoMaxTokens: true,
|
|
68
|
+
|
|
69
|
+
// Fraction of source files to target for inclusion (0.0–1.0).
|
|
70
|
+
coverageTarget: 0.80,
|
|
71
|
+
|
|
72
|
+
// Model context window size (tokens). Used to compute the hard cap.
|
|
73
|
+
modelContextLimit: 128000,
|
|
74
|
+
|
|
75
|
+
// Fraction of the model context window reserved for SigMap output.
|
|
76
|
+
maxTokensHeadroom: 0.20,
|
|
77
|
+
|
|
65
78
|
// Scan signatures for secrets and redact matches
|
|
66
79
|
secretScan: true,
|
|
67
80
|
|
|
@@ -3281,6 +3294,72 @@ __factories["./src/format/cache"] = function(module, exports) {
|
|
|
3281
3294
|
};
|
|
3282
3295
|
|
|
3283
3296
|
// ── ./src/health/scorer ──
|
|
3297
|
+
// ── ./src/analysis/coverage-score ──
|
|
3298
|
+
__factories["./src/analysis/coverage-score"] = function(module, exports) {
|
|
3299
|
+
|
|
3300
|
+
'use strict';
|
|
3301
|
+
|
|
3302
|
+
function coverageScore(cwd, fileEntries, config) {
|
|
3303
|
+
const fs = require('fs');
|
|
3304
|
+
const path = require('path');
|
|
3305
|
+
|
|
3306
|
+
const srcDirs = (config && Array.isArray(config.srcDirs) && config.srcDirs.length > 0)
|
|
3307
|
+
? config.srcDirs
|
|
3308
|
+
: ['src', 'app', 'lib'];
|
|
3309
|
+
|
|
3310
|
+
const excludeSet = new Set([
|
|
3311
|
+
'node_modules', '.git', 'dist', 'build', 'out', '__pycache__',
|
|
3312
|
+
'.next', 'coverage', 'target', 'vendor', '.context',
|
|
3313
|
+
]);
|
|
3314
|
+
if (config && Array.isArray(config.exclude)) {
|
|
3315
|
+
for (const x of config.exclude) excludeSet.add(String(x));
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
const includedSet = new Set((fileEntries || []).map(f => f.filePath));
|
|
3319
|
+
|
|
3320
|
+
const allSource = [];
|
|
3321
|
+
for (const relDir of srcDirs) {
|
|
3322
|
+
const absDir = path.resolve(cwd, relDir);
|
|
3323
|
+
if (fs.existsSync(absDir)) _walkCov(absDir, excludeSet, allSource);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
const total = allSource.length;
|
|
3327
|
+
const included = allSource.filter(f => includedSet.has(f)).length;
|
|
3328
|
+
const dropped = total - included;
|
|
3329
|
+
const pct = total > 0 ? Math.round((included / total) * 100) : 100;
|
|
3330
|
+
|
|
3331
|
+
const grade = pct >= 90 ? 'A' : pct >= 75 ? 'B' : pct >= 50 ? 'C' : 'D';
|
|
3332
|
+
const confidence = pct >= 90 ? 'HIGH' : pct >= 70 ? 'MEDIUM' : 'LOW';
|
|
3333
|
+
|
|
3334
|
+
const perModule = new Map();
|
|
3335
|
+
for (const relDir of srcDirs) {
|
|
3336
|
+
const absDir = path.resolve(cwd, relDir);
|
|
3337
|
+
const modFiles = allSource.filter(f => f.startsWith(absDir + path.sep) || f === absDir);
|
|
3338
|
+
const modIncl = modFiles.filter(f => includedSet.has(f)).length;
|
|
3339
|
+
const modPct = modFiles.length > 0 ? Math.round((modIncl / modFiles.length) * 100) : 100;
|
|
3340
|
+
perModule.set(relDir, { total: modFiles.length, included: modIncl, pct: modPct });
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
return { score: pct, grade, total, included, dropped, confidence, perModule };
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function _walkCov(dir, excludeSet, out) {
|
|
3347
|
+
const fs = require('fs');
|
|
3348
|
+
const path = require('path');
|
|
3349
|
+
let entries;
|
|
3350
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
3351
|
+
for (const e of entries) {
|
|
3352
|
+
if (excludeSet.has(e.name)) continue;
|
|
3353
|
+
const full = path.join(dir, e.name);
|
|
3354
|
+
if (e.isDirectory()) { _walkCov(full, excludeSet, out); }
|
|
3355
|
+
else if (e.isFile()) { out.push(full); }
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
module.exports = { coverageScore };
|
|
3360
|
+
|
|
3361
|
+
};
|
|
3362
|
+
|
|
3284
3363
|
__factories["./src/health/scorer"] = function(module, exports) {
|
|
3285
3364
|
|
|
3286
3365
|
/**
|
|
@@ -4575,7 +4654,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
4575
4654
|
|
|
4576
4655
|
const SERVER_INFO = {
|
|
4577
4656
|
name: 'sigmap',
|
|
4578
|
-
version: '4.0
|
|
4657
|
+
version: '4.1.0',
|
|
4579
4658
|
description: 'SigMap MCP server — code signatures on demand',
|
|
4580
4659
|
};
|
|
4581
4660
|
|
|
@@ -6137,7 +6216,7 @@ const path = require('path');
|
|
|
6137
6216
|
const os = require('os');
|
|
6138
6217
|
const { execSync } = require('child_process');
|
|
6139
6218
|
|
|
6140
|
-
const VERSION = '4.0
|
|
6219
|
+
const VERSION = '4.1.0';
|
|
6141
6220
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
6142
6221
|
|
|
6143
6222
|
function requireSourceOrBundled(key) {
|
|
@@ -6357,6 +6436,61 @@ function isMockFile(filePath) {
|
|
|
6357
6436
|
/mock\.(ts|js|tsx|jsx)$/.test(p);
|
|
6358
6437
|
}
|
|
6359
6438
|
|
|
6439
|
+
/**
|
|
6440
|
+
* Compute the effective token budget based on repo size and config.
|
|
6441
|
+
*
|
|
6442
|
+
* Formula:
|
|
6443
|
+
* totalSigTokens = sum of estimated tokens for all extracted sig blocks
|
|
6444
|
+
* needed = ceil(totalSigTokens * coverageTarget) // tokens for target% coverage
|
|
6445
|
+
* hardCap = floor(modelContextLimit * maxTokensHeadroom)
|
|
6446
|
+
* effective = clamp(needed, 4000, hardCap)
|
|
6447
|
+
*
|
|
6448
|
+
* When autoMaxTokens is false the configured maxTokens is returned unchanged.
|
|
6449
|
+
*
|
|
6450
|
+
* @param {Array} fileEntries - All file entries BEFORE budget enforcement
|
|
6451
|
+
* @param {object} config
|
|
6452
|
+
* @returns {number} effective token budget
|
|
6453
|
+
*/
|
|
6454
|
+
function computeEffectiveMaxTokens(fileEntries, config) {
|
|
6455
|
+
if (config.autoMaxTokens === false) return config.maxTokens;
|
|
6456
|
+
|
|
6457
|
+
const coverageTarget = (config.coverageTarget != null) ? config.coverageTarget : 0.80;
|
|
6458
|
+
const modelContextLimit = (config.modelContextLimit != null) ? config.modelContextLimit : 128000;
|
|
6459
|
+
const maxTokensHeadroom = (config.maxTokensHeadroom != null) ? config.maxTokensHeadroom : 0.20;
|
|
6460
|
+
|
|
6461
|
+
const totalSigTokens = fileEntries.reduce(
|
|
6462
|
+
(s, e) => s + estimateTokens((e.sigs || []).join('\n')), 0
|
|
6463
|
+
);
|
|
6464
|
+
if (totalSigTokens === 0) return config.maxTokens;
|
|
6465
|
+
|
|
6466
|
+
const hardCap = Math.floor(modelContextLimit * maxTokensHeadroom);
|
|
6467
|
+
const needed = Math.ceil(totalSigTokens * coverageTarget);
|
|
6468
|
+
const MIN = 4000;
|
|
6469
|
+
const effective = Math.min(Math.max(MIN, needed), hardCap);
|
|
6470
|
+
|
|
6471
|
+
// Warn when repo is so large the hard cap prevents hitting the coverage target
|
|
6472
|
+
if (needed > hardCap) {
|
|
6473
|
+
const estimatedCovPct = Math.round((hardCap / totalSigTokens) * 100);
|
|
6474
|
+
const targetPct = Math.round(coverageTarget * 100);
|
|
6475
|
+
if (estimatedCovPct < targetPct - 10) {
|
|
6476
|
+
console.warn(
|
|
6477
|
+
`[sigmap] auto-budget: ${fileEntries.length} files need ~${Math.round(needed / 1000)}K tokens ` +
|
|
6478
|
+
`for ${targetPct}% coverage`
|
|
6479
|
+
);
|
|
6480
|
+
console.warn(
|
|
6481
|
+
`[sigmap] auto-budget: capped at ${hardCap} ` +
|
|
6482
|
+
`(${Math.round(maxTokensHeadroom * 100)}% of ${Math.round(modelContextLimit / 1000)}K model limit) ` +
|
|
6483
|
+
`→ est. ${estimatedCovPct}% coverage`
|
|
6484
|
+
);
|
|
6485
|
+
console.warn(
|
|
6486
|
+
`[sigmap] auto-budget: tip — set strategy:"per-module" for full coverage on large repos`
|
|
6487
|
+
);
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
|
|
6491
|
+
return effective;
|
|
6492
|
+
}
|
|
6493
|
+
|
|
6360
6494
|
function applyTokenBudget(fileEntries, maxTokens) {
|
|
6361
6495
|
// fileEntries: [{ filePath, sigs, mtime }]
|
|
6362
6496
|
// Reserve ~10% for formatting overhead (section headers, code fences, top-level header)
|
|
@@ -6823,7 +6957,7 @@ function _coverageBar(pct, width) {
|
|
|
6823
6957
|
return '\u2588'.repeat(filled) + '\u2591'.repeat(width - filled);
|
|
6824
6958
|
}
|
|
6825
6959
|
|
|
6826
|
-
function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson, budgetLimit, coverageResult) {
|
|
6960
|
+
function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson, budgetLimit, coverageResult, isAutoBudget) {
|
|
6827
6961
|
const reduction = inputTokens > 0 ? (100 - (finalTokens / inputTokens) * 100).toFixed(1) : 0;
|
|
6828
6962
|
const overBudget = finalTokens > (budgetLimit || 6000);
|
|
6829
6963
|
if (asJson) {
|
|
@@ -6838,6 +6972,7 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
6838
6972
|
reductionPct: parseFloat(reduction),
|
|
6839
6973
|
overBudget,
|
|
6840
6974
|
budgetLimit: budgetLimit || 6000,
|
|
6975
|
+
autoBudget: !!isAutoBudget,
|
|
6841
6976
|
};
|
|
6842
6977
|
if (coverageResult) {
|
|
6843
6978
|
payload.coverage = {
|
|
@@ -6857,13 +6992,16 @@ function printReport(inputTokens, finalTokens, fileCount, droppedCount, asJson,
|
|
|
6857
6992
|
// Exit 1 in CI if over budget — lets pipelines fail fast
|
|
6858
6993
|
if (overBudget) process.exitCode = 1;
|
|
6859
6994
|
} else {
|
|
6995
|
+
const budgetLabel = isAutoBudget
|
|
6996
|
+
? `${budgetLimit || 6000} (auto-scaled)`
|
|
6997
|
+
: `${budgetLimit || 6000} (fixed)`;
|
|
6860
6998
|
console.log(`[sigmap] report:`);
|
|
6861
6999
|
console.log(` version : ${VERSION}`);
|
|
6862
7000
|
console.log(` files processed : ${fileCount}`);
|
|
6863
7001
|
console.log(` files dropped : ${droppedCount}`);
|
|
6864
7002
|
console.log(` input tokens : ~${inputTokens}`);
|
|
6865
7003
|
console.log(` output tokens : ~${finalTokens}`);
|
|
6866
|
-
console.log(` budget limit : ${
|
|
7004
|
+
console.log(` budget limit : ${budgetLabel}`);
|
|
6867
7005
|
console.log(` reduction : ${reduction}%`);
|
|
6868
7006
|
if (coverageResult) {
|
|
6869
7007
|
console.log(` coverage : ${coverageResult.grade} (${coverageResult.score}%) — ${coverageResult.included} of ${coverageResult.total} source files included`);
|
|
@@ -7022,8 +7160,9 @@ function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
|
|
|
7022
7160
|
const outPath = path.join(cwd, '.github', outName);
|
|
7023
7161
|
const modEntries = modules[mod];
|
|
7024
7162
|
|
|
7025
|
-
// Per-module budget:
|
|
7026
|
-
|
|
7163
|
+
// Per-module budget: each module gets its own full effective budget
|
|
7164
|
+
// (per-module strategy is the recommended path for large repos — no sharing needed)
|
|
7165
|
+
const modBudget = Math.max(1000, config.maxTokens);
|
|
7027
7166
|
const budgeted = applyTokenBudget(modEntries, modBudget);
|
|
7028
7167
|
|
|
7029
7168
|
const content = formatOutput(budgeted, cwd, false, config, null);
|
|
@@ -7292,15 +7431,22 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7292
7431
|
});
|
|
7293
7432
|
}
|
|
7294
7433
|
|
|
7434
|
+
// v4.1: compute effective budget once; used by all strategies
|
|
7435
|
+
const effectiveMaxTokens = computeEffectiveMaxTokens(fileEntries, config);
|
|
7436
|
+
// Propagate to config so per-module / hot-cold strategies pick it up
|
|
7437
|
+
const configWithBudget = effectiveMaxTokens !== config.maxTokens
|
|
7438
|
+
? Object.assign({}, config, { maxTokens: effectiveMaxTokens, _autoMaxTokens: effectiveMaxTokens })
|
|
7439
|
+
: config;
|
|
7440
|
+
|
|
7295
7441
|
let result;
|
|
7296
7442
|
if (!reportMode) {
|
|
7297
7443
|
if (strategy === 'per-module') {
|
|
7298
|
-
result = runPerModuleStrategy(cwd,
|
|
7444
|
+
result = runPerModuleStrategy(cwd, configWithBudget, fileEntries, inputTokenTotal);
|
|
7299
7445
|
} else if (strategy === 'hot-cold') {
|
|
7300
|
-
result = runHotColdStrategy(cwd,
|
|
7446
|
+
result = runHotColdStrategy(cwd, configWithBudget, fileEntries, recentFiles, inputTokenTotal);
|
|
7301
7447
|
} else {
|
|
7302
7448
|
// 'full' — original behaviour
|
|
7303
|
-
fileEntries = applyTokenBudget(fileEntries,
|
|
7449
|
+
fileEntries = applyTokenBudget(fileEntries, effectiveMaxTokens);
|
|
7304
7450
|
const droppedCount = beforeCount - fileEntries.length;
|
|
7305
7451
|
const routingEnabled = !!(config.routing || process.argv.includes('--routing'));
|
|
7306
7452
|
const content = formatOutput(fileEntries, cwd, routingEnabled, config, null);
|
|
@@ -7343,21 +7489,21 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7343
7489
|
}
|
|
7344
7490
|
} else {
|
|
7345
7491
|
// report mode: always run full pipeline for accurate stats
|
|
7346
|
-
const budgeted = applyTokenBudget([...fileEntries],
|
|
7492
|
+
const budgeted = applyTokenBudget([...fileEntries], effectiveMaxTokens);
|
|
7347
7493
|
const droppedCount = beforeCount - budgeted.length;
|
|
7348
|
-
const content = formatOutput(budgeted, cwd, false,
|
|
7494
|
+
const content = formatOutput(budgeted, cwd, false, configWithBudget, null);
|
|
7349
7495
|
const finalTokens = estimateTokens(content);
|
|
7350
7496
|
// v4.0: compute coverage score for --report heatmap
|
|
7351
7497
|
let coverageResult = null;
|
|
7352
7498
|
try {
|
|
7353
7499
|
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7354
|
-
coverageResult = coverageScore(cwd, budgeted,
|
|
7500
|
+
coverageResult = coverageScore(cwd, budgeted, configWithBudget);
|
|
7355
7501
|
} catch (_) {}
|
|
7356
7502
|
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount, coverageResult };
|
|
7357
7503
|
}
|
|
7358
7504
|
|
|
7359
7505
|
if (reportMode || process.argv.includes('--report')) {
|
|
7360
|
-
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson,
|
|
7506
|
+
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, effectiveMaxTokens, result.coverageResult, config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens);
|
|
7361
7507
|
}
|
|
7362
7508
|
|
|
7363
7509
|
// Usage tracking (v0.9) — optional append-only NDJSON log
|
|
@@ -7371,8 +7517,9 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7371
7517
|
droppedCount: result.droppedCount,
|
|
7372
7518
|
rawTokens: result.inputTokenTotal,
|
|
7373
7519
|
finalTokens: result.finalTokens,
|
|
7374
|
-
overBudget: result.finalTokens >
|
|
7375
|
-
budgetLimit:
|
|
7520
|
+
overBudget: result.finalTokens > effectiveMaxTokens,
|
|
7521
|
+
budgetLimit: effectiveMaxTokens,
|
|
7522
|
+
autoBudget: config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens,
|
|
7376
7523
|
}, cwd);
|
|
7377
7524
|
} catch (err) {
|
|
7378
7525
|
console.warn(`[sigmap] tracking: ${err.message}`);
|
|
@@ -7393,8 +7540,15 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
7393
7540
|
let coverageLine = '';
|
|
7394
7541
|
try {
|
|
7395
7542
|
const { coverageScore } = requireSourceOrBundled('./src/analysis/coverage-score');
|
|
7396
|
-
const cov = coverageScore(cwd, fileEntries,
|
|
7397
|
-
|
|
7543
|
+
const cov = coverageScore(cwd, fileEntries, configWithBudget);
|
|
7544
|
+
const autoBudgetNote = (config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens)
|
|
7545
|
+
? ` [budget: ${effectiveMaxTokens} auto-scaled]`
|
|
7546
|
+
: '';
|
|
7547
|
+
coverageLine = ` Coverage : ${cov.grade} (${cov.score}%) \u2014 ${cov.included} of ${cov.total} source files included${autoBudgetNote}`;
|
|
7548
|
+
// Extra warning line when coverage is still poor despite auto-scaling
|
|
7549
|
+
if (cov.score < 40 && config.strategy !== 'per-module' && config.strategy !== 'hot-cold') {
|
|
7550
|
+
coverageLine += '\n [sigmap] tip: large repo — consider strategy:"per-module" for full coverage';
|
|
7551
|
+
}
|
|
7398
7552
|
} catch (_) {}
|
|
7399
7553
|
const lines = [
|
|
7400
7554
|
bar,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
|
|
5
5
|
"main": "gen-context.js",
|
|
6
6
|
"exports": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node test/run.js",
|
|
18
|
-
"test:integration": "node test/integration/strategy.test.js && node test/integration/secret-scan.test.js && node test/integration/token-budget.test.js && node test/integration/mcp-server.test.js",
|
|
18
|
+
"test:integration": "node test/integration/strategy.test.js && node test/integration/secret-scan.test.js && node test/integration/token-budget.test.js && node test/integration/auto-budget.test.js && node test/integration/mcp-server.test.js",
|
|
19
19
|
"test:integration:all": "node test/integration/all.js",
|
|
20
20
|
"test:all": "node test/run.js && node test/integration/strategy.test.js && node test/integration/secret-scan.test.js",
|
|
21
21
|
"generate": "node gen-context.js",
|
package/src/config/defaults.js
CHANGED
|
@@ -47,9 +47,30 @@ const DEFAULTS = {
|
|
|
47
47
|
// Maximum signatures extracted per file
|
|
48
48
|
maxSigsPerFile: 25,
|
|
49
49
|
|
|
50
|
-
// Maximum tokens in final output before budget enforcement kicks in
|
|
50
|
+
// Maximum tokens in final output before budget enforcement kicks in.
|
|
51
|
+
// Used only when autoMaxTokens is false, or as a floor for auto-scaling.
|
|
51
52
|
maxTokens: 6000,
|
|
52
53
|
|
|
54
|
+
// Automatically scale the token budget based on repo size.
|
|
55
|
+
// When true, SigMap targets `coverageTarget` fraction of source files and
|
|
56
|
+
// raises the budget up to `modelContextLimit * maxTokensHeadroom`.
|
|
57
|
+
// Set to false (or set maxTokens explicitly) to pin the budget.
|
|
58
|
+
autoMaxTokens: true,
|
|
59
|
+
|
|
60
|
+
// Fraction of source files to target for inclusion (0.0–1.0).
|
|
61
|
+
// 0.80 = include at least 80% of source files in the context output.
|
|
62
|
+
coverageTarget: 0.80,
|
|
63
|
+
|
|
64
|
+
// Model context window size (tokens). Used to compute the hard cap:
|
|
65
|
+
// hardCap = modelContextLimit × maxTokensHeadroom
|
|
66
|
+
// Default: GPT-4o / Claude Sonnet (128K). Set higher for Gemini 1M etc.
|
|
67
|
+
modelContextLimit: 128000,
|
|
68
|
+
|
|
69
|
+
// Fraction of the model context window reserved for SigMap output.
|
|
70
|
+
// Leaves the remaining fraction for the conversation, system prompt, etc.
|
|
71
|
+
// Default 0.20 = 20% of 128K = 25,600 token hard cap.
|
|
72
|
+
maxTokensHeadroom: 0.20,
|
|
73
|
+
|
|
53
74
|
// Scan signatures for secrets and redact matches
|
|
54
75
|
secretScan: true,
|
|
55
76
|
|
package/src/mcp/server.js
CHANGED