/** What a walk should do with one directory entry. */ export type EntryKind = "dir" | "file" | "skip"; /** A classified entry, plus the size the loader needs. */ export interface Entry { readonly kind: EntryKind; /** Byte size of the target; `0` for anything that is not a `"file"`. */ readonly size: number; } /** * Classify a directory entry for a recursive walk, refusing to descend into a * symlinked DIRECTORY. * * 🔴 THE SIZE COMES BACK FROM THE SAME `stat`, and that is not a convenience. The * loader needs both "is this a file" and "is it under the size cap", and asking * twice produced a SECOND failure path that nothing could reach: between a * successful classification and a second `statSync` the file has to vanish, so the * `catch` around the size check was dead code that the 100% coverage gate could * only ever fail on. One syscall answers both questions and there is no second * path to test. * * `lstat` describes the ENTRY, `stat` describes its target, and both are needed: * the first decides whether descending is safe, the second decides what a link * actually points at. A symlink to a FILE is still `"file"` — it cannot recurse, * it is genuinely part of the tree, and dropping it would lose real findings. * * A cycle is removed by CONSTRUCTION rather than by a visited-set: there is no * bookkeeping to get wrong, and nothing outside the checkout is ever entered. The * cost is a deliberate omission — files under a symlinked SUBdirectory are not * seen — which for an advisory finding is the correct direction (silence), and for * the loader means a linked-in tree is not loaded rather than loaded forty times. * * Anything unreadable (a dangling link, a permissions error, a socket) is * `"skip"`: a walk feeding a read-only report must not die on one bad entry. */ export declare function entryOf(path: string): Entry; /** * May a walk DESCEND INTO THIS ROOT — a top-level surface dir such as * `.claude/skills`, `skills` or `agents`? * * 🔴 THE POLICY ABOVE WAS APPLIED TO EVERY ENTRY INSIDE A WALK AND TO NO WALK'S * ENTRY POINT. Both walks take their roots straight to `readdirSync`, which * FOLLOWS a link, so a symlinked `.claude/skills` was never classified at all — * the containment each walk documents ("only the surface dirs are walked, so the * rest of the repo and any `node_modules` beside it is never entered"; "a foreign * tree is not read into the file map the whole report is computed from") simply * did not hold at the top. Same class as the entry-level defect, one level up. * * ⚠️ A ROOT GETS A DIFFERENT RULE FROM AN INNER ENTRY, AND THE DIFFERENCE IS * DELIBERATE — do not "fix" the inconsistency later. An inner symlinked dir is one * the walk FOUND BY ITSELF; following it is the walk deciding to leave the tree on * its own initiative, so it is refused outright. A surface root is one the LAYOUT * NAMES: `.claude/skills` is where a Claude Code repo keeps skills, and linking * that at a shared directory (one skills folder used by several checkouts) is an * ordinary, supported setup. Refusing it the way an inner entry is refused would * report ZERO skills for those repos, silently — a worse failure than the one * being fixed, and the exact silent-under-report this file exists to prevent. * * So a symlinked root IS followed, with one refusal: a target that CONTAINS the * scanned root. That is the cycle-and-swallow case — `.claude/skills -> ..`, * `skills -> /`, `.claude/skills -> .` — where the walk re-enters the very tree it * is scanning through a door the layout did not open, and starts reading * `node_modules`, `.git` and every sibling as harness surface. * * Termination is still by CONSTRUCTION, not by bookkeeping: everything BELOW a * followed root goes through {@link entryOf}, which refuses a symlinked dir, so * exactly one link hop is taken per root and the hop count for a whole scan is * bounded by the number of surface dirs. There is no visited-set to get wrong. * * WHAT THIS DELIBERATELY DOES NOT CATCH: a root linked at a huge unrelated * external tree (`.claude/skills -> ~/`). Reading it is slow and wide, but the * user pointed at it — refusing would be the silent-empty failure above, and * "the user asked for a big directory" is not a defect we can tell apart from * "the user has a big skills library". * * A root that is not a symlink at all is `true` unchanged: callers have already * established it is a directory, and this must not become a second existence * check that quietly disagrees with the first. */ export declare function walkableRoot(dir: string, scanRoot: string): boolean; //# sourceMappingURL=fs-walk.d.ts.map