@arnilo/prism-coding-agent 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,284 @@
1
+ /** Repository search family (0.2.5 plan 025 Task 1 split).
2
+ * Moved verbatim from repository.ts; public surface unchanged behind the barrel. */
3
+ import { HARD_MAX_SEARCH_CONTEXT_LINES, HARD_MAX_SEARCH_MATCHES, validateCodingLimit, validateCodingLimitAllowZero } from "../limits.js";
4
+ import { lstat, open } from "node:fs/promises";
5
+ import { RepositoryError } from "./types.js";
6
+ import { assertDeadline, assertNotAborted, isBinaryBuffer, resolveRepoPath } from "./path.js";
7
+ export function compileSearchPattern(query, caseSensitive, maxPatternBytes) {
8
+ const patternBytes = Buffer.byteLength(query, "utf8");
9
+ if (patternBytes < 1)
10
+ throw new RepositoryError("query must be non-empty");
11
+ if (patternBytes > maxPatternBytes) {
12
+ throw new RepositoryError(`query exceeds ${maxPatternBytes} byte pattern limit`);
13
+ }
14
+ if (caseSensitive) {
15
+ return {
16
+ patternBytes,
17
+ testLine: (line) => {
18
+ const column = line.indexOf(query);
19
+ return column >= 0 ? { column: column + 1 } : null;
20
+ },
21
+ };
22
+ }
23
+ const needle = query.toLowerCase();
24
+ return {
25
+ patternBytes,
26
+ testLine: (line) => {
27
+ const column = line.toLowerCase().indexOf(needle);
28
+ return column >= 0 ? { column: column + 1 } : null;
29
+ },
30
+ };
31
+ }
32
+ async function searchFileLines(absolutePath, relativePath, testLine, options) {
33
+ assertNotAborted(options.signal);
34
+ assertDeadline(options.deadlineAt);
35
+ const handle = await open(absolutePath, "r");
36
+ try {
37
+ const st = await handle.stat();
38
+ if (st.size > options.maxFileBytes)
39
+ return "oversize";
40
+ const sniff = Buffer.allocUnsafe(Math.min(options.binarySniffBytes, st.size));
41
+ const { bytesRead: sniffed } = await handle.read(sniff, 0, sniff.length, 0);
42
+ if (isBinaryBuffer(sniff.subarray(0, sniffed)))
43
+ return "binary";
44
+ // Rewind and stream the whole file (already size-capped).
45
+ let offset = 0;
46
+ let lineStart = 0;
47
+ let lineNumber = 1;
48
+ let pending = Buffer.alloc(0);
49
+ const before = [];
50
+ const pendingAfter = [];
51
+ const readBuf = Buffer.allocUnsafe(64 * 1024);
52
+ const emitLine = (raw) => {
53
+ assertNotAborted(options.signal);
54
+ assertDeadline(options.deadlineAt);
55
+ const lineBytes = raw.length;
56
+ if (lineBytes > options.maxLineBytes) {
57
+ // Skip oversized lines but still charge scan budget for the bytes seen.
58
+ options.chargeScan(lineBytes);
59
+ if (options.maxScanBytesRemaining() < 0)
60
+ return "scan";
61
+ lineNumber++;
62
+ return "ok";
63
+ }
64
+ options.chargeScan(lineBytes);
65
+ if (options.maxScanBytesRemaining() < 0)
66
+ return "scan";
67
+ const text = raw.toString("utf8");
68
+ // Drain after-context for previous matches.
69
+ for (let i = pendingAfter.length - 1; i >= 0; i--) {
70
+ const item = pendingAfter[i];
71
+ if (item.remaining > 0) {
72
+ item.match.after.push(text);
73
+ item.remaining--;
74
+ }
75
+ if (item.remaining <= 0)
76
+ pendingAfter.splice(i, 1);
77
+ }
78
+ const hit = testLine(text);
79
+ if (hit) {
80
+ if (options.maxMatchesRemaining() <= 0)
81
+ return "matches";
82
+ const match = {
83
+ path: relativePath,
84
+ line: lineNumber,
85
+ column: hit.column,
86
+ text,
87
+ before: before.slice(-options.context),
88
+ after: [],
89
+ };
90
+ options.pushMatch(match);
91
+ if (options.context > 0)
92
+ pendingAfter.push({ match, remaining: options.context });
93
+ }
94
+ if (options.context > 0) {
95
+ before.push(text);
96
+ if (before.length > options.context)
97
+ before.shift();
98
+ }
99
+ lineNumber++;
100
+ return "ok";
101
+ };
102
+ while (offset < st.size) {
103
+ assertNotAborted(options.signal);
104
+ assertDeadline(options.deadlineAt);
105
+ if (options.maxScanBytesRemaining() <= 0)
106
+ return "scan";
107
+ if (options.maxMatchesRemaining() <= 0)
108
+ return "matches";
109
+ const { bytesRead } = await handle.read(readBuf, 0, readBuf.length, offset);
110
+ if (bytesRead === 0)
111
+ break;
112
+ offset += bytesRead;
113
+ pending = Buffer.concat([pending, readBuf.subarray(0, bytesRead)]);
114
+ let start = 0;
115
+ for (let i = 0; i < pending.length; i++) {
116
+ if (pending[i] === 0x0a) {
117
+ const end = i > start && pending[i - 1] === 0x0d ? i - 1 : i;
118
+ const status = emitLine(pending.subarray(start, end));
119
+ if (status !== "ok")
120
+ return status;
121
+ start = i + 1;
122
+ lineStart = offset - (pending.length - start);
123
+ }
124
+ }
125
+ pending = pending.subarray(start);
126
+ void lineStart;
127
+ }
128
+ if (pending.length > 0) {
129
+ const status = emitLine(pending);
130
+ if (status !== "ok")
131
+ return status;
132
+ }
133
+ return "ok";
134
+ }
135
+ finally {
136
+ await handle.close();
137
+ }
138
+ }
139
+ export async function searchLocal(request, defaults, walk) {
140
+ const mode = request.mode ?? "literal";
141
+ if (mode !== "literal") {
142
+ throw new RepositoryError(`unsupported search mode: ${String(mode)} (literal only)`);
143
+ }
144
+ const caseSensitive = request.caseSensitive === true;
145
+ const { testLine } = compileSearchPattern(request.query, caseSensitive, defaults.maxPatternBytes);
146
+ const resolved = await resolveRepoPath(request.root, request.path);
147
+ const maxMatches = validateCodingLimit("maxMatches", request.maxMatches ?? defaults.maxMatches, HARD_MAX_SEARCH_MATCHES);
148
+ const context = validateCodingLimitAllowZero("context", request.context ?? defaults.maxContextLines, HARD_MAX_SEARCH_CONTEXT_LINES);
149
+ const exclude = new Set(request.exclude ?? defaults.exclude);
150
+ const deadlineAt = request.deadlineMs !== undefined ? Date.now() + request.deadlineMs : Date.now() + defaults.maxTimeMs;
151
+ const matches = [];
152
+ let scannedBytes = 0;
153
+ let scannedFiles = 0;
154
+ let scannedEntries = 0;
155
+ let filesSkippedBinary = 0;
156
+ let filesSkippedOversize = 0;
157
+ let truncated = false;
158
+ let truncatedBy = null;
159
+ const runFile = async (absolutePath, relativePath) => {
160
+ if (truncated)
161
+ return;
162
+ const status = await searchFileLines(absolutePath, relativePath, testLine, {
163
+ maxFileBytes: defaults.maxFileBytes,
164
+ maxLineBytes: defaults.maxLineBytes,
165
+ maxScanBytesRemaining: () => defaults.maxScanBytes - scannedBytes,
166
+ chargeScan: (n) => {
167
+ scannedBytes += n;
168
+ },
169
+ context,
170
+ maxMatchesRemaining: () => maxMatches - matches.length,
171
+ pushMatch: (match) => {
172
+ if (matches.length < maxMatches)
173
+ matches.push(match);
174
+ },
175
+ signal: request.signal,
176
+ deadlineAt,
177
+ binarySniffBytes: defaults.binarySniffBytes,
178
+ });
179
+ if (status === "binary")
180
+ filesSkippedBinary++;
181
+ else if (status === "oversize")
182
+ filesSkippedOversize++;
183
+ else if (status === "scan") {
184
+ truncated = true;
185
+ truncatedBy = "scan";
186
+ }
187
+ else if (status === "matches") {
188
+ truncated = true;
189
+ truncatedBy = "matches";
190
+ }
191
+ };
192
+ try {
193
+ const startStat = await lstat(resolved.absolute);
194
+ if (startStat.isFile()) {
195
+ scannedEntries = 1;
196
+ scannedFiles = 1;
197
+ await runFile(resolved.absolute, resolved.relative);
198
+ }
199
+ else if (startStat.isDirectory()) {
200
+ for await (const event of walk(resolved.rootReal, resolved.absolute, {
201
+ maxDepth: defaults.maxDepth,
202
+ maxEntries: defaults.maxEntries,
203
+ maxFiles: defaults.maxFiles,
204
+ exclude,
205
+ includeHidden: request.includeHidden === true,
206
+ signal: request.signal,
207
+ deadlineAt,
208
+ })) {
209
+ if (truncated)
210
+ break;
211
+ if (event.type === "limit") {
212
+ truncated = true;
213
+ truncatedBy = event.truncatedBy;
214
+ break;
215
+ }
216
+ scannedEntries++;
217
+ if (event.entry.kind !== "file")
218
+ continue;
219
+ scannedFiles++;
220
+ try {
221
+ await runFile(event.absolutePath, event.entry.path);
222
+ }
223
+ catch (error) {
224
+ if (error instanceof RepositoryError) {
225
+ if (error.message === "Operation aborted") {
226
+ truncated = true;
227
+ truncatedBy = "abort";
228
+ break;
229
+ }
230
+ if (error.message === "Repository operation exceeded time limit") {
231
+ truncated = true;
232
+ truncatedBy = "time";
233
+ break;
234
+ }
235
+ }
236
+ // Unreadable files are skipped; walk continues.
237
+ }
238
+ }
239
+ }
240
+ else if (startStat.isSymbolicLink()) {
241
+ // Symlink starts are not followed for search content.
242
+ scannedEntries = 1;
243
+ }
244
+ }
245
+ catch (error) {
246
+ if (error instanceof RepositoryError && error.message === "Operation aborted") {
247
+ truncated = true;
248
+ truncatedBy = "abort";
249
+ }
250
+ else if (error instanceof RepositoryError && error.message === "Repository operation exceeded time limit") {
251
+ truncated = true;
252
+ truncatedBy = "time";
253
+ }
254
+ else if (error instanceof RepositoryError) {
255
+ throw error;
256
+ }
257
+ else {
258
+ const message = error instanceof Error ? error.message : String(error);
259
+ throw new RepositoryError(`cannot search path: ${message}`);
260
+ }
261
+ }
262
+ if (!truncated && matches.length >= maxMatches) {
263
+ truncated = true;
264
+ truncatedBy = "matches";
265
+ }
266
+ matches.sort((a, b) => {
267
+ if (a.path !== b.path)
268
+ return a.path < b.path ? -1 : 1;
269
+ if (a.line !== b.line)
270
+ return a.line - b.line;
271
+ return a.column - b.column;
272
+ });
273
+ return {
274
+ matches: matches.slice(0, maxMatches),
275
+ truncated,
276
+ truncatedBy,
277
+ scannedBytes,
278
+ scannedFiles,
279
+ scannedEntries,
280
+ filesSkippedBinary,
281
+ filesSkippedOversize,
282
+ };
283
+ }
284
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,126 @@
1
+ export type RepoEntryKind = "file" | "directory" | "symlink" | "other";
2
+ export interface RepoListEntry {
3
+ readonly path: string;
4
+ readonly kind: RepoEntryKind;
5
+ readonly size?: number;
6
+ }
7
+ export interface RepositoryListResult {
8
+ readonly entries: readonly RepoListEntry[];
9
+ readonly truncated: boolean;
10
+ readonly truncatedBy: "results" | "entries" | "files" | "depth" | "time" | "abort" | null;
11
+ readonly scannedEntries: number;
12
+ readonly scannedFiles: number;
13
+ readonly nextOffset?: number;
14
+ readonly offset: number;
15
+ }
16
+ export interface RepositoryGlobResult {
17
+ readonly paths: readonly string[];
18
+ readonly truncated: boolean;
19
+ readonly truncatedBy: RepositoryListResult["truncatedBy"];
20
+ readonly scannedEntries: number;
21
+ readonly scannedFiles: number;
22
+ readonly nextOffset?: number;
23
+ readonly offset: number;
24
+ }
25
+ export type RepoSearchOutputMode = "content" | "files_with_matches" | "count";
26
+ export interface RepositorySearchMatch {
27
+ readonly path: string;
28
+ readonly line: number;
29
+ readonly column: number;
30
+ readonly text: string;
31
+ readonly before: readonly string[];
32
+ readonly after: readonly string[];
33
+ }
34
+ export interface RepositorySearchResult {
35
+ readonly matches: readonly RepositorySearchMatch[];
36
+ readonly truncated: boolean;
37
+ readonly truncatedBy: "matches" | "scan" | "file" | "entries" | "files" | "depth" | "time" | "abort" | "pattern" | null;
38
+ readonly scannedBytes: number;
39
+ readonly scannedFiles: number;
40
+ readonly scannedEntries: number;
41
+ readonly filesSkippedBinary: number;
42
+ readonly filesSkippedOversize: number;
43
+ }
44
+ export interface ResolvedRepositoryLimits {
45
+ readonly maxDepth: number;
46
+ readonly maxEntries: number;
47
+ readonly maxFiles: number;
48
+ readonly maxResults: number;
49
+ readonly maxConcurrency: number;
50
+ readonly maxScanBytes: number;
51
+ readonly maxFileBytes: number;
52
+ readonly maxMatches: number;
53
+ readonly maxPatternBytes: number;
54
+ readonly maxLineBytes: number;
55
+ readonly maxContextLines: number;
56
+ readonly maxTimeMs: number;
57
+ readonly binarySniffBytes: number;
58
+ readonly exclude: readonly string[];
59
+ }
60
+ export interface RepositoryLimitOptions {
61
+ readonly maxDepth?: number;
62
+ readonly maxEntries?: number;
63
+ readonly maxFiles?: number;
64
+ readonly maxResults?: number;
65
+ readonly maxConcurrency?: number;
66
+ readonly maxScanBytes?: number;
67
+ readonly maxFileBytes?: number;
68
+ readonly maxMatches?: number;
69
+ readonly maxPatternBytes?: number;
70
+ readonly maxLineBytes?: number;
71
+ readonly maxContextLines?: number;
72
+ readonly maxTimeMs?: number;
73
+ /** Basename denylist skipped during descent (default `.git`, `node_modules`, `dist`). */
74
+ readonly exclude?: readonly string[];
75
+ }
76
+ export interface RepositoryListRequest {
77
+ readonly root: string;
78
+ readonly path?: string;
79
+ readonly includeHidden?: boolean;
80
+ readonly exclude?: readonly string[];
81
+ readonly maxDepth?: number;
82
+ readonly maxResults?: number;
83
+ readonly offset?: number;
84
+ readonly signal?: AbortSignal;
85
+ readonly deadlineMs?: number;
86
+ }
87
+ export interface RepositorySearchRequest {
88
+ readonly root: string;
89
+ readonly query: string;
90
+ readonly path?: string;
91
+ readonly mode?: "literal";
92
+ readonly outputMode?: RepoSearchOutputMode;
93
+ readonly caseSensitive?: boolean;
94
+ readonly includeHidden?: boolean;
95
+ readonly exclude?: readonly string[];
96
+ readonly context?: number;
97
+ readonly maxMatches?: number;
98
+ readonly signal?: AbortSignal;
99
+ readonly deadlineMs?: number;
100
+ }
101
+ export interface RepositoryGlobRequest {
102
+ readonly root: string;
103
+ readonly pattern: string;
104
+ readonly path?: string;
105
+ readonly includeHidden?: boolean;
106
+ readonly exclude?: readonly string[];
107
+ readonly maxDepth?: number;
108
+ readonly maxResults?: number;
109
+ readonly offset?: number;
110
+ /** Opt-in bounded `{a,b}` expansion (default false; expansion bounds in glob-match.ts). */
111
+ readonly braceExpansion?: boolean;
112
+ readonly signal?: AbortSignal;
113
+ readonly deadlineMs?: number;
114
+ }
115
+ export interface RepositoryOperations {
116
+ list(request: RepositoryListRequest): Promise<RepositoryListResult>;
117
+ search(request: RepositorySearchRequest): Promise<RepositorySearchResult>;
118
+ glob(request: RepositoryGlobRequest): Promise<RepositoryGlobResult>;
119
+ }
120
+ export declare const DEFAULT_REPO_EXCLUDE: readonly string[];
121
+ export declare class RepositoryError extends Error {
122
+ readonly code = "ERR_PRISM_REPOSITORY";
123
+ constructor(message: string);
124
+ }
125
+ export declare function resolveRepositoryLimits(options?: RepositoryLimitOptions): ResolvedRepositoryLimits;
126
+ /** Normalize a workspace-relative path to stable forward-slash form. */
@@ -0,0 +1,31 @@
1
+ /** Repository types family (0.2.5 plan 025 Task 1 split).
2
+ * Moved verbatim from repository.ts; public surface unchanged behind the barrel. */
3
+ import { DEFAULT_BINARY_SNIFF_BYTES, DEFAULT_MAX_REPO_CONCURRENCY, DEFAULT_MAX_REPO_DEPTH, DEFAULT_MAX_REPO_ENTRIES, DEFAULT_MAX_REPO_FILES, DEFAULT_MAX_REPO_RESULTS, DEFAULT_MAX_SEARCH_CONTEXT_LINES, DEFAULT_MAX_SEARCH_FILE_BYTES, DEFAULT_MAX_SEARCH_LINE_BYTES, DEFAULT_MAX_SEARCH_MATCHES, DEFAULT_MAX_SEARCH_PATTERN_BYTES, DEFAULT_MAX_SEARCH_SCAN_BYTES, DEFAULT_MAX_SEARCH_TIME_MS, HARD_MAX_REPO_CONCURRENCY, HARD_MAX_REPO_DEPTH, HARD_MAX_REPO_ENTRIES, HARD_MAX_REPO_FILES, HARD_MAX_REPO_RESULTS, HARD_MAX_SEARCH_CONTEXT_LINES, HARD_MAX_SEARCH_FILE_BYTES, HARD_MAX_SEARCH_LINE_BYTES, HARD_MAX_SEARCH_MATCHES, HARD_MAX_SEARCH_PATTERN_BYTES, HARD_MAX_SEARCH_SCAN_BYTES, HARD_MAX_SEARCH_TIME_MS, validateCodingLimit, validateCodingLimitAllowZero, } from "../limits.js";
4
+ export const DEFAULT_REPO_EXCLUDE = Object.freeze([".git", "node_modules", "dist"]);
5
+ export class RepositoryError extends Error {
6
+ code = "ERR_PRISM_REPOSITORY";
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "RepositoryError";
10
+ }
11
+ }
12
+ export function resolveRepositoryLimits(options) {
13
+ return {
14
+ maxDepth: validateCodingLimit("maxDepth", options?.maxDepth ?? DEFAULT_MAX_REPO_DEPTH, HARD_MAX_REPO_DEPTH),
15
+ maxEntries: validateCodingLimit("maxEntries", options?.maxEntries ?? DEFAULT_MAX_REPO_ENTRIES, HARD_MAX_REPO_ENTRIES),
16
+ maxFiles: validateCodingLimit("maxFiles", options?.maxFiles ?? DEFAULT_MAX_REPO_FILES, HARD_MAX_REPO_FILES),
17
+ maxResults: validateCodingLimit("maxResults", options?.maxResults ?? DEFAULT_MAX_REPO_RESULTS, HARD_MAX_REPO_RESULTS),
18
+ maxConcurrency: validateCodingLimit("maxConcurrency", options?.maxConcurrency ?? DEFAULT_MAX_REPO_CONCURRENCY, HARD_MAX_REPO_CONCURRENCY),
19
+ maxScanBytes: validateCodingLimit("maxScanBytes", options?.maxScanBytes ?? DEFAULT_MAX_SEARCH_SCAN_BYTES, HARD_MAX_SEARCH_SCAN_BYTES),
20
+ maxFileBytes: validateCodingLimit("maxFileBytes", options?.maxFileBytes ?? DEFAULT_MAX_SEARCH_FILE_BYTES, HARD_MAX_SEARCH_FILE_BYTES),
21
+ maxMatches: validateCodingLimit("maxMatches", options?.maxMatches ?? DEFAULT_MAX_SEARCH_MATCHES, HARD_MAX_SEARCH_MATCHES),
22
+ maxPatternBytes: validateCodingLimit("maxPatternBytes", options?.maxPatternBytes ?? DEFAULT_MAX_SEARCH_PATTERN_BYTES, HARD_MAX_SEARCH_PATTERN_BYTES),
23
+ maxLineBytes: validateCodingLimit("maxLineBytes", options?.maxLineBytes ?? DEFAULT_MAX_SEARCH_LINE_BYTES, HARD_MAX_SEARCH_LINE_BYTES),
24
+ maxContextLines: validateCodingLimitAllowZero("maxContextLines", options?.maxContextLines ?? DEFAULT_MAX_SEARCH_CONTEXT_LINES, HARD_MAX_SEARCH_CONTEXT_LINES),
25
+ maxTimeMs: validateCodingLimit("maxTimeMs", options?.maxTimeMs ?? DEFAULT_MAX_SEARCH_TIME_MS, HARD_MAX_SEARCH_TIME_MS),
26
+ binarySniffBytes: DEFAULT_BINARY_SNIFF_BYTES,
27
+ exclude: Object.freeze([...(options?.exclude ?? DEFAULT_REPO_EXCLUDE)]),
28
+ };
29
+ }
30
+ /** Normalize a workspace-relative path to stable forward-slash form. */
31
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,22 @@
1
+ import type { RepoListEntry } from "./types.js";
2
+ export interface RepositoryWalkLimits {
3
+ maxDepth: number;
4
+ maxEntries: number;
5
+ maxFiles: number;
6
+ exclude: ReadonlySet<string>;
7
+ includeHidden: boolean;
8
+ signal?: AbortSignal;
9
+ deadlineAt?: number;
10
+ }
11
+ export type RepositoryWalkEvent = {
12
+ type: "entry";
13
+ entry: RepoListEntry;
14
+ absolutePath: string;
15
+ depth: number;
16
+ } | {
17
+ type: "limit";
18
+ truncatedBy: "entries" | "files" | "depth";
19
+ };
20
+ /** Injectable enumerator for list/search/glob. Default is the native opendir walker. */
21
+ export type RepositoryWalk = (rootReal: string, startAbsolute: string, limits: RepositoryWalkLimits) => AsyncGenerator<RepositoryWalkEvent>;
22
+ export declare function walkRepository(rootReal: string, startAbsolute: string, limits: RepositoryWalkLimits): AsyncGenerator<RepositoryWalkEvent>;
@@ -0,0 +1,99 @@
1
+ import { join } from "node:path";
2
+ import { lstat, opendir } from "node:fs/promises";
3
+ import { RepositoryError } from "./types.js";
4
+ import { assertDeadline, assertNotAborted, isPathInsideRoot, kindFromDirent, shouldSkipName, toRepoRelative } from "./path.js";
5
+ export async function* walkRepository(rootReal, startAbsolute, limits) {
6
+ const queue = [
7
+ {
8
+ absolute: startAbsolute,
9
+ relative: toRepoRelative(rootReal, startAbsolute),
10
+ depth: 0,
11
+ },
12
+ ];
13
+ let scannedEntries = 0;
14
+ let scannedFiles = 0;
15
+ while (queue.length > 0) {
16
+ assertNotAborted(limits.signal);
17
+ assertDeadline(limits.deadlineAt);
18
+ const current = queue.shift();
19
+ if (current.depth > limits.maxDepth) {
20
+ yield { type: "limit", truncatedBy: "depth" };
21
+ return;
22
+ }
23
+ let dir;
24
+ try {
25
+ dir = await opendir(current.absolute);
26
+ }
27
+ catch (error) {
28
+ if (current.relative === "." || current.depth === 0) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ throw new RepositoryError(`cannot open directory: ${message}`);
31
+ }
32
+ continue;
33
+ }
34
+ const names = [];
35
+ try {
36
+ for await (const dirent of dir) {
37
+ assertNotAborted(limits.signal);
38
+ assertDeadline(limits.deadlineAt);
39
+ names.push(dirent);
40
+ }
41
+ }
42
+ finally {
43
+ await dir.close().catch(() => undefined);
44
+ }
45
+ names.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
46
+ for (const dirent of names) {
47
+ assertNotAborted(limits.signal);
48
+ assertDeadline(limits.deadlineAt);
49
+ if (shouldSkipName(dirent.name, limits.includeHidden, limits.exclude))
50
+ continue;
51
+ if (scannedEntries >= limits.maxEntries) {
52
+ yield { type: "limit", truncatedBy: "entries" };
53
+ return;
54
+ }
55
+ scannedEntries++;
56
+ const absolutePath = join(current.absolute, dirent.name);
57
+ if (!isPathInsideRoot(rootReal, absolutePath))
58
+ continue;
59
+ let kind = kindFromDirent(dirent);
60
+ let size;
61
+ // Re-check with lstat so we never follow symlinks for type/size.
62
+ try {
63
+ const st = await lstat(absolutePath);
64
+ if (st.isSymbolicLink())
65
+ kind = "symlink";
66
+ else if (st.isDirectory())
67
+ kind = "directory";
68
+ else if (st.isFile())
69
+ kind = "file";
70
+ else
71
+ kind = "other";
72
+ if (kind === "file")
73
+ size = st.size;
74
+ }
75
+ catch {
76
+ continue;
77
+ }
78
+ if (kind === "file") {
79
+ if (scannedFiles >= limits.maxFiles) {
80
+ yield { type: "limit", truncatedBy: "files" };
81
+ return;
82
+ }
83
+ scannedFiles++;
84
+ }
85
+ const relativePath = current.relative === "." ? dirent.name : `${current.relative}/${dirent.name}`;
86
+ const entry = size === undefined ? { path: relativePath, kind } : { path: relativePath, kind, size };
87
+ yield { type: "entry", entry, absolutePath, depth: current.depth };
88
+ if (kind === "directory") {
89
+ const nextDepth = current.depth + 1;
90
+ if (nextDepth > limits.maxDepth) {
91
+ yield { type: "limit", truncatedBy: "depth" };
92
+ return;
93
+ }
94
+ queue.push({ absolute: absolutePath, relative: relativePath, depth: nextDepth });
95
+ }
96
+ }
97
+ }
98
+ }
99
+ //# sourceMappingURL=walk.js.map