@j0hanz/filesystem-mcp 1.14.0 → 1.14.1

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.
Files changed (66) hide show
  1. package/README.md +47 -47
  2. package/dist/cli.js +2 -2
  3. package/dist/completions.js +54 -51
  4. package/dist/config.d.ts +13 -13
  5. package/dist/config.js +12 -12
  6. package/dist/index.js +1 -1
  7. package/dist/lib/abort.d.ts +7 -0
  8. package/dist/lib/abort.js +81 -0
  9. package/dist/lib/constants.d.ts +3 -1
  10. package/dist/lib/constants.js +8 -2
  11. package/dist/lib/errors.d.ts +7 -3
  12. package/dist/lib/errors.js +59 -39
  13. package/dist/lib/file-operations/core.d.ts +3 -3
  14. package/dist/lib/file-operations/core.js +23 -20
  15. package/dist/lib/file-operations/metadata.d.ts +2 -2
  16. package/dist/lib/file-operations/metadata.js +60 -19
  17. package/dist/lib/file-operations/search.js +83 -84
  18. package/dist/lib/file-operations/traversal.js +13 -15
  19. package/dist/lib/fs-helpers.d.ts +3 -10
  20. package/dist/lib/fs-helpers.js +20 -98
  21. package/dist/lib/globs.js +1 -1
  22. package/dist/lib/logger.d.ts +28 -0
  23. package/dist/lib/logger.js +91 -0
  24. package/dist/lib/observability.d.ts +7 -0
  25. package/dist/lib/observability.js +19 -9
  26. package/dist/lib/paths.js +55 -55
  27. package/dist/lib/resource-store.js +4 -4
  28. package/dist/lib/utils.d.ts +0 -12
  29. package/dist/lib/utils.js +0 -13
  30. package/dist/resources/generated-instructions.js +40 -31
  31. package/dist/resources/tool-catalog.js +32 -26
  32. package/dist/resources/tool-info.js +34 -29
  33. package/dist/resources/workflows.js +39 -18
  34. package/dist/resources.d.ts +1 -1
  35. package/dist/resources.js +4 -4
  36. package/dist/schemas.d.ts +66 -66
  37. package/dist/schemas.js +21 -44
  38. package/dist/server/bootstrap.d.ts +12 -11
  39. package/dist/server/bootstrap.js +95 -86
  40. package/dist/server/roots-manager.d.ts +5 -2
  41. package/dist/server/roots-manager.js +8 -6
  42. package/dist/server/task-store.d.ts +10 -0
  43. package/dist/server/task-store.js +73 -0
  44. package/dist/tools/apply-patch.js +26 -18
  45. package/dist/tools/calculate-hash.js +11 -22
  46. package/dist/tools/create-directory.js +10 -8
  47. package/dist/tools/delete-file.js +17 -15
  48. package/dist/tools/diff-files.js +15 -15
  49. package/dist/tools/edit-file.js +7 -4
  50. package/dist/tools/list-directory.js +6 -6
  51. package/dist/tools/move-file.js +98 -81
  52. package/dist/tools/read-multiple.js +4 -4
  53. package/dist/tools/read.js +5 -5
  54. package/dist/tools/replace-in-files.js +20 -23
  55. package/dist/tools/roots.js +1 -1
  56. package/dist/tools/search-content.js +32 -41
  57. package/dist/tools/search-files.js +54 -41
  58. package/dist/tools/shared.d.ts +3 -0
  59. package/dist/tools/shared.js +70 -28
  60. package/dist/tools/stat-many.js +11 -7
  61. package/dist/tools/stat.js +4 -4
  62. package/dist/tools/task-support.d.ts +10 -9
  63. package/dist/tools/task-support.js +94 -23
  64. package/dist/tools/tree.js +3 -3
  65. package/dist/tools/write-file.js +10 -7
  66. package/package.json +9 -9
@@ -1,6 +1,7 @@
1
1
  import { constants as osConstants } from 'node:os';
2
2
  import { getSystemErrorMap, getSystemErrorName, inspect } from 'node:util';
3
3
  import { ErrorCode, joinLines } from '../config.js';
4
+ import { getTraceContext } from './observability.js';
4
5
  export { ErrorCode };
5
6
  function isNativeError(error) {
6
7
  const candidate = Error;
@@ -100,20 +101,20 @@ export function normalizeUnknownError(error) {
100
101
  : new Error(formatUnknownErrorMessage(error));
101
102
  }
102
103
  const NODE_ERROR_CODE_MAP = {
103
- ENOENT: ErrorCode.E_NOT_FOUND,
104
- EACCES: ErrorCode.E_PERMISSION_DENIED,
105
- EPERM: ErrorCode.E_PERMISSION_DENIED,
106
- ENOTDIR: ErrorCode.E_NOT_DIRECTORY,
107
- EISDIR: ErrorCode.E_NOT_FILE,
108
- ELOOP: ErrorCode.E_SYMLINK_NOT_ALLOWED,
109
- ENAMETOOLONG: ErrorCode.E_INVALID_INPUT,
110
- ETIMEDOUT: ErrorCode.E_TIMEOUT,
111
- EMFILE: ErrorCode.E_TIMEOUT,
112
- ENFILE: ErrorCode.E_TIMEOUT,
113
- EBUSY: ErrorCode.E_PERMISSION_DENIED,
114
- ENOTEMPTY: ErrorCode.E_NOT_DIRECTORY,
115
- EEXIST: ErrorCode.E_INVALID_INPUT,
116
- EINVAL: ErrorCode.E_INVALID_INPUT,
104
+ ENOENT: ErrorCode.NOT_FOUND,
105
+ EACCES: ErrorCode.PERMISSION_DENIED,
106
+ EPERM: ErrorCode.PERMISSION_DENIED,
107
+ ENOTDIR: ErrorCode.NOT_DIRECTORY,
108
+ EISDIR: ErrorCode.NOT_FILE,
109
+ ELOOP: ErrorCode.SYMLINK_NOT_ALLOWED,
110
+ ENAMETOOLONG: ErrorCode.INVALID_INPUT,
111
+ ETIMEDOUT: ErrorCode.TIMEOUT,
112
+ EMFILE: ErrorCode.TIMEOUT,
113
+ ENFILE: ErrorCode.TIMEOUT,
114
+ EBUSY: ErrorCode.PERMISSION_DENIED,
115
+ ENOTEMPTY: ErrorCode.NOT_DIRECTORY,
116
+ EEXIST: ErrorCode.INVALID_INPUT,
117
+ EINVAL: ErrorCode.INVALID_INPUT,
117
118
  };
118
119
  function isKnownNodeErrorCode(code) {
119
120
  return code in NODE_ERROR_CODE_MAP;
@@ -169,24 +170,39 @@ export class McpError extends Error {
169
170
  super(message, { cause });
170
171
  this.code = code;
171
172
  this.path = path;
172
- this.details = details;
173
173
  this.name = 'McpError';
174
174
  Object.setPrototypeOf(this, McpError.prototype);
175
+ const trace = getTraceContext();
176
+ if (trace?.traceparent || details) {
177
+ this.details = { ...trace, ...details };
178
+ }
179
+ }
180
+ static notFound(message, path, details, cause) {
181
+ return new McpError(ErrorCode.NOT_FOUND, message, path, details, cause);
182
+ }
183
+ static invalidInput(message, path, details, cause) {
184
+ return new McpError(ErrorCode.INVALID_INPUT, message, path, details, cause);
185
+ }
186
+ static accessDenied(message, path, details, cause) {
187
+ return new McpError(ErrorCode.ACCESS_DENIED, message, path, details, cause);
188
+ }
189
+ static timeout(message, path, details, cause) {
190
+ return new McpError(ErrorCode.TIMEOUT, message, path, details, cause);
175
191
  }
176
192
  }
177
193
  const ERROR_SUGGESTIONS = {
178
- [ErrorCode.E_ACCESS_DENIED]: 'Check that the path is within an allowed directory. Use roots to see available workspace roots.',
179
- [ErrorCode.E_NOT_FOUND]: 'Verify the path exists. Use ls to explore available files and directories.',
180
- [ErrorCode.E_NOT_FILE]: 'The path points to a directory or other non-file. Use ls to explore its contents.',
181
- [ErrorCode.E_NOT_DIRECTORY]: 'The path points to a file, not a directory. Use read to read file contents.',
182
- [ErrorCode.E_TOO_LARGE]: 'The file exceeds the size limit. Use head to read a partial preview, or narrow the scope of what you read.',
183
- [ErrorCode.E_TIMEOUT]: 'The operation timed out. Try a smaller scope (narrower path), fewer results (maxResults), or search fewer files.',
184
- [ErrorCode.E_CANCELLED]: 'The operation was cancelled. This is not an error — no retry is needed unless you want to re-run the operation.',
185
- [ErrorCode.E_INVALID_PATTERN]: 'The glob or regex pattern is invalid. Check syntax and escape special characters.',
186
- [ErrorCode.E_INVALID_INPUT]: 'One or more input parameters are invalid. Check the tool documentation for correct usage.',
187
- [ErrorCode.E_PERMISSION_DENIED]: 'Permission denied by the operating system. Check file permissions.',
188
- [ErrorCode.E_SYMLINK_NOT_ALLOWED]: 'Symbolic links that escape allowed directories are not permitted for security reasons.',
189
- [ErrorCode.E_UNKNOWN]: 'An unexpected error occurred. Check the error message for details.',
194
+ [ErrorCode.ACCESS_DENIED]: 'Run roots to list allowed directories.',
195
+ [ErrorCode.NOT_FOUND]: 'Run ls or find to verify the path.',
196
+ [ErrorCode.NOT_FILE]: 'Target is a directory, not a file.',
197
+ [ErrorCode.NOT_DIRECTORY]: 'Target is a file, not a directory.',
198
+ [ErrorCode.TOO_LARGE]: 'Use head/tail or line ranges to read partially.',
199
+ [ErrorCode.TIMEOUT]: 'Reduce scope, depth, or maxResults.',
200
+ [ErrorCode.CANCELLED]: undefined,
201
+ [ErrorCode.INVALID_PATTERN]: 'Check syntax and escape special characters.',
202
+ [ErrorCode.INVALID_INPUT]: undefined,
203
+ [ErrorCode.PERMISSION_DENIED]: 'Check OS file permissions.',
204
+ [ErrorCode.SYMLINK_NOT_ALLOWED]: 'Symlink escapes allowed directories.',
205
+ [ErrorCode.UNKNOWN]: undefined,
190
206
  };
191
207
  const NOT_FOUND_PATTERNS = [
192
208
  'no such file or directory',
@@ -207,16 +223,16 @@ function classifyMessageError(error) {
207
223
  const message = isNativeError(error) ? error.message : String(error);
208
224
  const lower = message.toLowerCase();
209
225
  if (messageIncludesAny(lower, NOT_FOUND_PATTERNS)) {
210
- return ErrorCode.E_NOT_FOUND;
226
+ return ErrorCode.NOT_FOUND;
211
227
  }
212
228
  if (messageIncludesAny(lower, PERMISSION_DENIED_PATTERNS)) {
213
- return ErrorCode.E_PERMISSION_DENIED;
229
+ return ErrorCode.PERMISSION_DENIED;
214
230
  }
215
231
  if (lower.includes('not a directory')) {
216
- return ErrorCode.E_NOT_DIRECTORY;
232
+ return ErrorCode.NOT_DIRECTORY;
217
233
  }
218
234
  if (lower.includes('is a directory')) {
219
- return ErrorCode.E_NOT_FILE;
235
+ return ErrorCode.NOT_FILE;
220
236
  }
221
237
  return undefined;
222
238
  }
@@ -225,16 +241,16 @@ function classifyError(error) {
225
241
  let fallbackCode;
226
242
  const terminalCode = walkErrorChain(error, (candidate) => {
227
243
  if (isAbortErrorSingle(candidate)) {
228
- return ErrorCode.E_CANCELLED;
244
+ return ErrorCode.CANCELLED;
229
245
  }
230
246
  if (timeoutCode === undefined && isTimeoutErrorSingle(candidate)) {
231
- timeoutCode = ErrorCode.E_TIMEOUT;
247
+ timeoutCode = ErrorCode.TIMEOUT;
232
248
  }
233
249
  fallbackCode ??=
234
250
  getDirectErrorCode(candidate) ?? classifyMessageError(candidate);
235
251
  return undefined;
236
252
  });
237
- return terminalCode ?? timeoutCode ?? fallbackCode ?? ErrorCode.E_UNKNOWN;
253
+ return terminalCode ?? timeoutCode ?? fallbackCode ?? ErrorCode.UNKNOWN;
238
254
  }
239
255
  export function createDetailedError(error, path, additionalDetails) {
240
256
  const message = formatUnknownErrorMessage(error);
@@ -242,7 +258,11 @@ export function createDetailedError(error, path, additionalDetails) {
242
258
  const suggestion = ERROR_SUGGESTIONS[code];
243
259
  const resolvedPath = resolveErrorPath(error, path);
244
260
  const details = mergeErrorDetails(error, additionalDetails);
245
- const result = { code, message, suggestion };
261
+ const result = {
262
+ code,
263
+ message,
264
+ ...(suggestion ? { suggestion } : {}),
265
+ };
246
266
  if (resolvedPath)
247
267
  result.path = resolvedPath;
248
268
  if (details)
@@ -267,12 +287,12 @@ function mergeErrorDetails(error, additionalDetails) {
267
287
  return mergedDetails;
268
288
  }
269
289
  export function formatDetailedError(error) {
270
- const lines = [`Error [${error.code}]: ${error.message}`];
271
- if (error.path) {
272
- lines.push(`Path: ${error.path}`);
290
+ const lines = [`${error.code}: ${error.message}`];
291
+ if (error.path && !error.message.includes(error.path)) {
292
+ lines.push(error.path);
273
293
  }
274
294
  if (error.suggestion) {
275
- lines.push(`Suggestion: ${error.suggestion}`);
295
+ lines.push(error.suggestion);
276
296
  }
277
297
  return joinLines(lines);
278
298
  }
@@ -1,8 +1,8 @@
1
1
  import { type Ignore } from 'ignore';
2
2
  export declare function needsStatsForSort(sortBy: string): boolean;
3
- export declare function withOptionalStoppedReason<T extends object, R extends string>(summary: T, stoppedReason: R | undefined): T | (T & {
4
- stoppedReason: R;
5
- });
3
+ export declare function withOptionalStoppedReason<T extends object, R extends string>(summary: T, stoppedReason: R | undefined): T & {
4
+ stoppedReason?: R;
5
+ };
6
6
  export interface DirentLike {
7
7
  isDirectory(): boolean;
8
8
  isFile(): boolean;
@@ -1,5 +1,5 @@
1
- import * as fs from 'node:fs/promises';
2
- import * as path from 'node:path';
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
3
  import ignore, {} from 'ignore';
4
4
  import { isNodeError } from '../errors.js';
5
5
  import { toPosixPath } from '../paths.js';
@@ -40,7 +40,8 @@ export function compareOptionalNumberDesc(left, right, tieBreak) {
40
40
  }
41
41
  export function stableSortByDerivedString(items, derive, tieBreak) {
42
42
  const decorated = [];
43
- for (let index = 0; index < items.length; index += 1) {
43
+ const len = items.length;
44
+ for (let index = 0; index < len; index++) {
44
45
  const item = items[index];
45
46
  if (item === undefined)
46
47
  continue;
@@ -59,7 +60,8 @@ export function stableSortByDerivedString(items, derive, tieBreak) {
59
60
  return tiedCompare;
60
61
  return left.index - right.index;
61
62
  });
62
- for (let index = 0; index < decorated.length; index += 1) {
63
+ const decoratedLen = decorated.length;
64
+ for (let index = 0; index < decoratedLen; index++) {
63
65
  const entry = decorated[index];
64
66
  if (!entry)
65
67
  continue;
@@ -74,13 +76,14 @@ export function applyIndexedValues(output, results) {
74
76
  }
75
77
  }
76
78
  export function applyIndexedErrors(options) {
77
- for (const failure of options.errors) {
78
- const resolvedIndex = options.resolveIndex(failure.index);
79
+ const { output, errors, resolveIndex, buildValue } = options;
80
+ for (const failure of errors) {
81
+ const resolvedIndex = resolveIndex(failure.index);
79
82
  if (resolvedIndex === undefined)
80
83
  continue;
81
- if (resolvedIndex < 0 || resolvedIndex >= options.output.length)
84
+ if (resolvedIndex < 0 || resolvedIndex >= output.length)
82
85
  continue;
83
- options.output[resolvedIndex] = options.buildValue(resolvedIndex, failure.error);
86
+ output[resolvedIndex] = buildValue(resolvedIndex, failure.error);
84
87
  }
85
88
  }
86
89
  export async function isEntryAccessibleByType(entryPath, entryType, rootDirectories, signal, deps) {
@@ -101,8 +104,9 @@ export async function isEntryAccessibleByType(entryPath, entryType, rootDirector
101
104
  }
102
105
  function parseGitignoreLines(contents) {
103
106
  const lines = [];
104
- for (const line of contents.split(/\r?\n/u)) {
105
- const trimmed = line.trim();
107
+ const parts = contents.split(/\r?\n/u);
108
+ for (const part of parts) {
109
+ const trimmed = part.trim();
106
110
  if (trimmed.length > 0) {
107
111
  lines.push(trimmed);
108
112
  }
@@ -110,13 +114,15 @@ function parseGitignoreLines(contents) {
110
114
  return lines;
111
115
  }
112
116
  export async function loadRootGitignore(root, signal) {
113
- const gitignorePath = path.join(root, '.gitignore');
114
- let contents;
117
+ const gitignorePath = join(root, '.gitignore');
115
118
  try {
116
- contents = await fs.readFile(gitignorePath, {
119
+ const contents = await readFile(gitignorePath, {
117
120
  encoding: 'utf-8',
118
121
  signal,
119
122
  });
123
+ const matcher = ignore();
124
+ matcher.add(parseGitignoreLines(contents));
125
+ return matcher;
120
126
  }
121
127
  catch (error) {
122
128
  if (isNodeError(error) && error.code === 'ENOENT') {
@@ -124,16 +130,13 @@ export async function loadRootGitignore(root, signal) {
124
130
  }
125
131
  throw error;
126
132
  }
127
- const matcher = ignore();
128
- matcher.add(parseGitignoreLines(contents));
129
- return matcher;
130
133
  }
131
134
  export function isIgnoredByGitignore(matcher, root, absolutePath, options = {}) {
132
- let relative = options.relativePath;
133
- relative ??= path.relative(root, absolutePath);
134
- if (relative.length === 0)
135
+ let { relativePath } = options;
136
+ relativePath ??= relative(root, absolutePath);
137
+ if (relativePath.length === 0)
135
138
  return false;
136
- const normalized = toPosixPath(relative);
139
+ const normalized = toPosixPath(relativePath);
137
140
  if (options.isDirectory) {
138
141
  return matcher.ignores(normalized.endsWith('/') ? normalized : `${normalized}/`);
139
142
  }
@@ -1,5 +1,5 @@
1
1
  import type { FileInfo, GetMultipleFileInfoResult, ListDirectoryResult } from '../../config.js';
2
- import type { EntryType } from './core.js';
2
+ import { type EntryType } from './core.js';
3
3
  interface FileInfoOptions {
4
4
  includeMimeType?: boolean | undefined;
5
5
  signal?: AbortSignal | undefined;
@@ -60,7 +60,7 @@ interface ReadMultipleResult {
60
60
  endLine?: number;
61
61
  linesRead?: number;
62
62
  hasMoreLines?: boolean;
63
- error?: string;
63
+ error?: Error;
64
64
  }
65
65
  interface ReadMultipleOptions {
66
66
  encoding?: BufferEncoding;
@@ -1,12 +1,53 @@
1
- import * as fsp from 'node:fs/promises';
2
- import * as path from 'node:path';
1
+ import { lstat, readdir, readlink, stat, } from 'node:fs';
2
+ import { basename, join, parse, relative } from 'node:path';
3
+ import { assertNotAborted, withAbort, withTimedAbortSignal } from '../abort.js';
3
4
  import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_LIST_MAX_ENTRIES, DEFAULT_MAX_DEPTH, DEFAULT_READ_MANY_MAX_TOTAL_SIZE, DEFAULT_SEARCH_TIMEOUT_MS, getMimeType, MAX_TEXT_FILE_SIZE, PARALLEL_CONCURRENCY, } from '../constants.js';
4
5
  import { isAbortError } from '../errors.js';
5
- import { assertNotAborted, getFileType, isHidden, processInParallel, readFile, readFileWithStats, withAbort, withTimedAbortSignal, } from '../fs-helpers.js';
6
+ import { getFileType, isHidden, processInParallel, readFile, readFileWithStats, } from '../fs-helpers.js';
6
7
  import { assertSafeGlobPattern } from '../globs.js';
7
8
  import { assertAllowedFileAccess, isPathWithinDirectories, isSensitivePath, normalizePath, toPosixPath, validateExistingDirectory, validateExistingPath, validateExistingPathDetailed, } from '../paths.js';
8
9
  import { applyIndexedErrors, applyIndexedValues, isEntryAccessibleByType, isIgnoredByGitignore, loadRootGitignore, needsStatsForSort, resolveEntryType, resolveStopReason, withOptionalStoppedReason, } from './core.js';
9
10
  import { globEntries } from './traversal.js';
11
+ function statAsync(filePath) {
12
+ return new Promise((resolve, reject) => {
13
+ stat(filePath, (err, stats) => {
14
+ if (err)
15
+ reject(err);
16
+ else
17
+ resolve(stats);
18
+ });
19
+ });
20
+ }
21
+ function readlinkAsync(filePath) {
22
+ return new Promise((resolve, reject) => {
23
+ readlink(filePath, (err, linkString) => {
24
+ if (err)
25
+ reject(err);
26
+ else
27
+ resolve(linkString);
28
+ });
29
+ });
30
+ }
31
+ function readdirAsync(dirPath, options) {
32
+ return new Promise((resolve, reject) => {
33
+ readdir(dirPath, options, (err, files) => {
34
+ if (err)
35
+ reject(err);
36
+ else
37
+ resolve(files);
38
+ });
39
+ });
40
+ }
41
+ function lstatAsync(filePath) {
42
+ return new Promise((resolve, reject) => {
43
+ lstat(filePath, (err, stats) => {
44
+ if (err)
45
+ reject(err);
46
+ else
47
+ resolve(stats);
48
+ });
49
+ });
50
+ }
10
51
  const ACCESS_DEPS = {
11
52
  normalizePath,
12
53
  isPathWithinDirectories,
@@ -49,7 +90,7 @@ function buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, sy
49
90
  async function getSymlinkTarget(pathToRead, signal) {
50
91
  assertNotAborted(signal);
51
92
  try {
52
- return await withAbort(fsp.readlink(pathToRead), signal);
93
+ return await withAbort(readlinkAsync(pathToRead), signal);
53
94
  }
54
95
  catch (error) {
55
96
  if (isAbortError(error))
@@ -62,14 +103,14 @@ export async function getFileInfo(filePath, options = {}) {
62
103
  assertNotAborted(signal);
63
104
  const { requestedPath, resolvedPath, isSymlink } = await validateExistingPathDetailed(filePath, signal);
64
105
  assertAllowedFileAccess(requestedPath, resolvedPath);
65
- const { base: name, ext: rawExt } = path.parse(requestedPath);
106
+ const { base: name, ext: rawExt } = parse(requestedPath);
66
107
  const ext = rawExt.toLowerCase();
67
108
  const includeMimeType = options.includeMimeType !== false;
68
109
  const mimeType = includeMimeType && ext.length > 0 ? getMimeType(ext) : undefined;
69
110
  const symlinkTarget = isSymlink
70
111
  ? await getSymlinkTarget(requestedPath, signal)
71
112
  : undefined;
72
- const stats = await withAbort(fsp.stat(resolvedPath), signal);
113
+ const stats = await withAbort(statAsync(resolvedPath), signal);
73
114
  return buildFileInfoResult(name, requestedPath, isSymlink, stats, mimeType, symlinkTarget);
74
115
  }
75
116
  function buildEmptyResult() {
@@ -131,7 +172,7 @@ export async function getMultipleFileInfo(paths, options = {}) {
131
172
  : undefined,
132
173
  buildValue: (resolvedIndex, error) => ({
133
174
  path: paths[resolvedIndex] ?? UNKNOWN_PATH,
134
- error: error.message,
175
+ error,
135
176
  }),
136
177
  });
137
178
  return {
@@ -168,12 +209,12 @@ function resolveMaxDepth(normalized) {
168
209
  return normalized.pattern ? normalized.maxDepth : 1;
169
210
  }
170
211
  async function* readDirectoryEntries(basePath, normalized, needsStats, signal) {
171
- const dirents = await withAbort(fsp.readdir(basePath, { withFileTypes: true }), signal);
212
+ const dirents = await withAbort(readdirAsync(basePath, { withFileTypes: true }), signal);
172
213
  if (!needsStats) {
173
214
  for (const dirent of dirents) {
174
215
  if (!normalized.includeHidden && isHidden(dirent.name))
175
216
  continue;
176
- yield { path: path.join(basePath, dirent.name), dirent };
217
+ yield { path: join(basePath, dirent.name), dirent };
177
218
  }
178
219
  return;
179
220
  }
@@ -181,12 +222,12 @@ async function* readDirectoryEntries(basePath, normalized, needsStats, signal) {
181
222
  for (const dirent of dirents) {
182
223
  if (!normalized.includeHidden && isHidden(dirent.name))
183
224
  continue;
184
- filtered.push({ dirent, entryPath: path.join(basePath, dirent.name) });
225
+ filtered.push({ dirent, entryPath: join(basePath, dirent.name) });
185
226
  }
186
227
  const { results, errors } = await processInParallel(filtered, async ({ entryPath, dirent }) => ({
187
228
  path: entryPath,
188
229
  dirent,
189
- stats: await withAbort(fsp.lstat(entryPath), signal),
230
+ stats: await withAbort(lstatAsync(entryPath), signal),
190
231
  }), PARALLEL_CONCURRENCY, signal);
191
232
  if (errors.length > 0) {
192
233
  throw errors[0]?.error ?? new Error('Failed to read entry stats');
@@ -216,13 +257,13 @@ function shouldUseFastPath(normalized, maxDepth) {
216
257
  maxDepth === 1);
217
258
  }
218
259
  function resolveRelativePath(basePath, entryPath) {
219
- return path.relative(basePath, entryPath) || path.basename(entryPath);
260
+ return relative(basePath, entryPath) || basename(entryPath);
220
261
  }
221
262
  async function resolveSymlinkTarget(entryType, includeSymlinkTargets, entryPath) {
222
263
  if (entryType !== 'symlink' || !includeSymlinkTargets) {
223
264
  return undefined;
224
265
  }
225
- return fsp.readlink(entryPath).catch(() => undefined);
266
+ return readlinkAsync(entryPath).catch(() => undefined);
226
267
  }
227
268
  function updateTotals(entryType, totals) {
228
269
  if (entryType === 'file')
@@ -234,7 +275,7 @@ function buildDirectoryEntry(basePath, entry, entryType, needsStats, symlinkTarg
234
275
  const size = needsStats && entry.stats?.isFile() ? entry.stats.size : undefined;
235
276
  const modified = needsStats ? entry.stats?.mtime : undefined;
236
277
  return {
237
- name: path.basename(entry.path),
278
+ name: basename(entry.path),
238
279
  path: entry.path,
239
280
  relativePath: resolveRelativePath(basePath, entry.path),
240
281
  type: entryType,
@@ -423,7 +464,7 @@ async function resolveTreeEntry(entry, root, rootDirectories, gitignoreMatcher,
423
464
  return null;
424
465
  }
425
466
  const relativePosix = toPosixPath(resolveRelativePath(root, entry.path));
426
- const name = path.basename(entry.path);
467
+ const name = basename(entry.path);
427
468
  return { type, relativePosix, name };
428
469
  }
429
470
  function upsertChildNode(parent, nodeByPath, resolved, childPathIndexByParent) {
@@ -523,7 +564,7 @@ export async function treeDirectory(dirPath, options = {}) {
523
564
  ? null
524
565
  : await loadRootGitignore(root, signal);
525
566
  const rootNode = {
526
- name: path.basename(root) || root,
567
+ name: basename(root) || root,
527
568
  type: 'directory',
528
569
  relativePath: '.',
529
570
  children: [],
@@ -675,7 +716,7 @@ function resolveNormalizedReadOptions(options) {
675
716
  }
676
717
  async function validateFile(filePath, index, signal) {
677
718
  const validPath = await validateExistingPath(filePath, signal);
678
- const stats = await withAbort(fsp.stat(validPath), signal);
719
+ const stats = await withAbort(statAsync(validPath), signal);
679
720
  return { filePath, index, validPath, stats };
680
721
  }
681
722
  function markRemainingSkipped(startIndex, total, skippedBudget) {
@@ -810,7 +851,7 @@ function applySkippedBudget(output, skippedBudget, filePaths, maxTotalSize) {
810
851
  continue;
811
852
  output[index] = {
812
853
  path: filePath,
813
- error: `Skipped: combined estimated read would exceed maxTotalSize (${maxTotalSize} bytes)`,
854
+ error: new Error(`Skipped: combined estimated read would exceed maxTotalSize (${maxTotalSize} bytes)`),
814
855
  };
815
856
  }
816
857
  }
@@ -829,7 +870,7 @@ export async function readMultipleFiles(filePaths, options = {}) {
829
870
  resolveIndex: (failureIndex) => resolveErrorOriginalIndex(failureIndex, filesToProcess, filePaths.length),
830
871
  buildValue: (resolvedIndex, error) => ({
831
872
  path: filePaths[resolvedIndex] ?? UNKNOWN_PATH,
832
- error: error.message,
873
+ error,
833
874
  }),
834
875
  });
835
876
  applySkippedBudget(output, skippedBudget, filePaths, normalized.maxTotalSize);