sigmap 2.10.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,7 +6,29 @@ Format: [Semantic Versioning](https://semver.org/)
6
6
 
7
7
  ---
8
8
 
9
- ## [2.10.0] — upcoming · [#25](https://github.com/manojmallick/sigmap/issues/25) · branch: `feat/v2.10-reporting-charts-advanced-metrics`
9
+ ## [3.0.0] — 2026-04-06 Platform: Multi-Adapter Architecture
10
+
11
+ ### Added
12
+ - **Multi-adapter platform** — `packages/adapters/` with 6 output adapters: `copilot`, `claude`, `cursor`, `windsurf`, `openai`, `gemini`
13
+ - **`--adapter <name>` CLI flag** — generate output for a specific adapter only (e.g. `node gen-context.js --adapter openai`)
14
+ - **`adapt()` in packages/core** — programmatic API: `const { adapt } = require('sigmap'); adapt(context, 'openai')`
15
+ - **New config key `adapters`** — replaces `outputs`; old `outputs` key is silently mapped for full backward compatibility
16
+ - **OpenAI adapter** — formats context as an OpenAI system prompt, writes `.github/openai-context.md`
17
+ - **Gemini adapter** — formats context as a Gemini system instruction, writes `.github/gemini-context.md`
18
+ - **API stability guarantee** — `packages/core` API is now semver-stable; breaking changes require v4.0
19
+ - **20 new integration tests** in `test/integration/adapters.test.js`
20
+
21
+ ### Changed
22
+ - `packages/core/index.js` — adds `adapt()` export alongside existing `extract`, `rank`, `scan`, `score`, `buildSigIndex`
23
+ - `writeOutputs()` in `gen-context.js` — now routes `openai`, `gemini` through adapter pipeline
24
+
25
+ ### Backward compat
26
+ - `outputs: ["copilot","claude"]` config still works — automatically mirrored to `adapters`
27
+ - All existing CLI flags unchanged
28
+
29
+ ---
30
+
31
+ ## [2.10.0] — 2026-04-06 · [#25](https://github.com/manojmallick/sigmap/issues/25)
10
32
 
11
33
  ### Planned additions
12
34
  - **Report charts** — add chart-ready output for token reduction, signatures per file, and budget utilization trends.
package/README.md CHANGED
@@ -123,20 +123,38 @@ AI agent session starts with full context
123
123
  | **Evaluation dashboard output** | Generate shareable HTML/JSON benchmark summaries from CLI runs |
124
124
  | **CI-friendly metrics export** | Persist machine-readable metrics for release gates and regression tracking |
125
125
  | **Release quality gates** | Add pass/fail thresholds for hit@5 and precision before publish |
126
+ ## 🔌 v3.0 — Platform: Multi-Adapter Architecture
126
127
 
127
- ---
128
- | **`get_impact` MCP tool** | 9th MCP tool — `{ file, depth? }` → impacted files + signatures |
129
- | **`src/map/dep-graph.js`** | Reverse-dependency graph built from the import analysis; circular deps handled safely |
130
- | **15 new tests** | `impact.test.js` — direct deps, transitive deps, depth limit, JSON output |
128
+ SigMap is now an **adapter platform**. Any AI assistant — Copilot, Claude, Cursor, Windsurf, OpenAI, or Gemini — plugs in through a standard interface.
131
129
 
132
- ---
130
+ ```bash
131
+ # Generate for a specific AI assistant
132
+ node gen-context.js --adapter copilot # → .github/copilot-instructions.md
133
+ node gen-context.js --adapter openai # → .github/openai-context.md
134
+ node gen-context.js --adapter gemini # → .github/gemini-context.md
135
+ node gen-context.js --adapter claude # → CLAUDE.md (append)
136
+ ```
133
137
 
134
- ## 🚀 Quick start
138
+ ```js
139
+ // Programmatic API — fully semver-stable from v3.0
140
+ const { adapt } = require('sigmap');
141
+ const systemPrompt = adapt(context, 'openai', { version: '3.0.0' });
142
+ ```
135
143
 
136
- **No install required just Node.js 18+.**
144
+ | Adapter | Output file | AI assistant |
145
+ |---|---|---|
146
+ | `copilot` | `.github/copilot-instructions.md` | GitHub Copilot |
147
+ | `claude` | `CLAUDE.md` (append) | Claude / Claude Code |
148
+ | `cursor` | `.cursorrules` | Cursor |
149
+ | `windsurf` | `.windsurfrules` | Windsurf |
150
+ | `openai` | `.github/openai-context.md` | Any OpenAI model |
151
+ | `gemini` | `.github/gemini-context.md` | Google Gemini |
137
152
 
138
- ```bash
139
- # 1. Copy gen-context.js into your project root
153
+ **Backward compat:** existing `outputs` config key silently maps to `adapters` — no migration needed.
154
+
155
+ See full roadmap: [manojmallick.github.io/sigmap/roadmap.html](https://manojmallick.github.io/sigmap/roadmap.html)
156
+
157
+ ---
140
158
  curl -O https://raw.githubusercontent.com/manojmallick/sigmap/main/gen-context.js
141
159
 
142
160
  # 2. Generate your context file
package/gen-context.js CHANGED
@@ -33,6 +33,10 @@ __factories["./src/config/defaults"] = function(module, exports) {
33
33
 
34
34
  // Output targets: 'copilot' | 'claude' | 'cursor' | 'windsurf'
35
35
  outputs: ['copilot'],
36
+
37
+ // Adapter targets (v3.0+): replaces 'outputs'. Same names, adds 'openai' | 'gemini'.
38
+ // Old 'outputs' config key is still accepted and silently maps to 'adapters'.
39
+ adapters: null, // null means: use 'outputs' for backward compat
36
40
 
37
41
  // Directories to scan (relative to project root)
38
42
  srcDirs: [
@@ -163,6 +167,14 @@ __factories["./src/config/loader"] = function(module, exports) {
163
167
  merged[key] = val;
164
168
  }
165
169
  }
170
+ // Backward compat (v3.0+): if user specified 'adapters', use it as 'outputs' too.
171
+ // If user specified only 'outputs' (old configs), mirror to 'adapters'.
172
+ if (merged.adapters && !Array.isArray(merged.adapters)) merged.adapters = null;
173
+ if (!merged.adapters && Array.isArray(merged.outputs)) {
174
+ merged.adapters = merged.outputs.slice();
175
+ } else if (Array.isArray(merged.adapters) && !userConfig.outputs) {
176
+ merged.outputs = merged.adapters.filter((a) => ['copilot','claude','cursor','windsurf'].includes(a));
177
+ }
166
178
  return merged;
167
179
  }
168
180
 
@@ -4727,6 +4739,158 @@ __factories["./src/eval/runner"] = function(module, exports) {
4727
4739
  };
4728
4740
 
4729
4741
 
4742
+ // ── ./packages/adapters/copilot (bundled) ──
4743
+ __factories["./packages/adapters/copilot"] = function(module, exports) {
4744
+ const path = require('path');
4745
+ const name = 'copilot';
4746
+ function format(context, opts = {}) {
4747
+ if (!context || typeof context !== 'string') return '';
4748
+ const version = (opts && opts.version) || 'unknown';
4749
+ const timestamp = new Date().toISOString();
4750
+ return [
4751
+ `<!-- Generated by SigMap gen-context.js v${version} -->`,
4752
+ `<!-- Updated: ${timestamp} -->`,
4753
+ `<!-- Do not edit below — regenerate with: node gen-context.js -->`,
4754
+ '',
4755
+ '# Code signatures',
4756
+ '',
4757
+ context,
4758
+ ].join('\n');
4759
+ }
4760
+ function outputPath(cwd) { return path.join(cwd, '.github', 'copilot-instructions.md'); }
4761
+ module.exports = { name, format, outputPath };
4762
+ };
4763
+
4764
+ // ── ./packages/adapters/claude (bundled) ──
4765
+ __factories["./packages/adapters/claude"] = function(module, exports) {
4766
+ const path = require('path');
4767
+ const fs = require('fs');
4768
+ const name = 'claude';
4769
+ const CLAUDE_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
4770
+ function format(context, opts = {}) {
4771
+ if (!context || typeof context !== 'string') return '';
4772
+ const version = (opts && opts.version) || 'unknown';
4773
+ const timestamp = new Date().toISOString();
4774
+ return [`<!-- Generated by SigMap v${version} — ${timestamp} -->`, '', context].join('\n');
4775
+ }
4776
+ function outputPath(cwd) { return path.join(cwd, 'CLAUDE.md'); }
4777
+ function write(context, cwd, opts = {}) {
4778
+ const filePath = outputPath(cwd);
4779
+ let existing = '';
4780
+ if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
4781
+ const formatted = format(context, opts);
4782
+ const markerIdx = existing.indexOf('## Auto-generated signatures');
4783
+ const newContent = markerIdx !== -1
4784
+ ? existing.slice(0, markerIdx) + CLAUDE_MARKER.trimStart() + formatted
4785
+ : existing + CLAUDE_MARKER + formatted;
4786
+ fs.writeFileSync(filePath, newContent, 'utf8');
4787
+ }
4788
+ module.exports = { name, format, outputPath, write };
4789
+ };
4790
+
4791
+ // ── ./packages/adapters/cursor (bundled) ──
4792
+ __factories["./packages/adapters/cursor"] = function(module, exports) {
4793
+ const path = require('path');
4794
+ const name = 'cursor';
4795
+ function format(context, opts = {}) {
4796
+ if (!context || typeof context !== 'string') return '';
4797
+ const version = (opts && opts.version) || 'unknown';
4798
+ const timestamp = new Date().toISOString();
4799
+ return [`# Code signatures — generated by SigMap v${version}`, `# Updated: ${timestamp}`, `# Regenerate: node gen-context.js`, '', context].join('\n');
4800
+ }
4801
+ function outputPath(cwd) { return path.join(cwd, '.cursorrules'); }
4802
+ module.exports = { name, format, outputPath };
4803
+ };
4804
+
4805
+ // ── ./packages/adapters/windsurf (bundled) ──
4806
+ __factories["./packages/adapters/windsurf"] = function(module, exports) {
4807
+ const path = require('path');
4808
+ const name = 'windsurf';
4809
+ function format(context, opts = {}) {
4810
+ if (!context || typeof context !== 'string') return '';
4811
+ const version = (opts && opts.version) || 'unknown';
4812
+ const timestamp = new Date().toISOString();
4813
+ return [`# Code signatures — generated by SigMap v${version}`, `# Updated: ${timestamp}`, `# Regenerate: node gen-context.js`, '', context].join('\n');
4814
+ }
4815
+ function outputPath(cwd) { return path.join(cwd, '.windsurfrules'); }
4816
+ module.exports = { name, format, outputPath };
4817
+ };
4818
+
4819
+ // ── ./packages/adapters/openai (bundled) ──
4820
+ __factories["./packages/adapters/openai"] = function(module, exports) {
4821
+ const path = require('path');
4822
+ const name = 'openai';
4823
+ function format(context, opts = {}) {
4824
+ if (!context || typeof context !== 'string') return '';
4825
+ const version = (opts && opts.version) || 'unknown';
4826
+ const timestamp = new Date().toISOString();
4827
+ const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
4828
+ return [
4829
+ `You are a coding assistant with full knowledge of this codebase.`,
4830
+ `Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
4831
+ projectLine,
4832
+ `Use these signatures to answer questions about the code accurately.`,
4833
+ ``,
4834
+ `## Code Signatures`,
4835
+ ``,
4836
+ context,
4837
+ ].join('\n');
4838
+ }
4839
+ function outputPath(cwd) { return path.join(cwd, '.github', 'openai-context.md'); }
4840
+ module.exports = { name, format, outputPath };
4841
+ };
4842
+
4843
+ // ── ./packages/adapters/gemini (bundled) ──
4844
+ __factories["./packages/adapters/gemini"] = function(module, exports) {
4845
+ const path = require('path');
4846
+ const name = 'gemini';
4847
+ function format(context, opts = {}) {
4848
+ if (!context || typeof context !== 'string') return '';
4849
+ const version = (opts && opts.version) || 'unknown';
4850
+ const timestamp = new Date().toISOString();
4851
+ const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
4852
+ return [
4853
+ `You are a coding assistant with complete knowledge of this codebase.`,
4854
+ `The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
4855
+ projectLine,
4856
+ `These signatures represent every public function, class, and type in the project.`,
4857
+ ``,
4858
+ `## Code Signatures`,
4859
+ ``,
4860
+ context,
4861
+ ].join('\n');
4862
+ }
4863
+ function outputPath(cwd) { return path.join(cwd, '.github', 'gemini-context.md'); }
4864
+ module.exports = { name, format, outputPath };
4865
+ };
4866
+
4867
+ // ── ./packages/adapters/index (bundled) ──
4868
+ __factories["./packages/adapters/index"] = function(module, exports) {
4869
+ const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini'];
4870
+ const _adapterCache = {};
4871
+ function getAdapter(name) {
4872
+ if (!name || typeof name !== 'string') return null;
4873
+ const key = name.toLowerCase();
4874
+ if (!ADAPTER_NAMES.includes(key)) return null;
4875
+ if (!_adapterCache[key]) {
4876
+ try { _adapterCache[key] = __require('./packages/adapters/' + key); } catch (_) { return null; }
4877
+ }
4878
+ return _adapterCache[key];
4879
+ }
4880
+ function listAdapters() { return ADAPTER_NAMES.slice(); }
4881
+ function adapt(context, adapterName, opts = {}) {
4882
+ if (!context || typeof context !== 'string') return '';
4883
+ const adapter = getAdapter(adapterName);
4884
+ if (!adapter || typeof adapter.format !== 'function') return '';
4885
+ try { return adapter.format(context, opts); } catch (_) { return ''; }
4886
+ }
4887
+ function outputsToAdapters(outputs) {
4888
+ if (!Array.isArray(outputs)) return ['copilot'];
4889
+ return outputs.map((o) => ADAPTER_NAMES.includes(o) ? o : o);
4890
+ }
4891
+ module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
4892
+ };
4893
+
4730
4894
  /**
4731
4895
  * SigMap — gen-context.js v1.2.0
4732
4896
  * Zero-dependency AI context engine.
@@ -4739,7 +4903,7 @@ const path = require('path');
4739
4903
  const os = require('os');
4740
4904
  const { execSync } = require('child_process');
4741
4905
 
4742
- const VERSION = '2.10.0';
4906
+ const VERSION = '3.0.0';
4743
4907
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
4744
4908
 
4745
4909
  function requireSourceOrBundled(key) {
@@ -5297,6 +5461,9 @@ function writeCacheOutput(content, cwd) {
5297
5461
  }
5298
5462
 
5299
5463
  function writeOutputs(content, targets, cwd) {
5464
+ // v3.0+: adapter-aware output targets
5465
+ const ADAPTER_TARGETS = new Set(['openai', 'gemini']);
5466
+
5300
5467
  const targetMap = {
5301
5468
  copilot: path.join(cwd, '.github', 'copilot-instructions.md'),
5302
5469
  cursor: path.join(cwd, '.cursorrules'),
@@ -5308,6 +5475,20 @@ function writeOutputs(content, targets, cwd) {
5308
5475
  writeClaude(content, cwd);
5309
5476
  continue;
5310
5477
  }
5478
+ // v3.0+ adapter targets (openai, gemini) — use bundled adapter to format + write
5479
+ if (ADAPTER_TARGETS.has(target)) {
5480
+ try {
5481
+ const adapterMod = __require('./packages/adapters/' + target);
5482
+ const formatted = adapterMod.format(content, { version: VERSION });
5483
+ const outPath = adapterMod.outputPath(cwd);
5484
+ ensureDir(outPath);
5485
+ fs.writeFileSync(outPath, formatted, 'utf8');
5486
+ console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
5487
+ } catch (err) {
5488
+ console.warn(`[sigmap] adapter "${target}" failed: ${err.message}`);
5489
+ }
5490
+ continue;
5491
+ }
5311
5492
  const outPath = targetMap[target];
5312
5493
  if (!outPath) {
5313
5494
  console.warn(`[sigmap] unknown output target: ${target}`);
@@ -5948,6 +6129,8 @@ Usage:
5948
6129
  node gen-context.js --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
5949
6130
  node gen-context.js --diff --staged Generate context for staged files only
5950
6131
  node gen-context.js --benchmark Run retrieval benchmark (benchmarks/tasks/retrieval.jsonl)
6132
+ node gen-context.js --adapter <name> Generate for a specific adapter only (v3.0+)
6133
+ node gen-context.js --adapter <name> --json Show adapter output path as JSON
5951
6134
  node gen-context.js --benchmark --json Benchmark results as JSON
5952
6135
  node gen-context.js --eval Alias for --benchmark
5953
6136
  node gen-context.js --analyze Per-file breakdown: sigs, tokens, extractor, coverage
@@ -5972,6 +6155,10 @@ Strategies (set via config "strategy" key):
5972
6155
  ~90% fewer tokens. Best with MCP (Claude Code, Cursor).
5973
6156
  Set "hotCommits": N to control how many commits count as hot (default 10).
5974
6157
 
6158
+ Adapters (v3.0+): copilot | claude | cursor | windsurf | openai | gemini
6159
+ Set "adapters": ["copilot","openai"] in config to write multiple adapter outputs.
6160
+ Old "outputs" config key is still accepted (maps to adapters automatically).
6161
+
5975
6162
  Config: gen-context.config.json
5976
6163
  Ignore: .contextignore, .repomixignore
5977
6164
  Output: .github/copilot-instructions.md (default)
@@ -6303,6 +6490,44 @@ function main() {
6303
6490
  process.exit(0);
6304
6491
  }
6305
6492
 
6493
+ // ── --adapter <name> ───────────────────────────────────────────────────────
6494
+ if (args.includes('--adapter')) {
6495
+ try {
6496
+ const adpIdx = args.indexOf('--adapter');
6497
+ const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
6498
+ const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini'];
6499
+ if (!adapterName || adapterName.startsWith('--')) {
6500
+ console.error('[sigmap] --adapter requires an adapter name');
6501
+ console.error(` Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
6502
+ console.error(' Example: node gen-context.js --adapter copilot');
6503
+ process.exit(1);
6504
+ }
6505
+ if (!VALID_ADAPTERS.includes(adapterName)) {
6506
+ console.error(`[sigmap] unknown adapter: "${adapterName}"`);
6507
+ console.error(` Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
6508
+ process.exit(1);
6509
+ }
6510
+ if (args.includes('--json')) {
6511
+ const adapterMod = __require('./packages/adapters/' + adapterName);
6512
+ const outPath = adapterMod.outputPath(cwd);
6513
+ process.stdout.write(JSON.stringify({ adapter: adapterName, outputPath: outPath, version: VERSION }) + '\n');
6514
+ process.exit(0);
6515
+ }
6516
+ // Run generate with this adapter's output targets
6517
+ const singleAdapterConfig = Object.assign({}, config, {
6518
+ outputs: adapterName === 'claude' ? ['claude'] : [adapterName],
6519
+ adapters: [adapterName],
6520
+ });
6521
+ runGenerate(cwd, singleAdapterConfig, false);
6522
+ const adapterMod = __require('./packages/adapters/' + adapterName);
6523
+ console.warn(`[sigmap] adapter "${adapterName}" → ${path.relative(cwd, adapterMod.outputPath(cwd))}`);
6524
+ } catch (err) {
6525
+ console.error(`[sigmap] adapter error: ${err.message}`);
6526
+ process.exit(1);
6527
+ }
6528
+ process.exit(0);
6529
+ }
6530
+
6306
6531
  // ── --impact <file> ────────────────────────────────────────────────────────
6307
6532
  if (args.includes('--impact')) {
6308
6533
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "2.10.0",
3
+ "version": "3.0.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": {
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Claude adapter — appends to CLAUDE.md under a marker line.
5
+ * Never overwrites human-written content above the marker.
6
+ *
7
+ * Contract:
8
+ * format(context, opts?) → string
9
+ * outputPath(cwd) → string
10
+ * write(context, cwd, opts?) → void (handles append logic)
11
+ */
12
+
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const name = 'claude';
17
+
18
+ const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
19
+
20
+ /**
21
+ * Format context suited for CLAUDE.md.
22
+ * @param {string} context - Raw signature context string
23
+ * @param {object} [opts]
24
+ * @param {string} [opts.version] - SigMap version string
25
+ * @returns {string}
26
+ */
27
+ function format(context, opts = {}) {
28
+ if (!context || typeof context !== 'string') return '';
29
+ const version = opts.version || 'unknown';
30
+ const timestamp = new Date().toISOString();
31
+ return [
32
+ `<!-- Generated by SigMap v${version} — ${timestamp} -->`,
33
+ '',
34
+ context,
35
+ ].join('\n');
36
+ }
37
+
38
+ /**
39
+ * Return the output file path for this adapter.
40
+ * @param {string} cwd - Project root
41
+ * @returns {string}
42
+ */
43
+ function outputPath(cwd) {
44
+ return path.join(cwd, 'CLAUDE.md');
45
+ }
46
+
47
+ /**
48
+ * Write signatures into CLAUDE.md using the append-under-marker strategy.
49
+ * Human content above the marker is never touched.
50
+ * @param {string} context - Raw signature context string
51
+ * @param {string} cwd - Project root
52
+ * @param {object} [opts]
53
+ */
54
+ function write(context, cwd, opts = {}) {
55
+ const filePath = outputPath(cwd);
56
+ let existing = '';
57
+ if (fs.existsSync(filePath)) {
58
+ existing = fs.readFileSync(filePath, 'utf8');
59
+ }
60
+ const formatted = format(context, opts);
61
+ const markerIdx = existing.indexOf('## Auto-generated signatures');
62
+ let newContent;
63
+ if (markerIdx !== -1) {
64
+ newContent = existing.slice(0, markerIdx) + MARKER.trimStart() + formatted;
65
+ } else {
66
+ newContent = existing + MARKER + formatted;
67
+ }
68
+ fs.writeFileSync(filePath, newContent, 'utf8');
69
+ }
70
+
71
+ module.exports = { name, format, outputPath, write };
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Copilot adapter — writes to .github/copilot-instructions.md
5
+ * GitHub Copilot reads this file automatically in every workspace.
6
+ *
7
+ * Contract:
8
+ * format(context, opts?) → string
9
+ * outputPath(cwd) → string
10
+ */
11
+
12
+ const path = require('path');
13
+
14
+ const name = 'copilot';
15
+
16
+ /**
17
+ * Format context for GitHub Copilot instructions.
18
+ * @param {string} context - Raw signature context string
19
+ * @param {object} [opts]
20
+ * @param {string} [opts.version] - SigMap version string
21
+ * @returns {string}
22
+ */
23
+ function format(context, opts = {}) {
24
+ if (!context || typeof context !== 'string') return '';
25
+ const version = opts.version || 'unknown';
26
+ const timestamp = new Date().toISOString();
27
+ const header = [
28
+ `<!-- Generated by SigMap gen-context.js v${version} -->`,
29
+ `<!-- Updated: ${timestamp} -->`,
30
+ `<!-- Do not edit below — regenerate with: node gen-context.js -->`,
31
+ '',
32
+ '# Code signatures',
33
+ '',
34
+ ].join('\n');
35
+ return header + context;
36
+ }
37
+
38
+ /**
39
+ * Return the output file path for this adapter.
40
+ * @param {string} cwd - Project root
41
+ * @returns {string}
42
+ */
43
+ function outputPath(cwd) {
44
+ return path.join(cwd, '.github', 'copilot-instructions.md');
45
+ }
46
+
47
+ module.exports = { name, format, outputPath };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Cursor adapter — writes to .cursorrules
5
+ * Cursor reads .cursorrules automatically in every workspace.
6
+ *
7
+ * Contract:
8
+ * format(context, opts?) → string
9
+ * outputPath(cwd) → string
10
+ */
11
+
12
+ const path = require('path');
13
+
14
+ const name = 'cursor';
15
+
16
+ /**
17
+ * Format context for Cursor rules file.
18
+ * @param {string} context - Raw signature context string
19
+ * @param {object} [opts]
20
+ * @param {string} [opts.version] - SigMap version string
21
+ * @returns {string}
22
+ */
23
+ function format(context, opts = {}) {
24
+ if (!context || typeof context !== 'string') return '';
25
+ const version = opts.version || 'unknown';
26
+ const timestamp = new Date().toISOString();
27
+ const header = [
28
+ `# Code signatures — generated by SigMap v${version}`,
29
+ `# Updated: ${timestamp}`,
30
+ `# Regenerate: node gen-context.js`,
31
+ '',
32
+ ].join('\n');
33
+ return header + context;
34
+ }
35
+
36
+ /**
37
+ * Return the output file path for this adapter.
38
+ * @param {string} cwd - Project root
39
+ * @returns {string}
40
+ */
41
+ function outputPath(cwd) {
42
+ return path.join(cwd, '.cursorrules');
43
+ }
44
+
45
+ module.exports = { name, format, outputPath };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Gemini adapter — formats context as a Gemini system instruction.
5
+ * Use the output as the `system_instruction` field in a Gemini API request.
6
+ *
7
+ * Example usage:
8
+ * const { format } = require('sigmap/adapters/gemini');
9
+ * const instruction = format(context);
10
+ * // Pass to: genAI.getGenerativeModel({ model: 'gemini-pro', systemInstruction: instruction })
11
+ *
12
+ * Contract:
13
+ * format(context, opts?) → string
14
+ * outputPath(cwd) → string
15
+ */
16
+
17
+ const path = require('path');
18
+
19
+ const name = 'gemini';
20
+
21
+ /**
22
+ * Format context as a Gemini system instruction.
23
+ * @param {string} context - Raw signature context string
24
+ * @param {object} [opts]
25
+ * @param {string} [opts.version] - SigMap version string
26
+ * @param {string} [opts.projectName] - Optional project name
27
+ * @returns {string}
28
+ */
29
+ function format(context, opts = {}) {
30
+ if (!context || typeof context !== 'string') return '';
31
+ const version = opts.version || 'unknown';
32
+ const timestamp = new Date().toISOString();
33
+ const projectLine = opts.projectName
34
+ ? `Project: ${opts.projectName}\n`
35
+ : '';
36
+
37
+ return [
38
+ `You are a coding assistant with complete knowledge of this codebase.`,
39
+ `The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
40
+ projectLine,
41
+ `These signatures represent every public function, class, and type in the project.`,
42
+ `Refer to them when answering questions about code structure, APIs, and implementation.`,
43
+ ``,
44
+ `## Code Signatures`,
45
+ ``,
46
+ context,
47
+ ].join('\n');
48
+ }
49
+
50
+ /**
51
+ * Return the output file path for this adapter.
52
+ * @param {string} cwd - Project root
53
+ * @returns {string}
54
+ */
55
+ function outputPath(cwd) {
56
+ return path.join(cwd, '.github', 'gemini-context.md');
57
+ }
58
+
59
+ module.exports = { name, format, outputPath };
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * packages/adapters/index.js
5
+ * Central registry for all SigMap output adapters.
6
+ *
7
+ * Usage:
8
+ * const { getAdapter, listAdapters, adapt } = require('sigmap/adapters');
9
+ * const output = adapt(context, 'copilot', { version: '3.0.0' });
10
+ */
11
+
12
+ const path = require('path');
13
+
14
+ const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini'];
15
+
16
+ // Lazy-load adapters so unused ones don't pay any require() cost
17
+ const _cache = {};
18
+
19
+ /**
20
+ * Load and return an adapter module by name.
21
+ * @param {string} name - Adapter name (copilot|claude|cursor|windsurf|openai|gemini)
22
+ * @returns {{ name: string, format: Function, outputPath: Function }|null}
23
+ */
24
+ function getAdapter(name) {
25
+ if (!name || typeof name !== 'string') return null;
26
+ const key = name.toLowerCase();
27
+ if (!ADAPTER_NAMES.includes(key)) return null;
28
+ if (!_cache[key]) {
29
+ try {
30
+ _cache[key] = require(path.join(__dirname, key + '.js'));
31
+ } catch (_) {
32
+ return null;
33
+ }
34
+ }
35
+ return _cache[key];
36
+ }
37
+
38
+ /**
39
+ * List all available adapter names.
40
+ * @returns {string[]}
41
+ */
42
+ function listAdapters() {
43
+ return ADAPTER_NAMES.slice();
44
+ }
45
+
46
+ /**
47
+ * Format context using the named adapter.
48
+ * @param {string} context - Raw signature context string
49
+ * @param {string} adapterName - Adapter name
50
+ * @param {object} [opts] - Options passed to adapter.format()
51
+ * @returns {string} Formatted output string (empty string if adapter not found or context empty)
52
+ */
53
+ function adapt(context, adapterName, opts = {}) {
54
+ if (!context || typeof context !== 'string') return '';
55
+ const adapter = getAdapter(adapterName);
56
+ if (!adapter || typeof adapter.format !== 'function') return '';
57
+ try {
58
+ return adapter.format(context, opts);
59
+ } catch (_) {
60
+ return '';
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Map old `outputs` config values to new `adapters` names.
66
+ * Provides backward compatibility for existing configurations.
67
+ * @param {string[]} outputs - Legacy outputs array
68
+ * @returns {string[]} Equivalent adapters array
69
+ */
70
+ function outputsToAdapters(outputs) {
71
+ if (!Array.isArray(outputs)) return ['copilot'];
72
+ return outputs.map((o) => {
73
+ // All current output names already match adapter names
74
+ if (ADAPTER_NAMES.includes(o)) return o;
75
+ return o; // pass through unknowns — getAdapter() will handle gracefully
76
+ });
77
+ }
78
+
79
+ module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * OpenAI adapter — formats context as an OpenAI system message.
5
+ * Use the output as the `content` field of a system role message.
6
+ *
7
+ * Example usage in code:
8
+ * const { format } = require('sigmap/adapters/openai');
9
+ * const systemPrompt = format(context);
10
+ * // Pass to: openai.chat.completions.create({ messages: [{ role: 'system', content: systemPrompt }] })
11
+ *
12
+ * Contract:
13
+ * format(context, opts?) → string
14
+ * outputPath(cwd) → string
15
+ */
16
+
17
+ const path = require('path');
18
+
19
+ const name = 'openai';
20
+
21
+ /**
22
+ * Format context as an OpenAI system prompt.
23
+ * @param {string} context - Raw signature context string
24
+ * @param {object} [opts]
25
+ * @param {string} [opts.version] - SigMap version string
26
+ * @param {string} [opts.projectName] - Optional project name
27
+ * @returns {string}
28
+ */
29
+ function format(context, opts = {}) {
30
+ if (!context || typeof context !== 'string') return '';
31
+ const version = opts.version || 'unknown';
32
+ const timestamp = new Date().toISOString();
33
+ const projectLine = opts.projectName
34
+ ? `Project: ${opts.projectName}\n`
35
+ : '';
36
+
37
+ return [
38
+ `You are a coding assistant with full knowledge of this codebase.`,
39
+ `Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
40
+ projectLine,
41
+ `Use these signatures to answer questions about the code accurately.`,
42
+ `When the user asks about a specific file or function, refer to the signatures below.`,
43
+ ``,
44
+ `## Code Signatures`,
45
+ ``,
46
+ context,
47
+ ].join('\n');
48
+ }
49
+
50
+ /**
51
+ * Return the output file path for this adapter.
52
+ * Writes a .openai-context.md file that can be loaded at runtime.
53
+ * @param {string} cwd - Project root
54
+ * @returns {string}
55
+ */
56
+ function outputPath(cwd) {
57
+ return path.join(cwd, '.github', 'openai-context.md');
58
+ }
59
+
60
+ module.exports = { name, format, outputPath };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Windsurf adapter — writes to .windsurfrules
5
+ * Windsurf reads .windsurfrules automatically in every workspace.
6
+ *
7
+ * Contract:
8
+ * format(context, opts?) → string
9
+ * outputPath(cwd) → string
10
+ */
11
+
12
+ const path = require('path');
13
+
14
+ const name = 'windsurf';
15
+
16
+ /**
17
+ * Format context for Windsurf rules file.
18
+ * @param {string} context - Raw signature context string
19
+ * @param {object} [opts]
20
+ * @param {string} [opts.version] - SigMap version string
21
+ * @returns {string}
22
+ */
23
+ function format(context, opts = {}) {
24
+ if (!context || typeof context !== 'string') return '';
25
+ const version = opts.version || 'unknown';
26
+ const timestamp = new Date().toISOString();
27
+ const header = [
28
+ `# Code signatures — generated by SigMap v${version}`,
29
+ `# Updated: ${timestamp}`,
30
+ `# Regenerate: node gen-context.js`,
31
+ '',
32
+ ].join('\n');
33
+ return header + context;
34
+ }
35
+
36
+ /**
37
+ * Return the output file path for this adapter.
38
+ * @param {string} cwd - Project root
39
+ * @returns {string}
40
+ */
41
+ function outputPath(cwd) {
42
+ return path.join(cwd, '.windsurfrules');
43
+ }
44
+
45
+ module.exports = { name, format, outputPath };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "2.10.0",
3
+ "version": "3.0.0",
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
  # sigmap-core
2
2
 
3
- Programmatic API for [SigMap](https://manojmallick.github.io/sigmap/) — zero-dependency code signature extraction, ranked retrieval, secret scanning, and project health scoring.
3
+ Programmatic API for [SigMap](https://manojmallick.github.io/sigmap/) — zero-dependency code signature extraction, ranked retrieval, secret scanning, project health scoring, and multi-adapter output formatting (v3.0+).
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,7 +13,7 @@ npm install sigmap # installs the full package (CLI + core)
13
13
  ## Quick start
14
14
 
15
15
  ```js
16
- const { extract, rank, buildSigIndex, scan, score } = require('sigmap');
16
+ const { extract, rank, buildSigIndex, scan, score, adapt } = require('sigmap');
17
17
 
18
18
  // 1. Extract signatures from any source file
19
19
  const sigs = extract('function hello() { return "world"; }', 'javascript');
@@ -128,9 +128,21 @@ const health = score('/path/to/project');
128
128
 
129
129
  All existing CLI flags (`--generate`, `--watch`, `--mcp`, `--query`, `--analyze`, `--benchmark`, `--health`, …) are unchanged.
130
130
 
131
- ## What's next v2.10
131
+ ## v3.0Multi-Adapter Architecture (released)
132
132
 
133
- v2.10 adds reporting charts and advanced metrics for benchmark visibility. This milestone focuses on chart-ready report output, precision@K/recall@K/MRR trends, and CI-friendly metrics artifacts. See [issue #25](https://github.com/manojmallick/sigmap/issues/25).
133
+ v3.0 adds the `adapt()` function to `packages/core`, making the API **semver-stable**. Breaking changes now require v4.0.
134
+
135
+ ```js
136
+ const { adapt } = require('sigmap');
137
+
138
+ // Format context as an OpenAI system prompt
139
+ const systemPrompt = adapt(context, 'openai', { version: '3.0.0' });
140
+
141
+ // Format context as a Gemini system instruction
142
+ const geminiInstruction = adapt(context, 'gemini');
143
+
144
+ // All 6 adapters: copilot | claude | cursor | windsurf | openai | gemini
145
+ ```
134
146
 
135
147
  See the full [roadmap](https://manojmallick.github.io/sigmap/roadmap.html).
136
148
 
@@ -198,6 +198,33 @@ function score(cwd) {
198
198
  }
199
199
  }
200
200
 
201
+ // ---------------------------------------------------------------------------
202
+ // adapt(context, adapterName, opts?) → string (v3.0+)
203
+ // ---------------------------------------------------------------------------
204
+ /**
205
+ * Format a context string using the named output adapter.
206
+ *
207
+ * @param {string} context - Raw signature context string
208
+ * @param {string} adapterName - One of: 'copilot'|'claude'|'cursor'|'windsurf'|'openai'|'gemini'
209
+ * @param {object} [opts] - Passed through to adapter.format()
210
+ * @returns {string} Formatted output string (empty string if adapter not found)
211
+ *
212
+ * @example
213
+ * const { adapt } = require('sigmap');
214
+ * const systemPrompt = adapt(context, 'openai', { version: '3.0.0' });
215
+ *
216
+ * const copilotMd = adapt(context, 'copilot');
217
+ */
218
+ function adapt(context, adapterName, opts = {}) {
219
+ try {
220
+ const adaptersPath = path.resolve(__dirname, '..', 'adapters', 'index.js');
221
+ const { adapt: _adapt } = require(adaptersPath);
222
+ return _adapt(context, adapterName, opts);
223
+ } catch (_) {
224
+ return '';
225
+ }
226
+ }
227
+
201
228
  // ---------------------------------------------------------------------------
202
229
  // Exports
203
230
  // ---------------------------------------------------------------------------
@@ -212,4 +239,6 @@ module.exports = {
212
239
  scan,
213
240
  /** Compute project health score */
214
241
  score,
242
+ /** Format context using a named output adapter (v3.0+) */
243
+ adapt,
215
244
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "2.10.0",
3
+ "version": "3.0.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -11,6 +11,10 @@ const DEFAULTS = {
11
11
  // Output targets: 'copilot' | 'claude' | 'cursor' | 'windsurf'
12
12
  outputs: ['copilot'],
13
13
 
14
+ // Adapter targets (v3.0+): replaces 'outputs'. Same names, adds 'openai' | 'gemini'.
15
+ // Old 'outputs' config key is still accepted and silently maps to 'adapters'.
16
+ adapters: null,
17
+
14
18
  // Directories to scan (relative to project root)
15
19
  srcDirs: [
16
20
  'src', 'app', 'lib', 'packages', 'services', 'api',
@@ -50,6 +50,13 @@ function loadConfig(cwd) {
50
50
  merged[key] = val;
51
51
  }
52
52
  }
53
+ // Backward compat (v3.0+): mirror outputs ↔ adapters
54
+ if (merged.adapters && !Array.isArray(merged.adapters)) merged.adapters = null;
55
+ if (!merged.adapters && Array.isArray(merged.outputs)) {
56
+ merged.adapters = merged.outputs.slice();
57
+ } else if (Array.isArray(merged.adapters) && !userConfig.outputs) {
58
+ merged.outputs = merged.adapters.filter((a) => ['copilot','claude','cursor','windsurf'].includes(a));
59
+ }
53
60
  return merged;
54
61
  }
55
62
 
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: '2.10.0',
21
+ version: '3.0.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24