/** * FileWalker Service * * Traverses the codebase intelligently, filtering noise and respecting ignore patterns. * Collects metadata about each file for significance scoring and analysis. * * The walker is where the substrate's honesty starts: every directory entry it does not analyze * is accounted for under a named skip reason or a truncation receipt, `includePatterns` override * every exclusion layer (down to directory pruning), and nested `.gitignore` files are honored * with git's subtree scoping — so a corpus is never silently smaller than it claims to be * (change: harden-walker-corpus-boundary). */ import type { FileWalkerResult } from '../../types/index.js'; /** * The `ignore` package matches POSIX-separated paths only. `path.relative()` yields * backslash separators on Windows, so every relative path handed to `ig.ignores()` must be * normalized first — otherwise gitignore/exclude/include matching silently no-ops there * (nothing excluded, or everything walked). This is the concrete site the * `fix-windows-invocation-surface` change names. */ export declare function toPosixPath(p: string): string; /** * The glob-free leading directory of an include pattern — the deepest ancestor a walk must * descend into to ever reach a file the pattern can match. `vendor/mylib/**` → `vendor/mylib`, * `src/**\/*.ts` → `src`, a bare `foo.ts` → `foo.ts`. Returned POSIX-normalized. */ export declare function includePatternPrefix(pattern: string): string; /** * Options for the FileWalker */ export interface FileWalkerOptions { /** Maximum number of files to process */ maxFiles?: number; /** Additional glob patterns to include */ includePatterns?: string[]; /** Additional glob patterns to exclude */ excludePatterns?: string[]; /** Progress callback for UI updates */ onProgress?: (progress: FileWalkerProgress) => void; /** AbortController signal for cancellation */ signal?: AbortSignal; /** Maximum concurrent file reads */ concurrency?: number; } /** * Progress information during file walking */ export interface FileWalkerProgress { filesFound: number; directoriesScanned: number; currentPath: string; } /** * FileWalker class for traversing codebases */ export declare class FileWalker { private rootPath; private options; private ig; /** Separate ignore instance used to check if a file matches includePatterns. */ private igInclude; /** * Glob-free directory prefixes of the include patterns. A directory on the lineage of any * of these must be descended even when a built-in skip / gitignore / excludePatterns rule * would otherwise prune it, so the documented "includePatterns override all exclusions" * contract holds at directory granularity, not only at file granularity. */ private includePrefixes; /** * True when an include pattern begins with a glob segment (`**\/*.ts`, `*.ts`) and therefore * has no glob-free directory prefix to anchor on. Such a pattern can match a file at ANY depth, * so every directory is on its lineage — no directory may be pruned, or the override is a * silent no-op inside pruned trees. */ private includeMatchesAnyDir; /** * Where the walk stopped when it hit `maxFiles`, if it did. Non-null means the corpus is a * truncated prefix of the repository, and the walk summary must say so rather than present * a partial corpus as complete. */ private truncatedAtPath; /** * Set once truncation is confirmed (an admissible file was denied because the cap was full). * Unwinds the recursion promptly instead of scanning the rest of the tree. */ private stopWalk; /** Entries examined past the `maxFiles` cap while probing for an overflow file (see the bound). */ private postCapEntriesExamined; private files; private skippedCount; private skippedReasons; private directoriesScanned; private extensionCounts; /** * Keyed by directory path, which comes from the scanned repository — so a directory * literally named `__proto__` would otherwise read and write `Object.prototype` * instead of this table. `Object.create(null)` has no prototype to reach, and still * serializes as a plain object for `byDirectory`. */ private directoryCounts; constructor(rootPath: string, options?: FileWalkerOptions); /** Real paths of directories already walked — stops a symlink cycle from walking forever. */ private readonly visitedRealDirs; /** The root, resolved, so confinement compares like with like when the root itself is a link. */ private realRootPath; /** How many followed symlinks (dir or file) entered the corpus — disclosed in the summary. */ private symlinkFollowedCount; /** Record a skipped directory entry under a named reason. */ private recordSkip; /** * Record where the walk hit the `maxFiles` cap. Only the first stop location wins. * * Called from exactly one site — the point where a file that passed every skip check cannot be * added because the corpus is full — so a non-null value means at least one genuinely * analyzable file was dropped. A complete corpus (even one that exactly fills the cap) is never * marked, because no admissible file is ever denied in that case. */ private markTruncated; /** * Count one entry examined past a full corpus and, if the probe budget is spent, conservatively * declare truncation and arm the unwind. Returns true when the caller should stop. A no-op (and * false) while the corpus is not yet full — the probe only runs past the cap. Reaching the budget * without an admissible file means a large trailing all-skipped subtree: continuing would re-scan * the repository the cap exists to bound, and a corpus with that much beyond it is honestly partial. */ private probePastCap; /** * Is `relativeDir` on the lineage of an include pattern — i.e. it either contains (is an * ancestor of) or lies under a directory an include pattern targets? Such a directory must * be descended regardless of any exclusion layer, or the include is a silent no-op because * its directory was pruned before any file inside it was ever tested. */ private matchesIncludeLineage; /** * Does any active nested `.gitignore` exclude this POSIX path? Each scope only governs its * own subtree (git semantics), so a pattern in `packages/app/.gitignore` never leaks to * `packages/lib/`. The directory holding a `.gitignore` is never excluded by its own file. * * BOUNDARY (deliberate): the root and nested `.gitignore` files are evaluated as INDEPENDENT * subtree filters (ignored if the root `this.ig` OR any scope says so), not as one git-style * depth-ordered chain. So a deeper `!important.log` that re-includes what a shallower `*.log` * excluded is NOT honored — the walker errs toward over-exclusion (a smaller, never a wrongly * larger, corpus). Same-file negation works (delegated to the `ignore` package). Full * cross-file negation precedence is a separate, larger change (it must not resurrect the * builtin skip dirs); see the follow-up task. */ private isIgnoredByNested; /** Read a directory's own `.gitignore` (if any) into a subtree-scoped matcher. */ private loadDirectoryGitignore; /** * Check if we should skip a directory */ private shouldSkipDirectory; /** * Check if we should skip a file. `posixPath` is the caller's already-normalized relative path * (avoids re-running `toPosixPath` per file on the walk's hot path). */ private shouldSkipFile; /** * Walk a directory recursively */ private walkDirectory; /** * Process a single file and collect metadata. Returns true when the file was added to the * corpus, false when a stat/read failure dropped it (recorded under a skip reason). */ private processFile; /** * Walk the codebase and collect file metadata */ walk(): Promise; } /** * Convenience function to walk a directory */ export declare function walkDirectory(rootPath: string, options?: FileWalkerOptions): Promise; //# sourceMappingURL=file-walker.d.ts.map