sigmap 3.5.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -12,46 +12,33 @@ 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.0 on 2026-04-14T21:58:46.968Z.
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.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 — 11 minutes ago)
26
+ ## changes (last 5 commits — 19 minutes ago)
27
27
  ```
28
28
  src/config/loader.js ~detectAutoSrcDirs
29
29
  src/eval/analyzer.js ~isDockerfile
30
30
  src/extractors/markdown.js +extract
31
+ src/extractors/patterns.js +extract +implementing
31
32
  src/extractors/properties.js +extract
33
+ src/extractors/python_dataclass.js +extract +definitions
32
34
  src/extractors/toml.js +extract
35
+ src/extractors/typescript_react.js +extract +declarations
36
+ src/extractors/vue_sfc.js +extract
33
37
  src/extractors/xml.js +attributes +extract
34
38
  ```
35
39
 
36
40
  ## packages
37
41
 
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
42
  ### packages/core/index.js
56
43
  ```
57
44
  module.exports = { extract, rank, buildSigIndex, scan, score, adapt }
@@ -72,6 +59,14 @@ function outputPath(cwd) → string
72
59
  function write(context, cwd, opts = {})
73
60
  ```
74
61
 
62
+ ### packages/adapters/codex.js
63
+ ```
64
+ module.exports = { name, format, outputPath, write }
65
+ function format(context, opts = {}) → string
66
+ function outputPath(cwd) → string
67
+ function write(context, cwd, opts = {})
68
+ ```
69
+
75
70
  ### packages/adapters/copilot.js
76
71
  ```
77
72
  module.exports = { name, format, outputPath, write }
@@ -95,6 +90,15 @@ function outputPath(cwd) → string
95
90
  function write(context, cwd, opts = {})
96
91
  ```
97
92
 
93
+ ### packages/adapters/index.js
94
+ ```
95
+ module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters }
96
+ function getAdapter(name) → { name: string, format: F
97
+ function listAdapters() → string[]
98
+ function adapt(context, adapterName, opts = {}) → string
99
+ function outputsToAdapters(outputs) → string[]
100
+ ```
101
+
98
102
  ### packages/adapters/openai.js
99
103
  ```
100
104
  module.exports = { name, format, outputPath }
@@ -175,6 +179,12 @@ module.exports = { extract }
175
179
  function extract(src) → string[]
176
180
  ```
177
181
 
182
+ ### src/extractors/patterns.js
183
+ ```
184
+ module.exports = { extract }
185
+ function extract(src) → string[]
186
+ ```
187
+
178
188
  ### src/extractors/properties.js
179
189
  ```
180
190
  module.exports = { extract }
@@ -187,6 +197,12 @@ module.exports = { extract }
187
197
  function extract(src) → string[]
188
198
  ```
189
199
 
200
+ ### src/extractors/python_dataclass.js
201
+ ```
202
+ module.exports = { extract }
203
+ function extract(src) → string[]
204
+ ```
205
+
190
206
  ### src/extractors/sql.js
191
207
  ```
192
208
  module.exports = { extract }
@@ -207,6 +223,18 @@ module.exports = { extract }
207
223
  function extract(src) → string[]
208
224
  ```
209
225
 
226
+ ### src/extractors/typescript_react.js
227
+ ```
228
+ module.exports = { extract }
229
+ function extract(src) → string[]
230
+ ```
231
+
232
+ ### src/extractors/vue_sfc.js
233
+ ```
234
+ module.exports = { extract }
235
+ function extract(src) → string[]
236
+ ```
237
+
210
238
  ### src/extractors/xml.js
211
239
  ```
212
240
  module.exports = { extract }
@@ -381,12 +409,6 @@ function normalizeParams(params)
381
409
  function extractDocHint(src, fnName, fnSigLine)
382
410
  ```
383
411
 
384
- ### src/extractors/python_dataclass.js
385
- ```
386
- module.exports = { extract }
387
- function extract(src) → string[]
388
- ```
389
-
390
412
  ### src/extractors/ruby.js
391
413
  ```
392
414
  module.exports = { extract }
@@ -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 }
package/README.md CHANGED
@@ -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
 
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.5.1';
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 [];
@@ -6980,7 +7128,37 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
6980
7128
  const finalTokens = estimateTokens(content);
6981
7129
  const formatIdx = process.argv.indexOf('--format');
6982
7130
  const formatValue = formatIdx >= 0 ? process.argv[formatIdx + 1] : (config.format || 'default');
6983
- writeOutputs(content, config.outputs, cwd, config);
7131
+ // Feature 2: --mode write matrix
7132
+ const activeMode = process.argv.indexOf('--mode') >= 0
7133
+ ? process.argv[process.argv.indexOf('--mode') + 1]
7134
+ : (config.mode || 'default');
7135
+ if (activeMode === 'fast') {
7136
+ const llmTxtMod = requireSourceOrBundled('./src/format/llm-txt');
7137
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7138
+ fs.writeFileSync(llmTxtMod.outputPath(cwd), llmTxtMod.format(syncContext, cwd, VERSION));
7139
+ console.warn(`[sigmap] --mode fast → llm.txt`);
7140
+ } else if (activeMode === 'full') {
7141
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7142
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7143
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7144
+ console.warn(`[sigmap] --mode full → llm-full.txt`);
7145
+ } else if (activeMode === 'both') {
7146
+ const llmTxtMod = requireSourceOrBundled('./src/format/llm-txt');
7147
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7148
+ const llmsMod = requireSourceOrBundled('./src/format/llms-txt');
7149
+ const syncContext = { projectName: path.basename(cwd), fileEntries, srcDirs: config.srcDirs || [] };
7150
+ fs.writeFileSync(llmTxtMod.outputPath(cwd), llmTxtMod.format(syncContext, cwd, VERSION));
7151
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7152
+ writeOutputs(content, config.outputs, cwd, config);
7153
+ const llmsContent = llmsMod.format(syncContext, cwd, [
7154
+ { path: llmTxtMod.outputPath(cwd), label: 'llm.txt' },
7155
+ { path: llmFullMod.outputPath(cwd), label: 'llm-full.txt' },
7156
+ ], VERSION);
7157
+ fs.writeFileSync(llmsMod.outputPath(cwd), llmsContent);
7158
+ console.warn(`[sigmap] --mode both → copilot-instructions.md + llm.txt + llm-full.txt + llms.txt`);
7159
+ } else {
7160
+ writeOutputs(content, config.outputs, cwd, config);
7161
+ }
6984
7162
  if (formatValue === 'cache') writeCacheOutput(content, cwd);
6985
7163
  result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
6986
7164
  }
@@ -7016,6 +7194,29 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
7016
7194
  }
7017
7195
  }
7018
7196
 
7197
+ // Feature 8: post-run summary — 6-line stdout after every normal run
7198
+ if (!reportMode
7199
+ && !process.argv.includes('--json')
7200
+ && !process.argv.includes('--report')
7201
+ && !process.argv.includes('--quiet')) {
7202
+ const bar = '\u2500'.repeat(43);
7203
+ const syms = fileEntries.reduce((n, f) => n + (f.sigs ? f.sigs.length : 0), 0);
7204
+ const pct = result.inputTokenTotal > 0
7205
+ ? Math.round((1 - result.finalTokens / result.inputTokenTotal) * 100)
7206
+ : 0;
7207
+ process.stderr.write([
7208
+ bar,
7209
+ ` SigMap v${VERSION}`,
7210
+ ` Files scanned : ${result.fileCount}`,
7211
+ ` Symbols found : ${syms.toLocaleString()}`,
7212
+ ` Token reduction: ${pct}% (${result.inputTokenTotal.toLocaleString()} \u2192 ${result.finalTokens.toLocaleString()})`,
7213
+ ` Output : .github/copilot-instructions.md`,
7214
+ bar,
7215
+ ` Try: "explain the architecture" \u00b7 "find the auth module"`,
7216
+ bar, '',
7217
+ ].join('\n'));
7218
+ }
7219
+
7019
7220
  return result;
7020
7221
  }
7021
7222
 
@@ -7327,6 +7528,12 @@ function registerMcp(cwd, scriptPath) {
7327
7528
 
7328
7529
  function main() {
7329
7530
  const args = process.argv.slice(2);
7531
+
7532
+ // Feature 1: `sigmap run` alias — strip positional 'run' so existing logic handles it
7533
+ if (args[0] === 'run' && !args[0].startsWith('--')) {
7534
+ args.shift();
7535
+ }
7536
+
7330
7537
  const invokedFrom = process.cwd();
7331
7538
  const cwd = resolveProjectRoot(invokedFrom);
7332
7539
  const scriptPath = process.argv[1] || path.join(invokedFrom, 'gen-context.js');
@@ -7354,6 +7561,73 @@ function main() {
7354
7561
 
7355
7562
  const config = loadConfig(cwd);
7356
7563
 
7564
+ // Feature 2: `--mode fast|full|both`
7565
+ const modeIdx = args.indexOf('--mode');
7566
+ const mode = modeIdx !== -1
7567
+ ? args[modeIdx + 1]
7568
+ : (config.mode || 'default');
7569
+ const VALID_MODES = ['default', 'fast', 'full', 'both'];
7570
+ if (mode && !VALID_MODES.includes(mode)) {
7571
+ console.error(`[sigmap] unknown --mode "${mode}". Valid: ${VALID_MODES.join(', ')}`);
7572
+ process.exit(1);
7573
+ }
7574
+
7575
+ // Feature 6: `sigmap sync` — write all outputs + llms.txt + print compact diff
7576
+ if (args[0] === 'sync') {
7577
+ try {
7578
+ // Generate copilot-instructions.md first (existing output)
7579
+ runGenerate(cwd, config, false);
7580
+ const llmTxt = requireSourceOrBundled('./src/format/llm-txt');
7581
+ const llmsGen = requireSourceOrBundled('./src/format/llms-txt');
7582
+ const llmFullMod = requireSourceOrBundled('./packages/adapters/llm-full');
7583
+
7584
+ // Gather file entries for llm.txt / llm-full.txt
7585
+ const ignorePatterns = loadIgnorePatterns(cwd);
7586
+ let syncFiles = buildFileList(cwd, config).filter((f) => {
7587
+ const rel = path.relative(cwd, f).replace(/\\/g, '/');
7588
+ return !matchesIgnore(rel, ignorePatterns);
7589
+ });
7590
+ const syncEntries = [];
7591
+ for (const fp of syncFiles) {
7592
+ let content = '';
7593
+ try { content = fs.readFileSync(fp, 'utf8'); } catch (_) { continue; }
7594
+ const sigs = detectAndExtract(fp, content, config.maxSigsPerFile || 25);
7595
+ if (sigs.length === 0) continue;
7596
+ syncEntries.push({ filePath: fp, sigs, language: null });
7597
+ }
7598
+ const syncContext = {
7599
+ projectName: path.basename(cwd),
7600
+ fileEntries: syncEntries,
7601
+ srcDirs: config.srcDirs || [],
7602
+ };
7603
+
7604
+ const llmTxtPath = llmTxt.outputPath(cwd);
7605
+ const llmFullPath = llmFullMod.outputPath(cwd);
7606
+ const llmsTxtPath = llmsGen.outputPath(cwd);
7607
+
7608
+ fs.writeFileSync(llmTxtPath, llmTxt.format(syncContext, cwd, VERSION));
7609
+ llmFullMod.write(syncContext, cwd, { version: VERSION });
7610
+
7611
+ const prev = fs.existsSync(llmsTxtPath) ? fs.readFileSync(llmsTxtPath, 'utf8') : '';
7612
+ const next = llmsGen.format(syncContext, cwd, [
7613
+ { path: llmTxtPath, label: 'llm.txt' },
7614
+ { path: llmFullPath, label: 'llm-full.txt' },
7615
+ ], VERSION);
7616
+ const changed = prev !== next ? 'updated' : 'no change';
7617
+ fs.writeFileSync(llmsTxtPath, next);
7618
+
7619
+ console.log('[sigmap] sync complete');
7620
+ console.log(` .github/copilot-instructions.md updated`);
7621
+ console.log(` llm.txt updated`);
7622
+ console.log(` llm-full.txt updated`);
7623
+ console.log(` llms.txt ${changed}`);
7624
+ } catch (err) {
7625
+ console.error(`[sigmap] sync error: ${err.message}`);
7626
+ process.exit(1);
7627
+ }
7628
+ process.exit(0);
7629
+ }
7630
+
7357
7631
  if (args.includes('--init')) {
7358
7632
  writeInitConfig(cwd);
7359
7633
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
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
+ }