MCPcopy Index your code
hub / github.com/colbymchenry/codegraph / handleFileView

Method handleFileView

src/mcp/tools.ts:3757–3885  ·  view source on GitHub ↗

* FILE READ MODE: resolve `fileArg` (path or basename) to an indexed file and * read it like the Read tool — its current on-disk source with line numbers, * narrowable with `offset`/`limit` exactly as Read's are — preceded by a * one-line blast-radius header (which files depend on it). `sym

(
    cg: CodeGraph,
    fileArg: string,
    opts: { offset?: number; limit?: number; symbolsOnly?: boolean } = {},
  )

Source from the content-addressed store, hash-verified

3755 * through validatePathWithinRoot (#527).
3756 */
3757 private async handleFileView(
3758 cg: CodeGraph,
3759 fileArg: string,
3760 opts: { offset?: number; limit?: number; symbolsOnly?: boolean } = {},
3761 ): Promise<ToolResult> {
3762 const normalize = (p: string) => p.replace(/\\/g, '/').replace(/^(?:\.?\/+)+/, '').replace(/\/+$/, '');
3763 const wantLower = normalize(fileArg).toLowerCase();
3764 const allFiles = cg.getFiles();
3765 if (allFiles.length === 0) return this.textResult('No files indexed. Run `codegraph index` first.');
3766
3767 let resolved = allFiles.find((f) => f.path.toLowerCase() === wantLower);
3768 let candidates: typeof allFiles = [];
3769 if (!resolved) {
3770 candidates = allFiles.filter((f) => f.path.toLowerCase().endsWith('/' + wantLower));
3771 if (candidates.length === 1) resolved = candidates[0];
3772 }
3773 if (!resolved && candidates.length === 0) {
3774 candidates = allFiles.filter((f) => f.path.toLowerCase().includes(wantLower));
3775 if (candidates.length === 1) resolved = candidates[0];
3776 }
3777 if (!resolved && candidates.length > 1) {
3778 return this.textResult(
3779 [`"${fileArg}" matches ${candidates.length} indexed files — pass a longer path:`, '',
3780 ...candidates.slice(0, 25).map((f) => `- ${f.path}`)].join('\n'),
3781 );
3782 }
3783 if (!resolved) {
3784 return this.textResult(
3785 `No indexed file matches "${fileArg}". Codegraph indexes source files; configs/docs it doesn't parse won't appear — Read those directly.`,
3786 );
3787 }
3788
3789 const filePath = resolved.path;
3790 const nodes = cg.getNodesInFile(filePath)
3791 .filter((n) => n.kind !== 'file' && n.kind !== 'import' && n.kind !== 'export')
3792 .sort((a, b) => a.startLine - b.startLine);
3793 const dependents = cg.getFileDependents(filePath);
3794
3795 // Compact, one-line blast radius (codegraph's value-add over a plain Read).
3796 const depSummary = dependents.length
3797 ? `used by ${dependents.length} file${dependents.length === 1 ? '' : 's'}: ${dependents.slice(0, 8).join(', ')}${dependents.length > 8 ? `, +${dependents.length - 8} more` : ''}`
3798 : 'no other indexed file depends on it';
3799
3800 // Symbol-map renderer — for symbolsOnly, the config fallback, and read errors.
3801 const symbolMap = (heading: string, limit = 200): string[] => {
3802 const lines: string[] = [heading];
3803 for (const n of nodes.slice(0, limit)) {
3804 const sig = n.signature ? ` ${n.signature.replace(/\s+/g, ' ').trim()}` : '';
3805 lines.push(`- \`${n.name}\` (${n.kind})${sig} — :${n.startLine}`);
3806 }
3807 if (nodes.length > limit) lines.push(`- … +${nodes.length - limit} more`);
3808 return lines;
3809 };
3810
3811 // symbolsOnly → the cheap structural overview, no source.
3812 if (opts.symbolsOnly) {
3813 const out = [`**${filePath}** — ${nodes.length} symbol${nodes.length === 1 ? '' : 's'}, ${depSummary}`, ''];
3814 if (nodes.length) out.push(...symbolMap('**Symbols**'));

Callers 1

handleNodeMethod · 0.95

Calls 9

textResultMethod · 0.95
truncateOutputMethod · 0.95
validatePathWithinRootFunction · 0.90
getFilesMethod · 0.80
joinMethod · 0.80
hasMethod · 0.80
getNodesInFileMethod · 0.65
getProjectRootMethod · 0.65
getFileDependentsMethod · 0.45

Tested by

no test coverage detected