@@ -260,7 +260,7 @@ export const make = Effect.gen(function* () {
260260 const normalizedQuery = normalizeSearchQuery ( input . query , {
261261 trimLeadingPattern : / ^ [ @ . / ] + / ,
262262 } ) ;
263- return yield * Effect . gen ( function * ( ) {
263+ const result = yield * Effect . gen ( function * ( ) {
264264 const searchIndex = yield * WorkspaceSearchIndex . WorkspaceSearchIndex ;
265265 return yield * searchIndex . search ( normalizedQuery , input . limit , input . kind , input . imageOnly ) ;
266266 } ) . pipe (
@@ -270,9 +270,83 @@ export const make = Effect.gen(function* () {
270270 ) ,
271271 ) ,
272272 ) ;
273+ if ( input . kind === "directory" || input . imageOnly ) return result ;
274+ const worktreeFiles = yield * findNestedWorktreeFiles (
275+ normalizedCwd ,
276+ input . query ,
277+ result . entries ,
278+ ) ;
279+ if ( worktreeFiles . length === 0 ) return result ;
280+ const entries = [ ...worktreeFiles , ...result . entries ] ;
281+ return {
282+ entries : entries . slice ( 0 , input . limit ) ,
283+ truncated : result . truncated || entries . length > input . limit ,
284+ } ;
273285 } ,
274286 ) ;
275287
288+ // The index skips gitignored folders, where agents often put worktrees (`.worktrees/x`).
289+ // A path relative to such a worktree is found only when it names an existing file exactly.
290+ const findNestedWorktreeFiles = Effect . fn ( "WorkspaceEntries.findNestedWorktreeFiles" ) ( function * (
291+ cwd : string ,
292+ query : string ,
293+ indexed : ReadonlyArray < ProjectEntry > ,
294+ ) {
295+ const relativePath = query . trim ( ) . replaceAll ( "\\" , "/" ) ;
296+ if (
297+ ! relativePath . includes ( "/" ) ||
298+ relativePath . endsWith ( "/" ) ||
299+ path . isAbsolute ( relativePath ) ||
300+ isWindowsAbsolutePath ( relativePath ) ||
301+ relativePath . split ( "/" ) . some ( ( segment ) => segment === ".." ) ||
302+ indexed . some (
303+ ( entry ) =>
304+ entry . kind === "file" &&
305+ ( entry . path === relativePath || entry . path . endsWith ( `/${ relativePath } ` ) ) ,
306+ )
307+ ) {
308+ return [ ] ;
309+ }
310+ const listed = yield * vcsProcess
311+ . run ( {
312+ operation : "WorkspaceEntries.listWorktrees" ,
313+ command : "git" ,
314+ args : [ "worktree" , "list" , "--porcelain" ] ,
315+ cwd,
316+ allowNonZeroExit : true ,
317+ timeoutMs : 5_000 ,
318+ } )
319+ . pipe ( Effect . orElseSucceed ( ( ) => undefined ) ) ;
320+ if ( ! listed || listed . exitCode !== 0 ) return [ ] ;
321+ const realCwd = yield * Effect . promise ( ( ) => NodeFSP . realpath ( cwd ) . catch ( ( ) => cwd ) ) ;
322+ const worktreeRoots = listed . stdout . split ( / \n \s * \n / ) . flatMap ( ( record ) => {
323+ const lines = record . split ( "\n" ) ;
324+ const worktree = lines . find ( ( line ) => line . startsWith ( "worktree " ) ) ;
325+ // A `prunable` record's directory is gone; another checkout may now sit there.
326+ if ( worktree === undefined || lines . some ( ( line ) => line . startsWith ( "prunable" ) ) ) return [ ] ;
327+ const root = worktree . slice ( "worktree " . length ) ;
328+ const relativeRoot = path . relative ( realCwd , root ) ;
329+ return relativeRoot === "" ||
330+ relativeRoot === ".." ||
331+ relativeRoot . startsWith ( `..${ path . sep } ` ) ||
332+ path . isAbsolute ( relativeRoot )
333+ ? [ ]
334+ : [ relativeRoot . split ( path . sep ) . join ( "/" ) ] ;
335+ } ) ;
336+ const matches = yield * Effect . promise ( ( ) =>
337+ Promise . all (
338+ worktreeRoots . map ( async ( relativeRoot ) => {
339+ const candidate = `${ relativeRoot } /${ relativePath } ` ;
340+ const stat = await NodeFSP . stat ( path . join ( realCwd , candidate ) ) . catch ( ( ) => null ) ;
341+ return stat ?. isFile ( ) ? candidate : null ;
342+ } ) ,
343+ ) ,
344+ ) ;
345+ return matches
346+ . filter ( ( candidate ) => candidate !== null )
347+ . map ( ( candidate ) : ProjectEntry => ( { path : candidate , kind : "file" } ) ) ;
348+ } ) ;
349+
276350 const searchContents : WorkspaceEntries [ "Service" ] [ "searchContents" ] = Effect . fn (
277351 "WorkspaceEntries.searchContents" ,
278352 ) ( function * ( input ) {
0 commit comments