(dir: string, depth: number, prefix: string, ctx: TreeContext)
| 138 | } |
| 139 | |
| 140 | async function walk(dir: string, depth: number, prefix: string, ctx: TreeContext): Promise<void> { |
| 141 | if (depth > ctx.maxDepth || ctx.entryCount >= ctx.maxEntries || ctx.truncated) return; |
| 142 | |
| 143 | let entries: import('fs').Dirent[]; |
| 144 | try { |
| 145 | entries = await fs.readdir(dir, { withFileTypes: true }); |
| 146 | } catch { |
| 147 | return; |
| 148 | } |
| 149 | |
| 150 | // Filter ignored + hidden (except .github which is informative) |
| 151 | entries = entries |
| 152 | .filter(e => { |
| 153 | if (IGNORED_DIRS.has(e.name) || IGNORED_FILES.has(e.name)) return false; |
| 154 | if (e.name.startsWith('.') && e.name !== '.github') return false; |
| 155 | return true; |
| 156 | }) |
| 157 | .sort((a, b) => { |
| 158 | if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1; |
| 159 | return a.name.localeCompare(b.name); |
| 160 | }); |
| 161 | |
| 162 | for (let i = 0; i < entries.length; i++) { |
| 163 | if (ctx.entryCount >= ctx.maxEntries || ctx.truncated) { |
| 164 | pushLine(prefix + '... (truncated)', ctx); |
| 165 | return; |
| 166 | } |
| 167 | |
| 168 | const entry = entries[i]!; |
| 169 | const isLast = i === entries.length - 1; |
| 170 | const connector = isLast ? '└── ' : '├── '; |
| 171 | const childPrefix = prefix + (isLast ? ' ' : '│ '); |
| 172 | |
| 173 | if (entry.isDirectory()) { |
| 174 | const expand = depth >= ctx.maxDepth ? false : shouldExpandDir(entry.name, ctx); |
| 175 | if (!expand) { |
| 176 | // Summarise: name + child count + truncation marker |
| 177 | const summary = await summariseDir(path.join(dir, entry.name)); |
| 178 | pushLine(prefix + connector + entry.name + '/ ' + summary, ctx); |
| 179 | } else { |
| 180 | pushLine(prefix + connector + entry.name + '/', ctx); |
| 181 | await walk(path.join(dir, entry.name), depth + 1, childPrefix, ctx); |
| 182 | } |
| 183 | } else { |
| 184 | pushLine(prefix + connector + entry.name, ctx); |
| 185 | } |
| 186 | |
| 187 | if (ctx.byteCount > ctx.maxBytes) { |
| 188 | pushLine(prefix + '... (size limit reached)', ctx); |
| 189 | ctx.truncated = true; |
| 190 | return; |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | /** Cheap one-line summary of a directory: `(N items)`. Best-effort; failure → empty. */ |
| 196 | async function summariseDir(dir: string): Promise<string> { |
no test coverage detected