@@ -876,6 +876,20 @@ export const make = Effect.gen(function* () {
876876 return `path:${ normalizeProjectPathForComparison ( realPath ) } ` ;
877877 } ) ;
878878
879+ /** Stats a directory that may be offered as a project, or none when it is missing or excluded. */
880+ const projectDirectoryStats = Effect . fn ( "AgentSessionScanner.projectDirectoryStats" ) ( function * (
881+ directory : string ,
882+ ) {
883+ if ( isExcludedProjectPath ( directory ) ) return Option . none < FileSystem . File . Info > ( ) ;
884+ const stats = yield * statOption ( directory ) ;
885+ if ( Option . isNone ( stats ) || stats . value . type !== "Directory" ) return Option . none ( ) ;
886+ const realPath = yield * fileSystem
887+ . realPath ( directory )
888+ . pipe ( Effect . orElseSucceed ( ( ) => directory ) ) ;
889+ // Symlinks can disguise excluded worktree paths; re-check their targets.
890+ return isExcludedProjectPath ( realPath ) ? Option . none ( ) : stats ;
891+ } ) ;
892+
879893 // Paths whose own read outlived the deadline, mapped to when they may be read
880894 // again. Without this every later scan would wait out the deadline again for
881895 // each phase that touches them.
@@ -1711,17 +1725,8 @@ export const make = Effect.gen(function* () {
17111725 const expanded = expandHomePath ( candidate . cwd . trim ( ) , homeDir ) ;
17121726 if ( ! path . isAbsolute ( expanded ) ) continue ;
17131727 const resolved = path . resolve ( expanded ) ;
1714- if ( directories . has ( resolved ) || isExcludedProjectPath ( resolved ) ) continue ;
1715- const stats = yield * statOption ( resolved ) ;
1716- if ( Option . isNone ( stats ) || stats . value . type !== "Directory" ) {
1717- directories . set ( resolved , Option . none ( ) ) ;
1718- continue ;
1719- }
1720- const realPath = yield * fileSystem
1721- . realPath ( resolved )
1722- . pipe ( Effect . orElseSucceed ( ( ) => resolved ) ) ;
1723- // Symlinks can disguise excluded worktree paths; re-check their targets.
1724- directories . set ( resolved , isExcludedProjectPath ( realPath ) ? Option . none ( ) : stats ) ;
1728+ if ( directories . has ( resolved ) ) continue ;
1729+ directories . set ( resolved , yield * projectDirectoryStats ( resolved ) ) ;
17251730 }
17261731 const directoryGitIdentities = yield * readGitIdentities (
17271732 Array . from ( directories ) . flatMap ( ( [ directory , stats ] ) =>
@@ -1752,21 +1757,10 @@ export const make = Effect.gen(function* () {
17521757 } else if ( gitIdentity . _tag === "Worktree" ) {
17531758 // A linked worktree's sessions count toward its main checkout's project.
17541759 const { mainCheckout } = gitIdentity ;
1755- // Symlinks can disguise excluded paths here too; re-check the target.
17561760 const mainStats =
1757- mainCheckout === null ||
1758- isExcludedProjectPath ( mainCheckout ) ||
1759- isExcludedProjectPath (
1760- yield * fileSystem
1761- . realPath ( mainCheckout )
1762- . pipe ( Effect . orElseSucceed ( ( ) => mainCheckout ) ) ,
1763- )
1764- ? Option . none ( )
1765- : yield * statOption ( mainCheckout ) ;
1761+ mainCheckout === null ? Option . none ( ) : yield * projectDirectoryStats ( mainCheckout ) ;
17661762 project =
1767- mainCheckout !== null &&
1768- Option . isSome ( mainStats ) &&
1769- mainStats . value . type === "Directory"
1763+ mainCheckout !== null && Option . isSome ( mainStats )
17701764 ? {
17711765 key : yield * directoryIdentity ( mainCheckout , mainStats . value ) ,
17721766 path : mainCheckout ,
0 commit comments