sigmap 7.26.0 → 7.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,17 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [7.27.0] — 2026-06-22
14
+
15
+ 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.
16
+
17
+ ### Added
18
+ - **`get_diff_context` MCP tool (#376):** for every changed file (working tree, staged via `staged`, or vs a base ref via `base`) returns its current **signatures** + **blast radius** (direct importers, transitive count, affected tests/routes) + a risk label — one call gives an agent everything a review or a safe edit needs. Lists changed files **shell-free** through `src/util/git.js` (no `/bin/sh`). Optional `depth` controls the blast-radius BFS.
19
+ - **`get_architecture_overview` MCP tool (#376):** a one-call codebase map — module breakdown (files/tokens), the most depended-on **hub files**, the dependency-**cycle** count, and route totals. Extends `get_map` for orienting in an unfamiliar repo. Composed from `buildSigIndex`, `buildFromCwd`, and `detectCycles`.
20
+
21
+ ### Changed
22
+ - MCP surface count is now **17 tools** across `--help`, README, `docs-vp/guide/mcp.md`, `version.json` (`mcp_tools`), and the generated `llms.txt`/`llms-full.txt`.
23
+
13
24
  ## [7.26.0] — 2026-06-22
14
25
 
15
26
  Minor release — **v8.0 "The Evidence Pack & the Pivot" (E1):** the keystone artifact that makes SigMap consumable by machines instead of copy-paste.
package/README.md CHANGED
@@ -91,7 +91,7 @@ Ask → Rank → Context → Validate → Judge → Learn
91
91
 
92
92
  <!--SM:benchmarkBlock-->
93
93
  ```
94
- Benchmark : sigmap-v7.26-main (21 repositories, including R language)
94
+ Benchmark : sigmap-v7.27-main (21 repositories, including R language)
95
95
  Date : 2026-06-22
96
96
 
97
97
  Hit@5 : 75.6% (baseline 13.6% — 5.6× lift)
@@ -191,13 +191,13 @@ Use SigMap with open-source tools and fully self-hosted setups:
191
191
  | **JetBrains** | [Marketplace](https://plugins.jetbrains.com/plugin/31109-sigmap--ai-context-engine/) | [github.com/manojmallick/sigmap-jetbrains](https://github.com/manojmallick/sigmap-jetbrains) | IntelliJ IDEA, WebStorm, PyCharm, GoLand — tool window + actions |
192
192
  | **Neovim** | lazy.nvim / packer / vim-plug | [github.com/manojmallick/sigmap.nvim](https://github.com/manojmallick/sigmap.nvim) | `:SigMap`, `:SigMapQuery` float window, statusline widget |
193
193
 
194
- **MCP server** — 15 on-demand tools for Claude Code and Cursor:
194
+ **MCP server** — 17 on-demand tools for Claude Code and Cursor:
195
195
 
196
196
  ```bash
197
197
  sigmap --mcp
198
198
  ```
199
199
 
200
- Tools: `read_context`, `search_signatures`, `get_map`, `create_checkpoint`, `get_routing`, `explain_file`, `list_modules`, `query_context`, `get_impact`, `get_lines`, `read_memory`, `get_callee_signatures`, plus the live-index notifications `sigmap_notify_file_created`, `sigmap_notify_symbol_added`, and `sigmap_notify_file_deleted`. Full reference: [llms-full.txt](llms-full.txt).
200
+ Tools: `read_context`, `search_signatures`, `get_map`, `create_checkpoint`, `get_routing`, `explain_file`, `list_modules`, `query_context`, `get_impact`, `get_lines`, `read_memory`, `get_callee_signatures`, `get_diff_context` (changed files + signatures + blast radius), `get_architecture_overview` (modules, hub files, cycles), plus the live-index notifications `sigmap_notify_file_created`, `sigmap_notify_symbol_added`, and `sigmap_notify_file_deleted`. Full reference: [llms-full.txt](llms-full.txt).
201
201
 
202
202
  ---
203
203
 
package/gen-context.js CHANGED
@@ -12109,7 +12109,182 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
12109
12109
  }
12110
12110
  }
12111
12111
 
12112
- module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted };
12112
+ /**
12113
+ * List the files changed in the working tree, staged area, or vs a base ref.
12114
+ * Shell-free (routes through src/util/git.js). Returns relative paths.
12115
+ */
12116
+ function _changedFiles(cwd, args) {
12117
+ const { tryGit } = __require('./src/util/git');
12118
+ let out = '';
12119
+ if (args.base) {
12120
+ if (!/^[A-Za-z0-9._/\-~^]+$/.test(args.base)) return [];
12121
+ out = tryGit(['diff', `${args.base}..HEAD`, '--name-only'], { cwd });
12122
+ } else if (args.staged) {
12123
+ out = tryGit(['diff', '--cached', '--name-only'], { cwd });
12124
+ } else {
12125
+ out = tryGit(['diff', 'HEAD', '--name-only'], { cwd });
12126
+ }
12127
+ return out.split('\n').map((s) => s.trim()).filter(Boolean);
12128
+ }
12129
+
12130
+ /**
12131
+ * get_diff_context({ base?, staged?, depth? }) → string
12132
+ *
12133
+ * For each changed file: its extracted signatures + blast radius (direct
12134
+ * importers, transitive count, affected tests/routes) + a risk label.
12135
+ * Hands an agent everything a review or a safe edit needs in one call.
12136
+ */
12137
+ function getDiffContext(args, cwd) {
12138
+ try {
12139
+ const a = args || {};
12140
+ const files = _changedFiles(cwd, a);
12141
+ if (files.length === 0) {
12142
+ return '_No changed files detected._ (Outside a git repo, or the working tree / selected range is clean.)';
12143
+ }
12144
+
12145
+ const { extractFile, langFor } = __require('./src/extractors/dispatch');
12146
+ const { analyzeImpact } = __require('./src/graph/impact');
12147
+ let riskLabelFor;
12148
+ try { ({ riskLabelFor } = __require('./src/evidence/pack')); } catch (_) { riskLabelFor = () => 'source'; }
12149
+
12150
+ const depth = Math.max(0, parseInt(a.depth, 10) || 2);
12151
+ const srcFiles = files.filter((f) => langFor(f));
12152
+ let impactByFile = new Map();
12153
+ try {
12154
+ const impacts = analyzeImpact(srcFiles, cwd, { depth });
12155
+ impactByFile = new Map(impacts.map((r) => [r.file, r.impact]));
12156
+ } catch (_) { /* graph optional */ }
12157
+
12158
+ const scope = a.base ? `vs ${a.base}` : (a.staged ? 'staged' : 'working tree');
12159
+ const out = [
12160
+ `# Diff context (${scope})`,
12161
+ '',
12162
+ `**${files.length} changed file${files.length === 1 ? '' : 's'}** · ${srcFiles.length} with extractable signatures`,
12163
+ '',
12164
+ ];
12165
+
12166
+ for (const rel of files) {
12167
+ out.push(`## \`${rel}\``);
12168
+ if (!langFor(rel)) { out.push('_non-source file (no signatures)_', ''); continue; }
12169
+
12170
+ out.push(`_risk: ${riskLabelFor(rel)}_`);
12171
+ const impact = impactByFile.get(rel);
12172
+ if (impact) {
12173
+ out.push(
12174
+ `**Blast radius:** ${impact.totalImpact} file(s) impacted — ` +
12175
+ `${impact.direct.length} direct importer(s), ${impact.transitive.length} transitive` +
12176
+ (impact.tests.length ? `, ${impact.tests.length} test(s)` : '') +
12177
+ (impact.routes.length ? `, ${impact.routes.length} route(s)` : '')
12178
+ );
12179
+ if (impact.direct.length) {
12180
+ out.push(`Direct importers: ${impact.direct.slice(0, 8).map((f) => '`' + f + '`').join(', ')}` + (impact.direct.length > 8 ? ' …' : ''));
12181
+ }
12182
+ if (impact.tests.length) {
12183
+ out.push(`Tests to run: ${impact.tests.slice(0, 8).map((f) => '`' + f + '`').join(', ')}`);
12184
+ }
12185
+ } else {
12186
+ out.push('**Blast radius:** (not in dependency graph — new or leaf file)');
12187
+ }
12188
+ out.push('');
12189
+
12190
+ let src = '';
12191
+ try { src = fs.readFileSync(path.resolve(cwd, rel), 'utf8'); } catch (_) {}
12192
+ const sigs = src ? extractFile(rel, src) : [];
12193
+ if (sigs.length) {
12194
+ out.push('```');
12195
+ for (const s of sigs.slice(0, 40)) out.push(s);
12196
+ if (sigs.length > 40) out.push(`… +${sigs.length - 40} more`);
12197
+ out.push('```');
12198
+ } else {
12199
+ out.push('_(no signatures extracted — file may be deleted or empty)_');
12200
+ }
12201
+ out.push('');
12202
+ }
12203
+
12204
+ return out.join('\n');
12205
+ } catch (err) {
12206
+ return `_get_diff_context failed: ${err.message}_`;
12207
+ }
12208
+ }
12209
+
12210
+ /**
12211
+ * get_architecture_overview({}) → string
12212
+ *
12213
+ * A high-level map of the codebase: module breakdown (files/tokens), the most
12214
+ * depended-on "hub" files, dependency-cycle count, and route totals. Extends
12215
+ * get_map — one call to orient in an unfamiliar repo.
12216
+ */
12217
+ function getArchitectureOverview(args, cwd) {
12218
+ try {
12219
+ const { buildSigIndex } = __require('./src/retrieval/ranker');
12220
+ const index = buildSigIndex(cwd);
12221
+ const out = ['# Architecture overview', ''];
12222
+
12223
+ if (index.size === 0) {
12224
+ out.push('_No context file found. Run: node gen-context.js_', '');
12225
+ } else {
12226
+ const groups = {};
12227
+ let totalTokens = 0;
12228
+ let totalFiles = 0;
12229
+ for (const [rel, sigs] of index.entries()) {
12230
+ const parts = rel.replace(/\\/g, '/').split('/');
12231
+ const mod = parts.length > 1 ? parts[0] : '.';
12232
+ const tok = Math.ceil(sigs.join('\n').length / 4);
12233
+ if (!groups[mod]) groups[mod] = { files: 0, tokens: 0 };
12234
+ groups[mod].files++;
12235
+ groups[mod].tokens += tok;
12236
+ totalTokens += tok;
12237
+ totalFiles++;
12238
+ }
12239
+ const sorted = Object.entries(groups)
12240
+ .map(([mod, d]) => ({ mod, files: d.files, tokens: d.tokens }))
12241
+ .sort((a, b) => b.tokens - a.tokens);
12242
+
12243
+ out.push(`**${totalFiles} indexed files · ${sorted.length} modules · ~${totalTokens} tokens**`, '');
12244
+ out.push('## Modules', '| Module | Files | Tokens |', '|--------|-------|--------|');
12245
+ for (const m of sorted.slice(0, 20)) out.push(`| ${m.mod} | ${m.files} | ~${m.tokens} |`);
12246
+ out.push('');
12247
+ }
12248
+
12249
+ // Hub files + cycle count from the dependency graph (optional).
12250
+ try {
12251
+ const { buildFromCwd } = __require('./src/graph/builder');
12252
+ const { detectCycles } = __require('./src/map/import-graph');
12253
+ const graph = buildFromCwd(cwd);
12254
+ if (graph && graph.reverse && graph.reverse.size) {
12255
+ const hubs = [...graph.reverse.entries()]
12256
+ .map(([f, importers]) => ({ file: path.relative(cwd, f).replace(/\\/g, '/'), in: importers.length }))
12257
+ .filter((h) => h.in > 0)
12258
+ .sort((a, b) => b.in - a.in)
12259
+ .slice(0, 10);
12260
+ if (hubs.length) {
12261
+ out.push('## Hub files (most depended-on)', '| File | Importers |', '|------|-----------|');
12262
+ for (const h of hubs) out.push(`| \`${h.file}\` | ${h.in} |`);
12263
+ out.push('');
12264
+ }
12265
+ let cycleCount = 0;
12266
+ try { cycleCount = detectCycles(graph.forward).length; } catch (_) {}
12267
+ out.push(`**Dependency cycles:** ${cycleCount}` + (cycleCount ? ' _(see import graph)_' : ' — none detected'), '');
12268
+ }
12269
+ } catch (_) { /* graph optional */ }
12270
+
12271
+ // Routes from PROJECT_MAP.md if present.
12272
+ const mapPath = path.join(cwd, 'PROJECT_MAP.md');
12273
+ if (fs.existsSync(mapPath)) {
12274
+ const mc = fs.readFileSync(mapPath, 'utf8');
12275
+ const routeCount = mc.split('\n').filter((l) => l.startsWith('| ') && !l.startsWith('| Method') && !l.startsWith('|---')).length;
12276
+ out.push('## Project map', `Routes detected: ${routeCount} _(use get_map for imports/classes/routes detail)_`, '');
12277
+ } else {
12278
+ out.push('_Run `node gen-project-map.js` for routes / class-hierarchy detail (get_map)._');
12279
+ }
12280
+
12281
+ return out.join('\n');
12282
+ } catch (err) {
12283
+ return `_get_architecture_overview failed: ${err.message}_`;
12284
+ }
12285
+ }
12286
+
12287
+ module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview };
12113
12288
 
12114
12289
  };
12115
12290
 
@@ -12124,17 +12299,17 @@ __factories["./src/mcp/server"] = function(module, exports) {
12124
12299
  *
12125
12300
  * Supported methods:
12126
12301
  * initialize → serverInfo + capabilities
12127
- * tools/list → 11 tool definitions
12302
+ * tools/list → 17 tool definitions
12128
12303
  * tools/call → dispatch to handler, return result
12129
12304
  */
12130
12305
 
12131
12306
  const readline = require('readline');
12132
12307
  const { TOOLS } = __require('./src/mcp/tools');
12133
- const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted } = __require('./src/mcp/handlers');
12308
+ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview } = __require('./src/mcp/handlers');
12134
12309
 
12135
12310
  const SERVER_INFO = {
12136
12311
  name: 'sigmap',
12137
- version: '7.26.0',
12312
+ version: '7.27.0',
12138
12313
  description: 'SigMap MCP server — code signatures on demand',
12139
12314
  };
12140
12315
 
@@ -12197,6 +12372,8 @@ __factories["./src/mcp/server"] = function(module, exports) {
12197
12372
  else if (name === 'sigmap_notify_file_created') text = notifyFileCreated(args, cwd);
12198
12373
  else if (name === 'sigmap_notify_symbol_added') text = notifySymbolAdded(args, cwd);
12199
12374
  else if (name === 'sigmap_notify_file_deleted') text = notifyFileDeleted(args, cwd);
12375
+ else if (name === 'get_diff_context') text = getDiffContext(args, cwd);
12376
+ else if (name === 'get_architecture_overview') text = getArchitectureOverview(args, cwd);
12200
12377
  else {
12201
12378
  respondError(id, -32601, `Unknown tool: ${name}`);
12202
12379
  return;
@@ -12257,11 +12434,11 @@ __factories["./src/mcp/server"] = function(module, exports) {
12257
12434
  __factories["./src/mcp/tools"] = function(module, exports) {
12258
12435
 
12259
12436
  /**
12260
- * MCP tool definitions for SigMap (15 tools).
12437
+ * MCP tool definitions for SigMap (17 tools).
12261
12438
  * read_context, search_signatures, get_map, create_checkpoint, get_routing,
12262
12439
  * explain_file, list_modules, query_context, get_impact, get_lines, read_memory,
12263
12440
  * get_callee_signatures, sigmap_notify_file_created, sigmap_notify_symbol_added,
12264
- * sigmap_notify_file_deleted.
12441
+ * sigmap_notify_file_deleted, get_diff_context, get_architecture_overview.
12265
12442
  */
12266
12443
 
12267
12444
  const TOOLS = [
@@ -12534,6 +12711,44 @@ __factories["./src/mcp/tools"] = function(module, exports) {
12534
12711
  required: ['path'],
12535
12712
  },
12536
12713
  },
12714
+ {
12715
+ name: 'get_diff_context',
12716
+ description:
12717
+ 'For every changed file in the working tree (or staged, or vs a base ref), return its ' +
12718
+ 'current signatures plus blast radius — direct importers, transitive count, and affected ' +
12719
+ 'tests/routes — with a risk label. One call gives an agent everything a code review or a ' +
12720
+ 'safe edit needs. Lists changed files shell-free (git binary, never a shell).',
12721
+ inputSchema: {
12722
+ type: 'object',
12723
+ properties: {
12724
+ base: {
12725
+ type: 'string',
12726
+ description: 'Optional git ref to diff against (e.g. "main"). Returns files changed in `base..HEAD`. Omit for working-tree changes.',
12727
+ },
12728
+ staged: {
12729
+ type: 'boolean',
12730
+ description: 'When true (and no base), report only staged changes (`git diff --cached`).',
12731
+ },
12732
+ depth: {
12733
+ type: 'number',
12734
+ description: 'Blast-radius BFS depth limit (default: 2). Use 0 for unlimited.',
12735
+ },
12736
+ },
12737
+ required: [],
12738
+ },
12739
+ },
12740
+ {
12741
+ name: 'get_architecture_overview',
12742
+ description:
12743
+ 'A high-level map of the codebase in one call: module breakdown (files/tokens), the most ' +
12744
+ 'depended-on "hub" files, the dependency-cycle count, and route totals. Extends get_map — ' +
12745
+ 'use it to orient in an unfamiliar repo before drilling in with read_context / query_context.',
12746
+ inputSchema: {
12747
+ type: 'object',
12748
+ properties: {},
12749
+ required: [],
12750
+ },
12751
+ },
12537
12752
  ];
12538
12753
 
12539
12754
  module.exports = { TOOLS };
@@ -15923,7 +16138,7 @@ function __tryGit(args, opts = {}) {
15923
16138
  catch (_) { return ''; }
15924
16139
  }
15925
16140
 
15926
- const VERSION = '7.26.0';
16141
+ const VERSION = '7.27.0';
15927
16142
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
15928
16143
 
15929
16144
  function requireSourceOrBundled(key) {
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.26.0 | Benchmark: sigmap-v7.26-main (2026-06-22)
12
+ # Version: 7.27.0 | Benchmark: sigmap-v7.27-main (2026-06-22)
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.26-main, 2026-06-22)
18
+ ## Core metrics (benchmark: sigmap-v7.27-main, 2026-06-22)
19
19
 
20
20
  | Metric | Without SigMap | With SigMap |
21
21
  |--------|----------------|-------------|
@@ -24,7 +24,7 @@ Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
24
24
  | Task success proxy | 10% | 52.2% |
25
25
  | Prompts per task | 2.84 | 1.72 (39.4% fewer) |
26
26
  | Supported languages | — | 33 |
27
- | MCP tools | — | 15 |
27
+ | MCP tools | — | 17 |
28
28
  | npm runtime dependencies | — | 0 |
29
29
 
30
30
  ---
@@ -122,7 +122,7 @@ sigmap --version Show version
122
122
 
123
123
  ---
124
124
 
125
- ## MCP server — 15 tools
125
+ ## MCP server — 17 tools
126
126
 
127
127
  Start with `sigmap --mcp` (stdio JSON-RPC). Configure once:
128
128
 
@@ -250,6 +250,22 @@ Tell SigMap a file was deleted so its symbols are dropped from the live index.
250
250
  Input: { path: string }
251
251
  ```
252
252
 
253
+ ### get_diff_context
254
+
255
+ For every changed file in the working tree (or staged, or vs a base ref), return its current signatures plus blast radius — direct importers, transitive count, and affected tests/routes — with a risk label. One call gives an agent everything a code review or a safe edit needs. Lists changed files shell-free (git binary, never a shell).
256
+
257
+ ```
258
+ Input: { base?: string, staged?: boolean, depth?: number }
259
+ ```
260
+
261
+ ### get_architecture_overview
262
+
263
+ A high-level map of the codebase in one call: module breakdown (files/tokens), the most depended-on "hub" files, the dependency-cycle count, and route totals. Extends get_map — use it to orient in an unfamiliar repo before drilling in with read_context / query_context.
264
+
265
+ ```
266
+ Input: { } (no arguments)
267
+ ```
268
+
253
269
  ---
254
270
 
255
271
  ## Configuration (gen-context.config.json)
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.26.0 | Benchmark: sigmap-v7.26-main (2026-06-22)
12
+ # Version: 7.27.0 | Benchmark: sigmap-v7.27-main (2026-06-22)
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,13 +21,13 @@ 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.26-main, 2026-06-22)
24
+ ## Core metrics (benchmark: sigmap-v7.27-main, 2026-06-22)
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
28
28
  - Task success: 52.2% vs 10% without SigMap
29
29
  - Prompts per task: 1.72 vs 2.84 baseline (39.4% fewer)
30
- - Languages: 33 supported · MCP tools: 15
30
+ - Languages: 33 supported · MCP tools: 17
31
31
  - Dependencies: zero npm runtime dependencies · fully offline
32
32
 
33
33
  ## Quick start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "7.26.0",
3
+ "version": "7.27.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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "7.26.0",
3
+ "version": "7.27.0",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "7.26.0",
3
+ "version": "7.27.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -674,4 +674,179 @@ function notifyFileDeleted(args, cwd) {
674
674
  }
675
675
  }
676
676
 
677
- module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted };
677
+ /**
678
+ * List the files changed in the working tree, staged area, or vs a base ref.
679
+ * Shell-free (routes through src/util/git.js). Returns relative paths.
680
+ */
681
+ function _changedFiles(cwd, args) {
682
+ const { tryGit } = require('../util/git');
683
+ let out = '';
684
+ if (args.base) {
685
+ if (!/^[A-Za-z0-9._/\-~^]+$/.test(args.base)) return [];
686
+ out = tryGit(['diff', `${args.base}..HEAD`, '--name-only'], { cwd });
687
+ } else if (args.staged) {
688
+ out = tryGit(['diff', '--cached', '--name-only'], { cwd });
689
+ } else {
690
+ out = tryGit(['diff', 'HEAD', '--name-only'], { cwd });
691
+ }
692
+ return out.split('\n').map((s) => s.trim()).filter(Boolean);
693
+ }
694
+
695
+ /**
696
+ * get_diff_context({ base?, staged?, depth? }) → string
697
+ *
698
+ * For each changed file: its extracted signatures + blast radius (direct
699
+ * importers, transitive count, affected tests/routes) + a risk label.
700
+ * Hands an agent everything a review or a safe edit needs in one call.
701
+ */
702
+ function getDiffContext(args, cwd) {
703
+ try {
704
+ const a = args || {};
705
+ const files = _changedFiles(cwd, a);
706
+ if (files.length === 0) {
707
+ return '_No changed files detected._ (Outside a git repo, or the working tree / selected range is clean.)';
708
+ }
709
+
710
+ const { extractFile, langFor } = require('../extractors/dispatch');
711
+ const { analyzeImpact } = require('../graph/impact');
712
+ let riskLabelFor;
713
+ try { ({ riskLabelFor } = require('../evidence/pack')); } catch (_) { riskLabelFor = () => 'source'; }
714
+
715
+ const depth = Math.max(0, parseInt(a.depth, 10) || 2);
716
+ const srcFiles = files.filter((f) => langFor(f));
717
+ let impactByFile = new Map();
718
+ try {
719
+ const impacts = analyzeImpact(srcFiles, cwd, { depth });
720
+ impactByFile = new Map(impacts.map((r) => [r.file, r.impact]));
721
+ } catch (_) { /* graph optional */ }
722
+
723
+ const scope = a.base ? `vs ${a.base}` : (a.staged ? 'staged' : 'working tree');
724
+ const out = [
725
+ `# Diff context (${scope})`,
726
+ '',
727
+ `**${files.length} changed file${files.length === 1 ? '' : 's'}** · ${srcFiles.length} with extractable signatures`,
728
+ '',
729
+ ];
730
+
731
+ for (const rel of files) {
732
+ out.push(`## \`${rel}\``);
733
+ if (!langFor(rel)) { out.push('_non-source file (no signatures)_', ''); continue; }
734
+
735
+ out.push(`_risk: ${riskLabelFor(rel)}_`);
736
+ const impact = impactByFile.get(rel);
737
+ if (impact) {
738
+ out.push(
739
+ `**Blast radius:** ${impact.totalImpact} file(s) impacted — ` +
740
+ `${impact.direct.length} direct importer(s), ${impact.transitive.length} transitive` +
741
+ (impact.tests.length ? `, ${impact.tests.length} test(s)` : '') +
742
+ (impact.routes.length ? `, ${impact.routes.length} route(s)` : '')
743
+ );
744
+ if (impact.direct.length) {
745
+ out.push(`Direct importers: ${impact.direct.slice(0, 8).map((f) => '`' + f + '`').join(', ')}` + (impact.direct.length > 8 ? ' …' : ''));
746
+ }
747
+ if (impact.tests.length) {
748
+ out.push(`Tests to run: ${impact.tests.slice(0, 8).map((f) => '`' + f + '`').join(', ')}`);
749
+ }
750
+ } else {
751
+ out.push('**Blast radius:** (not in dependency graph — new or leaf file)');
752
+ }
753
+ out.push('');
754
+
755
+ let src = '';
756
+ try { src = fs.readFileSync(path.resolve(cwd, rel), 'utf8'); } catch (_) {}
757
+ const sigs = src ? extractFile(rel, src) : [];
758
+ if (sigs.length) {
759
+ out.push('```');
760
+ for (const s of sigs.slice(0, 40)) out.push(s);
761
+ if (sigs.length > 40) out.push(`… +${sigs.length - 40} more`);
762
+ out.push('```');
763
+ } else {
764
+ out.push('_(no signatures extracted — file may be deleted or empty)_');
765
+ }
766
+ out.push('');
767
+ }
768
+
769
+ return out.join('\n');
770
+ } catch (err) {
771
+ return `_get_diff_context failed: ${err.message}_`;
772
+ }
773
+ }
774
+
775
+ /**
776
+ * get_architecture_overview({}) → string
777
+ *
778
+ * A high-level map of the codebase: module breakdown (files/tokens), the most
779
+ * depended-on "hub" files, dependency-cycle count, and route totals. Extends
780
+ * get_map — one call to orient in an unfamiliar repo.
781
+ */
782
+ function getArchitectureOverview(args, cwd) {
783
+ try {
784
+ const { buildSigIndex } = require('../retrieval/ranker');
785
+ const index = buildSigIndex(cwd);
786
+ const out = ['# Architecture overview', ''];
787
+
788
+ if (index.size === 0) {
789
+ out.push('_No context file found. Run: node gen-context.js_', '');
790
+ } else {
791
+ const groups = {};
792
+ let totalTokens = 0;
793
+ let totalFiles = 0;
794
+ for (const [rel, sigs] of index.entries()) {
795
+ const parts = rel.replace(/\\/g, '/').split('/');
796
+ const mod = parts.length > 1 ? parts[0] : '.';
797
+ const tok = Math.ceil(sigs.join('\n').length / 4);
798
+ if (!groups[mod]) groups[mod] = { files: 0, tokens: 0 };
799
+ groups[mod].files++;
800
+ groups[mod].tokens += tok;
801
+ totalTokens += tok;
802
+ totalFiles++;
803
+ }
804
+ const sorted = Object.entries(groups)
805
+ .map(([mod, d]) => ({ mod, files: d.files, tokens: d.tokens }))
806
+ .sort((a, b) => b.tokens - a.tokens);
807
+
808
+ out.push(`**${totalFiles} indexed files · ${sorted.length} modules · ~${totalTokens} tokens**`, '');
809
+ out.push('## Modules', '| Module | Files | Tokens |', '|--------|-------|--------|');
810
+ for (const m of sorted.slice(0, 20)) out.push(`| ${m.mod} | ${m.files} | ~${m.tokens} |`);
811
+ out.push('');
812
+ }
813
+
814
+ // Hub files + cycle count from the dependency graph (optional).
815
+ try {
816
+ const { buildFromCwd } = require('../graph/builder');
817
+ const { detectCycles } = require('../map/import-graph');
818
+ const graph = buildFromCwd(cwd);
819
+ if (graph && graph.reverse && graph.reverse.size) {
820
+ const hubs = [...graph.reverse.entries()]
821
+ .map(([f, importers]) => ({ file: path.relative(cwd, f).replace(/\\/g, '/'), in: importers.length }))
822
+ .filter((h) => h.in > 0)
823
+ .sort((a, b) => b.in - a.in)
824
+ .slice(0, 10);
825
+ if (hubs.length) {
826
+ out.push('## Hub files (most depended-on)', '| File | Importers |', '|------|-----------|');
827
+ for (const h of hubs) out.push(`| \`${h.file}\` | ${h.in} |`);
828
+ out.push('');
829
+ }
830
+ let cycleCount = 0;
831
+ try { cycleCount = detectCycles(graph.forward).length; } catch (_) {}
832
+ out.push(`**Dependency cycles:** ${cycleCount}` + (cycleCount ? ' _(see import graph)_' : ' — none detected'), '');
833
+ }
834
+ } catch (_) { /* graph optional */ }
835
+
836
+ // Routes from PROJECT_MAP.md if present.
837
+ const mapPath = path.join(cwd, 'PROJECT_MAP.md');
838
+ if (fs.existsSync(mapPath)) {
839
+ const mc = fs.readFileSync(mapPath, 'utf8');
840
+ const routeCount = mc.split('\n').filter((l) => l.startsWith('| ') && !l.startsWith('| Method') && !l.startsWith('|---')).length;
841
+ out.push('## Project map', `Routes detected: ${routeCount} _(use get_map for imports/classes/routes detail)_`, '');
842
+ } else {
843
+ out.push('_Run `node gen-project-map.js` for routes / class-hierarchy detail (get_map)._');
844
+ }
845
+
846
+ return out.join('\n');
847
+ } catch (err) {
848
+ return `_get_architecture_overview failed: ${err.message}_`;
849
+ }
850
+ }
851
+
852
+ module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview };
package/src/mcp/server.js CHANGED
@@ -8,17 +8,17 @@
8
8
  *
9
9
  * Supported methods:
10
10
  * initialize → serverInfo + capabilities
11
- * tools/list → 11 tool definitions
11
+ * tools/list → 17 tool definitions
12
12
  * tools/call → dispatch to handler, return result
13
13
  */
14
14
 
15
15
  const readline = require('readline');
16
16
  const { TOOLS } = require('./tools');
17
- const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted } = require('./handlers');
17
+ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures, notifyFileCreated, notifySymbolAdded, notifyFileDeleted, getDiffContext, getArchitectureOverview } = require('./handlers');
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '7.26.0',
21
+ version: '7.27.0',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
@@ -81,6 +81,8 @@ function dispatch(msg, cwd) {
81
81
  else if (name === 'sigmap_notify_file_created') text = notifyFileCreated(args, cwd);
82
82
  else if (name === 'sigmap_notify_symbol_added') text = notifySymbolAdded(args, cwd);
83
83
  else if (name === 'sigmap_notify_file_deleted') text = notifyFileDeleted(args, cwd);
84
+ else if (name === 'get_diff_context') text = getDiffContext(args, cwd);
85
+ else if (name === 'get_architecture_overview') text = getArchitectureOverview(args, cwd);
84
86
  else {
85
87
  respondError(id, -32601, `Unknown tool: ${name}`);
86
88
  return;
package/src/mcp/tools.js CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * MCP tool definitions for SigMap (15 tools).
4
+ * MCP tool definitions for SigMap (17 tools).
5
5
  * read_context, search_signatures, get_map, create_checkpoint, get_routing,
6
6
  * explain_file, list_modules, query_context, get_impact, get_lines, read_memory,
7
7
  * get_callee_signatures, sigmap_notify_file_created, sigmap_notify_symbol_added,
8
- * sigmap_notify_file_deleted.
8
+ * sigmap_notify_file_deleted, get_diff_context, get_architecture_overview.
9
9
  */
10
10
 
11
11
  const TOOLS = [
@@ -278,6 +278,44 @@ const TOOLS = [
278
278
  required: ['path'],
279
279
  },
280
280
  },
281
+ {
282
+ name: 'get_diff_context',
283
+ description:
284
+ 'For every changed file in the working tree (or staged, or vs a base ref), return its ' +
285
+ 'current signatures plus blast radius — direct importers, transitive count, and affected ' +
286
+ 'tests/routes — with a risk label. One call gives an agent everything a code review or a ' +
287
+ 'safe edit needs. Lists changed files shell-free (git binary, never a shell).',
288
+ inputSchema: {
289
+ type: 'object',
290
+ properties: {
291
+ base: {
292
+ type: 'string',
293
+ description: 'Optional git ref to diff against (e.g. "main"). Returns files changed in `base..HEAD`. Omit for working-tree changes.',
294
+ },
295
+ staged: {
296
+ type: 'boolean',
297
+ description: 'When true (and no base), report only staged changes (`git diff --cached`).',
298
+ },
299
+ depth: {
300
+ type: 'number',
301
+ description: 'Blast-radius BFS depth limit (default: 2). Use 0 for unlimited.',
302
+ },
303
+ },
304
+ required: [],
305
+ },
306
+ },
307
+ {
308
+ name: 'get_architecture_overview',
309
+ description:
310
+ 'A high-level map of the codebase in one call: module breakdown (files/tokens), the most ' +
311
+ 'depended-on "hub" files, the dependency-cycle count, and route totals. Extends get_map — ' +
312
+ 'use it to orient in an unfamiliar repo before drilling in with read_context / query_context.',
313
+ inputSchema: {
314
+ type: 'object',
315
+ properties: {},
316
+ required: [],
317
+ },
318
+ },
281
319
  ];
282
320
 
283
321
  module.exports = { TOOLS };