sigmap 7.27.0 → 7.29.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 +24 -0
- package/README.md +2 -2
- package/gen-context.js +455 -2
- package/llms-full.txt +5 -2
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/doctor/diagnose.js +236 -0
- package/src/mcp/install.js +142 -0
- package/src/mcp/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,30 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [7.29.0] — 2026-06-23
|
|
14
|
+
|
|
15
|
+
Minor release — **v8.0 E4:** one-command, per-client MCP install so a cold user reaches a working MCP setup fast (the v8.0 <5-minute-quickstart exit gate).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`sigmap mcp install <client>` (#385):** targeted MCP wiring for a single client — `claude`, `cursor`, `windsurf`, `vscode`, `zed`, `codex`, `gemini`, `opencode`, or portable `mcp` (`.mcp.json`). Unlike `sigmap --setup` (which wires *every* editor at once and only updates configs that already exist), this **creates** the config dir/file when absent, is idempotent (re-running reports already-registered, never duplicates), and emits the correct shape per client — `mcpServers` JSON, Zed `context_servers`, or Codex YAML. `--global` selects the user-level config for clients that have both a project and a global scope (Windsurf, OpenCode). Composed in the zero-dep `src/mcp/install.js` — no system-shell spawns, no install scripts.
|
|
19
|
+
- **`sigmap mcp list` (#385):** lists every supported MCP client and its resolved config path (`--json` for a machine-readable array).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Regenerated `llms-full.txt` so the published surface lists the new `mcp install`/`mcp list` commands.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Hardened two `--ci --min-coverage` integration tests (#385) that were order- and environment-dependent — they hard-coded an "unreachable" 99% threshold, but once the suite generates a full `src/` context the repo's measured coverage reaches 99–100%. They now pin the threshold relative to the actual measured coverage, so the gate-fails-above-achievable property holds deterministically everywhere.
|
|
26
|
+
|
|
27
|
+
## [7.28.0] — 2026-06-23
|
|
28
|
+
|
|
29
|
+
Minor release — **v8.0 E3:** a one-shot setup doctor so a cold user reaches a useful answer fast.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- **`sigmap doctor` (#381):** a one-shot diagnostic that runs seven resilient checks — git repository, config & source roots, generated context file, signature index, index freshness, coverage, and MCP wiring — and prints an **actionable fix** for anything wrong (e.g. "run: npx sigmap", "run: sigmap --setup", "increase maxTokens or expand srcDirs"). `sigmap doctor --json` emits `{ checks, ok, errors, warnings }`; the command exits **1** on a hard failure (no context file / invalid config) and **0** otherwise, so it is usable in CI. Composed from `loadConfig`, `coverageScore`, `buildSigIndex`, and the known adapter-output / MCP-config paths — zero new runtime dependencies, no system-shell spawns.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **Release CI self-heals `develop` (#380):** the develop→main release PR's head branch is `develop`, which the repo's delete-branch-on-merge setting removes on every release; the "Sync develop with main" workflow now fetches `main` and recreates `develop` from it when the ref is missing (else merges `main` in), so `develop` no longer needs manual restoration after a release.
|
|
36
|
+
|
|
13
37
|
## [7.27.0] — 2026-06-22
|
|
14
38
|
|
|
15
39
|
Minor release — **v8.0 D3:** two new MCP tools, taking the server from 15 to 17 tools. Both are composed from data SigMap already computes — zero new runtime dependencies, no system-shell spawns.
|
package/README.md
CHANGED
|
@@ -91,8 +91,8 @@ Ask → Rank → Context → Validate → Judge → Learn
|
|
|
91
91
|
|
|
92
92
|
<!--SM:benchmarkBlock-->
|
|
93
93
|
```
|
|
94
|
-
Benchmark : sigmap-v7.
|
|
95
|
-
Date : 2026-06-
|
|
94
|
+
Benchmark : sigmap-v7.29-main (21 repositories, including R language)
|
|
95
|
+
Date : 2026-06-23
|
|
96
96
|
|
|
97
97
|
Hit@5 : 75.6% (baseline 13.6% — 5.6× lift)
|
|
98
98
|
Token reduction: 97.0% (across 21 repos)
|
package/gen-context.js
CHANGED
|
@@ -3462,6 +3462,246 @@ __factories["./src/discovery/source-root-scorer"] = function(module, exports) {
|
|
|
3462
3462
|
|
|
3463
3463
|
};
|
|
3464
3464
|
|
|
3465
|
+
// ── ./src/doctor/diagnose ──
|
|
3466
|
+
__factories["./src/doctor/diagnose"] = function(module, exports) {
|
|
3467
|
+
|
|
3468
|
+
/**
|
|
3469
|
+
* sigmap doctor (v8.0 E3).
|
|
3470
|
+
*
|
|
3471
|
+
* One-shot diagnostic for a SigMap setup: each check reports ok/warn/fail with
|
|
3472
|
+
* an actionable fix, so a cold user can reach a useful answer in minutes. Pure,
|
|
3473
|
+
* resilient (no check ever throws), zero new runtime deps. Composes the config
|
|
3474
|
+
* loader, coverage scorer, signature index, and the known adapter-output /
|
|
3475
|
+
* MCP-config paths.
|
|
3476
|
+
*/
|
|
3477
|
+
|
|
3478
|
+
const fs = require('fs');
|
|
3479
|
+
const path = require('path');
|
|
3480
|
+
const os = require('os');
|
|
3481
|
+
|
|
3482
|
+
// Generated context files a `read_context`/`ask` flow can consume.
|
|
3483
|
+
const ADAPTER_OUTPUTS = [
|
|
3484
|
+
['.github', 'copilot-instructions.md'],
|
|
3485
|
+
['CLAUDE.md'],
|
|
3486
|
+
['AGENTS.md'],
|
|
3487
|
+
['.cursorrules'],
|
|
3488
|
+
['.windsurfrules'],
|
|
3489
|
+
['.github', 'openai-context.md'],
|
|
3490
|
+
['.github', 'gemini-context.md'],
|
|
3491
|
+
['llm-full.txt'],
|
|
3492
|
+
['llm.txt'],
|
|
3493
|
+
];
|
|
3494
|
+
|
|
3495
|
+
const EXCLUDE_DIRS = new Set([
|
|
3496
|
+
'node_modules', '.git', 'dist', 'build', 'out', '__pycache__',
|
|
3497
|
+
'.next', 'coverage', 'target', 'vendor', '.context',
|
|
3498
|
+
]);
|
|
3499
|
+
|
|
3500
|
+
const ICON = { ok: '✓', warn: '⚠', fail: '✗' };
|
|
3501
|
+
|
|
3502
|
+
function _short(p, cwd) {
|
|
3503
|
+
const rel = path.relative(cwd, p);
|
|
3504
|
+
return rel && !rel.startsWith('..') ? rel : p.replace(os.homedir(), '~');
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
function _contextFiles(cwd) {
|
|
3508
|
+
const out = [];
|
|
3509
|
+
for (const parts of ADAPTER_OUTPUTS) {
|
|
3510
|
+
const p = path.join(cwd, ...parts);
|
|
3511
|
+
try { if (fs.existsSync(p)) out.push(p); } catch (_) {}
|
|
3512
|
+
}
|
|
3513
|
+
return out;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
function _mcpTargets(cwd) {
|
|
3517
|
+
return [
|
|
3518
|
+
path.join(cwd, '.mcp.json'),
|
|
3519
|
+
path.join(cwd, '.claude', 'settings.json'),
|
|
3520
|
+
path.join(cwd, '.cursor', 'mcp.json'),
|
|
3521
|
+
path.join(cwd, '.windsurf', 'mcp.json'),
|
|
3522
|
+
path.join(cwd, '.vscode', 'mcp.json'),
|
|
3523
|
+
path.join(cwd, 'opencode.json'),
|
|
3524
|
+
path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
|
|
3525
|
+
path.join(os.homedir(), '.config', 'opencode', 'config.json'),
|
|
3526
|
+
path.join(os.homedir(), '.gemini', 'settings.json'),
|
|
3527
|
+
path.join(os.homedir(), '.codex', 'config.yaml'),
|
|
3528
|
+
path.join(os.homedir(), '.config', 'zed', 'settings.json'),
|
|
3529
|
+
];
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
/** Count code files under srcDirs modified after the context was generated. */
|
|
3533
|
+
function _countChangedSince(cwd, srcDirs, config, ctxMtime) {
|
|
3534
|
+
const { CODE_EXTS } = __require('./src/analysis/coverage-score');
|
|
3535
|
+
const exclude = new Set(EXCLUDE_DIRS);
|
|
3536
|
+
if (config && Array.isArray(config.exclude)) for (const x of config.exclude) exclude.add(String(x));
|
|
3537
|
+
|
|
3538
|
+
let changed = 0;
|
|
3539
|
+
let seen = 0;
|
|
3540
|
+
const walk = (dir, depth) => {
|
|
3541
|
+
if (depth > 8 || seen > 5000) return;
|
|
3542
|
+
let entries;
|
|
3543
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
3544
|
+
for (const e of entries) {
|
|
3545
|
+
if (exclude.has(e.name)) continue;
|
|
3546
|
+
const full = path.join(dir, e.name);
|
|
3547
|
+
if (e.isDirectory()) walk(full, depth + 1);
|
|
3548
|
+
else if (e.isFile() && CODE_EXTS.has(path.extname(e.name).toLowerCase())) {
|
|
3549
|
+
seen++;
|
|
3550
|
+
try { if (fs.statSync(full).mtimeMs > ctxMtime) changed++; } catch (_) {}
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
for (const d of srcDirs) {
|
|
3555
|
+
const abs = path.isAbsolute(d) ? d : path.join(cwd, d);
|
|
3556
|
+
if (fs.existsSync(abs)) walk(abs, 0);
|
|
3557
|
+
}
|
|
3558
|
+
return changed;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
/**
|
|
3562
|
+
* Run all diagnostic checks.
|
|
3563
|
+
* @param {string} cwd
|
|
3564
|
+
* @returns {{ checks: Array<{id,label,status,detail,fix}>, ok: boolean, errors: number, warnings: number }}
|
|
3565
|
+
*/
|
|
3566
|
+
function diagnose(cwd, opts = {}) {
|
|
3567
|
+
const checks = [];
|
|
3568
|
+
const add = (id, label, status, detail, fix) => checks.push({ id, label, status, detail: detail || '', fix: fix || null });
|
|
3569
|
+
|
|
3570
|
+
// 1. Git repository
|
|
3571
|
+
try {
|
|
3572
|
+
const { tryGit } = __require('./src/util/git');
|
|
3573
|
+
const inside = tryGit(['rev-parse', '--is-inside-work-tree'], { cwd });
|
|
3574
|
+
if (inside === 'true') add('git', 'Git repository', 'ok', 'recency boost + impact analysis enabled');
|
|
3575
|
+
else add('git', 'Git repository', 'warn', 'not a git repository', 'git init — enables recency boost and impact analysis');
|
|
3576
|
+
} catch (_) {
|
|
3577
|
+
add('git', 'Git repository', 'warn', 'git not available', 'install git for recency boost + impact analysis');
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
// 2. Config & source roots
|
|
3581
|
+
let config = {};
|
|
3582
|
+
try {
|
|
3583
|
+
const cfgPath = path.join(cwd, 'gen-context.config.json');
|
|
3584
|
+
let configBroken = false;
|
|
3585
|
+
if (fs.existsSync(cfgPath)) {
|
|
3586
|
+
try { JSON.parse(fs.readFileSync(cfgPath, 'utf8')); }
|
|
3587
|
+
catch (e) {
|
|
3588
|
+
configBroken = true;
|
|
3589
|
+
add('config', 'Config & source roots', 'fail', `gen-context.config.json is invalid JSON: ${e.message}`, 'fix the JSON syntax, or delete the file to fall back to defaults');
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
const { loadConfig } = __require('./src/config/loader');
|
|
3593
|
+
config = loadConfig(cwd) || {};
|
|
3594
|
+
if (!configBroken) {
|
|
3595
|
+
// Distinguish explicitly-configured srcDirs (validate each) from the
|
|
3596
|
+
// default candidate list (just report which ones actually exist).
|
|
3597
|
+
let explicitSrcDirs = null;
|
|
3598
|
+
if (fs.existsSync(cfgPath)) {
|
|
3599
|
+
try { const raw = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); if (Array.isArray(raw.srcDirs)) explicitSrcDirs = raw.srcDirs; } catch (_) {}
|
|
3600
|
+
}
|
|
3601
|
+
const exists = (d) => { try { return fs.existsSync(path.isAbsolute(d) ? d : path.join(cwd, d)); } catch (_) { return false; } };
|
|
3602
|
+
const srcDirs = Array.isArray(config.srcDirs) ? config.srcDirs : [];
|
|
3603
|
+
const present = srcDirs.filter(exists);
|
|
3604
|
+
|
|
3605
|
+
if (explicitSrcDirs) {
|
|
3606
|
+
const missing = explicitSrcDirs.filter((d) => !exists(d));
|
|
3607
|
+
if (missing.length) add('config', 'Config & source roots', 'warn', `configured srcDirs not found: ${missing.join(', ')}`, 'fix "srcDirs" in gen-context.config.json');
|
|
3608
|
+
else add('config', 'Config & source roots', 'ok', `srcDirs: ${explicitSrcDirs.join(', ')}`);
|
|
3609
|
+
} else if (present.length === 0) {
|
|
3610
|
+
add('config', 'Config & source roots', 'warn', 'no source directories found (looked for src/, app/, lib/, …)', 'create a src/ dir, set "srcDirs" in gen-context.config.json, or run: sigmap roots --fix');
|
|
3611
|
+
} else {
|
|
3612
|
+
add('config', 'Config & source roots', 'ok', `source roots: ${present.slice(0, 8).join(', ')}${present.length > 8 ? `, +${present.length - 8} more` : ''}`);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
} catch (e) {
|
|
3616
|
+
if (!checks.some((c) => c.id === 'config')) add('config', 'Config & source roots', 'warn', `could not load config: ${e.message}`);
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
// 3. Generated context file
|
|
3620
|
+
const ctxFiles = _contextFiles(cwd);
|
|
3621
|
+
if (ctxFiles.length === 0) {
|
|
3622
|
+
add('context', 'Generated context', 'fail', 'no context file found', 'run: npx sigmap (generates the signature map)');
|
|
3623
|
+
} else {
|
|
3624
|
+
add('context', 'Generated context', 'ok', `${ctxFiles.length} file(s): ${ctxFiles.map((f) => _short(f, cwd)).join(', ')}`);
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
// 4. Signature index
|
|
3628
|
+
let indexSize = 0;
|
|
3629
|
+
try {
|
|
3630
|
+
const { buildSigIndex } = __require('./src/retrieval/ranker');
|
|
3631
|
+
indexSize = buildSigIndex(cwd).size;
|
|
3632
|
+
} catch (_) {}
|
|
3633
|
+
if (indexSize === 0) {
|
|
3634
|
+
add('index', 'Signature index', ctxFiles.length === 0 ? 'fail' : 'warn', 'no signatures indexed', 'run: npx sigmap then: sigmap ask "<query>"');
|
|
3635
|
+
} else {
|
|
3636
|
+
add('index', 'Signature index', 'ok', `${indexSize} file(s) indexed`);
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
// 5. Index freshness
|
|
3640
|
+
try {
|
|
3641
|
+
if (ctxFiles.length) {
|
|
3642
|
+
const ctxMtime = Math.max(...ctxFiles.map((f) => { try { return fs.statSync(f).mtimeMs; } catch (_) { return 0; } }));
|
|
3643
|
+
const srcDirs = (config && Array.isArray(config.srcDirs) && config.srcDirs.length) ? config.srcDirs : ['src', 'app', 'lib'];
|
|
3644
|
+
const changed = _countChangedSince(cwd, srcDirs, config, ctxMtime);
|
|
3645
|
+
if (changed > 0) add('freshness', 'Index freshness', 'warn', `${changed} source file(s) changed since last generate`, 'run: sigmap (or: sigmap --watch to auto-refresh)');
|
|
3646
|
+
else add('freshness', 'Index freshness', 'ok', 'index is up to date with sources');
|
|
3647
|
+
}
|
|
3648
|
+
} catch (_) {}
|
|
3649
|
+
|
|
3650
|
+
// 6. Coverage
|
|
3651
|
+
try {
|
|
3652
|
+
if (indexSize > 0) {
|
|
3653
|
+
const { coverageScore } = __require('./src/analysis/coverage-score');
|
|
3654
|
+
const { buildSigIndex } = __require('./src/retrieval/ranker');
|
|
3655
|
+
const entries = [...buildSigIndex(cwd).keys()].map((rel) => ({ filePath: path.resolve(cwd, rel) }));
|
|
3656
|
+
const cov = coverageScore(cwd, entries, config);
|
|
3657
|
+
if (cov.score < 70) add('coverage', 'Coverage', 'warn', `${cov.score}% of source files in context (grade ${cov.grade})`, 'increase maxTokens or expand srcDirs in gen-context.config.json');
|
|
3658
|
+
else add('coverage', 'Coverage', 'ok', `${cov.score}% of source files in context (grade ${cov.grade})`);
|
|
3659
|
+
}
|
|
3660
|
+
} catch (_) {}
|
|
3661
|
+
|
|
3662
|
+
// 7. MCP wiring
|
|
3663
|
+
try {
|
|
3664
|
+
let wired = null;
|
|
3665
|
+
for (const t of _mcpTargets(cwd)) {
|
|
3666
|
+
try {
|
|
3667
|
+
if (!fs.existsSync(t)) continue;
|
|
3668
|
+
if (/sigmap/.test(fs.readFileSync(t, 'utf8'))) { wired = t; break; }
|
|
3669
|
+
} catch (_) {}
|
|
3670
|
+
}
|
|
3671
|
+
if (wired) add('mcp', 'MCP wiring', 'ok', `registered in ${_short(wired, cwd)}`);
|
|
3672
|
+
else add('mcp', 'MCP wiring', 'warn', 'MCP server not registered in any editor config', 'run: sigmap --setup (auto-wires Claude, Cursor, Windsurf, VS Code, …)');
|
|
3673
|
+
} catch (_) {}
|
|
3674
|
+
|
|
3675
|
+
const errors = checks.filter((c) => c.status === 'fail').length;
|
|
3676
|
+
const warnings = checks.filter((c) => c.status === 'warn').length;
|
|
3677
|
+
return { checks, ok: errors === 0, errors, warnings };
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
/** Human-readable checklist. */
|
|
3681
|
+
function formatDoctor(result) {
|
|
3682
|
+
const lines = ['sigmap doctor', ''];
|
|
3683
|
+
for (const c of result.checks) {
|
|
3684
|
+
lines.push(`${ICON[c.status] || '?'} ${c.label}${c.detail ? ' — ' + c.detail : ''}`);
|
|
3685
|
+
if (c.status !== 'ok' && c.fix) lines.push(` ↳ ${c.fix}`);
|
|
3686
|
+
}
|
|
3687
|
+
lines.push('');
|
|
3688
|
+
lines.push(
|
|
3689
|
+
result.errors === 0 && result.warnings === 0
|
|
3690
|
+
? '✓ All checks passed.'
|
|
3691
|
+
: `${result.errors} error(s), ${result.warnings} warning(s).`
|
|
3692
|
+
);
|
|
3693
|
+
return lines.join('\n');
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
/** Machine-readable result. */
|
|
3697
|
+
function formatDoctorJSON(result) {
|
|
3698
|
+
return JSON.stringify(result, null, 2);
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
module.exports = { diagnose, formatDoctor, formatDoctorJSON };
|
|
3702
|
+
|
|
3703
|
+
};
|
|
3704
|
+
|
|
3465
3705
|
// ── ./src/eval/analyzer ──
|
|
3466
3706
|
__factories["./src/eval/analyzer"] = function(module, exports) {
|
|
3467
3707
|
|
|
@@ -12288,6 +12528,152 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
|
|
|
12288
12528
|
|
|
12289
12529
|
};
|
|
12290
12530
|
|
|
12531
|
+
// ── ./src/mcp/install ──
|
|
12532
|
+
__factories["./src/mcp/install"] = function(module, exports) {
|
|
12533
|
+
|
|
12534
|
+
/**
|
|
12535
|
+
* Per-client MCP install (v8.0 E4).
|
|
12536
|
+
*
|
|
12537
|
+
* `sigmap --setup` wires *every* known editor at once and only touches config
|
|
12538
|
+
* files that already exist (to avoid creating clutter for editors the user does
|
|
12539
|
+
* not use). This module is the targeted counterpart: `sigmap mcp install <client>`
|
|
12540
|
+
* picks one client, and — because the user explicitly asked for it — CREATES the
|
|
12541
|
+
* config dir/file if it is missing. Idempotent: re-running never duplicates the
|
|
12542
|
+
* entry. Zero dependencies; only `fs`/`path`/`os`.
|
|
12543
|
+
*/
|
|
12544
|
+
|
|
12545
|
+
const fs = require('fs');
|
|
12546
|
+
const path = require('path');
|
|
12547
|
+
const os = require('os');
|
|
12548
|
+
|
|
12549
|
+
// Config shapes the supported clients use.
|
|
12550
|
+
// - 'json' → { mcpServers: { sigmap: { command, args } } }
|
|
12551
|
+
// - 'zed' → { context_servers: { sigmap: { command: { path, args } } } }
|
|
12552
|
+
// - 'yaml' → Codex CLI ~/.codex/config.yaml (mcpServers block, appended)
|
|
12553
|
+
const CLIENTS = {
|
|
12554
|
+
claude: { label: 'Claude Code', format: 'json', scope: 'project', project: ['.claude', 'settings.json'] },
|
|
12555
|
+
cursor: { label: 'Cursor', format: 'json', scope: 'project', project: ['.cursor', 'mcp.json'] },
|
|
12556
|
+
windsurf: { label: 'Windsurf', format: 'json', scope: 'both',
|
|
12557
|
+
project: ['.windsurf', 'mcp.json'],
|
|
12558
|
+
global: ['.codeium', 'windsurf', 'mcp_config.json'] },
|
|
12559
|
+
vscode: { label: 'VS Code', format: 'json', scope: 'project', project: ['.vscode', 'mcp.json'] },
|
|
12560
|
+
opencode: { label: 'OpenCode', format: 'json', scope: 'both',
|
|
12561
|
+
project: ['opencode.json'],
|
|
12562
|
+
global: ['.config', 'opencode', 'config.json'] },
|
|
12563
|
+
gemini: { label: 'Gemini CLI', format: 'json', scope: 'global', global: ['.gemini', 'settings.json'] },
|
|
12564
|
+
zed: { label: 'Zed', format: 'zed', scope: 'global', global: ['.config', 'zed', 'settings.json'] },
|
|
12565
|
+
codex: { label: 'Codex CLI', format: 'yaml', scope: 'global', global: ['.codex', 'config.yaml'] },
|
|
12566
|
+
mcp: { label: 'Portable (.mcp.json)', format: 'json', scope: 'project', project: ['.mcp.json'] },
|
|
12567
|
+
};
|
|
12568
|
+
|
|
12569
|
+
/** Resolve the absolute config path for a client, honoring `global`. */
|
|
12570
|
+
function resolveTarget(spec, cwd, home, useGlobal) {
|
|
12571
|
+
const wantGlobal = useGlobal || spec.scope === 'global';
|
|
12572
|
+
if (wantGlobal && spec.global) return path.join(home, ...spec.global);
|
|
12573
|
+
if (spec.project) return path.join(cwd, ...spec.project);
|
|
12574
|
+
if (spec.global) return path.join(home, ...spec.global);
|
|
12575
|
+
return null;
|
|
12576
|
+
}
|
|
12577
|
+
|
|
12578
|
+
/** List supported clients with their resolved target paths. */
|
|
12579
|
+
function listClients(opts = {}) {
|
|
12580
|
+
const cwd = opts.cwd || process.cwd();
|
|
12581
|
+
const home = opts.home || os.homedir();
|
|
12582
|
+
return Object.keys(CLIENTS).map((key) => {
|
|
12583
|
+
const spec = CLIENTS[key];
|
|
12584
|
+
return {
|
|
12585
|
+
client: key,
|
|
12586
|
+
label: spec.label,
|
|
12587
|
+
scope: spec.scope,
|
|
12588
|
+
format: spec.format,
|
|
12589
|
+
target: resolveTarget(spec, cwd, home, false),
|
|
12590
|
+
globalTarget: spec.scope === 'both' ? resolveTarget(spec, cwd, home, true) : null,
|
|
12591
|
+
};
|
|
12592
|
+
});
|
|
12593
|
+
}
|
|
12594
|
+
|
|
12595
|
+
function serverArgs(scriptPath) {
|
|
12596
|
+
return [path.resolve(scriptPath), '--mcp'];
|
|
12597
|
+
}
|
|
12598
|
+
|
|
12599
|
+
/** Install into a JSON `mcpServers` config (create file/dir if absent). */
|
|
12600
|
+
function _installJson(filePath, scriptPath) {
|
|
12601
|
+
let settings = {};
|
|
12602
|
+
if (fs.existsSync(filePath)) {
|
|
12603
|
+
try { settings = JSON.parse(fs.readFileSync(filePath, 'utf8')) || {}; }
|
|
12604
|
+
catch (_) { settings = {}; }
|
|
12605
|
+
}
|
|
12606
|
+
if (!settings.mcpServers) settings.mcpServers = {};
|
|
12607
|
+
if (settings.mcpServers.sigmap) return 'already';
|
|
12608
|
+
settings.mcpServers.sigmap = { command: 'node', args: serverArgs(scriptPath) };
|
|
12609
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
12610
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
|
|
12611
|
+
return 'installed';
|
|
12612
|
+
}
|
|
12613
|
+
|
|
12614
|
+
/** Install into Zed's `context_servers` config (create file/dir if absent). */
|
|
12615
|
+
function _installZed(filePath, scriptPath) {
|
|
12616
|
+
let settings = {};
|
|
12617
|
+
if (fs.existsSync(filePath)) {
|
|
12618
|
+
try { settings = JSON.parse(fs.readFileSync(filePath, 'utf8')) || {}; }
|
|
12619
|
+
catch (_) { settings = {}; }
|
|
12620
|
+
}
|
|
12621
|
+
if (!settings.context_servers) settings.context_servers = {};
|
|
12622
|
+
if (settings.context_servers.sigmap) return 'already';
|
|
12623
|
+
settings.context_servers.sigmap = { command: { path: 'node', args: serverArgs(scriptPath) } };
|
|
12624
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
12625
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
|
|
12626
|
+
return 'installed';
|
|
12627
|
+
}
|
|
12628
|
+
|
|
12629
|
+
/** Install into Codex CLI YAML (append block; create file if absent). */
|
|
12630
|
+
function _installYaml(filePath, scriptPath) {
|
|
12631
|
+
let raw = '';
|
|
12632
|
+
if (fs.existsSync(filePath)) {
|
|
12633
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
12634
|
+
if (raw.includes('sigmap')) return 'already';
|
|
12635
|
+
}
|
|
12636
|
+
const block = [
|
|
12637
|
+
'mcpServers:',
|
|
12638
|
+
' sigmap:',
|
|
12639
|
+
' command: node',
|
|
12640
|
+
' args:',
|
|
12641
|
+
` - ${path.resolve(scriptPath)}`,
|
|
12642
|
+
' - --mcp',
|
|
12643
|
+
].join('\n');
|
|
12644
|
+
const next = raw ? raw.trimEnd() + '\n\n' + block + '\n' : block + '\n';
|
|
12645
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
12646
|
+
fs.writeFileSync(filePath, next);
|
|
12647
|
+
return 'installed';
|
|
12648
|
+
}
|
|
12649
|
+
|
|
12650
|
+
/**
|
|
12651
|
+
* Install the sigmap MCP server for a single client.
|
|
12652
|
+
* @returns { client, label, path, status } where status is
|
|
12653
|
+
* 'installed' | 'already' | 'unknown'.
|
|
12654
|
+
*/
|
|
12655
|
+
function installClient(client, opts = {}) {
|
|
12656
|
+
const spec = CLIENTS[client];
|
|
12657
|
+
if (!spec) {
|
|
12658
|
+
return { client, status: 'unknown', valid: Object.keys(CLIENTS) };
|
|
12659
|
+
}
|
|
12660
|
+
const cwd = opts.cwd || process.cwd();
|
|
12661
|
+
const home = opts.home || os.homedir();
|
|
12662
|
+
const scriptPath = opts.scriptPath || path.join(cwd, 'gen-context.js');
|
|
12663
|
+
const filePath = resolveTarget(spec, cwd, home, opts.global);
|
|
12664
|
+
|
|
12665
|
+
let status;
|
|
12666
|
+
if (spec.format === 'zed') status = _installZed(filePath, scriptPath);
|
|
12667
|
+
else if (spec.format === 'yaml') status = _installYaml(filePath, scriptPath);
|
|
12668
|
+
else status = _installJson(filePath, scriptPath);
|
|
12669
|
+
|
|
12670
|
+
return { client, label: spec.label, path: filePath, status };
|
|
12671
|
+
}
|
|
12672
|
+
|
|
12673
|
+
module.exports = { CLIENTS, listClients, installClient, resolveTarget };
|
|
12674
|
+
|
|
12675
|
+
};
|
|
12676
|
+
|
|
12291
12677
|
// ── ./src/mcp/server ──
|
|
12292
12678
|
__factories["./src/mcp/server"] = function(module, exports) {
|
|
12293
12679
|
|
|
@@ -12309,7 +12695,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
12309
12695
|
|
|
12310
12696
|
const SERVER_INFO = {
|
|
12311
12697
|
name: 'sigmap',
|
|
12312
|
-
version: '7.
|
|
12698
|
+
version: '7.29.0',
|
|
12313
12699
|
description: 'SigMap MCP server — code signatures on demand',
|
|
12314
12700
|
};
|
|
12315
12701
|
|
|
@@ -16138,7 +16524,7 @@ function __tryGit(args, opts = {}) {
|
|
|
16138
16524
|
catch (_) { return ''; }
|
|
16139
16525
|
}
|
|
16140
16526
|
|
|
16141
|
-
const VERSION = '7.
|
|
16527
|
+
const VERSION = '7.29.0';
|
|
16142
16528
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
16143
16529
|
|
|
16144
16530
|
function requireSourceOrBundled(key) {
|
|
@@ -17951,6 +18337,9 @@ Usage:
|
|
|
17951
18337
|
${cmd} note "<text>" Append a note to the cross-session decision log
|
|
17952
18338
|
${cmd} note List recent notes (also: note --list <N>)
|
|
17953
18339
|
${cmd} status Show repo state — branch, dirty files, index freshness, notes
|
|
18340
|
+
${cmd} doctor Diagnose config, index, freshness, coverage, MCP wiring — with fixes (--json; exits 1 on hard failure)
|
|
18341
|
+
${cmd} mcp list List MCP clients and their config paths (--json)
|
|
18342
|
+
${cmd} mcp install <client> Wire MCP for one client (claude|cursor|windsurf|vscode|zed|codex|gemini|opencode|mcp); --global for user-level
|
|
17954
18343
|
${cmd} --init Write example config + .contextignore scaffold
|
|
17955
18344
|
${cmd} --help Show this message
|
|
17956
18345
|
${cmd} --version Show version
|
|
@@ -19375,6 +19764,70 @@ function main() {
|
|
|
19375
19764
|
process.exit(0);
|
|
19376
19765
|
}
|
|
19377
19766
|
|
|
19767
|
+
// `sigmap mcp install <client>` / `sigmap mcp list` — one-command per-client
|
|
19768
|
+
// MCP wiring (v8.0 E4). Unlike `--setup` (wires every editor, only touches
|
|
19769
|
+
// existing configs), this targets one client and creates the config if absent.
|
|
19770
|
+
if (args[0] === 'mcp') {
|
|
19771
|
+
const { listClients, installClient } = requireSourceOrBundled('./src/mcp/install');
|
|
19772
|
+
const sub = args[1];
|
|
19773
|
+
|
|
19774
|
+
if (sub === 'list') {
|
|
19775
|
+
const clients = listClients({ cwd });
|
|
19776
|
+
if (args.includes('--json')) {
|
|
19777
|
+
process.stdout.write(JSON.stringify(clients, null, 2) + '\n');
|
|
19778
|
+
process.exit(0);
|
|
19779
|
+
}
|
|
19780
|
+
console.log('Supported MCP clients:\n');
|
|
19781
|
+
for (const c of clients) {
|
|
19782
|
+
const scope = c.scope === 'both' ? 'project (or --global)' : c.scope;
|
|
19783
|
+
console.log(` ${c.client.padEnd(10)} ${c.label}`);
|
|
19784
|
+
console.log(` ${_displayPath(c.target, cwd)} [${scope}]`);
|
|
19785
|
+
}
|
|
19786
|
+
console.log('\nInstall with: sigmap mcp install <client>');
|
|
19787
|
+
process.exit(0);
|
|
19788
|
+
}
|
|
19789
|
+
|
|
19790
|
+
if (sub === 'install') {
|
|
19791
|
+
const client = args[2];
|
|
19792
|
+
if (!client || client.startsWith('--')) {
|
|
19793
|
+
console.error('[sigmap] usage: sigmap mcp install <client> (see: sigmap mcp list)');
|
|
19794
|
+
process.exit(1);
|
|
19795
|
+
}
|
|
19796
|
+
const result = installClient(client, {
|
|
19797
|
+
cwd,
|
|
19798
|
+
scriptPath,
|
|
19799
|
+
global: args.includes('--global'),
|
|
19800
|
+
});
|
|
19801
|
+
if (result.status === 'unknown') {
|
|
19802
|
+
console.error(`[sigmap] unknown client "${client}". Valid: ${result.valid.join(', ')}`);
|
|
19803
|
+
process.exit(1);
|
|
19804
|
+
}
|
|
19805
|
+
if (result.status === 'already') {
|
|
19806
|
+
console.log(`[sigmap] ${result.label}: sigmap already registered in ${_displayPath(result.path, cwd)}`);
|
|
19807
|
+
} else {
|
|
19808
|
+
console.log(`[sigmap] ${result.label}: registered MCP server in ${_displayPath(result.path, cwd)}`);
|
|
19809
|
+
}
|
|
19810
|
+
process.exit(0);
|
|
19811
|
+
}
|
|
19812
|
+
|
|
19813
|
+
console.error('[sigmap] usage: sigmap mcp install <client> | sigmap mcp list');
|
|
19814
|
+
process.exit(1);
|
|
19815
|
+
}
|
|
19816
|
+
|
|
19817
|
+
// `sigmap doctor` — diagnose config, index, freshness, coverage, and MCP
|
|
19818
|
+
// wiring; print an actionable fix for anything wrong. Exit 1 on a hard
|
|
19819
|
+
// failure (no context file / invalid config) so it is usable in CI.
|
|
19820
|
+
if (args[0] === 'doctor') {
|
|
19821
|
+
const { diagnose, formatDoctor, formatDoctorJSON } = requireSourceOrBundled('./src/doctor/diagnose');
|
|
19822
|
+
const result = diagnose(cwd);
|
|
19823
|
+
if (args.includes('--json')) {
|
|
19824
|
+
process.stdout.write(formatDoctorJSON(result) + '\n');
|
|
19825
|
+
} else {
|
|
19826
|
+
console.log(formatDoctor(result));
|
|
19827
|
+
}
|
|
19828
|
+
process.exit(result.ok ? 0 : 1);
|
|
19829
|
+
}
|
|
19830
|
+
|
|
19378
19831
|
// Layer 3: `sigmap conventions` — extract & report repo coding conventions
|
|
19379
19832
|
// (file naming, export style, test framework) for TS/JS/Python so generated
|
|
19380
19833
|
// code matches the house style. Writes .context/conventions.json.
|
package/llms-full.txt
CHANGED
|
@@ -9,13 +9,13 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.29.0 | Benchmark: sigmap-v7.29-main (2026-06-23)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
## Core metrics (benchmark: sigmap-v7.
|
|
18
|
+
## Core metrics (benchmark: sigmap-v7.29-main, 2026-06-23)
|
|
19
19
|
|
|
20
20
|
| Metric | Without SigMap | With SigMap |
|
|
21
21
|
|--------|----------------|-------------|
|
|
@@ -115,6 +115,9 @@ sigmap evidence "<query>" --top <n> --budget <n> --out <path> Tune ranked file
|
|
|
115
115
|
sigmap note "<text>" Append a note to the cross-session decision log
|
|
116
116
|
sigmap note List recent notes (also: note --list <N>)
|
|
117
117
|
sigmap status Show repo state — branch, dirty files, index freshness, notes
|
|
118
|
+
sigmap doctor Diagnose config, index, freshness, coverage, MCP wiring — with fixes (--json; exits 1 on hard failure)
|
|
119
|
+
sigmap mcp list List MCP clients and their config paths (--json)
|
|
120
|
+
sigmap mcp install <client> Wire MCP for one client (claude|cursor|windsurf|vscode|zed|codex|gemini|opencode|mcp); --global for user-level
|
|
118
121
|
sigmap --init Write example config + .contextignore scaffold
|
|
119
122
|
sigmap --help Show this message
|
|
120
123
|
sigmap --version Show version
|
package/llms.txt
CHANGED
|
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.29.0 | Benchmark: sigmap-v7.29-main (2026-06-23)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
|
21
21
|
- No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
|
|
22
22
|
- Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
|
|
23
23
|
|
|
24
|
-
## Core metrics (benchmark: sigmap-v7.
|
|
24
|
+
## Core metrics (benchmark: sigmap-v7.29-main, 2026-06-23)
|
|
25
25
|
|
|
26
26
|
- hit@5 retrieval: 75.6% vs 13.6% random baseline (5.6× lift)
|
|
27
27
|
- Token reduction: 97.0% average across benchmark repos
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.29.0",
|
|
4
4
|
"description": "97% token reduction for AI coding. Extracts function & class signatures with TF-IDF ranking to feed only the right files to Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP. Zero dependencies, runs offline via npx.",
|
|
5
5
|
"main": "packages/core/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* sigmap doctor (v8.0 E3).
|
|
5
|
+
*
|
|
6
|
+
* One-shot diagnostic for a SigMap setup: each check reports ok/warn/fail with
|
|
7
|
+
* an actionable fix, so a cold user can reach a useful answer in minutes. Pure,
|
|
8
|
+
* resilient (no check ever throws), zero new runtime deps. Composes the config
|
|
9
|
+
* loader, coverage scorer, signature index, and the known adapter-output /
|
|
10
|
+
* MCP-config paths.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
// Generated context files a `read_context`/`ask` flow can consume.
|
|
18
|
+
const ADAPTER_OUTPUTS = [
|
|
19
|
+
['.github', 'copilot-instructions.md'],
|
|
20
|
+
['CLAUDE.md'],
|
|
21
|
+
['AGENTS.md'],
|
|
22
|
+
['.cursorrules'],
|
|
23
|
+
['.windsurfrules'],
|
|
24
|
+
['.github', 'openai-context.md'],
|
|
25
|
+
['.github', 'gemini-context.md'],
|
|
26
|
+
['llm-full.txt'],
|
|
27
|
+
['llm.txt'],
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const EXCLUDE_DIRS = new Set([
|
|
31
|
+
'node_modules', '.git', 'dist', 'build', 'out', '__pycache__',
|
|
32
|
+
'.next', 'coverage', 'target', 'vendor', '.context',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const ICON = { ok: '✓', warn: '⚠', fail: '✗' };
|
|
36
|
+
|
|
37
|
+
function _short(p, cwd) {
|
|
38
|
+
const rel = path.relative(cwd, p);
|
|
39
|
+
return rel && !rel.startsWith('..') ? rel : p.replace(os.homedir(), '~');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _contextFiles(cwd) {
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const parts of ADAPTER_OUTPUTS) {
|
|
45
|
+
const p = path.join(cwd, ...parts);
|
|
46
|
+
try { if (fs.existsSync(p)) out.push(p); } catch (_) {}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _mcpTargets(cwd) {
|
|
52
|
+
return [
|
|
53
|
+
path.join(cwd, '.mcp.json'),
|
|
54
|
+
path.join(cwd, '.claude', 'settings.json'),
|
|
55
|
+
path.join(cwd, '.cursor', 'mcp.json'),
|
|
56
|
+
path.join(cwd, '.windsurf', 'mcp.json'),
|
|
57
|
+
path.join(cwd, '.vscode', 'mcp.json'),
|
|
58
|
+
path.join(cwd, 'opencode.json'),
|
|
59
|
+
path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
|
|
60
|
+
path.join(os.homedir(), '.config', 'opencode', 'config.json'),
|
|
61
|
+
path.join(os.homedir(), '.gemini', 'settings.json'),
|
|
62
|
+
path.join(os.homedir(), '.codex', 'config.yaml'),
|
|
63
|
+
path.join(os.homedir(), '.config', 'zed', 'settings.json'),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Count code files under srcDirs modified after the context was generated. */
|
|
68
|
+
function _countChangedSince(cwd, srcDirs, config, ctxMtime) {
|
|
69
|
+
const { CODE_EXTS } = require('../analysis/coverage-score');
|
|
70
|
+
const exclude = new Set(EXCLUDE_DIRS);
|
|
71
|
+
if (config && Array.isArray(config.exclude)) for (const x of config.exclude) exclude.add(String(x));
|
|
72
|
+
|
|
73
|
+
let changed = 0;
|
|
74
|
+
let seen = 0;
|
|
75
|
+
const walk = (dir, depth) => {
|
|
76
|
+
if (depth > 8 || seen > 5000) return;
|
|
77
|
+
let entries;
|
|
78
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
79
|
+
for (const e of entries) {
|
|
80
|
+
if (exclude.has(e.name)) continue;
|
|
81
|
+
const full = path.join(dir, e.name);
|
|
82
|
+
if (e.isDirectory()) walk(full, depth + 1);
|
|
83
|
+
else if (e.isFile() && CODE_EXTS.has(path.extname(e.name).toLowerCase())) {
|
|
84
|
+
seen++;
|
|
85
|
+
try { if (fs.statSync(full).mtimeMs > ctxMtime) changed++; } catch (_) {}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
for (const d of srcDirs) {
|
|
90
|
+
const abs = path.isAbsolute(d) ? d : path.join(cwd, d);
|
|
91
|
+
if (fs.existsSync(abs)) walk(abs, 0);
|
|
92
|
+
}
|
|
93
|
+
return changed;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run all diagnostic checks.
|
|
98
|
+
* @param {string} cwd
|
|
99
|
+
* @returns {{ checks: Array<{id,label,status,detail,fix}>, ok: boolean, errors: number, warnings: number }}
|
|
100
|
+
*/
|
|
101
|
+
function diagnose(cwd, opts = {}) {
|
|
102
|
+
const checks = [];
|
|
103
|
+
const add = (id, label, status, detail, fix) => checks.push({ id, label, status, detail: detail || '', fix: fix || null });
|
|
104
|
+
|
|
105
|
+
// 1. Git repository
|
|
106
|
+
try {
|
|
107
|
+
const { tryGit } = require('../util/git');
|
|
108
|
+
const inside = tryGit(['rev-parse', '--is-inside-work-tree'], { cwd });
|
|
109
|
+
if (inside === 'true') add('git', 'Git repository', 'ok', 'recency boost + impact analysis enabled');
|
|
110
|
+
else add('git', 'Git repository', 'warn', 'not a git repository', 'git init — enables recency boost and impact analysis');
|
|
111
|
+
} catch (_) {
|
|
112
|
+
add('git', 'Git repository', 'warn', 'git not available', 'install git for recency boost + impact analysis');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2. Config & source roots
|
|
116
|
+
let config = {};
|
|
117
|
+
try {
|
|
118
|
+
const cfgPath = path.join(cwd, 'gen-context.config.json');
|
|
119
|
+
let configBroken = false;
|
|
120
|
+
if (fs.existsSync(cfgPath)) {
|
|
121
|
+
try { JSON.parse(fs.readFileSync(cfgPath, 'utf8')); }
|
|
122
|
+
catch (e) {
|
|
123
|
+
configBroken = true;
|
|
124
|
+
add('config', 'Config & source roots', 'fail', `gen-context.config.json is invalid JSON: ${e.message}`, 'fix the JSON syntax, or delete the file to fall back to defaults');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const { loadConfig } = require('../config/loader');
|
|
128
|
+
config = loadConfig(cwd) || {};
|
|
129
|
+
if (!configBroken) {
|
|
130
|
+
// Distinguish explicitly-configured srcDirs (validate each) from the
|
|
131
|
+
// default candidate list (just report which ones actually exist).
|
|
132
|
+
let explicitSrcDirs = null;
|
|
133
|
+
if (fs.existsSync(cfgPath)) {
|
|
134
|
+
try { const raw = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); if (Array.isArray(raw.srcDirs)) explicitSrcDirs = raw.srcDirs; } catch (_) {}
|
|
135
|
+
}
|
|
136
|
+
const exists = (d) => { try { return fs.existsSync(path.isAbsolute(d) ? d : path.join(cwd, d)); } catch (_) { return false; } };
|
|
137
|
+
const srcDirs = Array.isArray(config.srcDirs) ? config.srcDirs : [];
|
|
138
|
+
const present = srcDirs.filter(exists);
|
|
139
|
+
|
|
140
|
+
if (explicitSrcDirs) {
|
|
141
|
+
const missing = explicitSrcDirs.filter((d) => !exists(d));
|
|
142
|
+
if (missing.length) add('config', 'Config & source roots', 'warn', `configured srcDirs not found: ${missing.join(', ')}`, 'fix "srcDirs" in gen-context.config.json');
|
|
143
|
+
else add('config', 'Config & source roots', 'ok', `srcDirs: ${explicitSrcDirs.join(', ')}`);
|
|
144
|
+
} else if (present.length === 0) {
|
|
145
|
+
add('config', 'Config & source roots', 'warn', 'no source directories found (looked for src/, app/, lib/, …)', 'create a src/ dir, set "srcDirs" in gen-context.config.json, or run: sigmap roots --fix');
|
|
146
|
+
} else {
|
|
147
|
+
add('config', 'Config & source roots', 'ok', `source roots: ${present.slice(0, 8).join(', ')}${present.length > 8 ? `, +${present.length - 8} more` : ''}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (!checks.some((c) => c.id === 'config')) add('config', 'Config & source roots', 'warn', `could not load config: ${e.message}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 3. Generated context file
|
|
155
|
+
const ctxFiles = _contextFiles(cwd);
|
|
156
|
+
if (ctxFiles.length === 0) {
|
|
157
|
+
add('context', 'Generated context', 'fail', 'no context file found', 'run: npx sigmap (generates the signature map)');
|
|
158
|
+
} else {
|
|
159
|
+
add('context', 'Generated context', 'ok', `${ctxFiles.length} file(s): ${ctxFiles.map((f) => _short(f, cwd)).join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 4. Signature index
|
|
163
|
+
let indexSize = 0;
|
|
164
|
+
try {
|
|
165
|
+
const { buildSigIndex } = require('../retrieval/ranker');
|
|
166
|
+
indexSize = buildSigIndex(cwd).size;
|
|
167
|
+
} catch (_) {}
|
|
168
|
+
if (indexSize === 0) {
|
|
169
|
+
add('index', 'Signature index', ctxFiles.length === 0 ? 'fail' : 'warn', 'no signatures indexed', 'run: npx sigmap then: sigmap ask "<query>"');
|
|
170
|
+
} else {
|
|
171
|
+
add('index', 'Signature index', 'ok', `${indexSize} file(s) indexed`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 5. Index freshness
|
|
175
|
+
try {
|
|
176
|
+
if (ctxFiles.length) {
|
|
177
|
+
const ctxMtime = Math.max(...ctxFiles.map((f) => { try { return fs.statSync(f).mtimeMs; } catch (_) { return 0; } }));
|
|
178
|
+
const srcDirs = (config && Array.isArray(config.srcDirs) && config.srcDirs.length) ? config.srcDirs : ['src', 'app', 'lib'];
|
|
179
|
+
const changed = _countChangedSince(cwd, srcDirs, config, ctxMtime);
|
|
180
|
+
if (changed > 0) add('freshness', 'Index freshness', 'warn', `${changed} source file(s) changed since last generate`, 'run: sigmap (or: sigmap --watch to auto-refresh)');
|
|
181
|
+
else add('freshness', 'Index freshness', 'ok', 'index is up to date with sources');
|
|
182
|
+
}
|
|
183
|
+
} catch (_) {}
|
|
184
|
+
|
|
185
|
+
// 6. Coverage
|
|
186
|
+
try {
|
|
187
|
+
if (indexSize > 0) {
|
|
188
|
+
const { coverageScore } = require('../analysis/coverage-score');
|
|
189
|
+
const { buildSigIndex } = require('../retrieval/ranker');
|
|
190
|
+
const entries = [...buildSigIndex(cwd).keys()].map((rel) => ({ filePath: path.resolve(cwd, rel) }));
|
|
191
|
+
const cov = coverageScore(cwd, entries, config);
|
|
192
|
+
if (cov.score < 70) add('coverage', 'Coverage', 'warn', `${cov.score}% of source files in context (grade ${cov.grade})`, 'increase maxTokens or expand srcDirs in gen-context.config.json');
|
|
193
|
+
else add('coverage', 'Coverage', 'ok', `${cov.score}% of source files in context (grade ${cov.grade})`);
|
|
194
|
+
}
|
|
195
|
+
} catch (_) {}
|
|
196
|
+
|
|
197
|
+
// 7. MCP wiring
|
|
198
|
+
try {
|
|
199
|
+
let wired = null;
|
|
200
|
+
for (const t of _mcpTargets(cwd)) {
|
|
201
|
+
try {
|
|
202
|
+
if (!fs.existsSync(t)) continue;
|
|
203
|
+
if (/sigmap/.test(fs.readFileSync(t, 'utf8'))) { wired = t; break; }
|
|
204
|
+
} catch (_) {}
|
|
205
|
+
}
|
|
206
|
+
if (wired) add('mcp', 'MCP wiring', 'ok', `registered in ${_short(wired, cwd)}`);
|
|
207
|
+
else add('mcp', 'MCP wiring', 'warn', 'MCP server not registered in any editor config', 'run: sigmap --setup (auto-wires Claude, Cursor, Windsurf, VS Code, …)');
|
|
208
|
+
} catch (_) {}
|
|
209
|
+
|
|
210
|
+
const errors = checks.filter((c) => c.status === 'fail').length;
|
|
211
|
+
const warnings = checks.filter((c) => c.status === 'warn').length;
|
|
212
|
+
return { checks, ok: errors === 0, errors, warnings };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Human-readable checklist. */
|
|
216
|
+
function formatDoctor(result) {
|
|
217
|
+
const lines = ['sigmap doctor', ''];
|
|
218
|
+
for (const c of result.checks) {
|
|
219
|
+
lines.push(`${ICON[c.status] || '?'} ${c.label}${c.detail ? ' — ' + c.detail : ''}`);
|
|
220
|
+
if (c.status !== 'ok' && c.fix) lines.push(` ↳ ${c.fix}`);
|
|
221
|
+
}
|
|
222
|
+
lines.push('');
|
|
223
|
+
lines.push(
|
|
224
|
+
result.errors === 0 && result.warnings === 0
|
|
225
|
+
? '✓ All checks passed.'
|
|
226
|
+
: `${result.errors} error(s), ${result.warnings} warning(s).`
|
|
227
|
+
);
|
|
228
|
+
return lines.join('\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Machine-readable result. */
|
|
232
|
+
function formatDoctorJSON(result) {
|
|
233
|
+
return JSON.stringify(result, null, 2);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = { diagnose, formatDoctor, formatDoctorJSON };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-client MCP install (v8.0 E4).
|
|
5
|
+
*
|
|
6
|
+
* `sigmap --setup` wires *every* known editor at once and only touches config
|
|
7
|
+
* files that already exist (to avoid creating clutter for editors the user does
|
|
8
|
+
* not use). This module is the targeted counterpart: `sigmap mcp install <client>`
|
|
9
|
+
* picks one client, and — because the user explicitly asked for it — CREATES the
|
|
10
|
+
* config dir/file if it is missing. Idempotent: re-running never duplicates the
|
|
11
|
+
* entry. Zero dependencies; only `fs`/`path`/`os`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
// Config shapes the supported clients use.
|
|
19
|
+
// - 'json' → { mcpServers: { sigmap: { command, args } } }
|
|
20
|
+
// - 'zed' → { context_servers: { sigmap: { command: { path, args } } } }
|
|
21
|
+
// - 'yaml' → Codex CLI ~/.codex/config.yaml (mcpServers block, appended)
|
|
22
|
+
const CLIENTS = {
|
|
23
|
+
claude: { label: 'Claude Code', format: 'json', scope: 'project', project: ['.claude', 'settings.json'] },
|
|
24
|
+
cursor: { label: 'Cursor', format: 'json', scope: 'project', project: ['.cursor', 'mcp.json'] },
|
|
25
|
+
windsurf: { label: 'Windsurf', format: 'json', scope: 'both',
|
|
26
|
+
project: ['.windsurf', 'mcp.json'],
|
|
27
|
+
global: ['.codeium', 'windsurf', 'mcp_config.json'] },
|
|
28
|
+
vscode: { label: 'VS Code', format: 'json', scope: 'project', project: ['.vscode', 'mcp.json'] },
|
|
29
|
+
opencode: { label: 'OpenCode', format: 'json', scope: 'both',
|
|
30
|
+
project: ['opencode.json'],
|
|
31
|
+
global: ['.config', 'opencode', 'config.json'] },
|
|
32
|
+
gemini: { label: 'Gemini CLI', format: 'json', scope: 'global', global: ['.gemini', 'settings.json'] },
|
|
33
|
+
zed: { label: 'Zed', format: 'zed', scope: 'global', global: ['.config', 'zed', 'settings.json'] },
|
|
34
|
+
codex: { label: 'Codex CLI', format: 'yaml', scope: 'global', global: ['.codex', 'config.yaml'] },
|
|
35
|
+
mcp: { label: 'Portable (.mcp.json)', format: 'json', scope: 'project', project: ['.mcp.json'] },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Resolve the absolute config path for a client, honoring `global`. */
|
|
39
|
+
function resolveTarget(spec, cwd, home, useGlobal) {
|
|
40
|
+
const wantGlobal = useGlobal || spec.scope === 'global';
|
|
41
|
+
if (wantGlobal && spec.global) return path.join(home, ...spec.global);
|
|
42
|
+
if (spec.project) return path.join(cwd, ...spec.project);
|
|
43
|
+
if (spec.global) return path.join(home, ...spec.global);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** List supported clients with their resolved target paths. */
|
|
48
|
+
function listClients(opts = {}) {
|
|
49
|
+
const cwd = opts.cwd || process.cwd();
|
|
50
|
+
const home = opts.home || os.homedir();
|
|
51
|
+
return Object.keys(CLIENTS).map((key) => {
|
|
52
|
+
const spec = CLIENTS[key];
|
|
53
|
+
return {
|
|
54
|
+
client: key,
|
|
55
|
+
label: spec.label,
|
|
56
|
+
scope: spec.scope,
|
|
57
|
+
format: spec.format,
|
|
58
|
+
target: resolveTarget(spec, cwd, home, false),
|
|
59
|
+
globalTarget: spec.scope === 'both' ? resolveTarget(spec, cwd, home, true) : null,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function serverArgs(scriptPath) {
|
|
65
|
+
return [path.resolve(scriptPath), '--mcp'];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Install into a JSON `mcpServers` config (create file/dir if absent). */
|
|
69
|
+
function _installJson(filePath, scriptPath) {
|
|
70
|
+
let settings = {};
|
|
71
|
+
if (fs.existsSync(filePath)) {
|
|
72
|
+
try { settings = JSON.parse(fs.readFileSync(filePath, 'utf8')) || {}; }
|
|
73
|
+
catch (_) { settings = {}; }
|
|
74
|
+
}
|
|
75
|
+
if (!settings.mcpServers) settings.mcpServers = {};
|
|
76
|
+
if (settings.mcpServers.sigmap) return 'already';
|
|
77
|
+
settings.mcpServers.sigmap = { command: 'node', args: serverArgs(scriptPath) };
|
|
78
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
79
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
|
|
80
|
+
return 'installed';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Install into Zed's `context_servers` config (create file/dir if absent). */
|
|
84
|
+
function _installZed(filePath, scriptPath) {
|
|
85
|
+
let settings = {};
|
|
86
|
+
if (fs.existsSync(filePath)) {
|
|
87
|
+
try { settings = JSON.parse(fs.readFileSync(filePath, 'utf8')) || {}; }
|
|
88
|
+
catch (_) { settings = {}; }
|
|
89
|
+
}
|
|
90
|
+
if (!settings.context_servers) settings.context_servers = {};
|
|
91
|
+
if (settings.context_servers.sigmap) return 'already';
|
|
92
|
+
settings.context_servers.sigmap = { command: { path: 'node', args: serverArgs(scriptPath) } };
|
|
93
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
94
|
+
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
|
|
95
|
+
return 'installed';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Install into Codex CLI YAML (append block; create file if absent). */
|
|
99
|
+
function _installYaml(filePath, scriptPath) {
|
|
100
|
+
let raw = '';
|
|
101
|
+
if (fs.existsSync(filePath)) {
|
|
102
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
103
|
+
if (raw.includes('sigmap')) return 'already';
|
|
104
|
+
}
|
|
105
|
+
const block = [
|
|
106
|
+
'mcpServers:',
|
|
107
|
+
' sigmap:',
|
|
108
|
+
' command: node',
|
|
109
|
+
' args:',
|
|
110
|
+
` - ${path.resolve(scriptPath)}`,
|
|
111
|
+
' - --mcp',
|
|
112
|
+
].join('\n');
|
|
113
|
+
const next = raw ? raw.trimEnd() + '\n\n' + block + '\n' : block + '\n';
|
|
114
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
115
|
+
fs.writeFileSync(filePath, next);
|
|
116
|
+
return 'installed';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Install the sigmap MCP server for a single client.
|
|
121
|
+
* @returns { client, label, path, status } where status is
|
|
122
|
+
* 'installed' | 'already' | 'unknown'.
|
|
123
|
+
*/
|
|
124
|
+
function installClient(client, opts = {}) {
|
|
125
|
+
const spec = CLIENTS[client];
|
|
126
|
+
if (!spec) {
|
|
127
|
+
return { client, status: 'unknown', valid: Object.keys(CLIENTS) };
|
|
128
|
+
}
|
|
129
|
+
const cwd = opts.cwd || process.cwd();
|
|
130
|
+
const home = opts.home || os.homedir();
|
|
131
|
+
const scriptPath = opts.scriptPath || path.join(cwd, 'gen-context.js');
|
|
132
|
+
const filePath = resolveTarget(spec, cwd, home, opts.global);
|
|
133
|
+
|
|
134
|
+
let status;
|
|
135
|
+
if (spec.format === 'zed') status = _installZed(filePath, scriptPath);
|
|
136
|
+
else if (spec.format === 'yaml') status = _installYaml(filePath, scriptPath);
|
|
137
|
+
else status = _installJson(filePath, scriptPath);
|
|
138
|
+
|
|
139
|
+
return { client, label: spec.label, path: filePath, status };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { CLIENTS, listClients, installClient, resolveTarget };
|
package/src/mcp/server.js
CHANGED