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/CHANGELOG.md +18 -0
- package/gen-context.js +842 -762
- package/llms-full.txt +11 -3
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/handlers.js +54 -1
- package/src/mcp/server.js +3 -2
- package/src/mcp/tools.js +22 -2
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
|
-
|
|
5532
|
-
|
|
5533
|
-
const
|
|
5534
|
-
const
|
|
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
|
|
5570
|
-
const
|
|
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
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
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
|
-
|
|
5545
|
+
return chunks.join('\n');
|
|
5587
5546
|
}
|
|
5588
5547
|
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
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
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
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
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
if (
|
|
5615
|
-
|
|
5616
|
-
|
|
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
|
|
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
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
function
|
|
5633
|
-
|
|
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
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
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
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
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
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
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
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
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
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
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
|
-
|
|
5676
|
-
|
|
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
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
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
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
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
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
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
|
-
//
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
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
|
-
|
|
5710
|
-
|
|
5711
|
-
lines.push(
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
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
|
-
|
|
5736
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
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
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
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
|
-
|
|
5778
|
-
|
|
5779
|
-
const
|
|
5780
|
-
const
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
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
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
function explainFile(args, cwd) {
|
|
5794
|
-
|
|
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
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
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
|
-
|
|
5800
|
+
const lines = ['# explain_file: ' + targetRel, ''];
|
|
5801
5801
|
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5802
|
+
// ── Signatures (hot + cold + cache via buildSigIndex) ───────────────────
|
|
5803
|
+
lines.push('## Signatures');
|
|
5804
|
+
let indexedFiles = [];
|
|
5805
5805
|
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
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
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
lines.push('
|
|
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
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
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
|
-
|
|
5853
|
+
lines.push('');
|
|
5854
5854
|
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
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
|
-
|
|
5880
|
-
}
|
|
5879
|
+
return lines.join('\n');
|
|
5880
|
+
}
|
|
5881
5881
|
|
|
5882
|
-
/**
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
function listModules(args, cwd) {
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
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
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
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
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
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
|
-
|
|
5909
|
+
if (sorted.length === 0) return 'No modules found in context file.';
|
|
5910
5910
|
|
|
5911
|
-
|
|
5911
|
+
const total = sorted.reduce((s, m) => s + m.tokenCount, 0);
|
|
5912
5912
|
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
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
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
function queryContext(args, cwd) {
|
|
5936
|
-
|
|
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
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
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
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
function getImpact(args, cwd) {
|
|
5962
|
-
|
|
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
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
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
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
function getLines(args, cwd) {
|
|
5983
|
-
|
|
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
|
-
|
|
5986
|
-
|
|
5985
|
+
const rel = String(args.file).replace(/\\/g, '/').replace(/^\//, '');
|
|
5986
|
+
const abs = path.resolve(cwd, rel);
|
|
5987
5987
|
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
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
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
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
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
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
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
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('
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
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/list → 11 tool definitions
|
|
6300
|
+
* tools/call → dispatch to handler, return result
|
|
6301
|
+
*/
|
|
6273
6302
|
|
|
6274
|
-
|
|
6275
|
-
|
|
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
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
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
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
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
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
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
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6326
|
+
// ---------------------------------------------------------------------------
|
|
6327
|
+
// Method dispatcher
|
|
6328
|
+
// ---------------------------------------------------------------------------
|
|
6329
|
+
function dispatch(msg, cwd) {
|
|
6330
|
+
const { method, id, params } = msg;
|
|
6302
6331
|
|
|
6303
|
-
|
|
6304
|
-
|
|
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
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
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
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
}
|
|
6346
|
+
if (method === 'tools/list') {
|
|
6347
|
+
respond(id, { tools: TOOLS });
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6336
6350
|
|
|
6337
|
-
|
|
6338
|
-
|
|
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
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
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
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
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
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
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
|
-
|
|
6364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
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
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
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
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
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
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
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
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
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
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
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
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
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
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
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
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
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
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
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
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
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
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
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
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
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.
|
|
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
|
-
|
|
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'];
|