Skip to content

Commit 08c0409

Browse files
committed
feat(symbol-resolver): implement findQualifiedPaths function for suggesting qualified paths
feat(file-read): enhance error handling with qualified path suggestions for unresolved targets feat(codebase-map): add directory validation for workspace paths before scanning
1 parent 19dc63e commit 08c0409

3 files changed

Lines changed: 56 additions & 1 deletion

File tree

src/tools/codebase/codebase-map.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
import {existsSync, statSync} from 'node:fs';
8+
import path from 'node:path';
9+
710
import {
811
codebaseGetOverview,
912
type CodebaseTreeNode,
@@ -242,6 +245,17 @@ export const map = defineTool({
242245
const symbols = params.symbols ?? false;
243246
const metadata = params.metadata ?? false;
244247

248+
// Resolve relative paths against workspace root for validation
249+
const resolvedDir = path.isAbsolute(dir) ? dir : path.resolve(rootDir, dir);
250+
251+
// Validate directory exists before scanning
252+
if (!existsSync(resolvedDir)) {
253+
throw new Error(`Directory not found: "${dir}". Verify the path exists and is accessible.`);
254+
}
255+
if (!statSync(resolvedDir).isDirectory()) {
256+
throw new Error(`Path is not a directory: "${dir}". The "dir" parameter must point to a folder, not a file.`);
257+
}
258+
245259
// Dynamic timeout based on request scope
246260
const dynamicTimeout =
247261
TIMEOUT_BASE_MS +

src/tools/file/file-read.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {getClientWorkspace} from '../../config.js';
1919
import {zod} from '../../third_party/index.js';
2020
import {ToolCategory} from '../categories.js';
2121
import {CHARACTER_LIMIT, defineTool} from '../ToolDefinition.js';
22-
import {resolveSymbolTarget} from './symbol-resolver.js';
22+
import {resolveSymbolTarget, findQualifiedPaths} from './symbol-resolver.js';
2323
import type {SymbolLike} from './symbol-resolver.js';
2424

2525
function resolveFilePath(file: string): string {
@@ -700,6 +700,15 @@ export const read = defineTool({
700700
response.appendResponseLine(
701701
`"${target}": Not found. Available: ${available || 'none'}`,
702702
);
703+
704+
// Check if the target exists as a nested child and suggest qualified path
705+
const qualifiedPaths = findQualifiedPaths(structure.symbols, target);
706+
if (qualifiedPaths.length > 0) {
707+
const suggestions = qualifiedPaths.map(p => `"${p}"`).join(', ');
708+
response.appendResponseLine(
709+
`Hint: Did you mean ${suggestions}? Use the qualified dot-path to target nested symbols.`,
710+
);
711+
}
703712
continue;
704713
}
705714

src/tools/file/symbol-resolver.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,38 @@ export function getChildNames(
101101
return symbol.children.map(c => c.name);
102102
}
103103

104+
/**
105+
* Search all children (recursively) of all top-level symbols for a name match.
106+
* Returns the qualified dot-path(s) if found, e.g. ["ParentSection.ChildName"].
107+
* Useful for suggesting the correct qualified path when an unqualified name fails.
108+
*/
109+
export function findQualifiedPaths(
110+
symbols: SymbolLike[],
111+
targetName: string,
112+
): string[] {
113+
const results: string[] = [];
114+
115+
function searchChildren(children: SymbolLike[], parentPath: string): void {
116+
for (const child of children) {
117+
const qualifiedPath = `${parentPath}.${child.name}`;
118+
if (nameMatches(child.name, targetName)) {
119+
results.push(qualifiedPath);
120+
}
121+
if (child.children.length > 0) {
122+
searchChildren(child.children, qualifiedPath);
123+
}
124+
}
125+
}
126+
127+
for (const symbol of symbols) {
128+
if (symbol.children.length > 0) {
129+
searchChildren(symbol.children, symbol.name);
130+
}
131+
}
132+
133+
return results;
134+
}
135+
104136
/**
105137
* Format a symbol's range as 1-indexed "lines X-Y of Z".
106138
* Takes 0-indexed line numbers (legacy path).

0 commit comments

Comments
 (0)