File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 44 * SPDX-License-Identifier: Apache-2.0
55 */
66
7+ import { existsSync , statSync } from 'node:fs' ;
8+ import path from 'node:path' ;
9+
710import {
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 +
Original file line number Diff line number Diff line change @@ -19,7 +19,7 @@ import {getClientWorkspace} from '../../config.js';
1919import { zod } from '../../third_party/index.js' ;
2020import { ToolCategory } from '../categories.js' ;
2121import { CHARACTER_LIMIT , defineTool } from '../ToolDefinition.js' ;
22- import { resolveSymbolTarget } from './symbol-resolver.js' ;
22+ import { resolveSymbolTarget , findQualifiedPaths } from './symbol-resolver.js' ;
2323import type { SymbolLike } from './symbol-resolver.js' ;
2424
2525function 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
Original file line number Diff line number Diff 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).
You can’t perform that action at this time.
0 commit comments