sigmap 3.5.0 → 3.6.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 CHANGED
@@ -12,46 +12,19 @@ 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 v3.4.0 on 2026-04-14T21:21:26.681Z.
15
+ Below are the code signatures extracted by SigMap v3.5.1 on 2026-04-14T23:05:11.451Z.
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 v3.4.0 -->
21
+ <!-- Generated by SigMap gen-context.js v3.5.1 -->
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 — 11 minutes ago)
27
- ```
28
- src/config/loader.js ~detectAutoSrcDirs
29
- src/eval/analyzer.js ~isDockerfile
30
- src/extractors/markdown.js +extract
31
- src/extractors/properties.js +extract
32
- src/extractors/toml.js +extract
33
- src/extractors/xml.js +attributes +extract
34
- ```
35
-
36
26
  ## packages
37
27
 
38
- ### packages/adapters/codex.js
39
- ```
40
- module.exports = { name, format, outputPath, write }
41
- function format(context, opts = {}) → string
42
- function outputPath(cwd) → string
43
- function write(context, cwd, opts = {})
44
- ```
45
-
46
- ### packages/adapters/index.js
47
- ```
48
- module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters }
49
- function getAdapter(name) → { name: string, format: F
50
- function listAdapters() → string[]
51
- function adapt(context, adapterName, opts = {}) → string
52
- function outputsToAdapters(outputs) → string[]
53
- ```
54
-
55
28
  ### packages/core/index.js
56
29
  ```
57
30
  module.exports = { extract, rank, buildSigIndex, scan, score, adapt }
@@ -72,6 +45,14 @@ function outputPath(cwd) → string
72
45
  function write(context, cwd, opts = {})
73
46
  ```
74
47
 
48
+ ### packages/adapters/codex.js
49
+ ```
50
+ module.exports = { name, format, outputPath, write }
51
+ function format(context, opts = {}) → string
52
+ function outputPath(cwd) → string
53
+ function write(context, cwd, opts = {})
54
+ ```
55
+
75
56
  ### packages/adapters/copilot.js
76
57
  ```
77
58
  module.exports = { name, format, outputPath, write }
@@ -95,6 +76,23 @@ function outputPath(cwd) → string
95
76
  function write(context, cwd, opts = {})
96
77
  ```
97
78
 
79
+ ### packages/adapters/index.js
80
+ ```
81
+ module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters }
82
+ function getAdapter(name) → { name: string, format: F
83
+ function listAdapters() → string[]
84
+ function adapt(context, adapterName, opts = {}) → string
85
+ function outputsToAdapters(outputs) → string[]
86
+ ```
87
+
88
+ ### packages/adapters/llm-full.js
89
+ ```
90
+ module.exports = { name: 'llm-full', format, outputPath, write }
91
+ function outputPath(cwd)
92
+ function format(context, opts)
93
+ function write(context, cwd, opts)
94
+ ```
95
+
98
96
  ### packages/adapters/openai.js
99
97
  ```
100
98
  module.exports = { name, format, outputPath }
@@ -163,13 +161,13 @@ function formatAnalysisTable(stats, showSlow) → string
163
161
  function formatAnalysisJSON(stats) → object
164
162
  ```
165
163
 
166
- ### src/extractors/graphql.js
164
+ ### src/extractors/markdown.js
167
165
  ```
168
166
  module.exports = { extract }
169
167
  function extract(src) → string[]
170
168
  ```
171
169
 
172
- ### src/extractors/markdown.js
170
+ ### src/extractors/patterns.js
173
171
  ```
174
172
  module.exports = { extract }
175
173
  function extract(src) → string[]
@@ -181,27 +179,25 @@ module.exports = { extract }
181
179
  function extract(src) → string[]
182
180
  ```
183
181
 
184
- ### src/extractors/protobuf.js
182
+ ### src/extractors/python_dataclass.js
185
183
  ```
186
184
  module.exports = { extract }
187
185
  function extract(src) → string[]
188
186
  ```
189
187
 
190
- ### src/extractors/sql.js
188
+ ### src/extractors/toml.js
191
189
  ```
192
190
  module.exports = { extract }
193
191
  function extract(src) → string[]
194
- function _cleanName(raw)
195
- function _normalizeParams(raw)
196
192
  ```
197
193
 
198
- ### src/extractors/terraform.js
194
+ ### src/extractors/typescript_react.js
199
195
  ```
200
196
  module.exports = { extract }
201
197
  function extract(src) → string[]
202
198
  ```
203
199
 
204
- ### src/extractors/toml.js
200
+ ### src/extractors/vue_sfc.js
205
201
  ```
206
202
  module.exports = { extract }
207
203
  function extract(src) → string[]
@@ -305,6 +301,12 @@ module.exports = { extract }
305
301
  function extract(src) → string[]
306
302
  ```
307
303
 
304
+ ### src/extractors/generic.js
305
+ ```
306
+ module.exports = { extract }
307
+ function extract(src)
308
+ ```
309
+
308
310
  ### src/extractors/go.js
309
311
  ```
310
312
  module.exports = { extract }
@@ -314,6 +316,12 @@ function extractInterfaceMethods(block)
314
316
  function normalizeParams(params)
315
317
  ```
316
318
 
319
+ ### src/extractors/graphql.js
320
+ ```
321
+ module.exports = { extract }
322
+ function extract(src) → string[]
323
+ ```
324
+
317
325
  ### src/extractors/html.js
318
326
  ```
319
327
  module.exports = { extract }
@@ -368,6 +376,12 @@ function diffSignatures(baseSigs, currentSigs) → {added:string[], removed:
368
376
  function extractName(sig)
369
377
  ```
370
378
 
379
+ ### src/extractors/protobuf.js
380
+ ```
381
+ module.exports = { extract }
382
+ function extract(src) → string[]
383
+ ```
384
+
371
385
  ### src/extractors/python.js
372
386
  ```
373
387
  module.exports = { extract }
@@ -381,12 +395,6 @@ function normalizeParams(params)
381
395
  function extractDocHint(src, fnName, fnSigLine)
382
396
  ```
383
397
 
384
- ### src/extractors/python_dataclass.js
385
- ```
386
- module.exports = { extract }
387
- function extract(src) → string[]
388
- ```
389
-
390
398
  ### src/extractors/ruby.js
391
399
  ```
392
400
  module.exports = { extract }
@@ -421,6 +429,14 @@ module.exports = { extract }
421
429
  function extract(src) → string[]
422
430
  ```
423
431
 
432
+ ### src/extractors/sql.js
433
+ ```
434
+ module.exports = { extract }
435
+ function extract(src) → string[]
436
+ function _cleanName(raw)
437
+ function _normalizeParams(raw)
438
+ ```
439
+
424
440
  ### src/extractors/svelte.js
425
441
  ```
426
442
  module.exports = { extract }
@@ -439,6 +455,12 @@ function normalizeParams(params)
439
455
  function extractArrowType(str)
440
456
  ```
441
457
 
458
+ ### src/extractors/terraform.js
459
+ ```
460
+ module.exports = { extract }
461
+ function extract(src) → string[]
462
+ ```
463
+
442
464
  ### src/extractors/todos.js
443
465
  ```
444
466
  module.exports = { extractTodos }
@@ -455,12 +477,6 @@ function extractClassMembers(block)
455
477
  function normalizeParams(params)
456
478
  ```
457
479
 
458
- ### src/extractors/typescript_react.js
459
- ```
460
- module.exports = { extract }
461
- function extract(src) → string[]
462
- ```
463
-
464
480
  ### src/extractors/vue.js
465
481
  ```
466
482
  module.exports = { extract }
@@ -469,12 +485,6 @@ function normalizeParams(params)
469
485
  function normalizeType(type)
470
486
  ```
471
487
 
472
- ### src/extractors/vue_sfc.js
473
- ```
474
- module.exports = { extract }
475
- function extract(src) → string[]
476
- ```
477
-
478
488
  ### src/extractors/yaml.js
479
489
  ```
480
490
  module.exports = { extract }
@@ -508,6 +518,22 @@ function generateDashboardHtml(cwd, health)
508
518
  function renderHistoryCharts(cwd, health)
509
519
  ```
510
520
 
521
+ ### src/format/llm-txt.js
522
+ ```
523
+ module.exports = { format, outputPath }
524
+ function outputPath(cwd)
525
+ function format(context, cwd, version)
526
+ ```
527
+
528
+ ### src/format/llms-txt.js
529
+ ```
530
+ module.exports = { format, outputPath }
531
+ function outputPath(cwd)
532
+ function getShortCommit(cwd)
533
+ function detectVersion(cwd)
534
+ function format(context, cwd, writtenFiles, sigmapVersion)
535
+ ```
536
+
511
537
  ### src/graph/builder.js
512
538
  ```
513
539
  module.exports = { build, buildFromCwd, extractFileDeps }
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="docs-vp/.vitepress/public/logo.svg" alt="SigMap logo" width="64" height="84" />
3
+ <img src="assets/logo.png" alt="SigMap logo" width="80" height="80" />
4
4
 
5
5
  <h1>⚡ SigMap</h1>
6
6
 
@@ -50,15 +50,15 @@ npx sigmap # 10 seconds. zero config. your AI never reads the wrong file again
50
50
 
51
51
  <div align="center">
52
52
  <img src="docs/comparison-chart.svg" alt="SigMap benchmark — before vs after across 3 RAG quality metrics" width="700" />
53
- <br/><sub><a href="https://manojmallick.github.io/sigmap/guide/task-benchmark.html">80 tasks · 16 real repos · no LLM API · <strong>full methodology →</strong></a></sub>
53
+ <br/><sub><a href="https://manojmallick.github.io/sigmap/guide/task-benchmark.html">90 tasks · 18 real repos · no LLM API · <strong>full methodology →</strong></a></sub>
54
54
  </div>
55
55
 
56
56
  | | Without SigMap | With SigMap |
57
57
  |---|:---:|:---:|
58
58
  | Task success | 10% | **59%** |
59
- | Prompts per task | 2.84 | **1.54** |
59
+ | Prompts per task | 2.84 | **1.78** |
60
60
  | Tokens per session | ~80,000 | **~2,000** |
61
- | Right file found | 13.7% | **87.5%** |
61
+ | Right file found | 13.6% | **84.4%** |
62
62
  | Hallucination risk | 92% | **0%** |
63
63
 
64
64
  </details>
@@ -126,24 +126,26 @@ Reproduced with `node scripts/run-benchmark.mjs` on public repos:
126
126
 
127
127
  | Repo | Language | Raw tokens | After SigMap | Reduction |
128
128
  |------|----------|------------|--------------|-----------|
129
- | express | JavaScript | 15.5K | 201 | **98.7%** |
130
- | flask | Python | 84.8K | 3.4K | **96.0%** |
131
- | gin | Go | 172.8K | 5.7K | **96.7%** |
132
- | spring-petclinic | Java | 77.0K | 634 | **99.2%** |
129
+ | express | JavaScript | 70.6K | 911 | **98.7%** |
130
+ | flask | Python | 147.9K | 6.7K | **95.4%** |
131
+ | gin | Go | 216.4K | 6.0K | **97.2%** |
132
+ | spring-petclinic | Java | 97.9K | 3.4K | **96.5%** |
133
133
  | rails | Ruby | 1.5M | 7.1K | **99.5%** |
134
- | axios | TypeScript | 31.7K | 1.5K | **95.2%** |
135
- | rust-analyzer | Rust | 3.5M | 5.9K | **99.8%** |
134
+ | axios | TypeScript | 105.7K | 6.1K | **94.3%** |
135
+ | rust-analyzer | Rust | 3.5M | 6.3K | **99.8%** |
136
136
  | abseil-cpp | C++ | 2.3M | 6.3K | **99.7%** |
137
- | serilog | C# | 113.7K | 5.8K | **94.9%** |
138
- | riverpod | Dart | 682.7K | 6.5K | **99.0%** |
137
+ | serilog | C# | 195.5K | 6.9K | **96.4%** |
138
+ | riverpod | Dart | 747.2K | 6.5K | **99.1%** |
139
139
  | okhttp | Kotlin | 31.3K | 1.4K | **95.5%** |
140
140
  | laravel | PHP | 1.7M | 7.2K | **99.6%** |
141
141
  | akka | Scala | 790.5K | 7.1K | **99.1%** |
142
- | vapor | Swift | 171.2K | 6.4K | **96.3%** |
143
- | vue-core | Vue | 404.2K | 8.8K | **97.8%** |
142
+ | vapor | Swift | 171.4K | 6.4K | **96.2%** |
143
+ | vue-core | Vue | 414.4K | 8.6K | **97.9%** |
144
144
  | svelte | Svelte | 438.2K | 8.0K | **98.2%** |
145
+ | fastify | JavaScript | 54.4K | 2.6K | **95.3%** |
146
+ | fastapi | Python | 178.4K | 5.2K | **97.1%** |
145
147
 
146
- **Average: 99.3% reduction across 16 languages.** See [`benchmarks/reports/token-reduction.md`](benchmarks/reports/token-reduction.md) or reproduce with `node scripts/run-benchmark.mjs`.
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`.
147
149
 
148
150
  ---
149
151
 
@@ -454,6 +456,10 @@ Activates on startup (`onStartupFinished`) — loads within 3 s, never blocks ed
454
456
 
455
457
  The official SigMap JetBrains plugin brings the same features to IntelliJ-based IDEs. Install it from the JetBrains Marketplace and it works identically to the VS Code extension.
456
458
 
459
+ <div align="center">
460
+ <img src="assets/intelij.gif" alt="SigMap JetBrains plugin — status bar health grade, regenerate action, and context auto-refresh in IntelliJ IDEA" width="700" />
461
+ </div>
462
+
457
463
  | Feature | Detail |
458
464
  |---|---|
459
465
  | **Status bar widget** | Shows health grade (`A`-`F`) + time since last regen; updates every 60 s |
package/gen-context.js CHANGED
@@ -5881,6 +5881,153 @@ __factories["./packages/adapters/index"] = function(module, exports) {
5881
5881
  module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
5882
5882
  };
5883
5883
 
5884
+ // ── ./src/extractors/generic ──
5885
+ __factories["./src/extractors/generic"] = function(module, exports) {
5886
+ 'use strict';
5887
+ const PATTERNS = [
5888
+ /^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
5889
+ /^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
5890
+ /^def\s+\w+[\s(|:]/,
5891
+ /^(?:pub\s+)?func\s+\w+\s*\(/,
5892
+ /^(?:let|let\s+rec)\s+\w+\s*[=(]/,
5893
+ /^class\s+\w+/,
5894
+ /^(?:proc|sub|method)\s+\w+\s*\(/,
5895
+ ];
5896
+ function extract(src) {
5897
+ if (!src || typeof src !== 'string') return [];
5898
+ const results = [];
5899
+ for (const raw of src.split('\n')) {
5900
+ const line = raw.trim();
5901
+ if (!line || /^[#\-]/.test(line) || /^\/\//.test(line) || line.includes('\0')) continue;
5902
+ for (const pat of PATTERNS) {
5903
+ if (pat.test(line)) { results.push(line.slice(0, 120)); break; }
5904
+ }
5905
+ if (results.length >= 15) break;
5906
+ }
5907
+ return results;
5908
+ }
5909
+ module.exports = { extract };
5910
+ };
5911
+
5912
+ // ── ./src/format/llm-txt ──
5913
+ __factories["./src/format/llm-txt"] = function(module, exports) {
5914
+ 'use strict';
5915
+ const path = require('path');
5916
+ function outputPath(cwd) { return path.join(cwd, 'llm.txt'); }
5917
+ function format(context, cwd, version) {
5918
+ const name = context.projectName || path.basename(cwd);
5919
+ const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
5920
+ const mods = context.srcDirs || [];
5921
+ return [
5922
+ `# Project: ${name}`,
5923
+ `Languages: ${langs.join(', ') || 'unknown'}`,
5924
+ `Root: ${mods[0] || 'src/'}`,
5925
+ '',
5926
+ '## Modules',
5927
+ ...mods.map(m => `- ${m}/`),
5928
+ '',
5929
+ '## Key flows',
5930
+ '- <!-- describe your main user flows here -->',
5931
+ '',
5932
+ '## Rules',
5933
+ '- <!-- describe your team conventions here -->',
5934
+ '',
5935
+ `Generated: ${new Date().toISOString()} | SigMap v${version}`,
5936
+ ].join('\n');
5937
+ }
5938
+ module.exports = { format, outputPath };
5939
+ };
5940
+
5941
+ // ── ./src/format/llms-txt ──
5942
+ __factories["./src/format/llms-txt"] = function(module, exports) {
5943
+ 'use strict';
5944
+ const path = require('path');
5945
+ const fs = require('fs');
5946
+ const { execSync } = require('child_process');
5947
+ function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
5948
+ function getShortCommit(cwd) {
5949
+ try { return execSync('git rev-parse --short HEAD', { cwd, timeout: 2000 }).toString().trim(); }
5950
+ catch (_) { return ''; }
5951
+ }
5952
+ function detectVersion(cwd) {
5953
+ try {
5954
+ const pkg = path.join(cwd, 'package.json');
5955
+ if (fs.existsSync(pkg)) return JSON.parse(fs.readFileSync(pkg, 'utf8')).version || '';
5956
+ } catch (_) {}
5957
+ return '';
5958
+ }
5959
+ function format(context, cwd, writtenFiles, sigmapVersion) {
5960
+ writtenFiles = writtenFiles || [];
5961
+ sigmapVersion = sigmapVersion || '';
5962
+ const name = context.projectName || path.basename(cwd);
5963
+ const ver = detectVersion(cwd);
5964
+ const commit = getShortCommit(cwd);
5965
+ const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
5966
+ const lines = [
5967
+ '# SigMap Context Index',
5968
+ `> Generated by SigMap v${sigmapVersion} — zero-dependency AI context engine`,
5969
+ '',
5970
+ '## Project',
5971
+ `- Name: ${name}`,
5972
+ ];
5973
+ if (ver) lines.push(`- Version: ${ver}`);
5974
+ if (langs.length) lines.push(`- Language: ${langs.join(', ')}`);
5975
+ if (commit) lines.push(`- Commit: ${commit}`);
5976
+ if (writtenFiles.length) {
5977
+ lines.push('', '## Context Files');
5978
+ for (const f of writtenFiles) {
5979
+ const rel = path.relative(cwd, f.path);
5980
+ const extra = f.tokens ? `: ${f.tokens} tokens` : '';
5981
+ lines.push(`- [${f.label || rel}](${rel})${extra}`);
5982
+ }
5983
+ }
5984
+ const mods = context.srcDirs || [];
5985
+ if (mods.length) {
5986
+ lines.push('', '## Source Modules');
5987
+ for (const m of mods) lines.push(`- [${m}/](${m}/)`);
5988
+ }
5989
+ const top5 = (context.fileEntries || [])
5990
+ .sort((a, b) => (b.sigs || []).length - (a.sigs || []).length)
5991
+ .slice(0, 5);
5992
+ if (top5.length) {
5993
+ lines.push('', '## Key Files');
5994
+ for (const f of top5) {
5995
+ const rel = path.relative(cwd, f.filePath);
5996
+ const preview = (f.sigs || []).slice(0, 3).join(', ');
5997
+ lines.push(`- ${rel}: ${preview}`);
5998
+ }
5999
+ }
6000
+ return lines.join('\n');
6001
+ }
6002
+ module.exports = { format, outputPath };
6003
+ };
6004
+
6005
+ // ── ./packages/adapters/llm-full ──
6006
+ __factories["./packages/adapters/llm-full"] = function(module, exports) {
6007
+ 'use strict';
6008
+ const path = require('path');
6009
+ const fs = require('fs');
6010
+ function outputPath(cwd) { return path.join(cwd, 'llm-full.txt'); }
6011
+ function format(context, opts) {
6012
+ opts = opts || {};
6013
+ const lines = [
6014
+ `# ${context.projectName || 'Project'} — SigMap Context`,
6015
+ `Generated: ${new Date().toISOString()} | SigMap v${opts.version || ''}`,
6016
+ '',
6017
+ ];
6018
+ for (const entry of (context.fileEntries || [])) {
6019
+ const rel = path.relative(opts.cwd || '', entry.filePath);
6020
+ lines.push(`## ${rel}`, '```', ...(entry.sigs || []), '```', '');
6021
+ }
6022
+ return lines.join('\n');
6023
+ }
6024
+ function write(context, cwd, opts) {
6025
+ opts = opts || {};
6026
+ fs.writeFileSync(outputPath(cwd), format(context, { ...opts, cwd }));
6027
+ }
6028
+ module.exports = { name: 'llm-full', format, outputPath, write };
6029
+ };
6030
+
5884
6031
  /**
5885
6032
  * SigMap — gen-context.js v1.2.0
5886
6033
  * Zero-dependency AI context engine.
@@ -5893,7 +6040,7 @@ const path = require('path');
5893
6040
  const os = require('os');
5894
6041
  const { execSync } = require('child_process');
5895
6042
 
5896
- const VERSION = '3.5.0';
6043
+ const VERSION = '3.6.0';
5897
6044
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
5898
6045
 
5899
6046
  function requireSourceOrBundled(key) {
@@ -6042,7 +6189,8 @@ function detectAndExtract(filePath, content, maxSigsPerFile) {
6042
6189
  const ext = path.extname(base).toLowerCase();
6043
6190
  let extractorName = EXT_MAP[ext] || null;
6044
6191
  if (!extractorName && isDockerfile(base)) extractorName = 'dockerfile';
6045
- if (!extractorName) return [];
6192
+ // Feature 7: generic fallback — catches Nix, Elixir, Gleam, Zig, Go templates, etc.
6193
+ if (!extractorName) extractorName = 'generic';
6046
6194
 
6047
6195
  const extractor = getExtractor(extractorName);
6048
6196
  if (!extractor) return [];
@@ -6122,12 +6270,13 @@ function applyTokenBudget(fileEntries, maxTokens) {
6122
6270
  // Sort by drop priority (drop first = index 0)
6123
6271
  const withPriority = fileEntries.map((e) => {
6124
6272
  let priority = 0;
6125
- if (isGeneratedFile(e.filePath)) priority = 10;
6126
- else if (isMockFile(e.filePath)) priority = 9;
6127
- else if (isTestFile(e.filePath)) priority = 8;
6128
- else if (isConfigFile(e.filePath)) priority = 6;
6273
+ let dropReason = 'budget: low recency';
6274
+ if (isGeneratedFile(e.filePath)) { priority = 10; dropReason = 'budget: generated file'; }
6275
+ else if (isMockFile(e.filePath)) { priority = 9; dropReason = 'budget: mock file'; }
6276
+ else if (isTestFile(e.filePath)) { priority = 8; dropReason = 'budget: test file'; }
6277
+ else if (isConfigFile(e.filePath)) { priority = 6; dropReason = 'budget: config file'; }
6129
6278
  else priority = 4;
6130
- return { ...e, priority };
6279
+ return { ...e, priority, dropReason };
6131
6280
  });
6132
6281
 
6133
6282
  // Within same priority, sort by mtime ascending (oldest first = drop first)
@@ -6137,20 +6286,27 @@ function applyTokenBudget(fileEntries, maxTokens) {
6137
6286
  });
6138
6287
 
6139
6288
  const kept = [];
6140
- let dropped = 0;
6289
+ const verboseDropped = [];
6141
6290
  // Iterate forward: highest drop-priority files (generated=10, mock=9, test=8) are at index 0
6142
6291
  // Drop those first until we're under budget, then keep everything else
6143
6292
  for (const entry of withPriority) {
6144
6293
  const entryTokens = estimateTokens(entry.sigs.join('\n'));
6145
6294
  if (total > effectiveBudget) {
6146
6295
  total -= entryTokens;
6147
- dropped++;
6296
+ verboseDropped.push({ filePath: entry.filePath, reason: entry.dropReason });
6148
6297
  } else {
6149
6298
  kept.push(entry);
6150
6299
  }
6151
6300
  }
6152
- if (dropped > 0) {
6153
- console.warn(`[sigmap] budget: dropped ${dropped} files to stay under ${maxTokens} tokens`);
6301
+ if (verboseDropped.length > 0) {
6302
+ console.warn(`[sigmap] budget: dropped ${verboseDropped.length} files to stay under ${maxTokens} tokens`);
6303
+ // Feature 7: --verbose — print per-file drop reason
6304
+ if (process.argv.includes('--verbose')) {
6305
+ for (const { filePath, reason } of verboseDropped) {
6306
+ console.warn(`[sigmap] dropped: ${path.relative(process.cwd(), filePath)} — ${reason}`);
6307
+ }
6308
+ console.warn(`[sigmap] included: ${kept.length} files, dropped: ${verboseDropped.length}`);
6309
+ }
6154
6310
  }
6155
6311
  return kept;
6156
6312
  }
@@ -6980,7 +7136,37 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
6980
7136
  const finalTokens = estimateTokens(content);
6981
7137
  const formatIdx = process.argv.indexOf('--format');
6982
7138
  const formatValue = formatIdx >= 0 ? process.argv[formatIdx + 1] : (config.format || 'default');
6983
- writeOutputs(content, config.outputs, cwd, config);
7139
+ // Feature 2: --mode write matrix
7140
+ const activeMode = process.argv.indexOf('--mode') >= 0
7141
+ ? process.argv[process.argv.indexOf('--mode') + 1]
7142
+ : (config.mode || 'default');
7143
+ if (activeMode === 'fast') {
7144
+ const llmTxtMod = requireSourceOrBundled('./src/format/llm-txt');
7145
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7146
+ fs.writeFileSync(llmTxtMod.outputPath(cwd), llmTxtMod.format(syncContext, cwd, VERSION));
7147
+ console.warn(`[sigmap] --mode fast → llm.txt`);
7148
+ } else if (activeMode === 'full') {
7149
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7150
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7151
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7152
+ console.warn(`[sigmap] --mode full → llm-full.txt`);
7153
+ } else if (activeMode === 'both') {
7154
+ const llmTxtMod = requireSourceOrBundled('./src/format/llm-txt');
7155
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7156
+ const llmsMod = requireSourceOrBundled('./src/format/llms-txt');
7157
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7158
+ fs.writeFileSync(llmTxtMod.outputPath(cwd), llmTxtMod.format(syncContext, cwd, VERSION));
7159
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7160
+ writeOutputs(content, config.outputs, cwd, config);
7161
+ const llmsContent = llmsMod.format(syncContext, cwd, [
7162
+ { path: llmTxtMod.outputPath(cwd), label: 'llm.txt' },
7163
+ { path: llmFullMod.outputPath(cwd), label: 'llm-full.txt' },
7164
+ ], VERSION);
7165
+ fs.writeFileSync(llmsMod.outputPath(cwd), llmsContent);
7166
+ console.warn(`[sigmap] --mode both → copilot-instructions.md + llm.txt + llm-full.txt + llms.txt`);
7167
+ } else {
7168
+ writeOutputs(content, config.outputs, cwd, config);
7169
+ }
6984
7170
  if (formatValue === 'cache') writeCacheOutput(content, cwd);
6985
7171
  result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
6986
7172
  }
@@ -7016,6 +7202,29 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
7016
7202
  }
7017
7203
  }
7018
7204
 
7205
+ // Feature 8: post-run summary — 6-line stdout after every normal run
7206
+ if (!reportMode
7207
+ && !process.argv.includes('--json')
7208
+ && !process.argv.includes('--report')
7209
+ && !process.argv.includes('--quiet')) {
7210
+ const bar = '\u2500'.repeat(43);
7211
+ const syms = fileEntries.reduce((n, f) => n + (f.sigs ? f.sigs.length : 0), 0);
7212
+ const pct = result.inputTokenTotal > 0
7213
+ ? Math.round((1 - result.finalTokens / result.inputTokenTotal) * 100)
7214
+ : 0;
7215
+ process.stderr.write([
7216
+ bar,
7217
+ ` SigMap v${VERSION}`,
7218
+ ` Files scanned : ${result.fileCount}`,
7219
+ ` Symbols found : ${syms.toLocaleString()}`,
7220
+ ` Token reduction: ${pct}% (${result.inputTokenTotal.toLocaleString()} \u2192 ${result.finalTokens.toLocaleString()})`,
7221
+ ` Output : .github/copilot-instructions.md`,
7222
+ bar,
7223
+ ` Try: "explain the architecture" \u00b7 "find the auth module"`,
7224
+ bar, '',
7225
+ ].join('\n'));
7226
+ }
7227
+
7019
7228
  return result;
7020
7229
  }
7021
7230
 
@@ -7327,6 +7536,12 @@ function registerMcp(cwd, scriptPath) {
7327
7536
 
7328
7537
  function main() {
7329
7538
  const args = process.argv.slice(2);
7539
+
7540
+ // Feature 1: `sigmap run` alias — strip positional 'run' so existing logic handles it
7541
+ if (args[0] === 'run' && !args[0].startsWith('--')) {
7542
+ args.shift();
7543
+ }
7544
+
7330
7545
  const invokedFrom = process.cwd();
7331
7546
  const cwd = resolveProjectRoot(invokedFrom);
7332
7547
  const scriptPath = process.argv[1] || path.join(invokedFrom, 'gen-context.js');
@@ -7354,6 +7569,154 @@ function main() {
7354
7569
 
7355
7570
  const config = loadConfig(cwd);
7356
7571
 
7572
+ // Feature 2: `--mode fast|full|both`
7573
+ const modeIdx = args.indexOf('--mode');
7574
+ const mode = modeIdx !== -1
7575
+ ? args[modeIdx + 1]
7576
+ : (config.mode || 'default');
7577
+ const VALID_MODES = ['default', 'fast', 'full', 'both'];
7578
+ if (mode && !VALID_MODES.includes(mode)) {
7579
+ console.error(`[sigmap] unknown --mode "${mode}". Valid: ${VALID_MODES.join(', ')}`);
7580
+ process.exit(1);
7581
+ }
7582
+
7583
+ // Feature 6: `sigmap sync` — write all outputs + llms.txt + print compact diff
7584
+ if (args[0] === 'sync') {
7585
+ try {
7586
+ // Generate copilot-instructions.md first (existing output)
7587
+ runGenerate(cwd, config, false);
7588
+ const llmTxt = requireSourceOrBundled('./src/format/llm-txt');
7589
+ const llmsGen = requireSourceOrBundled('./src/format/llms-txt');
7590
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7591
+
7592
+ // Gather file entries for llm.txt / llm-full.txt
7593
+ const ignorePatterns = loadIgnorePatterns(cwd);
7594
+ let syncFiles = buildFileList(cwd, config).filter((f) => {
7595
+ const rel = path.relative(cwd, f).replace(/\\/g, '/');
7596
+ return !matchesIgnore(rel, ignorePatterns);
7597
+ });
7598
+ const syncEntries = [];
7599
+ for (const fp of syncFiles) {
7600
+ let content = '';
7601
+ try { content = fs.readFileSync(fp, 'utf8'); } catch (_) { continue; }
7602
+ const sigs = detectAndExtract(fp, content, config.maxSigsPerFile || 25);
7603
+ if (sigs.length === 0) continue;
7604
+ syncEntries.push({ filePath: fp, sigs, language: null });
7605
+ }
7606
+ const syncContext = {
7607
+ projectName: path.basename(cwd),
7608
+ fileEntries: syncEntries,
7609
+ srcDirs: config.srcDirs || [],
7610
+ };
7611
+
7612
+ const llmTxtPath = llmTxt.outputPath(cwd);
7613
+ const llmFullPath = llmFullMod.outputPath(cwd);
7614
+ const llmsTxtPath = llmsGen.outputPath(cwd);
7615
+
7616
+ fs.writeFileSync(llmTxtPath, llmTxt.format(syncContext, cwd, VERSION));
7617
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7618
+
7619
+ const prev = fs.existsSync(llmsTxtPath) ? fs.readFileSync(llmsTxtPath, 'utf8') : '';
7620
+ const next = llmsGen.format(syncContext, cwd, [
7621
+ { path: llmTxtPath, label: 'llm.txt' },
7622
+ { path: llmFullPath, label: 'llm-full.txt' },
7623
+ ], VERSION);
7624
+ const changed = prev !== next ? 'updated' : 'no change';
7625
+ fs.writeFileSync(llmsTxtPath, next);
7626
+
7627
+ console.log('[sigmap] sync complete');
7628
+ console.log(` .github/copilot-instructions.md updated`);
7629
+ console.log(` llm.txt updated`);
7630
+ console.log(` llm-full.txt updated`);
7631
+ console.log(` llms.txt ${changed}`);
7632
+ } catch (err) {
7633
+ console.error(`[sigmap] sync error: ${err.message}`);
7634
+ process.exit(1);
7635
+ }
7636
+ process.exit(0);
7637
+ }
7638
+
7639
+ // Feature 1: `sigmap explain <file>` — why a file is included or excluded
7640
+ if (args[0] === 'explain' || args.includes('--explain')) {
7641
+ const target = args[0] === 'explain'
7642
+ ? args[1]
7643
+ : args[args.indexOf('--explain') + 1];
7644
+
7645
+ if (!target || target.startsWith('--')) {
7646
+ console.error('[sigmap] Usage: sigmap explain <file>');
7647
+ process.exit(1);
7648
+ }
7649
+
7650
+ const absPath = path.resolve(cwd, target);
7651
+ const rel = path.relative(cwd, absPath);
7652
+ const jsonOut = args.includes('--json');
7653
+
7654
+ // 1. Check .contextignore
7655
+ const ignorePatterns = loadIgnorePatterns(cwd);
7656
+ const ignored = matchesIgnore(rel.replace(/\\/g, '/'), ignorePatterns);
7657
+ if (ignored) {
7658
+ if (jsonOut) {
7659
+ process.stdout.write(JSON.stringify({ status: 'excluded', reason: '.contextignore', fix: `remove the pattern or add '!${rel}' as an exception` }) + '\n');
7660
+ } else {
7661
+ console.log(`[sigmap] ${rel} — EXCLUDED`);
7662
+ console.log(` Reason: matched .contextignore`);
7663
+ console.log(` Fix: remove the pattern or add '!${rel}' as an exception`);
7664
+ }
7665
+ process.exit(0);
7666
+ }
7667
+
7668
+ // 2. Check srcDirs membership
7669
+ const inSrcDirs = (config.srcDirs || []).some(d => absPath.startsWith(path.resolve(cwd, d)));
7670
+ if (!inSrcDirs) {
7671
+ if (jsonOut) {
7672
+ process.stdout.write(JSON.stringify({ status: 'excluded', reason: 'not in srcDirs', srcDirs: config.srcDirs, fix: 'add the containing directory to srcDirs in gen-context.config.json' }) + '\n');
7673
+ } else {
7674
+ console.log(`[sigmap] ${rel} — EXCLUDED`);
7675
+ console.log(` Reason: not under any srcDir (${(config.srcDirs || []).join(', ')})`);
7676
+ console.log(` Fix: add the containing directory to srcDirs in gen-context.config.json`);
7677
+ }
7678
+ process.exit(0);
7679
+ }
7680
+
7681
+ // 3. Detect extractor and extract sigs
7682
+ const base = path.basename(absPath);
7683
+ const ext = path.extname(base).toLowerCase();
7684
+ let extractorName = EXT_MAP[ext] || null;
7685
+ if (!extractorName && isDockerfile(base)) extractorName = 'dockerfile';
7686
+ if (!extractorName) extractorName = 'generic';
7687
+
7688
+ let sigs = [];
7689
+ if (fs.existsSync(absPath)) {
7690
+ try {
7691
+ const content = fs.readFileSync(absPath, 'utf8');
7692
+ const extractor = getExtractor(extractorName);
7693
+ if (extractor) sigs = extractor.extract(content).slice(0, config.maxSigsPerFile || 25);
7694
+ } catch (_) {}
7695
+ }
7696
+
7697
+ if (!sigs.length) {
7698
+ if (jsonOut) {
7699
+ process.stdout.write(JSON.stringify({ status: 'excluded', reason: 'no signatures', extractor: extractorName, fix: 'check that the file contains function/class definitions' }) + '\n');
7700
+ } else {
7701
+ console.log(`[sigmap] ${rel} — EXCLUDED`);
7702
+ console.log(` Reason: no extractable signatures (extractor: ${extractorName})`);
7703
+ console.log(` Fix: check that the file contains function/class definitions`);
7704
+ }
7705
+ process.exit(0);
7706
+ }
7707
+
7708
+ // 4. Included
7709
+ if (jsonOut) {
7710
+ process.stdout.write(JSON.stringify({ status: 'included', extractor: extractorName, signatures: sigs.length, preview: sigs.slice(0, 3), sigs }) + '\n');
7711
+ } else {
7712
+ console.log(`[sigmap] ${rel} — INCLUDED`);
7713
+ console.log(` Extractor : ${extractorName}`);
7714
+ console.log(` Signatures: ${sigs.length}`);
7715
+ console.log(` Preview : ${sigs.slice(0, 3).join(' · ')}`);
7716
+ }
7717
+ process.exit(0);
7718
+ }
7719
+
7357
7720
  if (args.includes('--init')) {
7358
7721
  writeInitConfig(cwd);
7359
7722
  process.exit(0);
@@ -7363,7 +7726,14 @@ function main() {
7363
7726
  const { score } = __require('./src/health/scorer');
7364
7727
  const result = score(cwd);
7365
7728
  if (args.includes('--json')) {
7366
- process.stdout.write(JSON.stringify(result) + '\n');
7729
+ // Feature 3 (VS Code) + Feature 5 (JetBrains): emit tokens + reduction for plugins
7730
+ const ctxPath = path.join(cwd, '.github', 'copilot-instructions.md');
7731
+ let tokens = 0;
7732
+ let reduction = result.tokenReductionPct || 0;
7733
+ if (fs.existsSync(ctxPath)) {
7734
+ try { tokens = estimateTokens(fs.readFileSync(ctxPath, 'utf8')); } catch (_) {}
7735
+ }
7736
+ process.stdout.write(JSON.stringify({ ...result, tokens, reduction }) + '\n');
7367
7737
  } else {
7368
7738
  console.log('[sigmap] health:');
7369
7739
  console.log(` score : ${result.score}/100 (grade ${result.grade})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "3.5.0",
3
+ "version": "3.6.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": {
@@ -24,6 +24,7 @@
24
24
  "init": "node gen-context.js --init",
25
25
  "report": "node gen-context.js --report",
26
26
  "audit:strategies": "node scripts/run-strategy-audit.mjs",
27
+ "benchmark:matrix": "node scripts/run-benchmark-matrix.mjs --save --skip-clone",
27
28
  "health": "node gen-context.js --health",
28
29
  "map": "node gen-project-map.js",
29
30
  "mcp": "node gen-context.js --mcp",
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ module.exports = { name: 'llm-full', format, outputPath, write };
5
+
6
+ function outputPath(cwd) { return path.join(cwd, 'llm-full.txt'); }
7
+
8
+ function format(context, opts) {
9
+ opts = opts || {};
10
+ const lines = [
11
+ `# ${context.projectName || 'Project'} — SigMap Context`,
12
+ `Generated: ${new Date().toISOString()} | SigMap v${opts.version || ''}`,
13
+ '',
14
+ ];
15
+ for (const entry of (context.fileEntries || [])) {
16
+ const rel = path.relative(opts.cwd || '', entry.filePath);
17
+ lines.push(`## ${rel}`, '```', ...(entry.sigs || []), '```', '');
18
+ }
19
+ return lines.join('\n');
20
+ }
21
+
22
+ function write(context, cwd, opts) {
23
+ opts = opts || {};
24
+ fs.writeFileSync(outputPath(cwd), format(context, { ...opts, cwd }));
25
+ }
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+ module.exports = { extract };
3
+
4
+ const PATTERNS = [
5
+ /^(?:pub\s+)?(?:async\s+)?function\s+\w+\s*\(/,
6
+ /^(?:pub\s+)?(?:async\s+)?fn\s+\w+[\s(<]/,
7
+ /^def\s+\w+[\s(|:]/,
8
+ /^(?:pub\s+)?func\s+\w+\s*\(/,
9
+ /^(?:let|let\s+rec)\s+\w+\s*[=(]/,
10
+ /^class\s+\w+/,
11
+ /^(?:proc|sub|method)\s+\w+\s*\(/,
12
+ ];
13
+
14
+ function extract(src) {
15
+ if (!src || typeof src !== 'string') return [];
16
+ const results = [];
17
+ for (const raw of src.split('\n')) {
18
+ const line = raw.trim();
19
+ if (!line || /^[#\-]/.test(line) || /^\/\//.test(line) || line.includes('\0')) continue;
20
+ for (const pat of PATTERNS) {
21
+ if (pat.test(line)) { results.push(line.slice(0, 120)); break; }
22
+ }
23
+ if (results.length >= 15) break;
24
+ }
25
+ return results;
26
+ }
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ module.exports = { format, outputPath };
4
+
5
+ function outputPath(cwd) { return path.join(cwd, 'llm.txt'); }
6
+
7
+ function format(context, cwd, version) {
8
+ const name = context.projectName || path.basename(cwd);
9
+ const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
10
+ const mods = context.srcDirs || [];
11
+
12
+ return [
13
+ `# Project: ${name}`,
14
+ `Languages: ${langs.join(', ') || 'unknown'}`,
15
+ `Root: ${mods[0] || 'src/'}`,
16
+ '',
17
+ '## Modules',
18
+ ...mods.map(m => `- ${m}/`),
19
+ '',
20
+ '## Key flows',
21
+ '- <!-- describe your main user flows here -->',
22
+ '',
23
+ '## Rules',
24
+ '- <!-- describe your team conventions here -->',
25
+ '',
26
+ `Generated: ${new Date().toISOString()} | SigMap v${version}`,
27
+ ].join('\n');
28
+ }
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { execSync } = require('child_process');
5
+ module.exports = { format, outputPath };
6
+
7
+ function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
8
+
9
+ function getShortCommit(cwd) {
10
+ try { return execSync('git rev-parse --short HEAD', { cwd, timeout: 2000 }).toString().trim(); }
11
+ catch (_) { return ''; }
12
+ }
13
+
14
+ function detectVersion(cwd) {
15
+ try {
16
+ const pkg = path.join(cwd, 'package.json');
17
+ if (fs.existsSync(pkg)) return JSON.parse(fs.readFileSync(pkg, 'utf8')).version || '';
18
+ } catch (_) {}
19
+ return '';
20
+ }
21
+
22
+ function format(context, cwd, writtenFiles, sigmapVersion) {
23
+ writtenFiles = writtenFiles || [];
24
+ sigmapVersion = sigmapVersion || '';
25
+
26
+ const name = context.projectName || path.basename(cwd);
27
+ const ver = detectVersion(cwd);
28
+ const commit = getShortCommit(cwd);
29
+ const langs = [...new Set((context.fileEntries || []).map(f => f.language).filter(Boolean))];
30
+
31
+ const lines = [
32
+ '# SigMap Context Index',
33
+ `> Generated by SigMap v${sigmapVersion} — zero-dependency AI context engine`,
34
+ '',
35
+ '## Project',
36
+ `- Name: ${name}`,
37
+ ];
38
+ if (ver) lines.push(`- Version: ${ver}`);
39
+ if (langs.length) lines.push(`- Language: ${langs.join(', ')}`);
40
+ if (commit) lines.push(`- Commit: ${commit}`);
41
+
42
+ if (writtenFiles.length) {
43
+ lines.push('', '## Context Files');
44
+ for (const f of writtenFiles) {
45
+ const rel = path.relative(cwd, f.path);
46
+ const extra = f.tokens ? `: ${f.tokens} tokens` : '';
47
+ lines.push(`- [${f.label || rel}](${rel})${extra}`);
48
+ }
49
+ }
50
+
51
+ const mods = context.srcDirs || [];
52
+ if (mods.length) {
53
+ lines.push('', '## Source Modules');
54
+ for (const m of mods) lines.push(`- [${m}/](${m}/)`);
55
+ }
56
+
57
+ const top5 = (context.fileEntries || [])
58
+ .sort((a, b) => (b.sigs || []).length - (a.sigs || []).length)
59
+ .slice(0, 5);
60
+ if (top5.length) {
61
+ lines.push('', '## Key Files');
62
+ for (const f of top5) {
63
+ const rel = path.relative(cwd, f.filePath);
64
+ const preview = (f.sigs || []).slice(0, 3).join(', ');
65
+ lines.push(`- ${rel}: ${preview}`);
66
+ }
67
+ }
68
+
69
+ return lines.join('\n');
70
+ }