sigmap 4.0.0 → 4.0.2

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 CHANGED
@@ -12,35 +12,31 @@ 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.0.0 on 2026-04-15T05:40:32.012Z.
15
+ Below are the code signatures extracted by SigMap v4.0.2 on 2026-04-15T07:28:24.633Z.
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.0.0 -->
21
+ <!-- Generated by SigMap gen-context.js v4.0.2 -->
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 — 6 hours ago)
26
+ ## changes (last 5 commits — 77 minutes ago)
27
27
  ```
28
- src/extractors/generic.js +extract
29
- src/format/llm-txt.js +outputPath +format
30
- src/format/llms-txt.js +outputPath +getShortCommit +detectVersion +format
31
- packages/adapters/llm-full.js +outputPath +format +write
28
+ src/analysis/coverage-score.js +coverageScore +_walk
29
+ src/eval/analyzer.js ~analyzeFiles
30
+ packages/adapters/claude.js +_confidenceMeta ~format
31
+ packages/adapters/copilot.js +_confidenceMeta ~format
32
+ packages/adapters/cursor.js +_confidenceMeta ~format
33
+ packages/adapters/gemini.js +_confidenceMeta ~format ~write
34
+ packages/adapters/openai.js +_confidenceMeta ~format ~outputPath
35
+ packages/adapters/windsurf.js +_confidenceMeta ~format
32
36
  ```
33
37
 
34
38
  ## packages
35
39
 
36
- ### packages/adapters/llm-full.js
37
- ```
38
- module.exports = { name: 'llm-full', format, outputPath, write }
39
- function outputPath(cwd)
40
- function format(context, opts)
41
- function write(context, cwd, opts)
42
- ```
43
-
44
40
  ### packages/adapters/claude.js
45
41
  ```
46
42
  module.exports = { name, format, outputPath, write }
@@ -50,14 +46,6 @@ function outputPath(cwd) → string
50
46
  function write(context, cwd, opts = {})
51
47
  ```
52
48
 
53
- ### packages/adapters/codex.js
54
- ```
55
- module.exports = { name, format, outputPath, write }
56
- function format(context, opts = {}) → string
57
- function outputPath(cwd) → string
58
- function write(context, cwd, opts = {})
59
- ```
60
-
61
49
  ### packages/adapters/copilot.js
62
50
  ```
63
51
  module.exports = { name, format, outputPath, write }
@@ -84,15 +72,6 @@ function write(context, cwd, opts = {})
84
72
  function _confidenceMeta(opts)
85
73
  ```
86
74
 
87
- ### packages/adapters/index.js
88
- ```
89
- module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters }
90
- function getAdapter(name) → { name: string, format: F
91
- function listAdapters() → string[]
92
- function adapt(context, adapterName, opts = {}) → string
93
- function outputsToAdapters(outputs) → string[]
94
- ```
95
-
96
75
  ### packages/adapters/openai.js
97
76
  ```
98
77
  module.exports = { name, format, outputPath }
@@ -109,6 +88,31 @@ function _confidenceMeta(opts)
109
88
  function outputPath(cwd) → string
110
89
  ```
111
90
 
91
+ ### packages/adapters/codex.js
92
+ ```
93
+ module.exports = { name, format, outputPath, write }
94
+ function format(context, opts = {}) → string
95
+ function outputPath(cwd) → string
96
+ function write(context, cwd, opts = {})
97
+ ```
98
+
99
+ ### packages/adapters/index.js
100
+ ```
101
+ module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters }
102
+ function getAdapter(name) → { name: string, format: F
103
+ function listAdapters() → string[]
104
+ function adapt(context, adapterName, opts = {}) → string
105
+ function outputsToAdapters(outputs) → string[]
106
+ ```
107
+
108
+ ### packages/adapters/llm-full.js
109
+ ```
110
+ module.exports = { name: 'llm-full', format, outputPath, write }
111
+ function outputPath(cwd)
112
+ function format(context, opts)
113
+ function write(context, cwd, opts)
114
+ ```
115
+
112
116
  ### packages/cli/index.js
113
117
  ```
114
118
  module.exports = { CLI_ENTRY, run }
@@ -149,33 +153,33 @@ function adapt(context, adapterName, opts = {}) → string
149
153
 
150
154
  ## src
151
155
 
152
- ### src/extractors/generic.js
153
- ```
154
- module.exports = { extract }
155
- function extract(src)
156
- ```
157
-
158
- ### src/format/llm-txt.js
156
+ ### src/analysis/coverage-score.js
159
157
  ```
160
- module.exports = { format, outputPath }
161
- function outputPath(cwd)
162
- function format(context, cwd, version)
158
+ module.exports = { coverageScore }
159
+ function coverageScore(cwd, fileEntries, config) → { * score: number, * grad
160
+ function _walk(dir, excludeSet, out)
163
161
  ```
164
162
 
165
- ### src/format/llms-txt.js
163
+ ### src/eval/analyzer.js
166
164
  ```
167
- module.exports = { format, outputPath }
168
- function outputPath(cwd)
169
- function getShortCommit(cwd)
170
- function detectVersion(cwd)
171
- function format(context, cwd, writtenFiles, sigmapVersion)
165
+ module.exports = { analyzeFiles, formatAnalysisTable, formatAnalysisJSON }
166
+ function isDockerfile(name)
167
+ function getExtractorName(filePath)
168
+ function tokenCount(sigs)
169
+ function hasCoverage(filePath, cwd)
170
+ function loadExtractor(name, cwd)
171
+ function analyzeFiles(files, cwd, opts) → object[]
172
+ function formatAnalysisTable(stats, showSlow) → string
173
+ function formatAnalysisJSON(stats) → object
172
174
  ```
173
175
 
174
- ### src/analysis/coverage-score.js
176
+ ### src/mcp/server.js
175
177
  ```
176
- module.exports = { coverageScore }
177
- function coverageScore(cwd, fileEntries, config) → { * score: number, * grad
178
- function _walk(dir, excludeSet, out)
178
+ module.exports = { start }
179
+ function respond(id, result)
180
+ function respondError(id, code, message)
181
+ function dispatch(msg, cwd)
182
+ function start(cwd)
179
183
  ```
180
184
 
181
185
  ### src/config/defaults.js
@@ -191,19 +195,6 @@ function loadConfig(cwd) → object
191
195
  function deepClone(obj)
192
196
  ```
193
197
 
194
- ### src/eval/analyzer.js
195
- ```
196
- module.exports = { analyzeFiles, formatAnalysisTable, formatAnalysisJSON }
197
- function isDockerfile(name)
198
- function getExtractorName(filePath)
199
- function tokenCount(sigs)
200
- function hasCoverage(filePath, cwd)
201
- function loadExtractor(name, cwd)
202
- function analyzeFiles(files, cwd, opts) → object[]
203
- function formatAnalysisTable(stats, showSlow) → string
204
- function formatAnalysisJSON(stats) → object
205
- ```
206
-
207
198
  ### src/eval/runner.js
208
199
  ```
209
200
  module.exports = { run, rank, loadTasks, buildSigIndex, formatTable, formatMetrics, tokenize }
@@ -287,6 +278,12 @@ module.exports = { extract }
287
278
  function extract(src) → string[]
288
279
  ```
289
280
 
281
+ ### src/extractors/generic.js
282
+ ```
283
+ module.exports = { extract }
284
+ function extract(src)
285
+ ```
286
+
290
287
  ### src/extractors/go.js
291
288
  ```
292
289
  module.exports = { extract }
@@ -546,6 +543,22 @@ function generateDashboardHtml(cwd, health)
546
543
  function renderHistoryCharts(cwd, health)
547
544
  ```
548
545
 
546
+ ### src/format/llm-txt.js
547
+ ```
548
+ module.exports = { format, outputPath }
549
+ function outputPath(cwd)
550
+ function format(context, cwd, version)
551
+ ```
552
+
553
+ ### src/format/llms-txt.js
554
+ ```
555
+ module.exports = { format, outputPath }
556
+ function outputPath(cwd)
557
+ function getShortCommit(cwd)
558
+ function detectVersion(cwd)
559
+ function format(context, cwd, writtenFiles, sigmapVersion)
560
+ ```
561
+
549
562
  ### src/graph/builder.js
550
563
  ```
551
564
  module.exports = { build, buildFromCwd, extractFileDeps }
@@ -609,15 +622,6 @@ function queryContext(args, cwd)
609
622
  function getImpact(args, cwd)
610
623
  ```
611
624
 
612
- ### src/mcp/server.js
613
- ```
614
- module.exports = { start }
615
- function respond(id, result)
616
- function respondError(id, code, message)
617
- function dispatch(msg, cwd)
618
- function start(cwd)
619
- ```
620
-
621
625
  ### src/mcp/tools.js
622
626
  ```
623
627
  module.exports = { TOOLS }
package/CHANGELOG.md CHANGED
@@ -10,6 +10,69 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [4.0.2] — 2026-04-15 — Bundle factory fix (re-release of 4.0.1)
14
+
15
+ ### Fixed
16
+ - 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.
17
+
18
+ ---
19
+
20
+ ## [4.0.1] — 2026-04-15 — Config auto-detection fix
21
+
22
+ ### Fixed
23
+ - **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`.
24
+ - **`--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.
25
+ - **`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/`.
26
+ - **Example `outputs` updated**: `gen-context.config.json.example` now lists all four standard adapters — `["copilot","codex","claude","gemini"]` — matching the recommended setup.
27
+
28
+ ### Benchmarks (v4.0.1)
29
+ - Token reduction: **97.6% average** across 18 repos ✅
30
+ - Retrieval hit@5: **84.4%** (up from 83.3% in v4.0.0)
31
+
32
+ ---
33
+
34
+ ## [4.0.0] — 2026-04-15 — Intelligence Layer
35
+
36
+ ### Added
37
+ - **Coverage score** (`src/analysis/coverage-score.js`): measures what fraction of source files made it into context after token-budget application.
38
+ - Grade scale: A ≥ 90% · B ≥ 75% · C ≥ 50% · D < 50%
39
+ - Confidence indicator: HIGH / MEDIUM / LOW
40
+ - Per-module breakdown per srcDir via `perModule` Map
41
+ - **Confidence indicators in all output writers**: every generated file now includes a metadata comment:
42
+ ```
43
+ <!-- sigmap: version=4.0.0 confidence=HIGH coverage=94% dropped=9 commit=abc1234 -->
44
+ ```
45
+ Applies to: `copilot`, `claude`, `cursor`, `windsurf`, `openai`, `gemini` adapters.
46
+ - **`--report` module heatmap**: ASCII bar chart per srcDir showing coverage percentage:
47
+ ```
48
+ Module Coverage:
49
+ src ████████████████ 100% (64/64 files)
50
+ packages ██████████████░░ 86% (12/14 files)
51
+ ```
52
+ `--report --json` gains a `coverage` object with `score`, `grade`, `confidence`, `totalFiles`, `includedFiles`, `droppedFiles`, and `perModule`.
53
+ - **`--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:
54
+ ```
55
+ [sigmap] Risk: Changed files (3):
56
+ src/auth/service.ts [HIGH] — exports public API, 5 downstream dependents
57
+ src/config/database.ts [MEDIUM] — config file
58
+ src/utils/format.ts [LOW] — no dependents, internal utility
59
+ ```
60
+ - **Coverage in post-run summary**: every normal run now prints a `Coverage` line:
61
+ ```
62
+ Coverage : A (97%) — 76 of 78 source files included
63
+ ```
64
+ - **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`.
65
+
66
+ ### Changed
67
+ - **Token budget drop order step 5**: now uses `signalQuality = sigs / linesOfCode` (least-informative files dropped first) instead of the previous "fewest sigs" heuristic.
68
+ - **`src/eval/analyzer.js` `analyzeFiles()` output**: each file stat now includes `linesOfCode` and `signalQuality` fields.
69
+
70
+ ### Benchmark (v4.0.0)
71
+ - Token reduction: **97.6% average** across 18 repos (target ≥ 97.0%) ✅
72
+ - Retrieval hit@5: 83.3% (retrieval improvement targeted in v4.5 with adaptive query)
73
+
74
+ ---
75
+
13
76
  ## [3.5.0] — 2026-04-14 — Phase C/D Intelligence Expansion
14
77
 
15
78
  ### 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: **v3.5.0** adds Phase C/D intelligence expansion with framework-specialized extractors and cross-module pattern detection.
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.5% 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`.
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
- # Append run metrics to .context/usage.ndjson
751
- sigmap --track
769
+ sigmap --report
770
+ ```
752
771
 
753
- # Structured JSON report for CI (exits 1 if over budget)
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": "2.0.0", "finalTokens": 3200, "reductionPct": 92.4, "overBudget": false }
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
- # Composite health score
795
+ ```bash
758
796
  sigmap --health
759
- # score: 95/100 (grade A) | reduction: 91.2% | 1 day since regen | 47 runs
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
- "srcDirs": ["src", "app", "lib", "packages", "services", "api"],
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
- return deepClone(DEFAULTS);
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
- return deepClone(DEFAULTS);
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 ──
@@ -3184,6 +3281,72 @@ __factories["./src/format/cache"] = function(module, exports) {
3184
3281
  };
3185
3282
 
3186
3283
  // ── ./src/health/scorer ──
3284
+ // ── ./src/analysis/coverage-score ──
3285
+ __factories["./src/analysis/coverage-score"] = function(module, exports) {
3286
+
3287
+ 'use strict';
3288
+
3289
+ function coverageScore(cwd, fileEntries, config) {
3290
+ const fs = require('fs');
3291
+ const path = require('path');
3292
+
3293
+ const srcDirs = (config && Array.isArray(config.srcDirs) && config.srcDirs.length > 0)
3294
+ ? config.srcDirs
3295
+ : ['src', 'app', 'lib'];
3296
+
3297
+ const excludeSet = new Set([
3298
+ 'node_modules', '.git', 'dist', 'build', 'out', '__pycache__',
3299
+ '.next', 'coverage', 'target', 'vendor', '.context',
3300
+ ]);
3301
+ if (config && Array.isArray(config.exclude)) {
3302
+ for (const x of config.exclude) excludeSet.add(String(x));
3303
+ }
3304
+
3305
+ const includedSet = new Set((fileEntries || []).map(f => f.filePath));
3306
+
3307
+ const allSource = [];
3308
+ for (const relDir of srcDirs) {
3309
+ const absDir = path.resolve(cwd, relDir);
3310
+ if (fs.existsSync(absDir)) _walkCov(absDir, excludeSet, allSource);
3311
+ }
3312
+
3313
+ const total = allSource.length;
3314
+ const included = allSource.filter(f => includedSet.has(f)).length;
3315
+ const dropped = total - included;
3316
+ const pct = total > 0 ? Math.round((included / total) * 100) : 100;
3317
+
3318
+ const grade = pct >= 90 ? 'A' : pct >= 75 ? 'B' : pct >= 50 ? 'C' : 'D';
3319
+ const confidence = pct >= 90 ? 'HIGH' : pct >= 70 ? 'MEDIUM' : 'LOW';
3320
+
3321
+ const perModule = new Map();
3322
+ for (const relDir of srcDirs) {
3323
+ const absDir = path.resolve(cwd, relDir);
3324
+ const modFiles = allSource.filter(f => f.startsWith(absDir + path.sep) || f === absDir);
3325
+ const modIncl = modFiles.filter(f => includedSet.has(f)).length;
3326
+ const modPct = modFiles.length > 0 ? Math.round((modIncl / modFiles.length) * 100) : 100;
3327
+ perModule.set(relDir, { total: modFiles.length, included: modIncl, pct: modPct });
3328
+ }
3329
+
3330
+ return { score: pct, grade, total, included, dropped, confidence, perModule };
3331
+ }
3332
+
3333
+ function _walkCov(dir, excludeSet, out) {
3334
+ const fs = require('fs');
3335
+ const path = require('path');
3336
+ let entries;
3337
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
3338
+ for (const e of entries) {
3339
+ if (excludeSet.has(e.name)) continue;
3340
+ const full = path.join(dir, e.name);
3341
+ if (e.isDirectory()) { _walkCov(full, excludeSet, out); }
3342
+ else if (e.isFile()) { out.push(full); }
3343
+ }
3344
+ }
3345
+
3346
+ module.exports = { coverageScore };
3347
+
3348
+ };
3349
+
3187
3350
  __factories["./src/health/scorer"] = function(module, exports) {
3188
3351
 
3189
3352
  /**
@@ -4478,7 +4641,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
4478
4641
 
4479
4642
  const SERVER_INFO = {
4480
4643
  name: 'sigmap',
4481
- version: '4.0.0',
4644
+ version: '4.0.2',
4482
4645
  description: 'SigMap MCP server — code signatures on demand',
4483
4646
  };
4484
4647
 
@@ -6040,7 +6203,7 @@ const path = require('path');
6040
6203
  const os = require('os');
6041
6204
  const { execSync } = require('child_process');
6042
6205
 
6043
- const VERSION = '4.0.0';
6206
+ const VERSION = '4.0.2';
6044
6207
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
6045
6208
 
6046
6209
  function requireSourceOrBundled(key) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
package/src/mcp/server.js CHANGED
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '4.0.0',
21
+ version: '4.0.2',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24