How I Built a Virtual Folder Tree from Flat Filenames — No Files Moved, No Symlinks, Just 300 Lines of TypeScript A developer built Logical Folders, a VSCode and IntelliJ plugin that displays flat filenames as a virtual directory tree without moving files on disk. The plugin uses a parsePath function to split filenames on a separator (default underscore) and constructs a virtual hierarchy, with an inverse function to create new files. It aims to help developers navigate projects where AI agents generate many flat files. I let an AI agent write code in my project for a week. By Friday there were 340 files in one directory. auth login handler.ts . auth login session.ts . utils helpers.ts . config env.ts . All flat. All in the root. The agent loved it. No path ambiguity, no directory hops, no "which folder was that in?" 鈥?every file one read file call away. Flat is the agent's native habitat. I hated it. Scrolling through 340 files looking for the one auth handler I needed. My brain doesn't grep. So I built Logical Folders 鈥?a VSCode and IntelliJ plugin that displays flat files as a virtual directory tree. The files don't move. Nothing changes on disk. The hierarchy is purely visual. On disk: In the tree: auth login handler.ts auth/ auth login session.ts login/ utils helpers.ts handler.ts config env.ts session.ts utils/ helpers.ts config/ env.ts The agent keeps its flat playground. I get my tree. The whole thing hinges on one function 鈥? parsePath . Split a filename on a separator, keep the extension glued to the last segment: js export function parsePath relPath: string, sep: string : string { const parts = relPath.split path.sep ; const filename = parts.pop ; // Dotfiles .gitignore, .env never split 鈥?returned as-is if sep || filename.startsWith '.' || filename.includes sep { return ...parts, filename ; } const ext = path.extname filename ; const base = ext ? filename.slice 0, -ext.length : filename; const segs = base.split sep ; if ext && segs.length 0 { segs segs.length - 1 += ext; } return ...parts, ...segs ; } auth login handler.ts with 鈫? "auth", "login", "handler.ts" . That's it. The extension stays on the last segment so handler.ts doesn't become handler + .ts as a fake folder. The inverse is constructFlatName 鈥?join segments with the separator, reattach the extension. When you right-click auth/login/ and create handler.ts , the plugin joins them back to auth login handler.ts and writes the flat file. The tree is a lie. The disk is the truth. VSCode's TreeDataProvider interface needs getChildren element? . I scan the workspace once, parse every file path, and insert into a tree of LogicalNode objects: js private async buildTree : Promise