sigmap 2.10.0 → 3.0.1-alpha.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/CHANGELOG.md +34 -1
- package/README.md +60 -11
- package/gen-context.js +258 -9
- package/package.json +1 -1
- package/packages/adapters/claude.js +71 -0
- package/packages/adapters/copilot.js +47 -0
- package/packages/adapters/cursor.js +45 -0
- package/packages/adapters/gemini.js +59 -0
- package/packages/adapters/index.js +79 -0
- package/packages/adapters/openai.js +60 -0
- package/packages/adapters/windsurf.js +45 -0
- package/packages/cli/package.json +1 -1
- package/packages/core/README.md +16 -4
- package/packages/core/index.js +29 -0
- package/packages/core/package.json +1 -1
- package/src/config/defaults.js +4 -0
- package/src/config/loader.js +7 -0
- package/src/mcp/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,40 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## [
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **`output` config key not honored for copilot adapter** · [#30](https://github.com/manojmallick/sigmap/issues/30)
|
|
13
|
+
- Custom `output` path in config now correctly used for copilot adapter instead of hard-wired `.github/copilot-instructions.md`
|
|
14
|
+
- Added `resolveAdapterPath()` helper to centralize adapter path resolution
|
|
15
|
+
- Other adapters (claude, cursor, windsurf) continue to use fixed paths as designed
|
|
16
|
+
- 5 new integration tests ensure custom paths work correctly across all config combinations
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [3.0.0] — 2026-04-06 — Platform: Multi-Adapter Architecture
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **Multi-adapter platform** — `packages/adapters/` with 6 output adapters: `copilot`, `claude`, `cursor`, `windsurf`, `openai`, `gemini`
|
|
24
|
+
- **`--adapter <name>` CLI flag** — generate output for a specific adapter only (e.g. `node gen-context.js --adapter openai`)
|
|
25
|
+
- **`adapt()` in packages/core** — programmatic API: `const { adapt } = require('sigmap'); adapt(context, 'openai')`
|
|
26
|
+
- **New config key `adapters`** — replaces `outputs`; old `outputs` key is silently mapped for full backward compatibility
|
|
27
|
+
- **OpenAI adapter** — formats context as an OpenAI system prompt, writes `.github/openai-context.md`
|
|
28
|
+
- **Gemini adapter** — formats context as a Gemini system instruction, writes `.github/gemini-context.md`
|
|
29
|
+
- **API stability guarantee** — `packages/core` API is now semver-stable; breaking changes require v4.0
|
|
30
|
+
- **20 new integration tests** in `test/integration/adapters.test.js`
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- `packages/core/index.js` — adds `adapt()` export alongside existing `extract`, `rank`, `scan`, `score`, `buildSigIndex`
|
|
34
|
+
- `writeOutputs()` in `gen-context.js` — now routes `openai`, `gemini` through adapter pipeline
|
|
35
|
+
|
|
36
|
+
### Backward compat
|
|
37
|
+
- `outputs: ["copilot","claude"]` config still works — automatically mirrored to `adapters`
|
|
38
|
+
- All existing CLI flags unchanged
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## [2.10.0] — 2026-04-06 · [#25](https://github.com/manojmallick/sigmap/issues/25)
|
|
10
43
|
|
|
11
44
|
### Planned additions
|
|
12
45
|
- **Report charts** — add chart-ready output for token reduction, signatures per file, and budget utilization trends.
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
[](CHANGELOG.md)
|
|
27
27
|
[](CONTRIBUTING.md)
|
|
28
28
|
[](https://marketplace.visualstudio.com/items?itemName=manojmallick.sigmap)
|
|
29
|
-
[](https://plugins.jetbrains.com/plugin/sigmap)
|
|
29
|
+
[](https://plugins.jetbrains.com/plugin/31109-sigmap--ai-context-engine/)
|
|
30
30
|
[](https://open-vsx.org/extension/manojmallick/sigmap)
|
|
31
31
|
|
|
32
32
|
</div>
|
|
@@ -124,19 +124,45 @@ AI agent session starts with full context
|
|
|
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
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
## 🔌 v3.0 — Platform: Multi-Adapter Architecture
|
|
128
|
+
|
|
129
|
+
SigMap is now an **adapter platform**. Any AI assistant — Copilot, Claude, Cursor, Windsurf, OpenAI, or Gemini — plugs in through a standard interface.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Generate for a specific AI assistant
|
|
133
|
+
node gen-context.js --adapter copilot # → .github/copilot-instructions.md
|
|
134
|
+
node gen-context.js --adapter openai # → .github/openai-context.md
|
|
135
|
+
node gen-context.js --adapter gemini # → .github/gemini-context.md
|
|
136
|
+
node gen-context.js --adapter claude # → CLAUDE.md (append)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
// Programmatic API — fully semver-stable from v3.0
|
|
141
|
+
const { adapt } = require('sigmap');
|
|
142
|
+
const systemPrompt = adapt(context, 'openai', { version: '3.0.0' });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| Adapter | Output file | AI assistant |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `copilot` | `.github/copilot-instructions.md` | GitHub Copilot |
|
|
148
|
+
| `claude` | `CLAUDE.md` (append) | Claude / Claude Code |
|
|
149
|
+
| `cursor` | `.cursorrules` | Cursor |
|
|
150
|
+
| `windsurf` | `.windsurfrules` | Windsurf |
|
|
151
|
+
| `openai` | `.github/openai-context.md` | Any OpenAI model |
|
|
152
|
+
| `gemini` | `.github/gemini-context.md` | Google Gemini |
|
|
153
|
+
|
|
154
|
+
**Backward compat:** existing `outputs` config key silently maps to `adapters` — no migration needed.
|
|
155
|
+
|
|
156
|
+
See full roadmap: [manojmallick.github.io/sigmap/roadmap.html](https://manojmallick.github.io/sigmap/roadmap.html)
|
|
131
157
|
|
|
132
158
|
---
|
|
133
159
|
|
|
134
|
-
##
|
|
160
|
+
## ⚡ Quick start
|
|
135
161
|
|
|
136
|
-
|
|
162
|
+
Download the single-file CLI and generate context immediately:
|
|
137
163
|
|
|
138
164
|
```bash
|
|
139
|
-
# 1.
|
|
165
|
+
# 1. Download
|
|
140
166
|
curl -O https://raw.githubusercontent.com/manojmallick/sigmap/main/gen-context.js
|
|
141
167
|
|
|
142
168
|
# 2. Generate your context file
|
|
@@ -220,7 +246,7 @@ The `jetbrains-plugin/` directory contains a Kotlin-based plugin for JetBrains I
|
|
|
220
246
|
|
|
221
247
|
Compatible with **IntelliJ IDEA 2024.1+** (Community & Ultimate), **WebStorm**, **PyCharm**, **GoLand**, **RubyMine**, **PhpStorm**, and all other IntelliJ-based IDEs.
|
|
222
248
|
|
|
223
|
-
**Install:** [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/sigmap) | [Manual setup guide](docs/JETBRAINS_SETUP.md)
|
|
249
|
+
**Install:** [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/31109-sigmap--ai-context-engine/) | [Manual setup guide](docs/JETBRAINS_SETUP.md)
|
|
224
250
|
|
|
225
251
|
---
|
|
226
252
|
|
|
@@ -423,6 +449,7 @@ Copy `gen-context.config.json.example` to `gen-context.config.json`:
|
|
|
423
449
|
|
|
424
450
|
```json
|
|
425
451
|
{
|
|
452
|
+
"output": ".github/copilot-instructions.md",
|
|
426
453
|
"srcDirs": ["src", "app", "lib"],
|
|
427
454
|
"maxTokens": 6000,
|
|
428
455
|
"outputs": ["copilot"],
|
|
@@ -433,6 +460,15 @@ Copy `gen-context.config.json.example` to `gen-context.config.json`:
|
|
|
433
460
|
}
|
|
434
461
|
```
|
|
435
462
|
|
|
463
|
+
**Key fields:**
|
|
464
|
+
|
|
465
|
+
- **`output`** — custom path for the primary markdown output file (used by `copilot` adapter). Default: `.github/copilot-instructions.md`
|
|
466
|
+
- **`outputs`** — which adapters to write to: `copilot` | `claude` | `cursor` | `windsurf`
|
|
467
|
+
- **`srcDirs`** — directories to scan (relative to project root)
|
|
468
|
+
- **`maxTokens`** — max tokens in final output before budget enforcement
|
|
469
|
+
- **`secretScan`** — redact secrets (AWS keys, tokens, etc.) from output
|
|
470
|
+
- **`strategy`** — output mode: `full` (default) | `per-module` | `hot-cold`
|
|
471
|
+
|
|
436
472
|
Exclusions go in `.contextignore` (gitignore syntax). Also reads `.repomixignore` if present.
|
|
437
473
|
|
|
438
474
|
```
|
|
@@ -450,12 +486,25 @@ Run `node gen-context.js --init` to scaffold both files in one step.
|
|
|
450
486
|
|
|
451
487
|
| Key | Output file | Read by |
|
|
452
488
|
|---|---|---|
|
|
453
|
-
| `"copilot"` | `.github/copilot-instructions.md` | GitHub Copilot |
|
|
489
|
+
| `"copilot"` | `.github/copilot-instructions.md` *(or custom path via `output`)* | GitHub Copilot |
|
|
454
490
|
| `"claude"` | `CLAUDE.md` (appends below marker) | Claude Code |
|
|
455
491
|
| `"cursor"` | `.cursorrules` | Cursor |
|
|
456
492
|
| `"windsurf"` | `.windsurfrules` | Windsurf |
|
|
457
493
|
|
|
458
|
-
|
|
494
|
+
The **`output`** config key sets the primary output file path. It is used by the `copilot` adapter when enabled. Other adapters always write to their fixed paths.
|
|
495
|
+
|
|
496
|
+
**Example:**
|
|
497
|
+
|
|
498
|
+
```json
|
|
499
|
+
{
|
|
500
|
+
"output": ".context/ai-context.md",
|
|
501
|
+
"outputs": ["copilot"]
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
This writes to `.context/ai-context.md` instead of `.github/copilot-instructions.md`.
|
|
506
|
+
|
|
507
|
+
If `output` is omitted, the default `.github/copilot-instructions.md` is used.
|
|
459
508
|
|
|
460
509
|
## 📊 Observability
|
|
461
510
|
|
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 = '
|
|
4906
|
+
const VERSION = '3.0.1';
|
|
4743
4907
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
4744
4908
|
|
|
4745
4909
|
function requireSourceOrBundled(key) {
|
|
@@ -5281,6 +5445,29 @@ function ensureDir(filePath) {
|
|
|
5281
5445
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
5282
5446
|
}
|
|
5283
5447
|
|
|
5448
|
+
// ---------------------------------------------------------------------------
|
|
5449
|
+
// Resolve output path for a given adapter
|
|
5450
|
+
// ---------------------------------------------------------------------------
|
|
5451
|
+
// Takes config.output into account for the copilot adapter.
|
|
5452
|
+
// Returns the full path where output should be written.
|
|
5453
|
+
function resolveAdapterPath(adapter, cwd, config) {
|
|
5454
|
+
config = config || {};
|
|
5455
|
+
|
|
5456
|
+
// Copilot uses config.output if available, otherwise default
|
|
5457
|
+
if (adapter === 'copilot') {
|
|
5458
|
+
return config.output
|
|
5459
|
+
? path.join(cwd, config.output)
|
|
5460
|
+
: path.join(cwd, '.github', 'copilot-instructions.md');
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
// Fixed paths for other adapters
|
|
5464
|
+
if (adapter === 'claude') return path.join(cwd, 'CLAUDE.md');
|
|
5465
|
+
if (adapter === 'cursor') return path.join(cwd, '.cursorrules');
|
|
5466
|
+
if (adapter === 'windsurf') return path.join(cwd, '.windsurfrules');
|
|
5467
|
+
|
|
5468
|
+
return null;
|
|
5469
|
+
}
|
|
5470
|
+
|
|
5284
5471
|
// ---------------------------------------------------------------------------
|
|
5285
5472
|
// Cache output writer (v0.8)
|
|
5286
5473
|
// ---------------------------------------------------------------------------
|
|
@@ -5296,11 +5483,15 @@ function writeCacheOutput(content, cwd) {
|
|
|
5296
5483
|
}
|
|
5297
5484
|
}
|
|
5298
5485
|
|
|
5299
|
-
function writeOutputs(content, targets, cwd) {
|
|
5486
|
+
function writeOutputs(content, targets, cwd, config) {
|
|
5487
|
+
config = config || {};
|
|
5488
|
+
// v3.0+: adapter-aware output targets
|
|
5489
|
+
const ADAPTER_TARGETS = new Set(['openai', 'gemini']);
|
|
5490
|
+
|
|
5300
5491
|
const targetMap = {
|
|
5301
|
-
copilot:
|
|
5302
|
-
cursor:
|
|
5303
|
-
windsurf:
|
|
5492
|
+
copilot: resolveAdapterPath('copilot', cwd, config),
|
|
5493
|
+
cursor: resolveAdapterPath('cursor', cwd, config),
|
|
5494
|
+
windsurf: resolveAdapterPath('windsurf', cwd, config),
|
|
5304
5495
|
};
|
|
5305
5496
|
|
|
5306
5497
|
for (const target of targets) {
|
|
@@ -5308,6 +5499,20 @@ function writeOutputs(content, targets, cwd) {
|
|
|
5308
5499
|
writeClaude(content, cwd);
|
|
5309
5500
|
continue;
|
|
5310
5501
|
}
|
|
5502
|
+
// v3.0+ adapter targets (openai, gemini) — use bundled adapter to format + write
|
|
5503
|
+
if (ADAPTER_TARGETS.has(target)) {
|
|
5504
|
+
try {
|
|
5505
|
+
const adapterMod = __require('./packages/adapters/' + target);
|
|
5506
|
+
const formatted = adapterMod.format(content, { version: VERSION });
|
|
5507
|
+
const outPath = adapterMod.outputPath(cwd);
|
|
5508
|
+
ensureDir(outPath);
|
|
5509
|
+
fs.writeFileSync(outPath, formatted, 'utf8');
|
|
5510
|
+
console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
|
|
5511
|
+
} catch (err) {
|
|
5512
|
+
console.warn(`[sigmap] adapter "${target}" failed: ${err.message}`);
|
|
5513
|
+
}
|
|
5514
|
+
continue;
|
|
5515
|
+
}
|
|
5311
5516
|
const outPath = targetMap[target];
|
|
5312
5517
|
if (!outPath) {
|
|
5313
5518
|
console.warn(`[sigmap] unknown output target: ${target}`);
|
|
@@ -5528,7 +5733,7 @@ function runPerModuleStrategy(cwd, config, fileEntries, inputTokenTotal) {
|
|
|
5528
5733
|
overviewLines.push('> For cross-module questions load both files.');
|
|
5529
5734
|
const overviewContent = overviewLines.join('\n') + '\n';
|
|
5530
5735
|
const primaryTargets = (config.outputs || ['copilot']).filter((t) => t !== 'claude');
|
|
5531
|
-
writeOutputs(overviewContent, primaryTargets, cwd);
|
|
5736
|
+
writeOutputs(overviewContent, primaryTargets, cwd, config);
|
|
5532
5737
|
|
|
5533
5738
|
const overviewTokens = estimateTokens(overviewContent);
|
|
5534
5739
|
console.warn(`[sigmap] per-module: overview ~${overviewTokens} tokens (always-on), modules total ~${totalOut} tokens (on-demand)`);
|
|
@@ -5547,7 +5752,7 @@ function runHotColdStrategy(cwd, config, fileEntries, recentFiles, inputTokenTot
|
|
|
5547
5752
|
? formatOutput(hotEntries, cwd, false, config, null)
|
|
5548
5753
|
: '<!-- Generated by SigMap — no recently changed files -->\n';
|
|
5549
5754
|
const primaryTargets = (config.outputs || ['copilot']).filter((t) => t !== 'claude');
|
|
5550
|
-
writeOutputs(hotContent, primaryTargets, cwd);
|
|
5755
|
+
writeOutputs(hotContent, primaryTargets, cwd, config);
|
|
5551
5756
|
const hotTokens = estimateTokens(hotContent);
|
|
5552
5757
|
|
|
5553
5758
|
// Cold → .github/context-cold.md (MCP reads this on demand)
|
|
@@ -5650,7 +5855,7 @@ function runDiff(cwd, config, stagedOnly, baseRef) {
|
|
|
5650
5855
|
const diffSection = baseRef ? buildDiffSectionFromBase(cwd, baseRef, fileEntries, config) : [];
|
|
5651
5856
|
const content = formatOutput(fileEntries, cwd, routingEnabled, config, { diffSection });
|
|
5652
5857
|
const finalTokens = estimateTokens(content);
|
|
5653
|
-
writeOutputs(content, config.outputs, cwd);
|
|
5858
|
+
writeOutputs(content, config.outputs, cwd, config);
|
|
5654
5859
|
|
|
5655
5860
|
const scope = baseRef ? `diff-vs-${baseRef}` : (stagedOnly ? 'staged' : 'diff');
|
|
5656
5861
|
console.warn(`[sigmap] ${scope} files: ${fileEntries.length}, diff tokens: ~${finalTokens}`);
|
|
@@ -5759,7 +5964,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
5759
5964
|
const finalTokens = estimateTokens(content);
|
|
5760
5965
|
const formatIdx = process.argv.indexOf('--format');
|
|
5761
5966
|
const formatValue = formatIdx >= 0 ? process.argv[formatIdx + 1] : (config.format || 'default');
|
|
5762
|
-
writeOutputs(content, config.outputs, cwd);
|
|
5967
|
+
writeOutputs(content, config.outputs, cwd, config);
|
|
5763
5968
|
if (formatValue === 'cache') writeCacheOutput(content, cwd);
|
|
5764
5969
|
result = { inputTokenTotal, finalTokens, fileCount: beforeCount, droppedCount };
|
|
5765
5970
|
}
|
|
@@ -5948,6 +6153,8 @@ Usage:
|
|
|
5948
6153
|
node gen-context.js --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
|
|
5949
6154
|
node gen-context.js --diff --staged Generate context for staged files only
|
|
5950
6155
|
node gen-context.js --benchmark Run retrieval benchmark (benchmarks/tasks/retrieval.jsonl)
|
|
6156
|
+
node gen-context.js --adapter <name> Generate for a specific adapter only (v3.0+)
|
|
6157
|
+
node gen-context.js --adapter <name> --json Show adapter output path as JSON
|
|
5951
6158
|
node gen-context.js --benchmark --json Benchmark results as JSON
|
|
5952
6159
|
node gen-context.js --eval Alias for --benchmark
|
|
5953
6160
|
node gen-context.js --analyze Per-file breakdown: sigs, tokens, extractor, coverage
|
|
@@ -5972,6 +6179,10 @@ Strategies (set via config "strategy" key):
|
|
|
5972
6179
|
~90% fewer tokens. Best with MCP (Claude Code, Cursor).
|
|
5973
6180
|
Set "hotCommits": N to control how many commits count as hot (default 10).
|
|
5974
6181
|
|
|
6182
|
+
Adapters (v3.0+): copilot | claude | cursor | windsurf | openai | gemini
|
|
6183
|
+
Set "adapters": ["copilot","openai"] in config to write multiple adapter outputs.
|
|
6184
|
+
Old "outputs" config key is still accepted (maps to adapters automatically).
|
|
6185
|
+
|
|
5975
6186
|
Config: gen-context.config.json
|
|
5976
6187
|
Ignore: .contextignore, .repomixignore
|
|
5977
6188
|
Output: .github/copilot-instructions.md (default)
|
|
@@ -6303,6 +6514,44 @@ function main() {
|
|
|
6303
6514
|
process.exit(0);
|
|
6304
6515
|
}
|
|
6305
6516
|
|
|
6517
|
+
// ── --adapter <name> ───────────────────────────────────────────────────────
|
|
6518
|
+
if (args.includes('--adapter')) {
|
|
6519
|
+
try {
|
|
6520
|
+
const adpIdx = args.indexOf('--adapter');
|
|
6521
|
+
const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
|
|
6522
|
+
const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini'];
|
|
6523
|
+
if (!adapterName || adapterName.startsWith('--')) {
|
|
6524
|
+
console.error('[sigmap] --adapter requires an adapter name');
|
|
6525
|
+
console.error(` Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
|
|
6526
|
+
console.error(' Example: node gen-context.js --adapter copilot');
|
|
6527
|
+
process.exit(1);
|
|
6528
|
+
}
|
|
6529
|
+
if (!VALID_ADAPTERS.includes(adapterName)) {
|
|
6530
|
+
console.error(`[sigmap] unknown adapter: "${adapterName}"`);
|
|
6531
|
+
console.error(` Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
|
|
6532
|
+
process.exit(1);
|
|
6533
|
+
}
|
|
6534
|
+
if (args.includes('--json')) {
|
|
6535
|
+
const adapterMod = __require('./packages/adapters/' + adapterName);
|
|
6536
|
+
const outPath = adapterMod.outputPath(cwd);
|
|
6537
|
+
process.stdout.write(JSON.stringify({ adapter: adapterName, outputPath: outPath, version: VERSION }) + '\n');
|
|
6538
|
+
process.exit(0);
|
|
6539
|
+
}
|
|
6540
|
+
// Run generate with this adapter's output targets
|
|
6541
|
+
const singleAdapterConfig = Object.assign({}, config, {
|
|
6542
|
+
outputs: adapterName === 'claude' ? ['claude'] : [adapterName],
|
|
6543
|
+
adapters: [adapterName],
|
|
6544
|
+
});
|
|
6545
|
+
runGenerate(cwd, singleAdapterConfig, false);
|
|
6546
|
+
const adapterMod = __require('./packages/adapters/' + adapterName);
|
|
6547
|
+
console.warn(`[sigmap] adapter "${adapterName}" → ${path.relative(cwd, adapterMod.outputPath(cwd))}`);
|
|
6548
|
+
} catch (err) {
|
|
6549
|
+
console.error(`[sigmap] adapter error: ${err.message}`);
|
|
6550
|
+
process.exit(1);
|
|
6551
|
+
}
|
|
6552
|
+
process.exit(0);
|
|
6553
|
+
}
|
|
6554
|
+
|
|
6306
6555
|
// ── --impact <file> ────────────────────────────────────────────────────────
|
|
6307
6556
|
if (args.includes('--impact')) {
|
|
6308
6557
|
try {
|
package/package.json
CHANGED
|
@@ -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 };
|
package/packages/core/README.md
CHANGED
|
@@ -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,
|
|
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
|
-
##
|
|
131
|
+
## v3.0 — Multi-Adapter Architecture (released)
|
|
132
132
|
|
|
133
|
-
|
|
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
|
|
package/packages/core/index.js
CHANGED
|
@@ -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
|
};
|
package/src/config/defaults.js
CHANGED
|
@@ -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',
|
package/src/config/loader.js
CHANGED
|
@@ -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