@j0hanz/filesystem-mcp 2.8.1 → 2.9.0

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 (113) hide show
  1. package/README.md +32 -26
  2. package/dist/cli-help.d.ts.map +1 -1
  3. package/dist/cli-help.js +10 -1
  4. package/dist/cli-help.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +10 -3
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/concurrency.d.ts +4 -4
  9. package/dist/core/concurrency.d.ts.map +1 -1
  10. package/dist/core/concurrency.js +1 -1
  11. package/dist/core/concurrency.js.map +1 -1
  12. package/dist/core/config.d.ts +2 -0
  13. package/dist/core/config.d.ts.map +1 -1
  14. package/dist/core/config.js.map +1 -1
  15. package/dist/core/file-uri.d.ts +12 -3
  16. package/dist/core/file-uri.d.ts.map +1 -1
  17. package/dist/core/file-uri.js +35 -7
  18. package/dist/core/file-uri.js.map +1 -1
  19. package/dist/core/fmt.d.ts +2 -0
  20. package/dist/core/fmt.d.ts.map +1 -1
  21. package/dist/core/fmt.js +16 -0
  22. package/dist/core/fmt.js.map +1 -1
  23. package/dist/core/fs.d.ts +16 -1
  24. package/dist/core/fs.d.ts.map +1 -1
  25. package/dist/core/fs.js +127 -32
  26. package/dist/core/fs.js.map +1 -1
  27. package/dist/core/glob.d.ts +8 -0
  28. package/dist/core/glob.d.ts.map +1 -1
  29. package/dist/core/glob.js +84 -11
  30. package/dist/core/glob.js.map +1 -1
  31. package/dist/core/path-completer.d.ts.map +1 -1
  32. package/dist/core/path-completer.js +5 -3
  33. package/dist/core/path-completer.js.map +1 -1
  34. package/dist/core/path-utils.d.ts +42 -6
  35. package/dist/core/path-utils.d.ts.map +1 -1
  36. package/dist/core/path-utils.js +56 -10
  37. package/dist/core/path-utils.js.map +1 -1
  38. package/dist/core/path.d.ts +111 -7
  39. package/dist/core/path.d.ts.map +1 -1
  40. package/dist/core/path.js +453 -65
  41. package/dist/core/path.js.map +1 -1
  42. package/dist/core/read.d.ts +6 -0
  43. package/dist/core/read.d.ts.map +1 -1
  44. package/dist/core/read.js +26 -3
  45. package/dist/core/read.js.map +1 -1
  46. package/dist/core/schema.d.ts +9 -6
  47. package/dist/core/schema.d.ts.map +1 -1
  48. package/dist/core/schema.js +22 -19
  49. package/dist/core/schema.js.map +1 -1
  50. package/dist/core/search.d.ts +6 -0
  51. package/dist/core/search.d.ts.map +1 -1
  52. package/dist/core/search.js +159 -33
  53. package/dist/core/search.js.map +1 -1
  54. package/dist/core/sensitive.d.ts.map +1 -1
  55. package/dist/core/sensitive.js +64 -15
  56. package/dist/core/sensitive.js.map +1 -1
  57. package/dist/core/store.d.ts +1 -0
  58. package/dist/core/store.d.ts.map +1 -1
  59. package/dist/core/store.js +3 -0
  60. package/dist/core/store.js.map +1 -1
  61. package/dist/core/util.d.ts +6 -1
  62. package/dist/core/util.d.ts.map +1 -1
  63. package/dist/core/util.js +8 -1
  64. package/dist/core/util.js.map +1 -1
  65. package/dist/resources.d.ts.map +1 -1
  66. package/dist/resources.js +18 -9
  67. package/dist/resources.js.map +1 -1
  68. package/dist/server.js +1 -1
  69. package/dist/server.js.map +1 -1
  70. package/dist/tools/create.d.ts.map +1 -1
  71. package/dist/tools/create.js +121 -43
  72. package/dist/tools/create.js.map +1 -1
  73. package/dist/tools/define.d.ts +8 -4
  74. package/dist/tools/define.d.ts.map +1 -1
  75. package/dist/tools/define.js +36 -9
  76. package/dist/tools/define.js.map +1 -1
  77. package/dist/tools/delete.d.ts.map +1 -1
  78. package/dist/tools/delete.js +50 -11
  79. package/dist/tools/delete.js.map +1 -1
  80. package/dist/tools/diff.js +1 -1
  81. package/dist/tools/diff.js.map +1 -1
  82. package/dist/tools/edit.d.ts.map +1 -1
  83. package/dist/tools/edit.js +14 -12
  84. package/dist/tools/edit.js.map +1 -1
  85. package/dist/tools/find-files.d.ts.map +1 -1
  86. package/dist/tools/find-files.js +15 -9
  87. package/dist/tools/find-files.js.map +1 -1
  88. package/dist/tools/list.d.ts.map +1 -1
  89. package/dist/tools/list.js +138 -86
  90. package/dist/tools/list.js.map +1 -1
  91. package/dist/tools/move.d.ts.map +1 -1
  92. package/dist/tools/move.js +72 -26
  93. package/dist/tools/move.js.map +1 -1
  94. package/dist/tools/patch.js +13 -6
  95. package/dist/tools/patch.js.map +1 -1
  96. package/dist/tools/read.d.ts.map +1 -1
  97. package/dist/tools/read.js +114 -45
  98. package/dist/tools/read.js.map +1 -1
  99. package/dist/tools/replace-text.d.ts.map +1 -1
  100. package/dist/tools/replace-text.js +49 -35
  101. package/dist/tools/replace-text.js.map +1 -1
  102. package/dist/tools/search-text.d.ts.map +1 -1
  103. package/dist/tools/search-text.js +15 -33
  104. package/dist/tools/search-text.js.map +1 -1
  105. package/dist/tools/stat.d.ts.map +1 -1
  106. package/dist/tools/stat.js +26 -8
  107. package/dist/tools/stat.js.map +1 -1
  108. package/dist/transport/http.d.ts.map +1 -1
  109. package/dist/transport/http.js +1 -0
  110. package/dist/transport/http.js.map +1 -1
  111. package/dist/transport/stdio.js +1 -1
  112. package/dist/transport/stdio.js.map +1 -1
  113. package/package.json +5 -5
@@ -25,18 +25,21 @@ export type SingleOrBatchShape<TExtra extends z.ZodRawShape> = TExtra & {
25
25
  };
26
26
  export declare function singleOrBatchPathsInput<TExtra extends z.ZodRawShape>(extra: TExtra): z.ZodObject<SingleOrBatchShape<TExtra>>;
27
27
  /**
28
- * Extract the filesystem paths from a {@link singleOrBatchPathsInput} shape
29
- * (`{ path?, paths?, files?[{path}] }`) for the executor's access-grant
30
- * pre-check. Exactly one of the three is set (enforced by the schema's
31
- * superRefine), so they are checked in priority order.
28
+ * `ToolDef.mapPaths` for a {@link singleOrBatchPathsInput} shape
29
+ * (`{ path?, paths?, files?[{path}] }`). The schema's superRefine lets exactly
30
+ * one of the three through; each present one is mapped.
32
31
  */
33
- export declare function singleOrBatchAccessPaths(args: {
32
+ export declare function mapSingleOrBatchPaths<T extends {
34
33
  path?: string | undefined;
35
34
  paths?: string[] | undefined;
36
35
  files?: readonly {
37
36
  path: string;
38
37
  }[] | undefined;
39
- }): string[];
38
+ }>(args: T, fn: (path: string) => string): T;
39
+ /** `ToolDef.mapPaths` for a tool whose one `path` is optional (omitted = the root). */
40
+ export declare function mapOptionalPath<T extends {
41
+ path?: string | undefined;
42
+ }>(args: T, fn: (path: string) => string): T;
40
43
  export declare const CursorSchema: z.ZodOptional<z.ZodString>;
41
44
  /**
42
45
  * Wraps a tool's input schema so a stringified-then-spread payload (see
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAK5B,eAAO,MAAM,WAAW,UAEe,CAAC;AAQxC;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,CAMzD;AAyBD,eAAO,MAAM,YAAY,aAS8C,CAAC;AAExE,eAAO,MAAM,YAAY,4BAA0B,CAAC;AAEpD,eAAO,MAAM,eAAe,aAYsC,CAAC;AAEnE,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B,EACD,GAAG,EAAE,CAAC,CAAC,aAAa,GACnB,IAAI,CAyCN;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAEnF;AAED,eAAO,MAAM,aAAa,4BAAgE,CAAC;AAC3F,eAAO,MAAM,cAAc,4BAE1B,CAAC;AACF,eAAO,MAAM,QAAQ,4BAKkE,CAAC;AAIxF,MAAM,MAAM,kBAAkB,CAAC,MAAM,SAAS,CAAC,CAAC,WAAW,IAAI,MAAM,GAAG;IACtE,IAAI,EAAE,CAAC,CAAC,WAAW,CAAC,OAAO,YAAY,CAAC,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;CACvD,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,MAAM,SAAS,CAAC,CAAC,WAAW,EAClE,KAAK,EAAE,MAAM,GACZ,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CA0CzC;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,SAAS,CAAC;CACjD,GAAG,MAAM,EAAE,CAKX;AAQD,eAAO,MAAM,YAAY,4BAKtB,CAAC;AAqBJ;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAoBxF"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAU5B,eAAO,MAAM,WAAW,UAEe,CAAC;AAQxC;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,CAMzD;AAyBD,eAAO,MAAM,YAAY,aAS8C,CAAC;AAExE,eAAO,MAAM,YAAY,4BAA0B,CAAC;AAEpD,eAAO,MAAM,eAAe,aAYsC,CAAC;AAEnE,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B,EACD,GAAG,EAAE,CAAC,CAAC,aAAa,GACnB,IAAI,CAyCN;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAEnF;AAED,eAAO,MAAM,aAAa,4BAAgE,CAAC;AAC3F,eAAO,MAAM,cAAc,4BAE1B,CAAC;AACF,eAAO,MAAM,QAAQ,4BAKkE,CAAC;AAIxF,MAAM,MAAM,kBAAkB,CAAC,MAAM,SAAS,CAAC,CAAC,WAAW,IAAI,MAAM,GAAG;IACtE,IAAI,EAAE,CAAC,CAAC,WAAW,CAAC,OAAO,YAAY,CAAC,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;CACvD,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,MAAM,SAAS,CAAC,CAAC,WAAW,EAClE,KAAK,EAAE,MAAM,GACZ,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAyCzC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,SAAS;IACR,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,SAAS,CAAC;CACjD,EACD,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,CAAC,CAS1C;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EACrE,IAAI,EAAE,CAAC,EACP,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAC3B,CAAC,CAEH;AAQD,eAAO,MAAM,YAAY,4BAKtB,CAAC;AAqBJ;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAoBxF"}
@@ -1,5 +1,5 @@
1
1
  import * as z from 'zod/v4';
2
- import { isSafeGlobSyntax, MAX_BRACE_ALTERNATIVES, MAX_DOTDOT_CHECK_CHARS } from "./glob.js";
2
+ import { isSafeGlobSyntax, MAX_BRACE_ALTERNATIVES, MAX_DOTDOT_CHECK_CHARS, MAX_SEGMENT_WILDCARDS, } from "./glob.js";
3
3
  import { MAX_SEARCH_DEPTH } from "./util.js";
4
4
  export const PositiveInt = z
5
5
  .int({ message: 'Must be integer' })
@@ -51,7 +51,7 @@ export const SafeGlobPattern = z
51
51
  .max(1000, { message: 'Max 1000 chars' })
52
52
  .superRefine(refineSafeText('Pattern', (val) => isSafeGlobSyntax(val)
53
53
  ? undefined
54
- : `Invalid glob or unsafe path (absolute/.. forbidden, at most ${String(MAX_BRACE_ALTERNATIVES)} brace alternatives and ${String(MAX_DOTDOT_CHECK_CHARS)} traversal-check characters)`))
54
+ : `Invalid glob or unsafe path (absolute/.. forbidden, at most ${String(MAX_BRACE_ALTERNATIVES)} brace alternatives, ${String(MAX_DOTDOT_CHECK_CHARS)} traversal-check characters, and ${String(MAX_SEGMENT_WILDCARDS)} * runs and extglob groups together per path segment; no |, * or nested group inside *(…) or +(…))`))
55
55
  .describe('Relative glob pattern under the search root (e.g. "**/*.ts", "src/**/*.js").')
56
56
  .meta({ examples: ['**/*.ts', 'src/**/*.js', '**/*.{ts,tsx}'] });
57
57
  export function validateReadRange(value, ctx) {
@@ -137,26 +137,29 @@ export function singleOrBatchPathsInput(extra) {
137
137
  });
138
138
  }
139
139
  });
140
- // Mirror the superRefine above on the wire: exactly one input mode. `{}` and
141
- // `path`+`paths` both fail this oneOf, matching the runtime rule.
142
- return base.meta({
143
- oneOf: [{ required: ['path'] }, { required: ['paths'] }],
144
- });
140
+ // No top-level oneOf on the wire: OpenAI and Anthropic reject top-level
141
+ // oneOf/anyOf/allOf in function parameters. The superRefine enforces
142
+ // "exactly one of path/paths" at call time.
143
+ return base;
145
144
  }
146
145
  /**
147
- * Extract the filesystem paths from a {@link singleOrBatchPathsInput} shape
148
- * (`{ path?, paths?, files?[{path}] }`) for the executor's access-grant
149
- * pre-check. Exactly one of the three is set (enforced by the schema's
150
- * superRefine), so they are checked in priority order.
146
+ * `ToolDef.mapPaths` for a {@link singleOrBatchPathsInput} shape
147
+ * (`{ path?, paths?, files?[{path}] }`). The schema's superRefine lets exactly
148
+ * one of the three through; each present one is mapped.
151
149
  */
152
- export function singleOrBatchAccessPaths(args) {
153
- if (args.path)
154
- return [args.path];
155
- if (args.paths)
156
- return [...args.paths];
157
- if (args.files)
158
- return args.files.map((f) => f.path);
159
- return [];
150
+ export function mapSingleOrBatchPaths(args, fn) {
151
+ return {
152
+ ...args,
153
+ ...(args.path !== undefined ? { path: fn(args.path) } : {}),
154
+ ...(args.paths !== undefined ? { paths: args.paths.map(fn) } : {}),
155
+ ...(args.files !== undefined
156
+ ? { files: args.files.map((file) => ({ ...file, path: fn(file.path) })) }
157
+ : {}),
158
+ };
159
+ }
160
+ /** `ToolDef.mapPaths` for a tool whose one `path` is optional (omitted = the root). */
161
+ export function mapOptionalPath(args, fn) {
162
+ return args.path === undefined ? args : { ...args, path: fn(args.path) };
160
163
  }
161
164
  // ponytail: charset regex, not z.base64url() — the SDK's AJV warns on the
162
165
  // unknown `base64url` format; the server's own decode (cursor.ts) is the real
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from 'zod/v4';\n\nimport { isSafeGlobSyntax, MAX_BRACE_ALTERNATIVES, MAX_DOTDOT_CHECK_CHARS } from './glob.ts';\nimport { MAX_SEARCH_DEPTH } from './util.ts';\n\nexport const PositiveInt = z\n .int({ message: 'Must be integer' })\n .positive({ message: 'Must be > 0' });\n\nconst MAX_PATH_LENGTH = 4096;\n\nconst LINE_BREAK_RE = /[\\n\\r]/;\n\nconst isBlank = (val: string): boolean => val.trim().length === 0;\n\n/**\n * Text that must carry non-blank content. `min(1)` stays so `minLength: 1`\n * still publishes on the wire; it aborts so an empty value reports one\n * message, not the same one twice.\n */\nexport function nonBlankString(label: string): z.ZodString {\n const message = `${label} cannot be empty or whitespace-only`;\n return z\n .string()\n .min(1, { message, abort: true })\n .refine((val) => !isBlank(val), { message });\n}\n\nfunction refineSafeText(\n label: string,\n specific: (val: string) => string | undefined,\n): (val: string, ctx: z.RefinementCtx) => void {\n return (val, ctx) => {\n if (val.length === 0) return;\n const issue = isBlank(val)\n ? `${label} cannot be empty or whitespace-only`\n : val.includes('\\0')\n ? `${label} cannot contain null bytes`\n : (specific(val) ??\n (LINE_BREAK_RE.test(val) ? `${label} cannot contain line breaks` : undefined));\n if (issue !== undefined) {\n ctx.addIssue({ code: 'custom', message: issue, fatal: true });\n }\n };\n}\n\n// Only a whole `..` segment is traversal. `..` inside a segment\n// (`[...slug]`, `v1..2.md`) is a plain file name; PathGuard re-checks\n// containment on the resolved path regardless.\nconst hasTraversalSegment = (val: string): boolean => val.split(/[\\\\/]/u).includes('..');\n\nexport const RequiredPath = z\n .string()\n .min(1, { message: 'Path required' })\n .max(MAX_PATH_LENGTH, { message: `Path too long (max ${MAX_PATH_LENGTH} chars)` })\n .superRefine(\n refineSafeText('Path', (val) =>\n hasTraversalSegment(val) ? 'Directory traversal sequences (\"..\") are forbidden' : undefined,\n ),\n )\n .describe('File or directory path inside an allowed workspace root.');\n\nexport const OptionalPath = RequiredPath.optional();\n\nexport const SafeGlobPattern = z\n .string()\n .min(1, { message: 'Pattern required' })\n .max(1000, { message: 'Max 1000 chars' })\n .superRefine(\n refineSafeText('Pattern', (val) =>\n isSafeGlobSyntax(val)\n ? undefined\n : `Invalid glob or unsafe path (absolute/.. forbidden, at most ${String(MAX_BRACE_ALTERNATIVES)} brace alternatives and ${String(MAX_DOTDOT_CHECK_CHARS)} traversal-check characters)`,\n ),\n )\n .describe('Relative glob pattern under the search root (e.g. \"**/*.ts\", \"src/**/*.js\").')\n .meta({ examples: ['**/*.ts', 'src/**/*.js', '**/*.{ts,tsx}'] });\n\nexport function validateReadRange(\n value: {\n head?: number | undefined;\n tail?: number | undefined;\n startLine?: number | undefined;\n endLine?: number | undefined;\n },\n ctx: z.RefinementCtx,\n): void {\n const hasHead = value.head !== undefined;\n const hasTail = value.tail !== undefined;\n const hasStart = value.startLine !== undefined;\n const hasEnd = value.endLine !== undefined;\n\n if (hasHead && (hasStart || hasEnd)) {\n ctx.addIssue({\n code: 'custom',\n path: ['head'],\n message:\n \"Cannot use 'head' with 'startLine'/'endLine'. Use either 'head' alone or 'startLine'/'endLine' together.\",\n input: value,\n });\n }\n if (hasTail && (hasHead || hasStart || hasEnd)) {\n ctx.addIssue({\n code: 'custom',\n path: ['tail'],\n message:\n \"Cannot use 'tail' with 'head'/'startLine'/'endLine'. Use 'tail' alone, or 'startLine'/'endLine' without 'tail'.\",\n input: value,\n });\n }\n if (hasEnd && !hasStart) {\n ctx.addIssue({\n code: 'custom',\n path: ['endLine'],\n message: \"'endLine' requires 'startLine' to be set. Provide both together.\",\n input: value,\n });\n }\n const effectiveStart = value.startLine ?? 1;\n if (value.endLine !== undefined && value.endLine < effectiveStart) {\n ctx.addIssue({\n code: 'custom',\n path: ['endLine'],\n message: \"'endLine' must be >= 'startLine'\",\n input: value,\n });\n }\n}\n\nexport function defaultFalseBoolean(description: string): z.ZodDefault<z.ZodBoolean> {\n return z.boolean().default(false).describe(description);\n}\n\nexport const IncludeHidden = defaultFalseBoolean('Include hidden items (starting with .)');\nexport const IncludeIgnored = defaultFalseBoolean(\n 'Include ignored items (node_modules, .git, etc).',\n);\nexport const MaxDepth = z\n .uint32()\n .min(0)\n .max(MAX_SEARCH_DEPTH)\n .optional()\n .describe('Max directory depth to scan; 0 = base directory only, omit for unlimited');\n\nconst DEFAULT_MAX_BATCH = 1000;\n\nexport type SingleOrBatchShape<TExtra extends z.ZodRawShape> = TExtra & {\n path: z.ZodOptional<typeof RequiredPath>;\n paths: z.ZodOptional<z.ZodArray<typeof RequiredPath>>;\n};\n\nexport function singleOrBatchPathsInput<TExtra extends z.ZodRawShape>(\n extra: TExtra,\n): z.ZodObject<SingleOrBatchShape<TExtra>> {\n const shape: z.ZodRawShape = {\n ...extra,\n path: RequiredPath.optional().describe('Single file path; mutually exclusive with paths'),\n paths: z\n .array(RequiredPath)\n .min(1)\n .max(DEFAULT_MAX_BATCH)\n .optional()\n .describe(\n `Array of file paths for batch mode (max ${String(DEFAULT_MAX_BATCH)}); mutually exclusive with path`,\n ),\n };\n\n const base = z.strictObject(shape).superRefine((value, ctx) => {\n const hasPath = value['path'] !== undefined;\n const hasPaths = value['paths'] !== undefined;\n\n if (!hasPath && !hasPaths) {\n ctx.addIssue({\n code: 'custom',\n path: ['path'],\n message: \"Either 'path' or 'paths' must be provided\",\n input: value,\n });\n return;\n }\n if (hasPath && hasPaths) {\n ctx.addIssue({\n code: 'custom',\n path: ['paths'],\n message: \"Cannot use both 'path' and 'paths'\",\n input: value,\n });\n }\n });\n\n // Mirror the superRefine above on the wire: exactly one input mode. `{}` and\n // `path`+`paths` both fail this oneOf, matching the runtime rule.\n return base.meta({\n oneOf: [{ required: ['path'] }, { required: ['paths'] }],\n }) as z.ZodObject<SingleOrBatchShape<TExtra>>;\n}\n\n/**\n * Extract the filesystem paths from a {@link singleOrBatchPathsInput} shape\n * (`{ path?, paths?, files?[{path}] }`) for the executor's access-grant\n * pre-check. Exactly one of the three is set (enforced by the schema's\n * superRefine), so they are checked in priority order.\n */\nexport function singleOrBatchAccessPaths(args: {\n path?: string | undefined;\n paths?: string[] | undefined;\n files?: readonly { path: string }[] | undefined;\n}): string[] {\n if (args.path) return [args.path];\n if (args.paths) return [...args.paths];\n if (args.files) return args.files.map((f) => f.path);\n return [];\n}\n\n// ponytail: charset regex, not z.base64url() — the SDK's AJV warns on the\n// unknown `base64url` format; the server's own decode (cursor.ts) is the real\n// validation, so loosening the client-facing schema to the alphabet is safe.\nconst base64urlCursor = z.string().regex(/^[A-Za-z0-9_-]+$/);\n\n// Ships once per paginated tool — wording is budgeted (TOOL-SURFACE-002).\nexport const CursorSchema = base64urlCursor\n .optional()\n .describe(\n 'Opaque pagination cursor; pass unchanged for the next page. Pages slice one snapshot ' +\n 'taken on the first call; it expires after ~60s — re-request without a cursor if rejected.',\n );\n\n/**\n * Reassembles tool arguments that a client JSON-serialized and then spread\n * into an object: `{ path }` arrives as `{ \"0\": \"{\", \"1\": \"\\\"\", … }`, one key\n * per UTF-16 code unit. Returns `undefined` for anything else. Integer keys\n * enumerate first in ascending order, so matching each key to its position\n * also proves the keys are exactly `\"0\"…\"n-1\"` with nothing else alongside.\n */\nfunction joinSplitString(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const entries = Object.entries(value);\n if (entries.length === 0) return undefined;\n let text = '';\n for (const [index, [key, char]] of entries.entries()) {\n if (key !== String(index) || typeof char !== 'string' || char.length !== 1) return undefined;\n text += char;\n }\n return text;\n}\n\n/**\n * Wraps a tool's input schema so a stringified-then-spread payload (see\n * {@link joinSplitString}) is parsed back into the object it encodes, and one\n * that does not encode an object fails with a message naming the cause rather\n * than `Unrecognized keys: \"0\", \"1\", …`. The published JSON Schema is the\n * wrapped schema's own: a preprocess step converts to its output side.\n */\nexport function withSplitArgsRecovery<T extends z.ZodType>(schema: T): z.ZodPreprocess<T> {\n return z.preprocess((value, ctx) => {\n const text = joinSplitString(value);\n if (text === undefined) return value;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = undefined;\n }\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) return parsed;\n ctx.addIssue({\n code: 'custom',\n message:\n `arguments must be a JSON object, but arrived as a ${String(text.length)}-character ` +\n 'string split into indexed keys (the client serialized the payload); send the arguments as an object',\n input: value,\n });\n return z.NEVER;\n }, schema);\n}\n"]}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from 'zod/v4';\n\nimport {\n isSafeGlobSyntax,\n MAX_BRACE_ALTERNATIVES,\n MAX_DOTDOT_CHECK_CHARS,\n MAX_SEGMENT_WILDCARDS,\n} from './glob.ts';\nimport { MAX_SEARCH_DEPTH } from './util.ts';\n\nexport const PositiveInt = z\n .int({ message: 'Must be integer' })\n .positive({ message: 'Must be > 0' });\n\nconst MAX_PATH_LENGTH = 4096;\n\nconst LINE_BREAK_RE = /[\\n\\r]/;\n\nconst isBlank = (val: string): boolean => val.trim().length === 0;\n\n/**\n * Text that must carry non-blank content. `min(1)` stays so `minLength: 1`\n * still publishes on the wire; it aborts so an empty value reports one\n * message, not the same one twice.\n */\nexport function nonBlankString(label: string): z.ZodString {\n const message = `${label} cannot be empty or whitespace-only`;\n return z\n .string()\n .min(1, { message, abort: true })\n .refine((val) => !isBlank(val), { message });\n}\n\nfunction refineSafeText(\n label: string,\n specific: (val: string) => string | undefined,\n): (val: string, ctx: z.RefinementCtx) => void {\n return (val, ctx) => {\n if (val.length === 0) return;\n const issue = isBlank(val)\n ? `${label} cannot be empty or whitespace-only`\n : val.includes('\\0')\n ? `${label} cannot contain null bytes`\n : (specific(val) ??\n (LINE_BREAK_RE.test(val) ? `${label} cannot contain line breaks` : undefined));\n if (issue !== undefined) {\n ctx.addIssue({ code: 'custom', message: issue, fatal: true });\n }\n };\n}\n\n// Only a whole `..` segment is traversal. `..` inside a segment\n// (`[...slug]`, `v1..2.md`) is a plain file name; PathGuard re-checks\n// containment on the resolved path regardless.\nconst hasTraversalSegment = (val: string): boolean => val.split(/[\\\\/]/u).includes('..');\n\nexport const RequiredPath = z\n .string()\n .min(1, { message: 'Path required' })\n .max(MAX_PATH_LENGTH, { message: `Path too long (max ${MAX_PATH_LENGTH} chars)` })\n .superRefine(\n refineSafeText('Path', (val) =>\n hasTraversalSegment(val) ? 'Directory traversal sequences (\"..\") are forbidden' : undefined,\n ),\n )\n .describe('File or directory path inside an allowed workspace root.');\n\nexport const OptionalPath = RequiredPath.optional();\n\nexport const SafeGlobPattern = z\n .string()\n .min(1, { message: 'Pattern required' })\n .max(1000, { message: 'Max 1000 chars' })\n .superRefine(\n refineSafeText('Pattern', (val) =>\n isSafeGlobSyntax(val)\n ? undefined\n : `Invalid glob or unsafe path (absolute/.. forbidden, at most ${String(MAX_BRACE_ALTERNATIVES)} brace alternatives, ${String(MAX_DOTDOT_CHECK_CHARS)} traversal-check characters, and ${String(MAX_SEGMENT_WILDCARDS)} * runs and extglob groups together per path segment; no |, * or nested group inside *(…) or +(…))`,\n ),\n )\n .describe('Relative glob pattern under the search root (e.g. \"**/*.ts\", \"src/**/*.js\").')\n .meta({ examples: ['**/*.ts', 'src/**/*.js', '**/*.{ts,tsx}'] });\n\nexport function validateReadRange(\n value: {\n head?: number | undefined;\n tail?: number | undefined;\n startLine?: number | undefined;\n endLine?: number | undefined;\n },\n ctx: z.RefinementCtx,\n): void {\n const hasHead = value.head !== undefined;\n const hasTail = value.tail !== undefined;\n const hasStart = value.startLine !== undefined;\n const hasEnd = value.endLine !== undefined;\n\n if (hasHead && (hasStart || hasEnd)) {\n ctx.addIssue({\n code: 'custom',\n path: ['head'],\n message:\n \"Cannot use 'head' with 'startLine'/'endLine'. Use either 'head' alone or 'startLine'/'endLine' together.\",\n input: value,\n });\n }\n if (hasTail && (hasHead || hasStart || hasEnd)) {\n ctx.addIssue({\n code: 'custom',\n path: ['tail'],\n message:\n \"Cannot use 'tail' with 'head'/'startLine'/'endLine'. Use 'tail' alone, or 'startLine'/'endLine' without 'tail'.\",\n input: value,\n });\n }\n if (hasEnd && !hasStart) {\n ctx.addIssue({\n code: 'custom',\n path: ['endLine'],\n message: \"'endLine' requires 'startLine' to be set. Provide both together.\",\n input: value,\n });\n }\n const effectiveStart = value.startLine ?? 1;\n if (value.endLine !== undefined && value.endLine < effectiveStart) {\n ctx.addIssue({\n code: 'custom',\n path: ['endLine'],\n message: \"'endLine' must be >= 'startLine'\",\n input: value,\n });\n }\n}\n\nexport function defaultFalseBoolean(description: string): z.ZodDefault<z.ZodBoolean> {\n return z.boolean().default(false).describe(description);\n}\n\nexport const IncludeHidden = defaultFalseBoolean('Include hidden items (starting with .)');\nexport const IncludeIgnored = defaultFalseBoolean(\n 'Include ignored items (node_modules, .git, etc).',\n);\nexport const MaxDepth = z\n .uint32()\n .min(0)\n .max(MAX_SEARCH_DEPTH)\n .optional()\n .describe('Max directory depth to scan; 0 = base directory only, omit for unlimited');\n\nconst DEFAULT_MAX_BATCH = 1000;\n\nexport type SingleOrBatchShape<TExtra extends z.ZodRawShape> = TExtra & {\n path: z.ZodOptional<typeof RequiredPath>;\n paths: z.ZodOptional<z.ZodArray<typeof RequiredPath>>;\n};\n\nexport function singleOrBatchPathsInput<TExtra extends z.ZodRawShape>(\n extra: TExtra,\n): z.ZodObject<SingleOrBatchShape<TExtra>> {\n const shape: z.ZodRawShape = {\n ...extra,\n path: RequiredPath.optional().describe('Single file path; mutually exclusive with paths'),\n paths: z\n .array(RequiredPath)\n .min(1)\n .max(DEFAULT_MAX_BATCH)\n .optional()\n .describe(\n `Array of file paths for batch mode (max ${String(DEFAULT_MAX_BATCH)}); mutually exclusive with path`,\n ),\n };\n\n const base = z.strictObject(shape).superRefine((value, ctx) => {\n const hasPath = value['path'] !== undefined;\n const hasPaths = value['paths'] !== undefined;\n\n if (!hasPath && !hasPaths) {\n ctx.addIssue({\n code: 'custom',\n path: ['path'],\n message: \"Either 'path' or 'paths' must be provided\",\n input: value,\n });\n return;\n }\n if (hasPath && hasPaths) {\n ctx.addIssue({\n code: 'custom',\n path: ['paths'],\n message: \"Cannot use both 'path' and 'paths'\",\n input: value,\n });\n }\n });\n\n // No top-level oneOf on the wire: OpenAI and Anthropic reject top-level\n // oneOf/anyOf/allOf in function parameters. The superRefine enforces\n // \"exactly one of path/paths\" at call time.\n return base as z.ZodObject<SingleOrBatchShape<TExtra>>;\n}\n\n/**\n * `ToolDef.mapPaths` for a {@link singleOrBatchPathsInput} shape\n * (`{ path?, paths?, files?[{path}] }`). The schema's superRefine lets exactly\n * one of the three through; each present one is mapped.\n */\nexport function mapSingleOrBatchPaths<\n T extends {\n path?: string | undefined;\n paths?: string[] | undefined;\n files?: readonly { path: string }[] | undefined;\n },\n>(args: T, fn: (path: string) => string): T {\n return {\n ...args,\n ...(args.path !== undefined ? { path: fn(args.path) } : {}),\n ...(args.paths !== undefined ? { paths: args.paths.map(fn) } : {}),\n ...(args.files !== undefined\n ? { files: args.files.map((file) => ({ ...file, path: fn(file.path) })) }\n : {}),\n };\n}\n\n/** `ToolDef.mapPaths` for a tool whose one `path` is optional (omitted = the root). */\nexport function mapOptionalPath<T extends { path?: string | undefined }>(\n args: T,\n fn: (path: string) => string,\n): T {\n return args.path === undefined ? args : { ...args, path: fn(args.path) };\n}\n\n// ponytail: charset regex, not z.base64url() — the SDK's AJV warns on the\n// unknown `base64url` format; the server's own decode (cursor.ts) is the real\n// validation, so loosening the client-facing schema to the alphabet is safe.\nconst base64urlCursor = z.string().regex(/^[A-Za-z0-9_-]+$/);\n\n// Ships once per paginated tool — wording is budgeted (TOOL-SURFACE-002).\nexport const CursorSchema = base64urlCursor\n .optional()\n .describe(\n 'Opaque pagination cursor; pass unchanged for the next page. Pages slice one snapshot ' +\n 'taken on the first call; it expires after ~60s — re-request without a cursor if rejected.',\n );\n\n/**\n * Reassembles tool arguments that a client JSON-serialized and then spread\n * into an object: `{ path }` arrives as `{ \"0\": \"{\", \"1\": \"\\\"\", … }`, one key\n * per UTF-16 code unit. Returns `undefined` for anything else. Integer keys\n * enumerate first in ascending order, so matching each key to its position\n * also proves the keys are exactly `\"0\"…\"n-1\"` with nothing else alongside.\n */\nfunction joinSplitString(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const entries = Object.entries(value);\n if (entries.length === 0) return undefined;\n let text = '';\n for (const [index, [key, char]] of entries.entries()) {\n if (key !== String(index) || typeof char !== 'string' || char.length !== 1) return undefined;\n text += char;\n }\n return text;\n}\n\n/**\n * Wraps a tool's input schema so a stringified-then-spread payload (see\n * {@link joinSplitString}) is parsed back into the object it encodes, and one\n * that does not encode an object fails with a message naming the cause rather\n * than `Unrecognized keys: \"0\", \"1\", …`. The published JSON Schema is the\n * wrapped schema's own: a preprocess step converts to its output side.\n */\nexport function withSplitArgsRecovery<T extends z.ZodType>(schema: T): z.ZodPreprocess<T> {\n return z.preprocess((value, ctx) => {\n const text = joinSplitString(value);\n if (text === undefined) return value;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = undefined;\n }\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) return parsed;\n ctx.addIssue({\n code: 'custom',\n message:\n `arguments must be a JSON object, but arrived as a ${String(text.length)}-character ` +\n 'string split into indexed keys (the client serialized the payload); send the arguments as an object',\n input: value,\n });\n return z.NEVER;\n }, schema);\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { RE2 } from '@adguard/re2-wasm';
2
+ import { GuardedFileSystem } from './fs.ts';
2
3
  import type { PathGuard } from './path.ts';
3
4
  interface SearchResult {
4
5
  file: string;
@@ -155,5 +156,10 @@ export declare function searchFiles(directory: string, pattern: string, options:
155
156
  stoppedReason?: 'maxResults' | 'timeout';
156
157
  };
157
158
  }>;
159
+ /** Explicit files bypass glob filtering; their parent is the directory walked. */
160
+ export declare function resolveSearchScope(path: string | undefined, fs: GuardedFileSystem, signal?: AbortSignal): Promise<{
161
+ basePath: string;
162
+ explicitFile?: string;
163
+ }>;
158
164
  export {};
159
165
  //# sourceMappingURL=search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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"}
@@ -50,9 +50,10 @@ 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 { basename } from 'node:path';
53
+ import { basename, dirname } from 'node:path';
54
54
  import { RE2 } from '@adguard/re2-wasm';
55
55
  import { processEntriesConcurrently, resolveStopReason } from "./concurrency.js";
56
+ import { ErrorCode, FsError, isFsError } from "./errors.js";
56
57
  import { GuardedFileSystem } from "./fs.js";
57
58
  import { globEntries } from "./glob.js";
58
59
  import { escapeRegExp, getMaxTextFileSize, MIB, PARALLEL_CONCURRENCY } from "./util.js";
@@ -88,6 +89,60 @@ export function createYielder(signal) {
88
89
  * aborts the module. 4 MiB leaves room for the copy and the pattern set.
89
90
  */
90
91
  export const RE2_MAX_INPUT_BYTES = 4 * MIB;
92
+ /** The chunk `FileHandle#readFile` reads in, and checks its signal between. */
93
+ const READ_CHUNK_BYTES = 512 * 1024;
94
+ /** The only non-ASCII characters RE2's case folding maps onto ASCII letters. */
95
+ const KELVIN_SIGN_UTF8 = Buffer.from('\u212A', 'utf8'); // folds onto k
96
+ const LONG_S_UTF8 = Buffer.from('\u017F', 'utf8'); // folds onto s
97
+ const ASCII_UPPER_RE = /[A-Z]+/gu;
98
+ /** Non-overlapping occurrences, left to right, as a global RE2 exec walks them. */
99
+ function countOccurrences(line, needle) {
100
+ const column = line.indexOf(needle);
101
+ if (column === -1)
102
+ return undefined;
103
+ let count = 0;
104
+ let at = column;
105
+ while (at !== -1 && count < MAX_MATCHES_PER_LINE) {
106
+ count++;
107
+ at = line.indexOf(needle, at + needle.length);
108
+ }
109
+ return { count, column };
110
+ }
111
+ /** `undefined` when RE2 must judge the needle: these shortcuts are exact only for the needles they take. */
112
+ function createLiteralMatcher(needle, caseSensitive) {
113
+ // The file is decoded with U+FFFD for every invalid byte, and a lone
114
+ // surrogate reaches RE2 as U+FFFD, so a needle holding either can match
115
+ // decoded text that the raw bytes, or indexOf, do not.
116
+ if (needle.includes('\uFFFD') || Buffer.from(needle, 'utf8').toString('utf8') !== needle) {
117
+ return undefined;
118
+ }
119
+ if (caseSensitive) {
120
+ const bytes = Buffer.from(needle, 'utf8');
121
+ return {
122
+ needsRe2: () => false,
123
+ mayMatch: (buffer) => buffer.includes(bytes),
124
+ matchLine: (line) => countOccurrences(line, needle),
125
+ };
126
+ }
127
+ // Every non-ASCII character takes more than one UTF-8 byte per UTF-16 unit.
128
+ if (Buffer.byteLength(needle, 'utf8') !== needle.length)
129
+ return undefined;
130
+ const lower = needle.toLowerCase();
131
+ const folds = [
132
+ ...(lower.includes('k') ? [KELVIN_SIGN_UTF8] : []),
133
+ ...(lower.includes('s') ? [LONG_S_UTF8] : []),
134
+ ];
135
+ return {
136
+ needsRe2: (buffer) => folds.some((fold) => buffer.includes(fold)),
137
+ // Latin-1 decoding keeps one char per byte, and lowercasing it maps A-Z
138
+ // onto a-z and every other byte onto a non-ASCII char, so an ASCII needle
139
+ // is found exactly where the bytes spell it in either case.
140
+ mayMatch: (buffer) => buffer.toString('latin1').toLowerCase().includes(lower),
141
+ // ASCII letters only: String#toLowerCase turns U+0130 into two code units,
142
+ // which shifts every later column and spells an `i` RE2 does not match.
143
+ matchLine: (line) => countOccurrences(line.replace(ASCII_UPPER_RE, (upper) => upper.toLowerCase()), lower),
144
+ };
145
+ }
91
146
  const BUFFER_ANCHOR_RE = /\\[AzZ]/u;
92
147
  /** `(?i)`, `(?-m)`, `(?s:…)`: inline flags can undo the `m` the prefilter relies on. */
93
148
  const INLINE_FLAGS_RE = /\(\?[a-zA-Z-]+[:)]/u;
@@ -215,10 +270,34 @@ async function scanFile(entryPath, scanCtx) {
215
270
  // re-checks the descriptor's real path (H4), so these bytes are the
216
271
  // validated file's, not whatever the name points at by now.
217
272
  const handle = __addDisposableResource(env_1, await scanCtx.fs.open(entryPath), true);
218
- const stats = await handle.stat();
219
- if (stats.size > scanCtx.maxFileSize)
273
+ const { size } = await handle.stat();
274
+ if (size > scanCtx.maxFileSize)
220
275
  return { kind: 'tooLarge' };
221
- buffer = await handle.readFile({ signal: scanCtx.signal });
276
+ // Read the size fstat reported: handle.readFile would fstat again and then
277
+ // read once more to find EOF, two extra round trips on every file a walk
278
+ // touches. A file that grows meanwhile is searched as it was at the fstat.
279
+ // A size of 0 is also what procfs and other virtual files report while
280
+ // holding text, so those still read to EOF.
281
+ if (size === 0) {
282
+ buffer = await handle.readFile({ signal: scanCtx.signal });
283
+ if (buffer.length > scanCtx.maxFileSize)
284
+ return { kind: 'tooLarge' };
285
+ }
286
+ else {
287
+ buffer = Buffer.allocUnsafe(size);
288
+ let filled = 0;
289
+ while (filled < size) {
290
+ // In readFile's own chunks, with its check of the signal before each,
291
+ // so a deadline on a slow filesystem lands as soon as it did there.
292
+ scanCtx.signal?.throwIfAborted();
293
+ const length = Math.min(size - filled, READ_CHUNK_BYTES);
294
+ const { bytesRead } = await handle.read(buffer, filled, length, filled);
295
+ if (bytesRead === 0)
296
+ break;
297
+ filled += bytesRead;
298
+ }
299
+ buffer = buffer.subarray(0, filled);
300
+ }
222
301
  }
223
302
  catch (e_1) {
224
303
  env_1.error = e_1;
@@ -233,6 +312,10 @@ async function scanFile(entryPath, scanCtx) {
233
312
  catch (error) {
234
313
  if (scanCtx.signal?.aborted)
235
314
  throw error;
315
+ if (scanCtx.fs.pathGuard.isSensitive(entryPath) ||
316
+ (isFsError(error) && error.message.startsWith('Sensitive file'))) {
317
+ return { kind: 'sensitive' };
318
+ }
236
319
  return { kind: 'inaccessible' };
237
320
  }
238
321
  // A NUL byte marks a binary file, as it does for `read` and `grep -I`.
@@ -240,8 +323,11 @@ async function scanFile(entryPath, scanCtx) {
240
323
  // bytes a pattern could not have matched anyway.
241
324
  if (buffer.includes(0))
242
325
  return { kind: 'binary' };
326
+ const literal = scanCtx.literal?.needsRe2(buffer) ? undefined : scanCtx.literal;
327
+ if (literal && !literal.mayMatch(buffer))
328
+ return { kind: 'scanned', matches: [] };
243
329
  const content = buffer.toString('utf-8');
244
- if (canPrefilter(scanCtx.source, buffer)) {
330
+ if (!literal && canPrefilter(scanCtx.source, buffer)) {
245
331
  scanCtx.prefilter.lastIndex = 0;
246
332
  if (scanCtx.prefilter.exec(content) === null)
247
333
  return { kind: 'scanned', matches: [] };
@@ -253,8 +339,9 @@ async function scanFile(entryPath, scanCtx) {
253
339
  const lineCount = content.length === 0 ? 0 : content.endsWith('\n') ? lines.length - 1 : lines.length;
254
340
  // A single line past RE2_MAX_INPUT_BYTES OOMs the wasm module for the rest of
255
341
  // the process; report the file tooLarge instead. Only a buffer over the bound
256
- // can hold such a line, so ordinary files skip the per-line measure.
257
- const checkLineSize = buffer.length > RE2_MAX_INPUT_BYTES;
342
+ // can hold such a line, so ordinary files skip the per-line measure, and a
343
+ // literal match never hands a line to RE2.
344
+ const checkLineSize = !literal && buffer.length > RE2_MAX_INPUT_BYTES;
258
345
  const matches = [];
259
346
  const tick = createYielder(scanCtx.signal);
260
347
  for (let i = 0; i < lineCount; i++) {
@@ -269,7 +356,9 @@ async function scanFile(entryPath, scanCtx) {
269
356
  break;
270
357
  // One scan per line: the helper resets lastIndex itself, so it
271
358
  // doubles as the "does this line match" test.
272
- const found = await findLineMatches(scanCtx.regex, line, tick);
359
+ const found = literal
360
+ ? literal.matchLine(line)
361
+ : await findLineMatches(scanCtx.regex, line, tick);
273
362
  if (found) {
274
363
  matches.push({
275
364
  file: entryPath,
@@ -341,6 +430,9 @@ export async function searchContent(directory, pattern, options, pathGuard) {
341
430
  maxResults,
342
431
  regex,
343
432
  prefilter,
433
+ literal: options.isRegex
434
+ ? undefined
435
+ : createLiteralMatcher(pattern, Boolean(options.caseSensitive)),
344
436
  source,
345
437
  context,
346
438
  };
@@ -369,6 +461,8 @@ export async function searchContent(directory, pattern, options, pathGuard) {
369
461
  if (!scan)
370
462
  continue;
371
463
  switch (scan.kind) {
464
+ case 'sensitive':
465
+ break;
372
466
  case 'inaccessible':
373
467
  skippedInaccessible++;
374
468
  break;
@@ -442,34 +536,47 @@ export async function searchFiles(directory, pattern, options, pathGuard) {
442
536
  const results = [];
443
537
  let filesScanned = 0;
444
538
  let skippedInaccessible = 0;
445
- let stoppedByAbort = false;
446
- // The signal carries both client cancellation and the tool's search timeout,
447
- // so an abort means "return what we have, marked incomplete" rather than
448
- // throw — but it must never be reported as a finished scan. The walk itself
449
- // throws when the signal stops it before a single entry is yielded.
450
- try {
451
- for await (const entry of entries) {
452
- if (options.signal?.aborted) {
453
- stoppedByAbort = true;
454
- break;
455
- }
539
+ const perFile = new Map();
540
+ let dispatched = 0;
541
+ let collected = 0;
542
+ const stopped = await processEntriesConcurrently(entries, {
543
+ signal: options.signal,
544
+ concurrency: PARALLEL_CONCURRENCY,
545
+ shouldStop: () => collected >= maxResults,
546
+ onEntry: () => {
547
+ dispatched++;
548
+ },
549
+ onError: () => undefined,
550
+ runEntry: async (entryPath) => {
551
+ const index = dispatched - 1;
456
552
  try {
457
- await pathGuard.validateExistingPath(entry.path);
553
+ await pathGuard.validateExistingPath(entryPath);
554
+ perFile.set(index, { path: entryPath });
555
+ collected++;
458
556
  }
459
- catch {
460
- skippedInaccessible++;
461
- continue;
557
+ catch (error) {
558
+ if (pathGuard.isSensitive(entryPath) ||
559
+ (isFsError(error) && error.message.startsWith('Sensitive file'))) {
560
+ perFile.set(index, 'sensitive');
561
+ }
562
+ else {
563
+ perFile.set(index, 'inaccessible');
564
+ }
462
565
  }
463
- if (results.length >= maxResults)
464
- break;
465
- filesScanned++;
466
- results.push({ path: entry.path });
566
+ },
567
+ });
568
+ for (let i = 0; i < dispatched; i++) {
569
+ const item = perFile.get(i);
570
+ if (!item || item === 'sensitive')
571
+ continue;
572
+ if (item === 'inaccessible') {
573
+ skippedInaccessible++;
574
+ continue;
467
575
  }
468
- }
469
- catch (error) {
470
- if (!options.signal?.aborted)
471
- throw error;
472
- stoppedByAbort = true;
576
+ if (results.length >= maxResults)
577
+ break;
578
+ filesScanned++;
579
+ results.push(item);
473
580
  }
474
581
  // Sorting — only name / path are supported; size / modified were removed
475
582
  // (the glob never collected stats, so they were always undefined).
@@ -480,7 +587,7 @@ export async function searchFiles(directory, pattern, options, pathGuard) {
480
587
  results.sort((a, b) => a.path.localeCompare(b.path));
481
588
  }
482
589
  // Same precedence as above: the result cap wins over a same-iteration abort.
483
- const stoppedReason = resolveStopReason(results.length >= maxResults, stoppedByAbort);
590
+ const stoppedReason = resolveStopReason(results.length >= maxResults, stopped === 'timeout' || Boolean(options.signal?.aborted));
484
591
  return {
485
592
  basePath: directory,
486
593
  results,
@@ -493,4 +600,23 @@ export async function searchFiles(directory, pattern, options, pathGuard) {
493
600
  },
494
601
  };
495
602
  }
603
+ /** Explicit files bypass glob filtering; their parent is the directory walked. */
604
+ export async function resolveSearchScope(path, fs, signal) {
605
+ const requested = fs.pathGuard.resolvePathOrRoot(path);
606
+ // One resolution, one stat: validateExistingDirectory would redo both.
607
+ const { stats, validPath: resolved } = await fs.stat(requested, { signal });
608
+ if (!stats.isFile()) {
609
+ if (!stats.isDirectory()) {
610
+ throw new FsError(ErrorCode.NOT_DIRECTORY, 'Not a directory', requested);
611
+ }
612
+ return { basePath: resolved };
613
+ }
614
+ return {
615
+ basePath: dirname(resolved),
616
+ // The name is NOT turned into a glob: `[slug].tsx` would read as a
617
+ // character class. searchContent scans exactly this file instead, and
618
+ // hidden/ignored filtering is moot for a file the caller named.
619
+ explicitFile: resolved,
620
+ };
621
+ }
496
622
  //# sourceMappingURL=search.js.map