sigmap 7.2.0 → 7.3.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/gen-context.js CHANGED
@@ -5528,552 +5528,605 @@ __factories["./src/graph/impact"] = function(module, exports) {
5528
5528
 
5529
5529
  // ── ./src/mcp/handlers ──
5530
5530
  __factories["./src/mcp/handlers"] = function(module, exports) {
5531
- 'use strict';
5532
-
5533
- const fs = require('fs');
5534
- const path = require('path');
5535
-
5536
- const CONTEXT_FILE = path.join('.github', 'copilot-instructions.md');
5537
- const CONTEXT_COLD_FILE = path.join('.github', 'context-cold.md');
5538
-
5539
- function _readContextFiles(cwd) {
5540
- const paths = [path.join(cwd, CONTEXT_FILE), path.join(cwd, CONTEXT_COLD_FILE)];
5541
- const chunks = [];
5542
- for (const p of paths) {
5543
- if (fs.existsSync(p)) chunks.push(fs.readFileSync(p, 'utf8'));
5544
- }
5545
- return chunks.join('\n');
5546
- }
5547
-
5548
- // Section header keywords in PROJECT_MAP.md
5549
- const MAP_SECTIONS = {
5550
- imports: '### Import graph',
5551
- classes: '### Class hierarchy',
5552
- routes: '### Route table',
5553
- };
5554
-
5555
- /**
5556
- * read_context({ module? }) → string
5557
- *
5558
- * Returns the full context file, or just the sections whose file paths
5559
- * contain the given module substring.
5560
- */
5561
- function readContext(args, cwd) {
5562
- const content = _readContextFiles(cwd);
5563
- if (!content) {
5564
- return 'No context file found. Run: node gen-context.js';
5565
- }
5566
-
5567
- if (!args || !args.module) return content;
5531
+
5532
+ const fs = require('fs');
5533
+ const path = require('path');
5534
+ const { git } = __require('./src/util/git');
5568
5535
 
5569
- const mod = args.module.replace(/\\/g, '/').replace(/\/$/, '');
5570
- const lines = content.split('\n');
5571
- const result = [];
5572
- let capturing = false;
5536
+ const CONTEXT_FILE = path.join('.github', 'copilot-instructions.md');
5537
+ const CONTEXT_COLD_FILE = path.join('.github', 'context-cold.md');
5573
5538
 
5574
- for (const line of lines) {
5575
- if (line.startsWith('### ')) {
5576
- const filePath = line.slice(4).trim().replace(/\\/g, '/');
5577
- // Match if file path starts with mod or contains /mod/ or /mod
5578
- capturing =
5579
- filePath === mod ||
5580
- filePath.startsWith(mod + '/') ||
5581
- filePath.includes('/' + mod + '/') ||
5582
- filePath.includes('/' + mod);
5583
- if (capturing) result.push(line);
5584
- continue;
5539
+ function _readContextFiles(cwd) {
5540
+ const paths = [path.join(cwd, CONTEXT_FILE), path.join(cwd, CONTEXT_COLD_FILE)];
5541
+ const chunks = [];
5542
+ for (const p of paths) {
5543
+ if (fs.existsSync(p)) chunks.push(fs.readFileSync(p, 'utf8'));
5585
5544
  }
5586
- if (capturing) result.push(line);
5545
+ return chunks.join('\n');
5587
5546
  }
5588
5547
 
5589
- if (result.length === 0) return `No signatures found for module: ${mod}`;
5590
- return result.join('\n');
5591
- }
5592
-
5593
- /**
5594
- * search_signatures({ query }) → string
5595
- *
5596
- * Case-insensitive search through all signature lines.
5597
- * Returns matching lines grouped by file path.
5598
- */
5599
- function searchSignatures(args, cwd) {
5600
- if (!args || !args.query) return 'Missing required argument: query';
5548
+ // Section header keywords in PROJECT_MAP.md
5549
+ const MAP_SECTIONS = {
5550
+ imports: '### Import graph',
5551
+ classes: '### Class hierarchy',
5552
+ routes: '### Route table',
5553
+ };
5601
5554
 
5602
- const query = args.query.toLowerCase();
5603
- try {
5604
- const { buildSigIndex } = __require('./src/retrieval/ranker');
5605
- const index = buildSigIndex(cwd);
5606
- if (index.size === 0) {
5555
+ /**
5556
+ * read_context({ module? }) → string
5557
+ *
5558
+ * Returns the full context file, or just the sections whose file paths
5559
+ * contain the given module substring.
5560
+ */
5561
+ function readContext(args, cwd) {
5562
+ const content = _readContextFiles(cwd);
5563
+ if (!content) {
5607
5564
  return 'No context file found. Run: node gen-context.js';
5608
5565
  }
5609
5566
 
5567
+ if (!args || !args.module) return content;
5568
+
5569
+ const mod = args.module.replace(/\\/g, '/').replace(/\/$/, '');
5570
+ const lines = content.split('\n');
5610
5571
  const result = [];
5611
- for (const [file, sigs] of index.entries()) {
5612
- const hits = sigs.filter((s) => s.toLowerCase().includes(query));
5613
- if (hits.length === 0) continue;
5614
- if (result.length > 0) result.push('');
5615
- result.push(`### ${file}`);
5616
- result.push(...hits);
5572
+ let capturing = false;
5573
+
5574
+ for (const line of lines) {
5575
+ if (line.startsWith('### ')) {
5576
+ const filePath = line.slice(4).trim().replace(/\\/g, '/');
5577
+ // Match if file path starts with mod or contains /mod/ or /mod
5578
+ capturing =
5579
+ filePath === mod ||
5580
+ filePath.startsWith(mod + '/') ||
5581
+ filePath.includes('/' + mod + '/') ||
5582
+ filePath.includes('/' + mod);
5583
+ if (capturing) result.push(line);
5584
+ continue;
5585
+ }
5586
+ if (capturing) result.push(line);
5617
5587
  }
5618
5588
 
5619
- if (result.length === 0) return `No signatures found matching: ${args.query}`;
5589
+ if (result.length === 0) return `No signatures found for module: ${mod}`;
5620
5590
  return result.join('\n');
5621
- } catch (err) {
5622
- return `_search_signatures failed: ${err.message}_`;
5623
5591
  }
5624
- }
5625
5592
 
5626
- /**
5627
- * get_map({ type }) → string
5628
- *
5629
- * Returns a section from PROJECT_MAP.md.
5630
- * type: 'imports' | 'classes' | 'routes'
5631
- */
5632
- function getMap(args, cwd) {
5633
- if (!args || !args.type) return 'Missing required argument: type';
5593
+ /**
5594
+ * search_signatures({ query }) → string
5595
+ *
5596
+ * Case-insensitive search through all signature lines.
5597
+ * Returns matching lines grouped by file path.
5598
+ */
5599
+ function searchSignatures(args, cwd) {
5600
+ if (!args || !args.query) return 'Missing required argument: query';
5634
5601
 
5635
- const header = MAP_SECTIONS[args.type];
5636
- if (!header) {
5637
- return `Unknown map type: "${args.type}". Use: imports, classes, routes`;
5638
- }
5602
+ const query = args.query.toLowerCase();
5603
+ try {
5604
+ const { buildSigIndex } = __require('./src/retrieval/ranker');
5605
+ const index = buildSigIndex(cwd);
5606
+ if (index.size === 0) {
5607
+ return 'No context file found. Run: node gen-context.js';
5608
+ }
5639
5609
 
5640
- const mapPath = path.join(cwd, 'PROJECT_MAP.md');
5641
- if (!fs.existsSync(mapPath)) {
5642
- return 'PROJECT_MAP.md not found. Run: node gen-project-map.js';
5643
- }
5610
+ const result = [];
5611
+ for (const [file, sigs] of index.entries()) {
5612
+ const hits = sigs.filter((s) => s.toLowerCase().includes(query));
5613
+ if (hits.length === 0) continue;
5614
+ if (result.length > 0) result.push('');
5615
+ result.push(`### ${file}`);
5616
+ result.push(...hits);
5617
+ }
5644
5618
 
5645
- const content = fs.readFileSync(mapPath, 'utf8');
5646
- const idx = content.indexOf(header);
5647
- if (idx === -1) {
5648
- return `Section "${header}" not found in PROJECT_MAP.md`;
5619
+ if (result.length === 0) return `No signatures found matching: ${args.query}`;
5620
+ return result.join('\n');
5621
+ } catch (err) {
5622
+ return `_search_signatures failed: ${err.message}_`;
5623
+ }
5649
5624
  }
5650
5625
 
5651
- // Extract from this header to the next ### header
5652
- const after = content.slice(idx);
5653
- const nextMatch = after.slice(header.length).search(/\n###\s/);
5654
- return nextMatch === -1 ? after : after.slice(0, header.length + nextMatch);
5655
- }
5626
+ /**
5627
+ * get_map({ type }) → string
5628
+ *
5629
+ * Returns a section from PROJECT_MAP.md.
5630
+ * type: 'imports' | 'classes' | 'routes'
5631
+ */
5632
+ function getMap(args, cwd) {
5633
+ if (!args || !args.type) return 'Missing required argument: type';
5656
5634
 
5657
- /**
5658
- * create_checkpoint({ note? }) → string
5659
- *
5660
- * Returns a markdown checkpoint summarising current project state:
5661
- * - Timestamp and optional user note
5662
- * - Active git branch + last 5 commit messages
5663
- * - Token count of current context file
5664
- * - List of modules present in the context
5665
- * - Route count (if PROJECT_MAP.md exists)
5666
- */
5667
- function createCheckpoint(args, cwd) {
5668
- const note = (args && args.note) ? args.note.trim() : '';
5669
- const now = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
5670
- const lines = [
5671
- '# SigMap Checkpoint',
5672
- `**Created:** ${now}`,
5673
- ];
5635
+ const header = MAP_SECTIONS[args.type];
5636
+ if (!header) {
5637
+ return `Unknown map type: "${args.type}". Use: imports, classes, routes`;
5638
+ }
5674
5639
 
5675
- if (note) lines.push(`**Note:** ${note}`);
5676
- lines.push('');
5640
+ const mapPath = path.join(cwd, 'PROJECT_MAP.md');
5641
+ if (!fs.existsSync(mapPath)) {
5642
+ return 'PROJECT_MAP.md not found. Run: node gen-project-map.js';
5643
+ }
5677
5644
 
5678
- // ── Git info ────────────────────────────────────────────────────────────
5679
- lines.push('## Git state');
5680
- try {
5681
- const branch = __git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }).trim();
5682
- lines.push(`**Branch:** ${branch}`);
5683
- } catch (_) {
5684
- lines.push('**Branch:** (not a git repo)');
5645
+ const content = fs.readFileSync(mapPath, 'utf8');
5646
+ const idx = content.indexOf(header);
5647
+ if (idx === -1) {
5648
+ return `Section "${header}" not found in PROJECT_MAP.md`;
5649
+ }
5650
+
5651
+ // Extract from this header to the next ### header
5652
+ const after = content.slice(idx);
5653
+ const nextMatch = after.slice(header.length).search(/\n###\s/);
5654
+ return nextMatch === -1 ? after : after.slice(0, header.length + nextMatch);
5685
5655
  }
5686
5656
 
5687
- try {
5688
- const log = __git(['log', '--oneline', '-5', '--no-decorate'], { cwd }).trim();
5689
- if (log) {
5690
- lines.push('');
5691
- lines.push('**Recent commits:**');
5692
- for (const l of log.split('\n')) lines.push(`- ${l}`);
5657
+ /**
5658
+ * create_checkpoint({ note? }) → string
5659
+ *
5660
+ * Returns a markdown checkpoint summarising current project state:
5661
+ * - Timestamp and optional user note
5662
+ * - Active git branch + last 5 commit messages
5663
+ * - Token count of current context file
5664
+ * - List of modules present in the context
5665
+ * - Route count (if PROJECT_MAP.md exists)
5666
+ */
5667
+ function createCheckpoint(args, cwd) {
5668
+ const note = (args && args.note) ? args.note.trim() : '';
5669
+ const now = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
5670
+ const lines = [
5671
+ '# SigMap Checkpoint',
5672
+ `**Created:** ${now}`,
5673
+ ];
5674
+
5675
+ if (note) lines.push(`**Note:** ${note}`);
5676
+ lines.push('');
5677
+
5678
+ // ── Git info ────────────────────────────────────────────────────────────
5679
+ lines.push('## Git state');
5680
+ try {
5681
+ const branch = git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }).trim();
5682
+ lines.push(`**Branch:** ${branch}`);
5683
+ } catch (_) {
5684
+ lines.push('**Branch:** (not a git repo)');
5693
5685
  }
5694
- } catch (_) {} // ignore — not every project uses git
5695
- lines.push('');
5696
5686
 
5697
- // ── Context stats ────────────────────────────────────────────────────────
5698
- lines.push('## Context snapshot');
5699
- const contextPath = path.join(cwd, CONTEXT_FILE);
5700
- if (fs.existsSync(contextPath)) {
5701
- const content = fs.readFileSync(contextPath, 'utf8');
5702
- const tokens = Math.ceil(content.length / 4);
5687
+ try {
5688
+ const log = git(['log', '--oneline', '-5', '--no-decorate'], { cwd }).trim();
5689
+ if (log) {
5690
+ lines.push('');
5691
+ lines.push('**Recent commits:**');
5692
+ for (const l of log.split('\n')) lines.push(`- ${l}`);
5693
+ }
5694
+ } catch (_) {} // ignore — not every project uses git
5695
+ lines.push('');
5703
5696
 
5704
- // Count modules (### headers are file paths)
5705
- const modules = content.split('\n').filter((l) => l.startsWith('### ')).map((l) => l.slice(4).trim());
5706
- lines.push(`**Token count:** ~${tokens}`);
5707
- lines.push(`**Modules in context:** ${modules.length}`);
5697
+ // ── Context stats ────────────────────────────────────────────────────────
5698
+ lines.push('## Context snapshot');
5699
+ const contextPath = path.join(cwd, CONTEXT_FILE);
5700
+ if (fs.existsSync(contextPath)) {
5701
+ const content = fs.readFileSync(contextPath, 'utf8');
5702
+ const tokens = Math.ceil(content.length / 4);
5708
5703
 
5709
- if (modules.length > 0) {
5710
- lines.push('');
5711
- lines.push('**Modules:**');
5712
- for (const m of modules.slice(0, 20)) lines.push(`- ${m}`);
5713
- if (modules.length > 20) lines.push(`- … and ${modules.length - 20} more`);
5714
- }
5715
- } else {
5716
- lines.push('_No context file found. Run: node gen-context.js_');
5717
- }
5718
- lines.push('');
5719
-
5720
- // ── Route summary ────────────────────────────────────────────────────────
5721
- const mapPath = path.join(cwd, 'PROJECT_MAP.md');
5722
- if (fs.existsSync(mapPath)) {
5723
- const mapContent = fs.readFileSync(mapPath, 'utf8');
5724
- const routeLines = mapContent.split('\n').filter((l) => l.startsWith('| ') && !l.startsWith('| Method') && !l.startsWith('|---'));
5725
- if (routeLines.length > 0) {
5726
- lines.push('## Routes');
5727
- lines.push(`**Total routes detected:** ${routeLines.length}`);
5728
- lines.push('');
5729
- for (const r of routeLines.slice(0, 10)) lines.push(r);
5730
- if (routeLines.length > 10) lines.push(`| … | +${routeLines.length - 10} more | |`);
5731
- lines.push('');
5704
+ // Count modules (### headers are file paths)
5705
+ const modules = content.split('\n').filter((l) => l.startsWith('### ')).map((l) => l.slice(4).trim());
5706
+ lines.push(`**Token count:** ~${tokens}`);
5707
+ lines.push(`**Modules in context:** ${modules.length}`);
5708
+
5709
+ if (modules.length > 0) {
5710
+ lines.push('');
5711
+ lines.push('**Modules:**');
5712
+ for (const m of modules.slice(0, 20)) lines.push(`- ${m}`);
5713
+ if (modules.length > 20) lines.push(`- … and ${modules.length - 20} more`);
5714
+ }
5715
+ } else {
5716
+ lines.push('_No context file found. Run: node gen-context.js_');
5732
5717
  }
5733
- }
5718
+ lines.push('');
5734
5719
 
5735
- lines.push('---');
5736
- lines.push('_Generated by SigMap `create_checkpoint`_');
5720
+ // ── Route summary ────────────────────────────────────────────────────────
5721
+ const mapPath = path.join(cwd, 'PROJECT_MAP.md');
5722
+ if (fs.existsSync(mapPath)) {
5723
+ const mapContent = fs.readFileSync(mapPath, 'utf8');
5724
+ const routeLines = mapContent.split('\n').filter((l) => l.startsWith('| ') && !l.startsWith('| Method') && !l.startsWith('|---'));
5725
+ if (routeLines.length > 0) {
5726
+ lines.push('## Routes');
5727
+ lines.push(`**Total routes detected:** ${routeLines.length}`);
5728
+ lines.push('');
5729
+ for (const r of routeLines.slice(0, 10)) lines.push(r);
5730
+ if (routeLines.length > 10) lines.push(`| … | +${routeLines.length - 10} more | |`);
5731
+ lines.push('');
5732
+ }
5733
+ }
5737
5734
 
5738
- return lines.join('\n');
5739
- }
5735
+ lines.push('---');
5736
+ lines.push('_Generated by SigMap `create_checkpoint`_');
5740
5737
 
5741
- /**
5742
- * get_routing({}) → string
5743
- *
5744
- * Reads the current context file, classifies all indexed files by complexity,
5745
- * and returns a formatted markdown routing guide showing which files belong
5746
- * to the fast/balanced/powerful model tier.
5747
- */
5748
- function getRouting(args, cwd) {
5749
- const contextPath = path.join(cwd, CONTEXT_FILE);
5750
- if (!fs.existsSync(contextPath)) {
5751
- return (
5752
- '_No context file found. Run `node gen-context.js --routing` first._\n\n' +
5753
- 'This generates routing hints that map each file to a model tier:\n' +
5754
- '- **fast** (haiku/gpt-4o-mini) — config, markup, trivial utilities\n' +
5755
- '- **balanced** (sonnet/gpt-4o) — standard application code\n' +
5756
- '- **powerful** (opus/gpt-4-turbo) — complex, security-critical, or large modules'
5757
- );
5738
+ return lines.join('\n');
5758
5739
  }
5759
5740
 
5760
- // Parse file list from context (### headings are file paths)
5761
- const content = fs.readFileSync(contextPath, 'utf8');
5762
- const fileRels = content.split('\n')
5763
- .filter((l) => l.startsWith('### '))
5764
- .map((l) => l.slice(4).trim());
5741
+ /**
5742
+ * get_routing({}) string
5743
+ *
5744
+ * Reads the current context file, classifies all indexed files by complexity,
5745
+ * and returns a formatted markdown routing guide showing which files belong
5746
+ * to the fast/balanced/powerful model tier.
5747
+ */
5748
+ function getRouting(args, cwd) {
5749
+ const contextPath = path.join(cwd, CONTEXT_FILE);
5750
+ if (!fs.existsSync(contextPath)) {
5751
+ return (
5752
+ '_No context file found. Run `node gen-context.js --routing` first._\n\n' +
5753
+ 'This generates routing hints that map each file to a model tier:\n' +
5754
+ '- **fast** (haiku/gpt-4o-mini) — config, markup, trivial utilities\n' +
5755
+ '- **balanced** (sonnet/gpt-4o) — standard application code\n' +
5756
+ '- **powerful** (opus/gpt-4-turbo) — complex, security-critical, or large modules'
5757
+ );
5758
+ }
5765
5759
 
5766
- // Build synthetic fileEntries for the classifier
5767
- // We don't have live sig arrays here, so rebuild from the context blocks
5768
- const entries = [];
5769
- const blocks = content.split(/^### /m).slice(1); // slice past the header
5770
- for (const block of blocks) {
5771
- const firstLine = block.split('\n')[0].trim();
5772
- const codeBlock = block.match(/```\n([\s\S]*?)```/);
5773
- const sigs = codeBlock ? codeBlock[1].trim().split('\n').filter(Boolean) : [];
5774
- entries.push({ filePath: path.join(cwd, firstLine), sigs });
5775
- }
5760
+ // Parse file list from context (### headings are file paths)
5761
+ const content = fs.readFileSync(contextPath, 'utf8');
5762
+ const fileRels = content.split('\n')
5763
+ .filter((l) => l.startsWith('### '))
5764
+ .map((l) => l.slice(4).trim());
5776
5765
 
5777
- try {
5778
- const { classifyAll } = __require('./src/routing/classifier');
5779
- const { formatRoutingSection } = __require('./src/routing/hints');
5780
- const groups = classifyAll(entries, cwd);
5781
- return formatRoutingSection(groups);
5782
- } catch (err) {
5783
- return `_Routing classification failed: ${err.message}_`;
5766
+ // Build synthetic fileEntries for the classifier
5767
+ // We don't have live sig arrays here, so rebuild from the context blocks
5768
+ const entries = [];
5769
+ const blocks = content.split(/^### /m).slice(1); // slice past the header
5770
+ for (const block of blocks) {
5771
+ const firstLine = block.split('\n')[0].trim();
5772
+ const codeBlock = block.match(/```\n([\s\S]*?)```/);
5773
+ const sigs = codeBlock ? codeBlock[1].trim().split('\n').filter(Boolean) : [];
5774
+ entries.push({ filePath: path.join(cwd, firstLine), sigs });
5775
+ }
5776
+
5777
+ try {
5778
+ const { classifyAll } = __require('./src/routing/classifier');
5779
+ const { formatRoutingSection } = __require('./src/routing/hints');
5780
+ const groups = classifyAll(entries, cwd);
5781
+ return formatRoutingSection(groups);
5782
+ } catch (err) {
5783
+ return `_Routing classification failed: ${err.message}_`;
5784
+ }
5784
5785
  }
5785
- }
5786
5786
 
5787
- /**
5788
- * explain_file({ path }) → string
5789
- *
5790
- * Returns a file's signatures, its direct imports, and files that import it.
5791
- * path: relative path from project root (e.g. 'src/services/auth.ts')
5792
- */
5793
- function explainFile(args, cwd) {
5794
- if (!args || !args.path) return 'Missing required argument: path';
5787
+ /**
5788
+ * explain_file({ path }) → string
5789
+ *
5790
+ * Returns a file's signatures, its direct imports, and files that import it.
5791
+ * path: relative path from project root (e.g. 'src/services/auth.ts')
5792
+ */
5793
+ function explainFile(args, cwd) {
5794
+ if (!args || !args.path) return 'Missing required argument: path';
5795
5795
 
5796
- const targetRel = args.path.replace(/\\/g, '/').replace(/^\//, '');
5797
- const targetAbs = path.resolve(cwd, targetRel);
5798
- const contextPath = path.join(cwd, CONTEXT_FILE);
5796
+ const targetRel = args.path.replace(/\\/g, '/').replace(/^\//, '');
5797
+ const targetAbs = path.resolve(cwd, targetRel);
5798
+ const contextPath = path.join(cwd, CONTEXT_FILE);
5799
5799
 
5800
- const lines = ['# explain_file: ' + targetRel, ''];
5800
+ const lines = ['# explain_file: ' + targetRel, ''];
5801
5801
 
5802
- // ── Signatures (hot + cold + cache via buildSigIndex) ───────────────────
5803
- lines.push('## Signatures');
5804
- let indexedFiles = [];
5802
+ // ── Signatures (hot + cold + cache via buildSigIndex) ───────────────────
5803
+ lines.push('## Signatures');
5804
+ let indexedFiles = [];
5805
5805
 
5806
- try {
5807
- const { buildSigIndex } = __require('./src/retrieval/ranker');
5808
- const index = buildSigIndex(cwd);
5809
- let sigs = index.get(targetRel);
5810
- if (!sigs) {
5811
- for (const [file, fileSigs] of index.entries()) {
5812
- if (file === targetRel || file.endsWith('/' + targetRel) || targetRel.endsWith('/' + file)) {
5813
- sigs = fileSigs;
5814
- break;
5806
+ try {
5807
+ const { buildSigIndex } = __require('./src/retrieval/ranker');
5808
+ const index = buildSigIndex(cwd);
5809
+ let sigs = index.get(targetRel);
5810
+ if (!sigs) {
5811
+ for (const [file, fileSigs] of index.entries()) {
5812
+ if (file === targetRel || file.endsWith('/' + targetRel) || targetRel.endsWith('/' + file)) {
5813
+ sigs = fileSigs;
5814
+ break;
5815
+ }
5815
5816
  }
5816
5817
  }
5818
+ if (sigs && sigs.length > 0) {
5819
+ lines.push(...sigs);
5820
+ } else {
5821
+ lines.push('_No signatures indexed for this file. Run: node gen-context.js_');
5822
+ }
5823
+ indexedFiles = [...index.keys()].map((rel) => path.resolve(cwd, rel));
5824
+ } catch (_) {
5825
+ lines.push('_No context file found. Run: node gen-context.js_');
5817
5826
  }
5818
- if (sigs && sigs.length > 0) {
5819
- lines.push(...sigs);
5820
- } else {
5821
- lines.push('_No signatures indexed for this file. Run: node gen-context.js_');
5827
+
5828
+ if (!fs.existsSync(targetAbs)) {
5829
+ lines.push('');
5830
+ lines.push('> File not found on disk: ' + targetRel);
5831
+ return lines.join('\n');
5822
5832
  }
5823
- indexedFiles = [...index.keys()].map((rel) => path.resolve(cwd, rel));
5824
- } catch (_) {
5825
- lines.push('_No context file found. Run: node gen-context.js_');
5826
- }
5827
5833
 
5828
- if (!fs.existsSync(targetAbs)) {
5829
5834
  lines.push('');
5830
- lines.push('> File not found on disk: ' + targetRel);
5831
- return lines.join('\n');
5832
- }
5833
5835
 
5834
- lines.push('');
5835
-
5836
- // ── Direct imports ────────────────────────────────────────────────────────
5837
- lines.push('## Imports (direct dependencies)');
5838
- try {
5839
- const { extractImports } = __require('./src/map/import-graph');
5840
- const fileContent = fs.readFileSync(targetAbs, 'utf8');
5841
- const fileSet = new Set(indexedFiles);
5842
- fileSet.add(targetAbs);
5843
- const imports = extractImports(targetAbs, fileContent, fileSet);
5844
- if (imports.length > 0) {
5845
- for (const imp of imports) lines.push('- ' + path.relative(cwd, imp).replace(/\\/g, '/'));
5846
- } else {
5847
- lines.push('_No resolvable relative imports found._');
5836
+ // ── Direct imports ────────────────────────────────────────────────────────
5837
+ lines.push('## Imports (direct dependencies)');
5838
+ try {
5839
+ const { extractImports } = __require('./src/map/import-graph');
5840
+ const fileContent = fs.readFileSync(targetAbs, 'utf8');
5841
+ const fileSet = new Set(indexedFiles);
5842
+ fileSet.add(targetAbs);
5843
+ const imports = extractImports(targetAbs, fileContent, fileSet);
5844
+ if (imports.length > 0) {
5845
+ for (const imp of imports) lines.push('- ' + path.relative(cwd, imp).replace(/\\/g, '/'));
5846
+ } else {
5847
+ lines.push('_No resolvable relative imports found._');
5848
+ }
5849
+ } catch (err) {
5850
+ lines.push('_Could not analyze imports: ' + err.message + '_');
5848
5851
  }
5849
- } catch (err) {
5850
- lines.push('_Could not analyze imports: ' + err.message + '_');
5851
- }
5852
5852
 
5853
- lines.push('');
5853
+ lines.push('');
5854
5854
 
5855
- // ── Callers (reverse-import lookup) ──────────────────────────────────────
5856
- lines.push('## Callers (files that import this file)');
5857
- try {
5858
- const { extractImports } = __require('./src/map/import-graph');
5859
- const fileSet = new Set(indexedFiles);
5860
- fileSet.add(targetAbs);
5861
- const callers = [];
5862
- for (const f of indexedFiles) {
5863
- if (f === targetAbs || !fs.existsSync(f)) continue;
5864
- try {
5865
- const fc = fs.readFileSync(f, 'utf8');
5866
- const imps = extractImports(f, fc, fileSet);
5867
- if (imps.includes(targetAbs)) callers.push(path.relative(cwd, f).replace(/\\/g, '/'));
5868
- } catch (_) {}
5869
- }
5870
- if (callers.length > 0) {
5871
- for (const c of callers) lines.push('- ' + c);
5872
- } else {
5873
- lines.push('_No indexed files import this file._');
5855
+ // ── Callers (reverse-import lookup) ──────────────────────────────────────
5856
+ lines.push('## Callers (files that import this file)');
5857
+ try {
5858
+ const { extractImports } = __require('./src/map/import-graph');
5859
+ const fileSet = new Set(indexedFiles);
5860
+ fileSet.add(targetAbs);
5861
+ const callers = [];
5862
+ for (const f of indexedFiles) {
5863
+ if (f === targetAbs || !fs.existsSync(f)) continue;
5864
+ try {
5865
+ const fc = fs.readFileSync(f, 'utf8');
5866
+ const imps = extractImports(f, fc, fileSet);
5867
+ if (imps.includes(targetAbs)) callers.push(path.relative(cwd, f).replace(/\\/g, '/'));
5868
+ } catch (_) {}
5869
+ }
5870
+ if (callers.length > 0) {
5871
+ for (const c of callers) lines.push('- ' + c);
5872
+ } else {
5873
+ lines.push('_No indexed files import this file._');
5874
+ }
5875
+ } catch (err) {
5876
+ lines.push('_Could not analyze callers: ' + err.message + '_');
5874
5877
  }
5875
- } catch (err) {
5876
- lines.push('_Could not analyze callers: ' + err.message + '_');
5877
- }
5878
5878
 
5879
- return lines.join('\n');
5880
- }
5879
+ return lines.join('\n');
5880
+ }
5881
5881
 
5882
- /**
5883
- * list_modules({}) → string
5884
- *
5885
- * Lists all srcDir modules present in the context file, sorted by token count
5886
- * descending. Helps agents decide which module to query with read_context.
5887
- */
5888
- function listModules(args, cwd) {
5889
- try {
5890
- const { buildSigIndex } = __require('./src/retrieval/ranker');
5891
- const index = buildSigIndex(cwd);
5892
- if (index.size === 0) {
5893
- return 'No context file found. Run: node gen-context.js';
5894
- }
5882
+ /**
5883
+ * list_modules({}) → string
5884
+ *
5885
+ * Lists all srcDir modules present in the context file, sorted by token count
5886
+ * descending. Helps agents decide which module to query with read_context.
5887
+ */
5888
+ function listModules(args, cwd) {
5889
+ try {
5890
+ const { buildSigIndex } = __require('./src/retrieval/ranker');
5891
+ const index = buildSigIndex(cwd);
5892
+ if (index.size === 0) {
5893
+ return 'No context file found. Run: node gen-context.js';
5894
+ }
5895
5895
 
5896
- const groups = {};
5897
- for (const [rel, sigs] of index.entries()) {
5898
- const parts = rel.replace(/\\/g, '/').split('/');
5899
- const mod = parts.length > 1 ? parts[0] : '.';
5900
- if (!groups[mod]) groups[mod] = { fileCount: 0, tokenCount: 0 };
5901
- groups[mod].fileCount++;
5902
- groups[mod].tokenCount += Math.ceil(sigs.join('\n').length / 4);
5903
- }
5896
+ const groups = {};
5897
+ for (const [rel, sigs] of index.entries()) {
5898
+ const parts = rel.replace(/\\/g, '/').split('/');
5899
+ const mod = parts.length > 1 ? parts[0] : '.';
5900
+ if (!groups[mod]) groups[mod] = { fileCount: 0, tokenCount: 0 };
5901
+ groups[mod].fileCount++;
5902
+ groups[mod].tokenCount += Math.ceil(sigs.join('\n').length / 4);
5903
+ }
5904
5904
 
5905
- const sorted = Object.entries(groups)
5906
- .map(([mod, data]) => ({ module: mod, fileCount: data.fileCount, tokenCount: data.tokenCount }))
5907
- .sort((a, b) => b.tokenCount - a.tokenCount);
5905
+ const sorted = Object.entries(groups)
5906
+ .map(([mod, data]) => ({ module: mod, fileCount: data.fileCount, tokenCount: data.tokenCount }))
5907
+ .sort((a, b) => b.tokenCount - a.tokenCount);
5908
5908
 
5909
- if (sorted.length === 0) return 'No modules found in context file.';
5909
+ if (sorted.length === 0) return 'No modules found in context file.';
5910
5910
 
5911
- const total = sorted.reduce((s, m) => s + m.tokenCount, 0);
5911
+ const total = sorted.reduce((s, m) => s + m.tokenCount, 0);
5912
5912
 
5913
- return [
5914
- '# Modules',
5915
- '',
5916
- '| Module | Files | Tokens |',
5917
- '|--------|-------|--------|',
5918
- ...sorted.map((m) => `| ${m.module} | ${m.fileCount} | ~${m.tokenCount} |`),
5919
- '',
5920
- `**Total context tokens: ~${total}**`,
5921
- '',
5922
- '_Use `read_context({ module: "name" })` to get signatures for a specific module._',
5923
- ].join('\n');
5924
- } catch (err) {
5925
- return `_list_modules failed: ${err.message}_`;
5913
+ return [
5914
+ '# Modules',
5915
+ '',
5916
+ '| Module | Files | Tokens |',
5917
+ '|--------|-------|--------|',
5918
+ ...sorted.map((m) => `| ${m.module} | ${m.fileCount} | ~${m.tokenCount} |`),
5919
+ '',
5920
+ `**Total context tokens: ~${total}**`,
5921
+ '',
5922
+ '_Use `read_context({ module: "name" })` to get signatures for a specific module._',
5923
+ ].join('\n');
5924
+ } catch (err) {
5925
+ return `_list_modules failed: ${err.message}_`;
5926
+ }
5926
5927
  }
5927
- }
5928
5928
 
5929
- /**
5930
- * query_context({ query, topK? }) → string
5931
- *
5932
- * Ranks context-file entries by relevance to the query and returns the
5933
- * top-K most relevant files with their signatures and scores.
5934
- */
5935
- function queryContext(args, cwd) {
5936
- if (!args || !args.query) return 'Missing required argument: query';
5929
+ /**
5930
+ * query_context({ query, topK? }) → string
5931
+ *
5932
+ * Ranks context-file entries by relevance to the query and returns the
5933
+ * top-K most relevant files with their signatures and scores.
5934
+ */
5935
+ function queryContext(args, cwd) {
5936
+ if (!args || !args.query) return 'Missing required argument: query';
5937
5937
 
5938
- try {
5939
- const { rank, buildSigIndex, formatRankTable } = __require('./src/retrieval/ranker');
5940
- const { buildFromCwd } = __require('./src/graph/builder');
5941
- const index = buildSigIndex(cwd);
5942
- if (index.size === 0) return 'No signatures indexed. Run: node gen-context.js';
5943
-
5944
- const topK = Math.min(Math.max(1, parseInt(args.topK, 10) || 10), 25);
5945
- // Build dependency graph for neighbor boost — non-fatal if it fails
5946
- let graph = null;
5947
- try { graph = buildFromCwd(cwd); } catch (_) {}
5948
- const results = rank(args.query, index, { topK, cwd, graph });
5949
- return formatRankTable(results, args.query);
5950
- } catch (err) {
5951
- return `_query_context failed: ${err.message}_`;
5938
+ try {
5939
+ const { rank, buildSigIndex, formatRankTable } = __require('./src/retrieval/ranker');
5940
+ const { buildFromCwd } = __require('./src/graph/builder');
5941
+ const index = buildSigIndex(cwd);
5942
+ if (index.size === 0) return 'No signatures indexed. Run: node gen-context.js';
5943
+
5944
+ const topK = Math.min(Math.max(1, parseInt(args.topK, 10) || 10), 25);
5945
+ // Build dependency graph for neighbor boost — non-fatal if it fails
5946
+ let graph = null;
5947
+ try { graph = buildFromCwd(cwd); } catch (_) {}
5948
+ const results = rank(args.query, index, { topK, cwd, graph });
5949
+ return formatRankTable(results, args.query);
5950
+ } catch (err) {
5951
+ return `_query_context failed: ${err.message}_`;
5952
+ }
5952
5953
  }
5953
- }
5954
5954
 
5955
- /**
5956
- * get_impact({ file, depth? }) → string
5957
- *
5958
- * Returns a formatted markdown impact report for the given file:
5959
- * direct importers, transitive importers, affected tests, affected routes.
5960
- */
5961
- function getImpact(args, cwd) {
5962
- if (!args || !args.file) return 'Missing required argument: file';
5955
+ /**
5956
+ * get_impact({ file, depth? }) → string
5957
+ *
5958
+ * Returns a formatted markdown impact report for the given file:
5959
+ * direct importers, transitive importers, affected tests, affected routes.
5960
+ */
5961
+ function getImpact(args, cwd) {
5962
+ if (!args || !args.file) return 'Missing required argument: file';
5963
5963
 
5964
- try {
5965
- const { analyzeImpact, formatImpact } = __require('./src/graph/impact');
5966
- const depth = Math.max(0, parseInt(args.depth, 10) || 3);
5967
- const results = analyzeImpact(args.file, cwd, { depth });
5968
- if (results.length === 0) return `No impact data for: ${args.file}`;
5969
- return results.map((r) => formatImpact(r.impact)).join('\n\n---\n\n');
5970
- } catch (err) {
5971
- return `_get_impact failed: ${err.message}_`;
5964
+ try {
5965
+ const { analyzeImpact, formatImpact } = __require('./src/graph/impact');
5966
+ const depth = Math.max(0, parseInt(args.depth, 10) || 3);
5967
+ const results = analyzeImpact(args.file, cwd, { depth });
5968
+ if (results.length === 0) return `No impact data for: ${args.file}`;
5969
+ return results.map((r) => formatImpact(r.impact)).join('\n\n---\n\n');
5970
+ } catch (err) {
5971
+ return `_get_impact failed: ${err.message}_`;
5972
+ }
5972
5973
  }
5973
- }
5974
5974
 
5975
- /**
5976
- * get_lines({ file, start, end }) → string
5977
- *
5978
- * Surgical Context demand-driven fetch: returns an exact, clamped line range from a
5979
- * source file. The path is resolved inside the project root (no traversal escape) and
5980
- * the returned lines are secret-scanned via the same redactor used for signatures.
5981
- */
5982
- function getLines(args, cwd) {
5983
- if (!args || !args.file) return 'Missing required argument: file';
5975
+ /**
5976
+ * get_lines({ file, start, end }) → string
5977
+ *
5978
+ * Surgical Context demand-driven fetch: returns an exact, clamped line range from a
5979
+ * source file. The path is resolved inside the project root (no traversal escape) and
5980
+ * the returned lines are secret-scanned via the same redactor used for signatures.
5981
+ */
5982
+ function getLines(args, cwd) {
5983
+ if (!args || !args.file) return 'Missing required argument: file';
5984
5984
 
5985
- const rel = String(args.file).replace(/\\/g, '/').replace(/^\//, '');
5986
- const abs = path.resolve(cwd, rel);
5985
+ const rel = String(args.file).replace(/\\/g, '/').replace(/^\//, '');
5986
+ const abs = path.resolve(cwd, rel);
5987
5987
 
5988
- // Sandbox: refuse paths that resolve outside the project root.
5989
- const root = path.resolve(cwd);
5990
- if (abs !== root && !abs.startsWith(root + path.sep)) {
5991
- return `Refused: ${rel} resolves outside the project root`;
5992
- }
5993
- if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
5994
- return `File not found: ${rel}`;
5995
- }
5996
-
5997
- const start = parseInt(args.start, 10);
5998
- const end = parseInt(args.end, 10);
5999
- if (!Number.isFinite(start) || !Number.isFinite(end)) {
6000
- return 'Arguments "start" and "end" must be numbers (1-based line numbers).';
6001
- }
5988
+ // Sandbox: refuse paths that resolve outside the project root.
5989
+ const root = path.resolve(cwd);
5990
+ if (abs !== root && !abs.startsWith(root + path.sep)) {
5991
+ return `Refused: ${rel} resolves outside the project root`;
5992
+ }
5993
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
5994
+ return `File not found: ${rel}`;
5995
+ }
6002
5996
 
6003
- let lines;
6004
- try {
6005
- lines = fs.readFileSync(abs, 'utf8').split('\n');
6006
- } catch (err) {
6007
- return `Could not read ${rel}: ${err.message}`;
6008
- }
5997
+ const start = parseInt(args.start, 10);
5998
+ const end = parseInt(args.end, 10);
5999
+ if (!Number.isFinite(start) || !Number.isFinite(end)) {
6000
+ return 'Arguments "start" and "end" must be numbers (1-based line numbers).';
6001
+ }
6009
6002
 
6010
- const total = lines.length;
6011
- const from = Math.max(1, Math.min(start, end));
6012
- const to = Math.min(total, Math.max(start, end));
6013
- if (from > total) return `${rel} has only ${total} lines; requested ${start}-${end}`;
6003
+ let lines;
6004
+ try {
6005
+ lines = fs.readFileSync(abs, 'utf8').split('\n');
6006
+ } catch (err) {
6007
+ return `Could not read ${rel}: ${err.message}`;
6008
+ }
6014
6009
 
6015
- const slice = lines.slice(from - 1, to);
6010
+ const total = lines.length;
6011
+ const from = Math.max(1, Math.min(start, end));
6012
+ const to = Math.min(total, Math.max(start, end));
6013
+ if (from > total) return `${rel} has only ${total} lines; requested ${start}-${end}`;
6016
6014
 
6017
- // Redaction scan: reuse the signature secret scanner line-by-line.
6018
- let safeLines = slice;
6019
- try {
6020
- const { scan } = __require('./src/security/scanner');
6021
- safeLines = scan(slice, rel).safe;
6022
- } catch (_) {} // non-fatal: fall back to raw slice
6015
+ const slice = lines.slice(from - 1, to);
6023
6016
 
6024
- return [
6025
- `# ${rel}:${from}-${to}`,
6026
- '```',
6027
- ...safeLines,
6028
- '```',
6029
- ].join('\n');
6030
- }
6017
+ // Redaction scan: reuse the signature secret scanner line-by-line.
6018
+ let safeLines = slice;
6019
+ try {
6020
+ const { scan } = __require('./src/security/scanner');
6021
+ safeLines = scan(slice, rel).safe;
6022
+ } catch (_) {} // non-fatal: fall back to raw slice
6031
6023
 
6032
- /**
6033
- * read_memory({ limit? }) → string
6034
- *
6035
- * Recall the cross-session decision log (notes) plus the last ranking-session
6036
- * focus, formatted for an agent to consume at the start of a task.
6037
- */
6038
- function readMemory(args, cwd) {
6039
- let limit = parseInt(args && args.limit, 10);
6040
- if (!Number.isFinite(limit) || limit <= 0) limit = 10;
6041
- limit = Math.min(limit, 50);
6024
+ return [
6025
+ `# ${rel}:${from}-${to}`,
6026
+ '```',
6027
+ ...safeLines,
6028
+ '```',
6029
+ ].join('\n');
6030
+ }
6042
6031
 
6043
- const out = ['# SigMap memory'];
6032
+ /**
6033
+ * read_memory({ limit? }) → string
6034
+ *
6035
+ * Recall the cross-session decision log (notes) plus the last ranking-session
6036
+ * focus, formatted for an agent to consume at the start of a task.
6037
+ */
6038
+ function readMemory(args, cwd) {
6039
+ let limit = parseInt(args && args.limit, 10);
6040
+ if (!Number.isFinite(limit) || limit <= 0) limit = 10;
6041
+ limit = Math.min(limit, 50);
6044
6042
 
6045
- let notes = [];
6046
- try {
6047
- const { readNotes, formatNotes } = __require('./src/session/notes');
6048
- notes = readNotes(cwd, limit);
6049
- out.push('');
6050
- out.push(`## Recent notes (${notes.length})`);
6051
- // Most recent first for quick scanning.
6052
- out.push(formatNotes(notes.slice().reverse()));
6053
- } catch (_) {
6054
- out.push('');
6055
- out.push('_No notes available._');
6056
- }
6043
+ const out = ['# SigMap memory'];
6057
6044
 
6058
- // Last ranking-session focus (if any) — extends src/session/memory.js.
6059
- try {
6060
- const { loadSession } = __require('./src/session/memory');
6061
- const s = loadSession(cwd);
6062
- if (s && (s.lastQuery || (s.topFiles && s.topFiles.length))) {
6045
+ let notes = [];
6046
+ try {
6047
+ const { readNotes, formatNotes } = __require('./src/session/notes');
6048
+ notes = readNotes(cwd, limit);
6049
+ out.push('');
6050
+ out.push(`## Recent notes (${notes.length})`);
6051
+ // Most recent first for quick scanning.
6052
+ out.push(formatNotes(notes.slice().reverse()));
6053
+ } catch (_) {
6063
6054
  out.push('');
6064
- out.push('## Last session');
6065
- if (s.lastQuery) out.push(`**Last query:** ${s.lastQuery}`);
6066
- if (s.topFiles && s.topFiles.length) {
6067
- out.push(`**Focus files:** ${s.topFiles.map((f) => f.file).slice(0, 5).join(', ')}`);
6055
+ out.push('_No notes available._');
6056
+ }
6057
+
6058
+ // Last ranking-session focus (if any) extends src/session/memory.js.
6059
+ try {
6060
+ const { loadSession } = __require('./src/session/memory');
6061
+ const s = loadSession(cwd);
6062
+ if (s && (s.lastQuery || (s.topFiles && s.topFiles.length))) {
6063
+ out.push('');
6064
+ out.push('## Last session');
6065
+ if (s.lastQuery) out.push(`**Last query:** ${s.lastQuery}`);
6066
+ if (s.topFiles && s.topFiles.length) {
6067
+ out.push(`**Focus files:** ${s.topFiles.map((f) => f.file).slice(0, 5).join(', ')}`);
6068
+ }
6068
6069
  }
6070
+ } catch (_) { /* session optional */ }
6071
+
6072
+ return out.join('\n');
6073
+ }
6074
+
6075
+ /**
6076
+ * get_callee_signatures — return the exact defining signature(s) of named
6077
+ * symbols from the index, so an agent never guesses a callee's parameter types.
6078
+ * Unknown names get a closest-match suggestion.
6079
+ * @param {{symbols:string[]}} args
6080
+ * @param {string} cwd
6081
+ */
6082
+ function getCalleeSignatures(args, cwd) {
6083
+ const symbols = args && Array.isArray(args.symbols)
6084
+ ? args.symbols.map((s) => String(s).trim()).filter(Boolean)
6085
+ : null;
6086
+ if (!symbols || symbols.length === 0) {
6087
+ return 'Missing required argument: symbols (non-empty string[])';
6069
6088
  }
6070
- } catch (_) { /* session optional */ }
6071
6089
 
6072
- return out.join('\n');
6073
- }
6090
+ try {
6091
+ const { buildSigIndex } = __require('./src/retrieval/ranker');
6092
+ const { buildSymbolCandidates, closestMatch, formatSuggestion } = __require('./src/verify/closest-match');
6093
+ const index = buildSigIndex(cwd);
6094
+ if (index.size === 0) return 'No context file found. Run: node gen-context.js';
6095
+
6096
+ // Extract the defining symbol name from a signature line (same rules as
6097
+ // buildSymbolCandidates) so we match definitions, not param occurrences.
6098
+ const defName = (sig) => {
6099
+ const cleaned = String(sig).replace(/\s*:\d+(?:-\d+)?\s*$/, '');
6100
+ const m = cleaned.match(/\b(?:async\s+function|function|class|def|interface|type|enum|const|let|var)\s+([A-Za-z_$][\w$]*)/)
6101
+ || cleaned.match(/([A-Za-z_$][\w$]*)\s*\(/);
6102
+ return m ? m[1] : null;
6103
+ };
6074
6104
 
6075
- module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory };
6105
+ const candidates = buildSymbolCandidates(index);
6106
+ const blocks = [];
6107
+ for (const symbol of symbols) {
6108
+ const matches = [];
6109
+ for (const [file, sigs] of index.entries()) {
6110
+ for (const sig of sigs) {
6111
+ if (defName(sig) === symbol) matches.push(`${sig} (${file})`);
6112
+ }
6113
+ }
6114
+ if (matches.length === 0) {
6115
+ const cm = closestMatch(symbol, candidates);
6116
+ const hint = cm ? ' — ' + formatSuggestion(cm) : '';
6117
+ blocks.push(`### ${symbol}\n_not found in index${hint}_`);
6118
+ } else {
6119
+ blocks.push(`### ${symbol}\n${matches.join('\n')}`);
6120
+ }
6121
+ }
6122
+ return blocks.join('\n\n');
6123
+ } catch (err) {
6124
+ return `_get_callee_signatures failed: ${err.message}_`;
6125
+ }
6126
+ }
6076
6127
 
6128
+ module.exports = { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures };
6129
+
6077
6130
  };
6078
6131
  // ── ./src/learning/weights ──
6079
6132
  __factories["./src/learning/weights"] = function(module, exports) {
@@ -6234,363 +6287,382 @@ __factories["./src/learning/weights"] = function(module, exports) {
6234
6287
 
6235
6288
  // ── ./src/mcp/server ──
6236
6289
  __factories["./src/mcp/server"] = function(module, exports) {
6237
- 'use strict';
6238
-
6239
- /**
6240
- * SigMap MCP server — zero npm dependencies.
6241
- *
6242
- * Wire protocol: JSON-RPC 2.0 over stdio.
6243
- * One JSON object per line on both stdin and stdout.
6244
- *
6245
- * Supported methods:
6246
- * initializeserverInfo + capabilities
6247
- * tools/list11 tool definitions
6248
- * tools/call → dispatch to handler, return result
6249
- */
6250
-
6251
- const readline = require('readline');
6252
- const { TOOLS } = __require('./src/mcp/tools');
6253
- const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory } = __require('./src/mcp/handlers');
6254
-
6255
- const SERVER_INFO = {
6256
- name: 'sigmap',
6257
- version: '7.2.0',
6258
- description: 'SigMap MCP server — code signatures on demand',
6259
- };
6260
-
6261
- // ---------------------------------------------------------------------------
6262
- // JSON-RPC helpers
6263
- // ---------------------------------------------------------------------------
6264
- function respond(id, result) {
6265
- process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n');
6266
- }
6267
-
6268
- function respondError(id, code, message) {
6269
- process.stdout.write(
6270
- JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } }) + '\n'
6271
- );
6272
- }
6290
+
6291
+ /**
6292
+ * SigMap MCP server — zero npm dependencies.
6293
+ *
6294
+ * Wire protocol: JSON-RPC 2.0 over stdio.
6295
+ * One JSON object per line on both stdin and stdout.
6296
+ *
6297
+ * Supported methods:
6298
+ * initialize → serverInfo + capabilities
6299
+ * tools/list11 tool definitions
6300
+ * tools/calldispatch to handler, return result
6301
+ */
6273
6302
 
6274
- // ---------------------------------------------------------------------------
6275
- // Method dispatcher
6276
- // ---------------------------------------------------------------------------
6277
- function dispatch(msg, cwd) {
6278
- const { method, id, params } = msg;
6303
+ const readline = require('readline');
6304
+ const { TOOLS } = __require('./src/mcp/tools');
6305
+ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, explainFile, listModules, queryContext, getImpact, getLines, readMemory, getCalleeSignatures } = __require('./src/mcp/handlers');
6279
6306
 
6280
- // Notifications (no id) need no response
6281
- if (method === 'notifications/initialized' || method === 'notifications/cancelled') {
6282
- return;
6283
- }
6307
+ const SERVER_INFO = {
6308
+ name: 'sigmap',
6309
+ version: '7.3.0',
6310
+ description: 'SigMap MCP server — code signatures on demand',
6311
+ };
6284
6312
 
6285
- if (method === 'initialize') {
6286
- respond(id, {
6287
- protocolVersion: (params && params.protocolVersion) || '2024-11-05',
6288
- serverInfo: SERVER_INFO,
6289
- capabilities: { tools: {} },
6290
- });
6291
- return;
6313
+ // ---------------------------------------------------------------------------
6314
+ // JSON-RPC helpers
6315
+ // ---------------------------------------------------------------------------
6316
+ function respond(id, result) {
6317
+ process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n');
6292
6318
  }
6293
6319
 
6294
- if (method === 'tools/list') {
6295
- respond(id, { tools: TOOLS });
6296
- return;
6320
+ function respondError(id, code, message) {
6321
+ process.stdout.write(
6322
+ JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } }) + '\n'
6323
+ );
6297
6324
  }
6298
6325
 
6299
- if (method === 'tools/call') {
6300
- const name = params && params.name;
6301
- const args = (params && params.arguments) || {};
6326
+ // ---------------------------------------------------------------------------
6327
+ // Method dispatcher
6328
+ // ---------------------------------------------------------------------------
6329
+ function dispatch(msg, cwd) {
6330
+ const { method, id, params } = msg;
6302
6331
 
6303
- let text;
6304
- try {
6305
- if (name === 'read_context') text = readContext(args, cwd);
6306
- else if (name === 'search_signatures') text = searchSignatures(args, cwd);
6307
- else if (name === 'get_map') text = getMap(args, cwd);
6308
- else if (name === 'create_checkpoint') text = createCheckpoint(args, cwd);
6309
- else if (name === 'get_routing') text = getRouting(args, cwd);
6310
- else if (name === 'explain_file') text = explainFile(args, cwd);
6311
- else if (name === 'list_modules') text = listModules(args, cwd);
6312
- else if (name === 'query_context') text = queryContext(args, cwd);
6313
- else if (name === 'get_impact') text = getImpact(args, cwd);
6314
- else if (name === 'get_lines') text = getLines(args, cwd);
6315
- else if (name === 'read_memory') text = readMemory(args, cwd);
6316
- else {
6317
- respondError(id, -32601, `Unknown tool: ${name}`);
6318
- return;
6319
- }
6320
- } catch (err) {
6321
- respondError(id, -32603, `Tool error: ${err.message}`);
6332
+ // Notifications (no id) need no response
6333
+ if (method === 'notifications/initialized' || method === 'notifications/cancelled') {
6322
6334
  return;
6323
6335
  }
6324
6336
 
6325
- respond(id, {
6326
- content: [{ type: 'text', text: String(text) }],
6327
- });
6328
- return;
6329
- }
6337
+ if (method === 'initialize') {
6338
+ respond(id, {
6339
+ protocolVersion: (params && params.protocolVersion) || '2024-11-05',
6340
+ serverInfo: SERVER_INFO,
6341
+ capabilities: { tools: {} },
6342
+ });
6343
+ return;
6344
+ }
6330
6345
 
6331
- // Unknown method
6332
- if (id !== undefined && id !== null) {
6333
- respondError(id, -32601, `Method not found: ${method}`);
6334
- }
6335
- }
6346
+ if (method === 'tools/list') {
6347
+ respond(id, { tools: TOOLS });
6348
+ return;
6349
+ }
6336
6350
 
6337
- // ---------------------------------------------------------------------------
6338
- // Server entry point
6339
- // ---------------------------------------------------------------------------
6340
- function start(cwd) {
6341
- const rl = readline.createInterface({ input: process.stdin, terminal: false });
6351
+ if (method === 'tools/call') {
6352
+ const name = params && params.name;
6353
+ const args = (params && params.arguments) || {};
6342
6354
 
6343
- rl.on('line', (line) => {
6344
- const trimmed = line.trim();
6345
- if (!trimmed) return;
6355
+ let text;
6356
+ try {
6357
+ if (name === 'read_context') text = readContext(args, cwd);
6358
+ else if (name === 'search_signatures') text = searchSignatures(args, cwd);
6359
+ else if (name === 'get_map') text = getMap(args, cwd);
6360
+ else if (name === 'create_checkpoint') text = createCheckpoint(args, cwd);
6361
+ else if (name === 'get_routing') text = getRouting(args, cwd);
6362
+ else if (name === 'explain_file') text = explainFile(args, cwd);
6363
+ else if (name === 'list_modules') text = listModules(args, cwd);
6364
+ else if (name === 'query_context') text = queryContext(args, cwd);
6365
+ else if (name === 'get_impact') text = getImpact(args, cwd);
6366
+ else if (name === 'get_lines') text = getLines(args, cwd);
6367
+ else if (name === 'read_memory') text = readMemory(args, cwd);
6368
+ else if (name === 'get_callee_signatures') text = getCalleeSignatures(args, cwd);
6369
+ else {
6370
+ respondError(id, -32601, `Unknown tool: ${name}`);
6371
+ return;
6372
+ }
6373
+ } catch (err) {
6374
+ respondError(id, -32603, `Tool error: ${err.message}`);
6375
+ return;
6376
+ }
6346
6377
 
6347
- let msg;
6348
- try {
6349
- msg = JSON.parse(trimmed);
6350
- } catch (_) {
6351
- // Cannot respond without a valid id — ignore malformed input
6378
+ respond(id, {
6379
+ content: [{ type: 'text', text: String(text) }],
6380
+ });
6352
6381
  return;
6353
6382
  }
6354
6383
 
6355
- try {
6356
- dispatch(msg, cwd);
6357
- } catch (err) {
6358
- const id = (msg && msg.id) != null ? msg.id : null;
6359
- respondError(id, -32603, `Internal error: ${err.message}`);
6384
+ // Unknown method
6385
+ if (id !== undefined && id !== null) {
6386
+ respondError(id, -32601, `Method not found: ${method}`);
6360
6387
  }
6361
- });
6388
+ }
6362
6389
 
6363
- rl.on('close', () => {
6364
- process.exit(0);
6365
- });
6366
- }
6390
+ // ---------------------------------------------------------------------------
6391
+ // Server entry point
6392
+ // ---------------------------------------------------------------------------
6393
+ function start(cwd) {
6394
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
6395
+
6396
+ rl.on('line', (line) => {
6397
+ const trimmed = line.trim();
6398
+ if (!trimmed) return;
6399
+
6400
+ let msg;
6401
+ try {
6402
+ msg = JSON.parse(trimmed);
6403
+ } catch (_) {
6404
+ // Cannot respond without a valid id — ignore malformed input
6405
+ return;
6406
+ }
6407
+
6408
+ try {
6409
+ dispatch(msg, cwd);
6410
+ } catch (err) {
6411
+ const id = (msg && msg.id) != null ? msg.id : null;
6412
+ respondError(id, -32603, `Internal error: ${err.message}`);
6413
+ }
6414
+ });
6367
6415
 
6368
- module.exports = { start };
6416
+ rl.on('close', () => {
6417
+ process.exit(0);
6418
+ });
6419
+ }
6369
6420
 
6421
+ module.exports = { start };
6422
+
6370
6423
  };
6371
6424
  // ── ./src/mcp/tools ──
6372
6425
  __factories["./src/mcp/tools"] = function(module, exports) {
6373
- 'use strict';
6374
-
6375
- /**
6376
- * MCP tool definitions for SigMap (11 tools).
6377
- * read_context, search_signatures, get_map, create_checkpoint, get_routing,
6378
- * explain_file, list_modules, query_context, get_impact, get_lines, read_memory.
6379
- */
6426
+
6427
+ /**
6428
+ * MCP tool definitions for SigMap (12 tools).
6429
+ * read_context, search_signatures, get_map, create_checkpoint, get_routing,
6430
+ * explain_file, list_modules, query_context, get_impact, get_lines, read_memory,
6431
+ * get_callee_signatures.
6432
+ */
6380
6433
 
6381
- const TOOLS = [
6382
- {
6383
- name: 'read_context',
6384
- description:
6385
- 'Read extracted code signatures for the project or a specific module path. ' +
6386
- 'Returns the full copilot-instructions.md content (~500–4K tokens) or a ' +
6387
- 'filtered subset when a module path is provided (~50–500 tokens).',
6388
- inputSchema: {
6389
- type: 'object',
6390
- properties: {
6391
- module: {
6392
- type: 'string',
6393
- description:
6394
- 'Optional subdirectory path to scope results (e.g. "src/services"). ' +
6395
- 'Omit to get the full codebase context.',
6434
+ const TOOLS = [
6435
+ {
6436
+ name: 'read_context',
6437
+ description:
6438
+ 'Read extracted code signatures for the project or a specific module path. ' +
6439
+ 'Returns the full copilot-instructions.md content (~500–4K tokens) or a ' +
6440
+ 'filtered subset when a module path is provided (~50–500 tokens).',
6441
+ inputSchema: {
6442
+ type: 'object',
6443
+ properties: {
6444
+ module: {
6445
+ type: 'string',
6446
+ description:
6447
+ 'Optional subdirectory path to scope results (e.g. "src/services"). ' +
6448
+ 'Omit to get the full codebase context.',
6449
+ },
6396
6450
  },
6451
+ required: [],
6397
6452
  },
6398
- required: [],
6399
6453
  },
6400
- },
6401
- {
6402
- name: 'search_signatures',
6403
- description:
6404
- 'Search extracted code signatures for a keyword, function name, or class name. ' +
6405
- 'Returns matching signature lines with their file paths.',
6406
- inputSchema: {
6407
- type: 'object',
6408
- properties: {
6409
- query: {
6410
- type: 'string',
6411
- description: 'Keyword to search for in signatures (case-insensitive).',
6454
+ {
6455
+ name: 'search_signatures',
6456
+ description:
6457
+ 'Search extracted code signatures for a keyword, function name, or class name. ' +
6458
+ 'Returns matching signature lines with their file paths.',
6459
+ inputSchema: {
6460
+ type: 'object',
6461
+ properties: {
6462
+ query: {
6463
+ type: 'string',
6464
+ description: 'Keyword to search for in signatures (case-insensitive).',
6465
+ },
6412
6466
  },
6467
+ required: ['query'],
6413
6468
  },
6414
- required: ['query'],
6415
6469
  },
6416
- },
6417
- {
6418
- name: 'get_map',
6419
- description:
6420
- 'Read a section from PROJECT_MAP.md import graph, class hierarchy, or route table. ' +
6421
- 'Requires gen-project-map.js to have been run first.',
6422
- inputSchema: {
6423
- type: 'object',
6424
- properties: {
6425
- type: {
6426
- type: 'string',
6427
- enum: ['imports', 'classes', 'routes'],
6428
- description: 'Which section to retrieve: imports, classes, or routes.',
6470
+ {
6471
+ name: 'get_map',
6472
+ description:
6473
+ 'Read a section from PROJECT_MAP.md — import graph, class hierarchy, or route table. ' +
6474
+ 'Requires gen-project-map.js to have been run first.',
6475
+ inputSchema: {
6476
+ type: 'object',
6477
+ properties: {
6478
+ type: {
6479
+ type: 'string',
6480
+ enum: ['imports', 'classes', 'routes'],
6481
+ description: 'Which section to retrieve: imports, classes, or routes.',
6482
+ },
6429
6483
  },
6484
+ required: ['type'],
6430
6485
  },
6431
- required: ['type'],
6432
6486
  },
6433
- },
6434
- {
6435
- name: 'create_checkpoint',
6436
- description:
6437
- 'Create a session checkpoint summarising current project state. ' +
6438
- 'Returns recent git commits, active branch, token count, and a ' +
6439
- 'compact snapshot of the codebase context — ideal for session handoffs ' +
6440
- 'or periodic saves during long coding sessions.',
6441
- inputSchema: {
6442
- type: 'object',
6443
- properties: {
6444
- note: {
6445
- type: 'string',
6446
- description: 'Optional free-text note to include in the checkpoint (e.g. what you were working on).',
6487
+ {
6488
+ name: 'create_checkpoint',
6489
+ description:
6490
+ 'Create a session checkpoint summarising current project state. ' +
6491
+ 'Returns recent git commits, active branch, token count, and a ' +
6492
+ 'compact snapshot of the codebase context ideal for session handoffs ' +
6493
+ 'or periodic saves during long coding sessions.',
6494
+ inputSchema: {
6495
+ type: 'object',
6496
+ properties: {
6497
+ note: {
6498
+ type: 'string',
6499
+ description: 'Optional free-text note to include in the checkpoint (e.g. what you were working on).',
6500
+ },
6447
6501
  },
6502
+ required: [],
6448
6503
  },
6449
- required: [],
6450
6504
  },
6451
- },
6452
- {
6453
- name: 'get_routing',
6454
- description:
6455
- 'Get model routing hints for this project which files belong to which complexity ' +
6456
- 'tier (fast/balanced/powerful) and which AI model to use for each type of task. ' +
6457
- 'Helps reduce API costs by 40–80% by routing simple tasks to cheaper models.',
6458
- inputSchema: {
6459
- type: 'object',
6460
- properties: {},
6461
- required: [],
6505
+ {
6506
+ name: 'get_routing',
6507
+ description:
6508
+ 'Get model routing hints for this project — which files belong to which complexity ' +
6509
+ 'tier (fast/balanced/powerful) and which AI model to use for each type of task. ' +
6510
+ 'Helps reduce API costs by 40–80% by routing simple tasks to cheaper models.',
6511
+ inputSchema: {
6512
+ type: 'object',
6513
+ properties: {},
6514
+ required: [],
6515
+ },
6462
6516
  },
6463
- },
6464
- {
6465
- name: 'explain_file',
6466
- description:
6467
- 'Explain a specific file: returns its extracted signatures, direct imports ' +
6468
- '(files it depends on), and callers (files that import it). ' +
6469
- 'Ideal for understanding a file in isolation without reading raw source. ' +
6470
- 'Requires the context file to have been generated first.',
6471
- inputSchema: {
6472
- type: 'object',
6473
- properties: {
6474
- path: {
6475
- type: 'string',
6476
- description:
6477
- 'Relative path from the project root (e.g. "src/services/auth.ts"). ' +
6478
- 'Use the paths shown in read_context output.',
6517
+ {
6518
+ name: 'explain_file',
6519
+ description:
6520
+ 'Explain a specific file: returns its extracted signatures, direct imports ' +
6521
+ '(files it depends on), and callers (files that import it). ' +
6522
+ 'Ideal for understanding a file in isolation without reading raw source. ' +
6523
+ 'Requires the context file to have been generated first.',
6524
+ inputSchema: {
6525
+ type: 'object',
6526
+ properties: {
6527
+ path: {
6528
+ type: 'string',
6529
+ description:
6530
+ 'Relative path from the project root (e.g. "src/services/auth.ts"). ' +
6531
+ 'Use the paths shown in read_context output.',
6532
+ },
6479
6533
  },
6534
+ required: ['path'],
6480
6535
  },
6481
- required: ['path'],
6482
6536
  },
6483
- },
6484
- {
6485
- name: 'list_modules',
6486
- description:
6487
- 'List all top-level modules (srcDirs) present in the context file, ' +
6488
- 'sorted by token count descending. Use this to decide which module to ' +
6489
- 'pass to read_context before querying a specific area of the codebase.',
6490
- inputSchema: {
6491
- type: 'object',
6492
- properties: {},
6493
- required: [],
6537
+ {
6538
+ name: 'list_modules',
6539
+ description:
6540
+ 'List all top-level modules (srcDirs) present in the context file, ' +
6541
+ 'sorted by token count descending. Use this to decide which module to ' +
6542
+ 'pass to read_context before querying a specific area of the codebase.',
6543
+ inputSchema: {
6544
+ type: 'object',
6545
+ properties: {},
6546
+ required: [],
6547
+ },
6494
6548
  },
6495
- },
6496
- {
6497
- name: 'query_context',
6498
- description:
6499
- 'Rank and return the most relevant files for a specific task or question. ' +
6500
- 'Uses keyword + symbol + path scoring to surface only the top-K files relevant ' +
6501
- 'to the query much cheaper than reading all context. ' +
6502
- 'Returns ranked file list with signatures and relevance scores.',
6503
- inputSchema: {
6504
- type: 'object',
6505
- properties: {
6506
- query: {
6507
- type: 'string',
6508
- description:
6509
- 'Natural language task description or keyword(s) to rank files against. ' +
6510
- 'E.g. "add a new language extractor", "fix secret scanning", "auth module".',
6511
- },
6512
- topK: {
6513
- type: 'number',
6514
- description: 'Maximum number of files to return (default: 10, max: 25).',
6549
+ {
6550
+ name: 'query_context',
6551
+ description:
6552
+ 'Rank and return the most relevant files for a specific task or question. ' +
6553
+ 'Uses keyword + symbol + path scoring to surface only the top-K files relevant ' +
6554
+ 'to the query much cheaper than reading all context. ' +
6555
+ 'Returns ranked file list with signatures and relevance scores.',
6556
+ inputSchema: {
6557
+ type: 'object',
6558
+ properties: {
6559
+ query: {
6560
+ type: 'string',
6561
+ description:
6562
+ 'Natural language task description or keyword(s) to rank files against. ' +
6563
+ 'E.g. "add a new language extractor", "fix secret scanning", "auth module".',
6564
+ },
6565
+ topK: {
6566
+ type: 'number',
6567
+ description: 'Maximum number of files to return (default: 10, max: 25).',
6568
+ },
6515
6569
  },
6570
+ required: ['query'],
6516
6571
  },
6517
- required: ['query'],
6518
6572
  },
6519
- },
6520
- {
6521
- name: 'get_impact',
6522
- description:
6523
- 'Show every file that is impacted when a given file changes — direct importers, ' +
6524
- 'transitive importers, affected tests, and affected routes/controllers. ' +
6525
- 'Gives agents instant blast-radius awareness before making a change. ' +
6526
- 'Handles circular dependencies safely (no infinite loops).',
6527
- inputSchema: {
6528
- type: 'object',
6529
- properties: {
6530
- file: {
6531
- type: 'string',
6532
- description:
6533
- 'Relative path from the project root of the file that changed ' +
6534
- '(e.g. "src/extractors/python.js"). Use forward slashes.',
6535
- },
6536
- depth: {
6537
- type: 'number',
6538
- description: 'BFS traversal depth limit (default: 3). Use 0 for unlimited.',
6573
+ {
6574
+ name: 'get_impact',
6575
+ description:
6576
+ 'Show every file that is impacted when a given file changes — direct importers, ' +
6577
+ 'transitive importers, affected tests, and affected routes/controllers. ' +
6578
+ 'Gives agents instant blast-radius awareness before making a change. ' +
6579
+ 'Handles circular dependencies safely (no infinite loops).',
6580
+ inputSchema: {
6581
+ type: 'object',
6582
+ properties: {
6583
+ file: {
6584
+ type: 'string',
6585
+ description:
6586
+ 'Relative path from the project root of the file that changed ' +
6587
+ '(e.g. "src/extractors/python.js"). Use forward slashes.',
6588
+ },
6589
+ depth: {
6590
+ type: 'number',
6591
+ description: 'BFS traversal depth limit (default: 3). Use 0 for unlimited.',
6592
+ },
6539
6593
  },
6594
+ required: ['file'],
6540
6595
  },
6541
- required: ['file'],
6542
6596
  },
6543
- },
6544
- {
6545
- name: 'get_lines',
6546
- description:
6547
- 'Fetch an exact line range from a source file on demand — the Surgical Context ' +
6548
- 'workhorse. Signatures carry `path:start-end` anchors; call this to read just those ' +
6549
- 'lines instead of re-opening the whole file. Lines are clamped to the file bounds and ' +
6550
- 'secret-scanned (redacted) before return. Path is sandboxed to the project root.',
6551
- inputSchema: {
6552
- type: 'object',
6553
- properties: {
6554
- file: {
6555
- type: 'string',
6556
- description:
6557
- 'Relative path from the project root (e.g. "src/config/loader.js"). ' +
6558
- 'Use the path shown in a signature anchor. Use forward slashes.',
6559
- },
6560
- start: {
6561
- type: 'number',
6562
- description: '1-based start line (inclusive). Clamped to the file bounds.',
6597
+ {
6598
+ name: 'get_lines',
6599
+ description:
6600
+ 'Fetch an exact line range from a source file on demand — the Surgical Context ' +
6601
+ 'workhorse. Signatures carry `path:start-end` anchors; call this to read just those ' +
6602
+ 'lines instead of re-opening the whole file. Lines are clamped to the file bounds and ' +
6603
+ 'secret-scanned (redacted) before return. Path is sandboxed to the project root.',
6604
+ inputSchema: {
6605
+ type: 'object',
6606
+ properties: {
6607
+ file: {
6608
+ type: 'string',
6609
+ description:
6610
+ 'Relative path from the project root (e.g. "src/config/loader.js"). ' +
6611
+ 'Use the path shown in a signature anchor. Use forward slashes.',
6612
+ },
6613
+ start: {
6614
+ type: 'number',
6615
+ description: '1-based start line (inclusive). Clamped to the file bounds.',
6616
+ },
6617
+ end: {
6618
+ type: 'number',
6619
+ description: '1-based end line (inclusive). Clamped to the file bounds.',
6620
+ },
6563
6621
  },
6564
- end: {
6565
- type: 'number',
6566
- description: '1-based end line (inclusive). Clamped to the file bounds.',
6622
+ required: ['file', 'start', 'end'],
6623
+ },
6624
+ },
6625
+ {
6626
+ name: 'read_memory',
6627
+ description:
6628
+ 'Recall the project decision log — recent notes left by humans or agents ' +
6629
+ 'across sessions (via `sigmap note`), plus the last ranking-session focus. ' +
6630
+ 'Call this at the start of a task to kill cold-start: it answers ' +
6631
+ '"what were we doing and why" without re-reading the whole codebase.',
6632
+ inputSchema: {
6633
+ type: 'object',
6634
+ properties: {
6635
+ limit: {
6636
+ type: 'number',
6637
+ description: 'How many of the most recent notes to return (default: 10, max: 50).',
6638
+ },
6567
6639
  },
6640
+ required: [],
6568
6641
  },
6569
- required: ['file', 'start', 'end'],
6570
6642
  },
6571
- },
6572
- {
6573
- name: 'read_memory',
6574
- description:
6575
- 'Recall the project decision log recent notes left by humans or agents ' +
6576
- 'across sessions (via `sigmap note`), plus the last ranking-session focus. ' +
6577
- 'Call this at the start of a task to kill cold-start: it answers ' +
6578
- '"what were we doing and why" without re-reading the whole codebase.',
6579
- inputSchema: {
6580
- type: 'object',
6581
- properties: {
6582
- limit: {
6583
- type: 'number',
6584
- description: 'How many of the most recent notes to return (default: 10, max: 50).',
6643
+ {
6644
+ name: 'get_callee_signatures',
6645
+ description:
6646
+ 'Return the EXACT current signature(s) of named symbols (functions, classes, ' +
6647
+ "methods) from the index so an agent never guesses a callee's parameter types " +
6648
+ 'from training memory. Call this before writing code that uses a symbol. ' +
6649
+ 'Unknown names get a closest-match suggestion.',
6650
+ inputSchema: {
6651
+ type: 'object',
6652
+ properties: {
6653
+ symbols: {
6654
+ type: 'array',
6655
+ items: { type: 'string' },
6656
+ description: 'Symbol names to resolve (e.g. ["validateToken", "UserService"]).',
6657
+ },
6585
6658
  },
6659
+ required: ['symbols'],
6586
6660
  },
6587
- required: [],
6588
6661
  },
6589
- },
6590
- ];
6591
-
6592
- module.exports = { TOOLS };
6662
+ ];
6593
6663
 
6664
+ module.exports = { TOOLS };
6665
+
6594
6666
  };
6595
6667
  // ── ./src/routing/classifier ──
6596
6668
  __factories["./src/routing/classifier"] = function(module, exports) {
@@ -11499,7 +11571,7 @@ function __tryGit(args, opts = {}) {
11499
11571
  catch (_) { return ''; }
11500
11572
  }
11501
11573
 
11502
- const VERSION = '7.2.0';
11574
+ const VERSION = '7.3.0';
11503
11575
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
11504
11576
 
11505
11577
  function requireSourceOrBundled(key) {
@@ -13807,7 +13879,15 @@ function main() {
13807
13879
  let coveragePct = 0;
13808
13880
  try { coveragePct = coverageScore(cwd, fakeEntries, config).score; } catch (_) {}
13809
13881
 
13810
- const rawTok = getRawTokenCount(cwd, config);
13882
+ // Realistic baseline: the full content of the files SigMap actually surfaced
13883
+ // for this query. Without SigMap you'd read these files in full; SigMap gives
13884
+ // you their signatures instead — so this is the true per-query saving. Falls
13885
+ // back to the whole-repo count only if nothing ranked.
13886
+ let rawTok = 0;
13887
+ for (const r of ranked) {
13888
+ try { rawTok += estimateTokens(fs.readFileSync(path.join(cwd, r.file), 'utf8')); } catch (_) {}
13889
+ }
13890
+ if (rawTok === 0) rawTok = getRawTokenCount(cwd, config);
13811
13891
  const savings = rawTok > 0 ? Math.round((1 - ctxTok / rawTok) * 100) : 0;
13812
13892
  const model = args[args.indexOf('--model') + 1] || 'gpt-4o';
13813
13893
  const rateK = MODEL_COSTS[model] || MODEL_COSTS['gpt-4o'];