@j0hanz/filesystem-mcp 1.13.2 → 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 (74) hide show
  1. package/README.md +162 -145
  2. package/dist/cli.js +2 -2
  3. package/dist/completions.js +54 -51
  4. package/dist/config.d.ts +13 -14
  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 +64 -41
  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 +69 -22
  17. package/dist/lib/file-operations/search.d.ts +0 -1
  18. package/dist/lib/file-operations/search.js +87 -95
  19. package/dist/lib/file-operations/traversal.js +13 -15
  20. package/dist/lib/fs-helpers.d.ts +3 -10
  21. package/dist/lib/fs-helpers.js +29 -108
  22. package/dist/lib/globs.d.ts +2 -0
  23. package/dist/lib/globs.js +19 -0
  24. package/dist/lib/logger.d.ts +28 -0
  25. package/dist/lib/logger.js +91 -0
  26. package/dist/lib/observability.d.ts +7 -0
  27. package/dist/lib/observability.js +19 -9
  28. package/dist/lib/paths.js +55 -55
  29. package/dist/lib/resource-store.js +4 -4
  30. package/dist/lib/utils.d.ts +0 -12
  31. package/dist/lib/utils.js +0 -13
  32. package/dist/lib/zod-codecs.d.ts +2 -0
  33. package/dist/lib/zod-codecs.js +18 -0
  34. package/dist/pkg-info.d.ts +1 -0
  35. package/dist/pkg-info.js +2 -2
  36. package/dist/prompts.js +3 -3
  37. package/dist/resources/generated-instructions.js +41 -41
  38. package/dist/resources/tool-catalog.js +33 -58
  39. package/dist/resources/tool-info.d.ts +0 -1
  40. package/dist/resources/tool-info.js +44 -67
  41. package/dist/resources/workflows.js +47 -19
  42. package/dist/resources.d.ts +1 -1
  43. package/dist/resources.js +4 -4
  44. package/dist/schemas.d.ts +185 -465
  45. package/dist/schemas.js +174 -206
  46. package/dist/server/bootstrap.d.ts +12 -11
  47. package/dist/server/bootstrap.js +95 -86
  48. package/dist/server/roots-manager.d.ts +5 -2
  49. package/dist/server/roots-manager.js +9 -7
  50. package/dist/server/task-store.d.ts +10 -0
  51. package/dist/server/task-store.js +73 -0
  52. package/dist/tools/apply-patch.js +39 -20
  53. package/dist/tools/calculate-hash.js +14 -27
  54. package/dist/tools/create-directory.js +11 -9
  55. package/dist/tools/delete-file.js +19 -19
  56. package/dist/tools/diff-files.js +16 -18
  57. package/dist/tools/edit-file.js +11 -5
  58. package/dist/tools/list-directory.js +16 -21
  59. package/dist/tools/move-file.js +105 -100
  60. package/dist/tools/read-multiple.js +15 -10
  61. package/dist/tools/read.js +6 -7
  62. package/dist/tools/replace-in-files.js +76 -115
  63. package/dist/tools/roots.js +3 -7
  64. package/dist/tools/search-content.js +158 -203
  65. package/dist/tools/search-files.js +59 -50
  66. package/dist/tools/shared.d.ts +10 -0
  67. package/dist/tools/shared.js +105 -36
  68. package/dist/tools/stat-many.js +15 -9
  69. package/dist/tools/stat.js +6 -6
  70. package/dist/tools/task-support.d.ts +10 -9
  71. package/dist/tools/task-support.js +94 -23
  72. package/dist/tools/tree.js +4 -4
  73. package/dist/tools/write-file.js +11 -12
  74. package/package.json +10 -9
@@ -50,22 +50,21 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
- import * as fsp from 'node:fs/promises';
54
- import * as path from 'node:path';
55
53
  import { AsyncResource } from 'node:async_hooks';
56
- import { existsSync } from 'node:fs';
57
- import { fileURLToPath, pathToFileURL } from 'node:url';
54
+ import { open, stat } from 'node:fs/promises';
55
+ import { basename, dirname } from 'node:path';
58
56
  import { debuglog } from 'node:util';
59
57
  import { parentPort, threadId, Worker, workerData } from 'node:worker_threads';
60
58
  import RE2 from 're2';
61
- import safeRegex from 'safe-regex2';
62
59
  import { z } from 'zod';
60
+ import { assertNotAborted, withAbort, withTimedAbortSignal } from '../abort.js';
63
61
  import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_SEARCH_MAX_FILES, DEFAULT_SEARCH_TIMEOUT_MS, MAX_LINE_CONTENT_LENGTH, MAX_SEARCHABLE_FILE_SIZE, SEARCH_WORKERS, } from '../constants.js';
64
62
  import { ErrorCode, formatUnknownErrorMessage, isTimeoutLikeError, McpError, } from '../errors.js';
65
- import { assertNotAborted, isProbablyBinary, withAbort, withTimedAbortSignal, } from '../fs-helpers.js';
63
+ import { isProbablyBinary } from '../fs-helpers.js';
64
+ import { isSafeGlobPattern } from '../globs.js';
66
65
  import { startPerfMeasure } from '../observability.js';
67
66
  import { assertAllowedFileAccess, isPathWithinDirectories, isSensitivePath, normalizePath, validateExistingDirectory, validateExistingPathDetailed, } from '../paths.js';
68
- import { mergeOptions, omitOptionKeys, reportPeriodicProgress, } from '../utils.js';
67
+ import { mergeOptions, omitOptionKeys } from '../utils.js';
69
68
  import { compareOptionalNumberDesc, compareStringValues, isEntryAccessibleByType, isIgnoredByGitignore, loadRootGitignore, needsStatsForSort, resolveEntryType, resolveStopReason, stableSortByDerivedString, withOptionalStoppedReason, } from './core.js';
70
69
  import { buildGlobOptions, globEntries } from './traversal.js';
71
70
  function countRegexLineMatches(regex, line) {
@@ -85,20 +84,10 @@ function buildRegexPattern(pattern, options) {
85
84
  const escaped = options.isLiteral ? escapeLiteral(pattern) : pattern;
86
85
  return options.wholeWord ? `\\b${escaped}\\b` : escaped;
87
86
  }
88
- function validatePattern(pattern, options) {
89
- if (options.isLiteral && pattern.length === 0)
90
- return;
91
- if (options.isLiteral && !options.wholeWord)
92
- return;
93
- const final = buildRegexPattern(pattern, options);
94
- if (!safeRegex(final)) {
95
- throw new Error(`Potentially unsafe regular expression (ReDoS risk): ${pattern}`);
96
- }
97
- }
98
87
  function buildLiteralMatcher(pattern, options) {
99
88
  if (!options.caseSensitive) {
100
89
  const final = escapeLiteral(pattern);
101
- const regex = new RegExp(final, 'gi');
90
+ const regex = new RE2(final, 'gi');
102
91
  return (line) => countRegexLineMatches(regex, line);
103
92
  }
104
93
  // Fast path for case-sensitive literal
@@ -117,10 +106,8 @@ function buildLiteralMatcher(pattern, options) {
117
106
  return count;
118
107
  };
119
108
  }
120
- function buildRegexMatcher(final, caseSensitive, multiline) {
121
- let flags = caseSensitive ? 'g' : 'gi';
122
- if (multiline)
123
- flags += 'm';
109
+ function buildRegexMatcher(final, caseSensitive) {
110
+ const flags = caseSensitive ? 'g' : 'gi';
124
111
  const regex = new RE2(final, flags);
125
112
  return (line) => countRegexLineMatches(regex, line);
126
113
  }
@@ -132,13 +119,19 @@ function buildMatcher(pattern, options) {
132
119
  return buildLiteralMatcher(pattern, options);
133
120
  }
134
121
  const final = buildRegexPattern(pattern, options);
135
- validatePattern(pattern, options); // Re-validate to be safe
136
- return buildRegexMatcher(final, options.caseSensitive, options.multiline);
122
+ return buildRegexMatcher(final, options.caseSensitive);
137
123
  }
138
124
  // --- Configuration & Schemas ---
139
125
  const SEARCH_CONTENT_MAX_RESULTS = 500;
126
+ const SafeFilePatternSchema = z
127
+ .string()
128
+ .min(1, 'Pattern required')
129
+ .max(1000, 'Max 1000 chars')
130
+ .refine((value) => isSafeGlobPattern(value), {
131
+ error: 'Invalid glob or unsafe path (absolute/.. forbidden)',
132
+ });
140
133
  const SearchOptionsSchema = z.strictObject({
141
- filePattern: z.string().min(1),
134
+ filePattern: SafeFilePatternSchema,
142
135
  excludePatterns: z.array(z.string()),
143
136
  caseSensitive: z.boolean(),
144
137
  maxResults: z.int().min(0),
@@ -149,7 +142,6 @@ const SearchOptionsSchema = z.strictObject({
149
142
  contextLines: z.int().min(0),
150
143
  wholeWord: z.boolean(),
151
144
  isLiteral: z.boolean(),
152
- multiline: z.boolean(),
153
145
  includeHidden: z.boolean(),
154
146
  baseNameMatch: z.boolean(),
155
147
  caseSensitiveFileMatch: z.boolean(),
@@ -166,14 +158,12 @@ const DEFAULTS = {
166
158
  contextLines: 0,
167
159
  wholeWord: false,
168
160
  isLiteral: true,
169
- multiline: false,
170
161
  includeHidden: false,
171
162
  baseNameMatch: false,
172
163
  caseSensitiveFileMatch: true,
173
164
  };
174
165
  const ERROR_SCAN_CANCELLED = 'Scan cancelled';
175
166
  const ERROR_WORKER_POOL_CLOSED = 'Worker pool closed';
176
- const SEARCH_PROGRESS_THROTTLE_MODULO = 25;
177
167
  const SEARCH_WORKER_NAME_PREFIX = 'filesystem-search';
178
168
  const SEARCH_WORKER_RESOURCE_TYPE = 'SearchWorkerTask';
179
169
  // --- Helpers ---
@@ -182,7 +172,7 @@ function resolveOptions(options) {
182
172
  const merged = mergeOptions(DEFAULTS, normalizedOptions);
183
173
  const result = SearchOptionsSchema.safeParse(merged);
184
174
  if (!result.success) {
185
- throw new McpError(ErrorCode.E_INVALID_INPUT, `Invalid search options: ${result.error.message}`, undefined, { errors: z.treeifyError(result.error) });
175
+ throw new McpError(ErrorCode.INVALID_INPUT, `Invalid search options:\n${z.prettifyError(result.error)}`, undefined, { errors: z.treeifyError(result.error) });
186
176
  }
187
177
  return result.data;
188
178
  }
@@ -258,9 +248,31 @@ class ContextBuffer {
258
248
  }
259
249
  function trimContent(line) {
260
250
  return line.length > MAX_LINE_CONTENT_LENGTH
261
- ? line.slice(0, MAX_LINE_CONTENT_LENGTH)
251
+ ? `${line.slice(0, MAX_LINE_CONTENT_LENGTH)}\u2026`
262
252
  : line;
263
253
  }
254
+ function processLineMatch(matches, rawLine, lineNumber, matchCount, requestedPath, ctx) {
255
+ const trimmedLine = trimContent(rawLine);
256
+ if (ctx) {
257
+ matches.push({
258
+ file: requestedPath,
259
+ line: lineNumber,
260
+ content: trimmedLine,
261
+ matchCount,
262
+ contextBefore: ctx.snapshotBefore(),
263
+ contextAfter: ctx.scheduleAfter(),
264
+ });
265
+ }
266
+ else {
267
+ matches.push({
268
+ file: requestedPath,
269
+ line: lineNumber,
270
+ content: trimmedLine,
271
+ matchCount,
272
+ });
273
+ }
274
+ return trimmedLine;
275
+ }
264
276
  async function readMatches(handle, requestedPath, matcher, options, maxMatches, isCancelled, signal) {
265
277
  if (maxMatches <= 0) {
266
278
  return [];
@@ -278,26 +290,12 @@ async function readMatches(handle, requestedPath, matcher, options, maxMatches,
278
290
  if (isCancelled())
279
291
  break;
280
292
  const matchCount = matcher(rawLine);
281
- const trimmedLine = hasContext || matchCount > 0 ? trimContent(rawLine) : '';
293
+ let trimmedLine = '';
282
294
  if (matchCount > 0) {
283
- if (ctx) {
284
- matches.push({
285
- file: requestedPath,
286
- line: lineNumber,
287
- content: trimmedLine,
288
- matchCount,
289
- contextBefore: ctx.snapshotBefore(),
290
- contextAfter: ctx.scheduleAfter(),
291
- });
292
- }
293
- else {
294
- matches.push({
295
- file: requestedPath,
296
- line: lineNumber,
297
- content: trimmedLine,
298
- matchCount,
299
- });
300
- }
295
+ trimmedLine = processLineMatch(matches, rawLine, lineNumber, matchCount, requestedPath, ctx);
296
+ }
297
+ else if (hasContext) {
298
+ trimmedLine = trimContent(rawLine);
301
299
  }
302
300
  if (ctx) {
303
301
  ctx.add(trimmedLine);
@@ -319,7 +317,7 @@ async function scanFileResolved(resolvedPath, requestedPath, matcher, options, s
319
317
  const env_1 = { stack: [], error: void 0, hasError: false };
320
318
  try {
321
319
  assertNotAborted(signal);
322
- const handle = __addDisposableResource(env_1, await withAbort(fsp.open(resolvedPath, 'r'), signal), true);
320
+ const handle = __addDisposableResource(env_1, await withAbort(open(resolvedPath, 'r'), signal), true);
323
321
  const stats = await withAbort(handle.stat(), signal);
324
322
  if (stats.size > options.maxFileSize) {
325
323
  return {
@@ -370,7 +368,6 @@ function buildMatcherOptions(opts) {
370
368
  caseSensitive: opts.caseSensitive,
371
369
  wholeWord: opts.wholeWord,
372
370
  isLiteral: opts.isLiteral,
373
- multiline: opts.multiline,
374
371
  };
375
372
  }
376
373
  function applyScanOutcome(summary, outcome) {
@@ -405,7 +402,6 @@ function buildSearchContentResult(root, pattern, filePattern, matches, summary)
405
402
  skippedTooLarge: summary.skippedTooLarge,
406
403
  skippedBinary: summary.skippedBinary,
407
404
  skippedInaccessible: summary.skippedInaccessible,
408
- linesSkippedDueToRegexTimeout: 0,
409
405
  };
410
406
  return {
411
407
  basePath: root,
@@ -415,12 +411,9 @@ function buildSearchContentResult(root, pattern, filePattern, matches, summary)
415
411
  summary: withOptionalStoppedReason(baseSummary, summary.stoppedReason),
416
412
  };
417
413
  }
418
- const currentDir = path.dirname(fileURLToPath(import.meta.url));
419
- const isSourceContext = currentDir.endsWith('src\\lib\\file-operations') ||
420
- currentDir.endsWith('src/lib/file-operations');
421
- const WORKER_SCRIPT_PATH = path.join(currentDir, isSourceContext ? 'search-worker.ts' : 'search-worker.js');
422
- const WORKER_SCRIPT_URL = pathToFileURL(WORKER_SCRIPT_PATH);
423
- const hasWorkerScript = existsSync(WORKER_SCRIPT_PATH);
414
+ const isSourceContext = import.meta.url.endsWith('.ts');
415
+ const WORKER_SCRIPT_URL = new URL(import.meta.url);
416
+ const hasWorkerScript = true;
424
417
  class SearchWorkerTaskResource extends AsyncResource {
425
418
  #settled = false;
426
419
  constructor() {
@@ -524,9 +517,21 @@ class SearchWorkerPool {
524
517
  if (this.closed)
525
518
  throw new Error(ERROR_WORKER_POOL_CLOSED);
526
519
  const id = this.nextRequestId++;
527
- const workerIndex = this.workerRoundRobin % this.size;
520
+ let workerIndex = 0;
521
+ const workerPendingCounts = new Array(this.size).fill(0);
522
+ for (const p of this.pending.values()) {
523
+ const idx = p.workerIndex;
524
+ workerPendingCounts[idx] = (workerPendingCounts[idx] ?? 0) + 1;
525
+ }
526
+ let minPending = workerPendingCounts[0] ?? 0;
527
+ for (let i = 1; i < this.size; i++) {
528
+ const pendingCount = workerPendingCounts[i] ?? 0;
529
+ if (pendingCount < minPending) {
530
+ minPending = pendingCount;
531
+ workerIndex = i;
532
+ }
533
+ }
528
534
  const worker = this.getWorker(workerIndex);
529
- this.workerRoundRobin++;
530
535
  const promise = new Promise((resolve, reject) => {
531
536
  const resource = new SearchWorkerTaskResource();
532
537
  const pendingRequest = {
@@ -764,7 +769,7 @@ async function searchSingleFile(details, opts, pattern, signal) {
764
769
  }, signal, opts.maxResults);
765
770
  if (result.matched)
766
771
  summary.filesMatched = 1;
767
- return buildSearchContentResult(path.dirname(details.resolvedPath), pattern, opts.filePattern, result.matches, summary);
772
+ return buildSearchContentResult(dirname(details.resolvedPath), pattern, opts.filePattern, result.matches, summary);
768
773
  }
769
774
  async function searchDirectory(details, opts, pattern, signal, onProgress) {
770
775
  const root = await validateExistingDirectory(details.resolvedPath, signal);
@@ -781,12 +786,12 @@ async function searchDirectory(details, opts, pattern, signal, onProgress) {
781
786
  stats: false,
782
787
  suppressErrors: true,
783
788
  }));
789
+ const summary = createScanSummary();
784
790
  async function* fileGenerator() {
785
- let scanned = 0;
786
791
  for await (const entry of stream) {
787
792
  if (signal.aborted)
788
793
  break;
789
- if (scanned >= opts.maxFilesScanned)
794
+ if (summary.filesScanned >= opts.maxFilesScanned)
790
795
  break;
791
796
  if (!entry.dirent.isFile())
792
797
  continue;
@@ -795,36 +800,25 @@ async function searchDirectory(details, opts, pattern, signal, onProgress) {
795
800
  continue;
796
801
  if (isSensitivePath(entry.path, normalized))
797
802
  continue;
798
- scanned++;
799
- reportPeriodicProgress(onProgress, scanned, {
803
+ summary.filesScanned++;
804
+ onProgress?.({
805
+ current: summary.filesScanned,
800
806
  total: opts.maxFilesScanned,
801
- throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
802
807
  });
803
808
  yield { resolvedPath: normalized, requestedPath: entry.path };
804
809
  }
805
- reportPeriodicProgress(onProgress, scanned, {
806
- total: opts.maxFilesScanned,
807
- throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
808
- force: true,
809
- });
810
- }
811
- const summary = createScanSummary();
812
- const resolvedStream = fileGenerator();
813
- async function* countingStream() {
814
- for await (const f of resolvedStream) {
815
- summary.filesScanned++;
816
- yield f;
817
- }
818
810
  if (summary.filesScanned >= opts.maxFilesScanned) {
819
811
  summary.truncated = true;
820
812
  summary.stoppedReason = 'maxFiles';
821
813
  }
814
+ onProgress?.({
815
+ current: summary.filesScanned,
816
+ total: opts.maxFilesScanned,
817
+ });
822
818
  }
823
- const matcherOpts = buildMatcherOptions(opts);
824
- validatePattern(pattern, matcherOpts);
825
819
  const matches = shouldUseWorkers()
826
- ? await executeParallel(countingStream(), pattern, opts, signal, summary)
827
- : await executeSequential(countingStream(), pattern, opts, signal, summary);
820
+ ? await executeParallel(fileGenerator(), pattern, opts, signal, summary)
821
+ : await executeSequential(fileGenerator(), pattern, opts, signal, summary);
828
822
  return buildSearchContentResult(root, pattern, opts.filePattern, matches, summary);
829
823
  }
830
824
  function buildTimeoutSearchResult(basePath, pattern, filePattern) {
@@ -834,19 +828,19 @@ function buildTimeoutSearchResult(basePath, pattern, filePattern) {
834
828
  }
835
829
  export async function searchContent(basePath, pattern, options = {}) {
836
830
  if (!basePath.trim())
837
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'basePath required');
831
+ throw new McpError(ErrorCode.INVALID_INPUT, 'basePath required');
838
832
  if (typeof pattern !== 'string')
839
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'pattern required');
833
+ throw new McpError(ErrorCode.INVALID_INPUT, 'pattern required');
840
834
  const opts = resolveOptions(options);
841
835
  try {
842
836
  return await withTimedAbortSignal(options.signal, opts.timeoutMs, async (signal) => {
843
837
  const details = await validateExistingPathDetailed(basePath, signal);
844
- const stats = await withAbort(fsp.stat(details.resolvedPath), signal);
838
+ const stats = await withAbort(stat(details.resolvedPath), signal);
845
839
  if (stats.isFile()) {
846
840
  return searchSingleFile(details, opts, pattern, signal);
847
841
  }
848
842
  if (!stats.isDirectory()) {
849
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'Path must be file or directory', basePath);
843
+ throw new McpError(ErrorCode.INVALID_INPUT, 'Path must be file or directory', basePath);
850
844
  }
851
845
  return searchDirectory(details, opts, pattern, signal, options.onProgress);
852
846
  });
@@ -961,9 +955,9 @@ async function collectFromStream(stream, signal, context) {
961
955
  if (shouldStopCollecting(state, normalized, signal))
962
956
  break;
963
957
  state.filesScanned++;
964
- reportPeriodicProgress(onProgress, state.filesScanned, {
958
+ onProgress?.({
959
+ current: state.filesScanned,
965
960
  total: normalized.maxFilesScanned,
966
- throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
967
961
  });
968
962
  if (isEntryIgnoredByGitignore(gitignoreMatcher, root, entry.path, entry.relativePath)) {
969
963
  continue;
@@ -981,10 +975,9 @@ async function collectFromStream(stream, signal, context) {
981
975
  if (state.truncated)
982
976
  break;
983
977
  }
984
- reportPeriodicProgress(onProgress, state.filesScanned, {
978
+ onProgress?.({
979
+ current: state.filesScanned,
985
980
  total: normalized.maxFilesScanned,
986
- throttleModulo: SEARCH_PROGRESS_THROTTLE_MODULO,
987
- force: true,
988
981
  });
989
982
  }
990
983
  function isEntryIgnoredByGitignore(matcher, root, entryPath, relativePath) {
@@ -1047,7 +1040,7 @@ const SORT_COMPARATORS = {
1047
1040
  };
1048
1041
  function sortSearchResults(results, sortBy) {
1049
1042
  if (sortBy === 'name') {
1050
- stableSortByDerivedString(results, (item) => path.basename(item.path ?? ''), (left, right) => comparePathThenName(left, right));
1043
+ stableSortByDerivedString(results, (item) => basename(item.path ?? ''), (left, right) => comparePathThenName(left, right));
1051
1044
  return;
1052
1045
  }
1053
1046
  const comparator = SORT_COMPARATORS[sortBy];
@@ -1080,8 +1073,7 @@ function getMatcherCacheKey(pattern, options) {
1080
1073
  const cs = options.caseSensitive ? '1' : '0';
1081
1074
  const ww = options.wholeWord ? '1' : '0';
1082
1075
  const lit = options.isLiteral ? '1' : '0';
1083
- const ml = options.multiline ? '1' : '0';
1084
- return `${pattern}|${cs}|${ww}|${lit}|${ml}`;
1076
+ return `${pattern}|${cs}|${ww}|${lit}`;
1085
1077
  }
1086
1078
  function getCachedMatcher(pattern, options) {
1087
1079
  const key = getMatcherCacheKey(pattern, options);
@@ -1,6 +1,5 @@
1
- import * as fs from 'node:fs/promises';
2
- import * as path from 'node:path';
3
- import { glob as fsGlob } from 'node:fs/promises';
1
+ import { glob as fsGlob, lstat, stat } from 'node:fs/promises';
2
+ import { isAbsolute, relative, resolve } from 'node:path';
4
3
  import { getToolContextSnapshot, publishOpsTraceEnd, publishOpsTraceError, publishOpsTraceStart, shouldPublishOpsTrace, startPerfMeasure, } from '../observability.js';
5
4
  import { toPosixPath } from '../paths.js';
6
5
  import { isRecord } from '../utils.js';
@@ -96,7 +95,7 @@ function assertOptionsShape(options) {
96
95
  }
97
96
  }
98
97
  function normalizeOptions(options) {
99
- const cwd = path.resolve(options.cwd);
98
+ const cwd = resolve(options.cwd);
100
99
  const normalizedPattern = normalizePattern(options.pattern, options.baseNameMatch);
101
100
  const patterns = options.includeHidden
102
101
  ? buildHiddenPatterns(normalizedPattern, options.maxDepth ?? DEFAULT_MAX_HIDDEN_DEPTH)
@@ -136,18 +135,16 @@ function isGlobDirentLike(value) {
136
135
  function resolveDirentBase(cwd, parentPath) {
137
136
  if (!parentPath)
138
137
  return cwd;
139
- return path.isAbsolute(parentPath)
140
- ? parentPath
141
- : path.resolve(cwd, parentPath);
138
+ return isAbsolute(parentPath) ? parentPath : resolve(cwd, parentPath);
142
139
  }
143
140
  function resolveStringMatchPath(cwd, match) {
144
- return path.isAbsolute(match) ? match : path.resolve(cwd, match);
141
+ return isAbsolute(match) ? match : resolve(cwd, match);
145
142
  }
146
143
  function* processDirentMatch(match, cwd, maxDepth, seen, onlyFiles) {
147
144
  const base = resolveDirentBase(cwd, match.parentPath);
148
- const absolutePath = path.resolve(base, match.name);
145
+ const absolutePath = resolve(base, match.name);
149
146
  if (maxDepth !== undefined) {
150
- const rel = path.relative(cwd, absolutePath);
147
+ const rel = relative(cwd, absolutePath);
151
148
  if (getRelativeDepth(rel) > maxDepth)
152
149
  return;
153
150
  }
@@ -170,12 +167,12 @@ async function resolveStringMatch(match, cwd, maxDepth, seen, onlyFiles, followS
170
167
  seen.add(absolutePath);
171
168
  try {
172
169
  const stats = followSymlinks
173
- ? await fs.stat(absolutePath)
174
- : await fs.lstat(absolutePath);
170
+ ? await stat(absolutePath)
171
+ : await lstat(absolutePath);
175
172
  if (onlyFiles && !stats.isFile())
176
173
  return null;
177
174
  const entry = { path: absolutePath, dirent: stats };
178
- if (!path.isAbsolute(match)) {
175
+ if (!isAbsolute(match)) {
179
176
  entry.relativePath = match;
180
177
  }
181
178
  if (returnStats)
@@ -190,12 +187,13 @@ async function resolveStringMatch(match, cwd, maxDepth, seen, onlyFiles, followS
190
187
  }
191
188
  async function* processIterable(iterable, context) {
192
189
  const { cwd, maxDepth, seen, onlyFiles, followSymlinks, returnStats, suppressErrors, } = context;
193
- const buffer = [];
190
+ let buffer = [];
194
191
  const flush = async function* () {
195
192
  if (buffer.length === 0)
196
193
  return;
197
194
  // Process buffer concurrently
198
- const currentBuffer = buffer.splice(0, buffer.length);
195
+ const currentBuffer = buffer;
196
+ buffer = [];
199
197
  const results = await Promise.all(currentBuffer.map((match) => resolveStringMatch(match, cwd, maxDepth, seen, onlyFiles, followSymlinks, returnStats, suppressErrors)));
200
198
  for (const entry of results) {
201
199
  if (entry !== null)
@@ -1,13 +1,7 @@
1
- import * as fsp from 'node:fs/promises';
2
1
  import type { Stats } from 'node:fs';
2
+ import { type FileHandle } from 'node:fs/promises';
3
3
  import type { FileType } from '../config.js';
4
- export declare function assertNotAborted(signal?: AbortSignal, message?: string): void;
5
- export declare function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T>;
6
- export declare function createTimedAbortSignal(baseSignal: AbortSignal | undefined, timeoutMs?: number): {
7
- signal: AbortSignal;
8
- cleanup: () => void;
9
- };
10
- export declare function withTimedAbortSignal<T>(baseSignal: AbortSignal | undefined, timeoutMs: number | undefined, run: (signal: AbortSignal) => Promise<T>): Promise<T>;
4
+ export { assertNotAborted, createTimedAbortSignal, withAbort, withTimedAbortSignal, } from './abort.js';
11
5
  interface ParallelResult<R> {
12
6
  results: R[];
13
7
  errors: {
@@ -18,7 +12,7 @@ interface ParallelResult<R> {
18
12
  export declare function processInParallel<T, R>(items: T[], processor: (item: T) => Promise<R>, concurrency?: number, signal?: AbortSignal): Promise<ParallelResult<R>>;
19
13
  export declare function getFileType(stats: Stats): FileType;
20
14
  export declare function isHidden(name: string): boolean;
21
- export declare function isProbablyBinary(filePath: string, existingHandle?: fsp.FileHandle, signal?: AbortSignal): Promise<boolean>;
15
+ export declare function isProbablyBinary(filePath: string, existingHandle?: FileHandle, signal?: AbortSignal): Promise<boolean>;
22
16
  type ReadMode = 'head' | 'full' | 'range' | 'tail';
23
17
  interface ReadFileOptions {
24
18
  encoding?: BufferEncoding;
@@ -49,4 +43,3 @@ export declare function atomicWriteFile(filePath: string, content: string, optio
49
43
  encoding?: BufferEncoding;
50
44
  signal?: AbortSignal | undefined;
51
45
  }): Promise<void>;
52
- export {};