@j0hanz/filesystem-mcp 2.6.3 → 2.7.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.
- package/README.md +28 -19
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +5 -2
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +0 -17
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +53 -47
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +7 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/diff.d.ts +0 -4
- package/dist/core/diff.d.ts.map +1 -1
- package/dist/core/diff.js +1 -15
- package/dist/core/diff.js.map +1 -1
- package/dist/core/file-uri.d.ts +9 -9
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +16 -11
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fs.d.ts +1 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +65 -56
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +6 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +80 -13
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +30 -13
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +35 -10
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/mime.d.ts +2 -4
- package/dist/core/mime.d.ts.map +1 -1
- package/dist/core/mime.js +3 -8
- package/dist/core/mime.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +7 -4
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.d.ts.map +1 -1
- package/dist/core/path-discovery.js +2 -2
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path-utils.d.ts +1 -3
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +0 -3
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +7 -0
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +69 -26
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +1 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +4 -0
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +0 -80
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +0 -89
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +8 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +191 -101
- package/dist/core/search.js.map +1 -1
- package/dist/core/store.d.ts +7 -11
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +19 -21
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +17 -0
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +19 -0
- package/dist/core/util.js.map +1 -1
- package/dist/instructions.js +1 -1
- package/dist/instructions.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +5 -2
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +3 -13
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +11 -27
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +25 -46
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +20 -23
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +11 -27
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +10 -37
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +2 -13
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +20 -53
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +3 -28
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-roots.d.ts.map +1 -1
- package/dist/tools/list-roots.js +1 -12
- package/dist/tools/list-roots.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +19 -53
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +54 -37
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts.map +1 -1
- package/dist/tools/patch.js +4 -24
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/progress.d.ts +0 -2
- package/dist/tools/progress.d.ts.map +1 -1
- package/dist/tools/progress.js +20 -12
- package/dist/tools/progress.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +29 -116
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +20 -43
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +24 -78
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +5 -19
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +1 -0
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +16 -27
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +3 -1
- package/dist/transport/shared.d.ts.map +1 -1
- package/dist/transport/shared.js +14 -1
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts.map +1 -1
- package/dist/transport/stdio.js +18 -3
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +3 -3
package/dist/core/schema.js
CHANGED
|
@@ -1,34 +1,9 @@
|
|
|
1
1
|
import * as z from 'zod/v4';
|
|
2
2
|
import { isSafeGlobSyntax } from "./glob.js";
|
|
3
|
-
import { MIME_KINDS } from "./mime.js";
|
|
4
|
-
import { ENTRY_TYPES } from "./path-utils.js";
|
|
5
3
|
import { MAX_SEARCH_DEPTH } from "./util.js";
|
|
6
|
-
// Runtime: full ISO-8601 UTC; emits the standard `date-time` format on the wire
|
|
7
|
-
// (no AJV warning — it is a known format, unlike sha256_hex / base64url).
|
|
8
|
-
// No `id` on this or any other shared schema below: an `id` hoists the schema
|
|
9
|
-
// into `$defs` and leaves a `$ref` at every use site, which is exactly the wire
|
|
10
|
-
// weight the published JSON schema (tools/define.ts) leaves out.
|
|
11
|
-
export const IsoDateTime = z.iso.datetime().meta({
|
|
12
|
-
description: 'ISO 8601 UTC date-time string (e.g. 2024-01-15T12:00:00.000Z)',
|
|
13
|
-
// `format: "date-time"` already pins the value; zod's ~330-char calendar
|
|
14
|
-
// regex is pure wire weight. Runtime validation still runs it — this
|
|
15
|
-
// suppresses the emitted keyword only.
|
|
16
|
-
pattern: undefined,
|
|
17
|
-
});
|
|
18
|
-
// pattern (not z.hash('sha256')) so no `format: "sha256_hex"` keyword is emitted;
|
|
19
|
-
// the SDK's AJV validator warns on that unknown format at every server start.
|
|
20
|
-
export const Sha256Hex = z
|
|
21
|
-
.string()
|
|
22
|
-
.regex(/^[0-9a-f]{64}$/)
|
|
23
|
-
.meta({ description: 'SHA-256 digest as a 64-character lowercase hex string' });
|
|
24
|
-
export const NonNegInt = z
|
|
25
|
-
.int({ message: 'Must be integer' })
|
|
26
|
-
.nonnegative({ message: 'Must be ≥ 0' });
|
|
27
4
|
export const PositiveInt = z
|
|
28
5
|
.int({ message: 'Must be integer' })
|
|
29
6
|
.positive({ message: 'Must be > 0' });
|
|
30
|
-
export const FileType = z.enum(ENTRY_TYPES);
|
|
31
|
-
export const FileKind = z.enum(MIME_KINDS);
|
|
32
7
|
const MAX_PATH_LENGTH = 4096;
|
|
33
8
|
const LINE_BREAK_RE = /[\n\r]/;
|
|
34
9
|
export const isBlank = (val) => val.trim().length === 0;
|
|
@@ -65,54 +40,6 @@ export const SafeGlobPattern = z
|
|
|
65
40
|
.superRefine(refineSafeText('Pattern', (val) => isSafeGlobSyntax(val) ? undefined : 'Invalid glob or unsafe path (absolute/.. forbidden)'))
|
|
66
41
|
.describe('Relative glob pattern under the search root (e.g. "**/*.ts", "src/**/*.js").')
|
|
67
42
|
.meta({ examples: ['**/*.ts', 'src/**/*.js', '**/*.{ts,tsx}'] });
|
|
68
|
-
// Only the fields whose key does not already say what they hold carry a
|
|
69
|
-
// description. A `.describe('Name')` on `name` costs every client tokens to
|
|
70
|
-
// learn nothing.
|
|
71
|
-
export const FileInfoSchema = z.strictObject({
|
|
72
|
-
name: z.string(),
|
|
73
|
-
path: z.string(),
|
|
74
|
-
type: FileType,
|
|
75
|
-
// A directory's own inode size says nothing about what is inside it — 0 on
|
|
76
|
-
// Windows, 4096 on ext4 — so say so rather than letting a caller read it as
|
|
77
|
-
// "empty". This caveat used to live in stat's text summary, which is gone.
|
|
78
|
-
size: NonNegInt.describe('Bytes; meaningless for a directory — use list for its contents'),
|
|
79
|
-
tokenEstimate: NonNegInt.optional().describe('Rough token estimate; use to pre-screen read cost'),
|
|
80
|
-
created: IsoDateTime,
|
|
81
|
-
modified: IsoDateTime,
|
|
82
|
-
accessed: IsoDateTime,
|
|
83
|
-
permissions: z.string(),
|
|
84
|
-
isHidden: z.boolean(),
|
|
85
|
-
mimeType: z.string().optional(),
|
|
86
|
-
symlinkTarget: z.string().optional().describe('Target (symlink)'),
|
|
87
|
-
});
|
|
88
|
-
export const OperationSummarySchema = z.strictObject({
|
|
89
|
-
total: NonNegInt,
|
|
90
|
-
succeeded: NonNegInt,
|
|
91
|
-
failed: NonNegInt,
|
|
92
|
-
});
|
|
93
|
-
export const PerFileErrorSchema = z.strictObject({
|
|
94
|
-
code: z.string(),
|
|
95
|
-
message: z.string(),
|
|
96
|
-
path: z.string().optional(),
|
|
97
|
-
suggestion: z.string().optional(),
|
|
98
|
-
});
|
|
99
|
-
/**
|
|
100
|
-
* The `{ path, value, error }` envelope every per-path tool (`delete`, `edit`,
|
|
101
|
-
* `read`, `stat`, `replace_text`) wraps its results in: the requested path, the
|
|
102
|
-
* tool-specific value on success, the error detail on failure.
|
|
103
|
-
*/
|
|
104
|
-
export function perPathEnvelope(value, valueDescription) {
|
|
105
|
-
return z.strictObject({
|
|
106
|
-
path: z.string().describe('Requested path'),
|
|
107
|
-
value: value.optional().describe(valueDescription),
|
|
108
|
-
error: PerFileErrorSchema.optional().describe('Error details; present on failure'),
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
/** One entry of a tool's `failures[]`: the path that failed and why. */
|
|
112
|
-
export const PathFailureSchema = z.strictObject({
|
|
113
|
-
path: z.string(),
|
|
114
|
-
error: PerFileErrorSchema,
|
|
115
|
-
});
|
|
116
43
|
export function validateReadRange(value, ctx) {
|
|
117
44
|
const hasHead = value.head !== undefined;
|
|
118
45
|
const hasTail = value.tail !== undefined;
|
|
@@ -217,19 +144,6 @@ export function singleOrBatchAccessPaths(args) {
|
|
|
217
144
|
return args.files.map((f) => f.path);
|
|
218
145
|
return [];
|
|
219
146
|
}
|
|
220
|
-
// `args` is the exact argument object the one producer (read's
|
|
221
|
-
// buildReadContinuation) emits, not a free-form record: a bare
|
|
222
|
-
// `z.record(z.string(), z.unknown())` renders as `additionalProperties: {}`,
|
|
223
|
-
// which carries no validation keyword and constrains nothing — the Inspector's
|
|
224
|
-
// portability check flags it, and a client cannot tell what to pass. Widen this
|
|
225
|
-
// (to a union) only when a second tool starts emitting continuations.
|
|
226
|
-
export const ContinuationSchema = z.strictObject({
|
|
227
|
-
tool: z.string().describe('Tool name to call for the next chunk'),
|
|
228
|
-
args: z
|
|
229
|
-
.strictObject({ path: z.string(), startLine: PositiveInt, endLine: PositiveInt })
|
|
230
|
-
.describe('Ready-to-use arguments for the next call; pass verbatim'),
|
|
231
|
-
hint: z.string().describe('One-sentence description of the data still remaining to be read'),
|
|
232
|
-
});
|
|
233
147
|
// ponytail: charset regex, not z.base64url() — the SDK's AJV warns on the
|
|
234
148
|
// unknown `base64url` format; the server's own decode (cursor.ts) is the real
|
|
235
149
|
// validation, so loosening the client-facing schema to the alphabet is safe.
|
|
@@ -239,7 +153,4 @@ export const CursorSchema = base64urlCursor
|
|
|
239
153
|
.optional()
|
|
240
154
|
.describe('Opaque pagination cursor; pass unchanged for the next page. Pages slice one snapshot ' +
|
|
241
155
|
'taken on the first call; it expires after ~60s — re-request without a cursor if rejected.');
|
|
242
|
-
export const NextCursorSchema = base64urlCursor
|
|
243
|
-
.optional()
|
|
244
|
-
.describe('Cursor for the next page; omitted when this is the final page.');
|
|
245
156
|
//# sourceMappingURL=schema.js.map
|
package/dist/core/schema.js.map
CHANGED
|
@@ -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 } from './glob.ts';\nimport { MIME_KINDS } from './mime.ts';\nimport { ENTRY_TYPES } from './path-utils.ts';\nimport { MAX_SEARCH_DEPTH } from './util.ts';\n\n// Runtime: full ISO-8601 UTC; emits the standard `date-time` format on the wire\n// (no AJV warning — it is a known format, unlike sha256_hex / base64url).\n// No `id` on this or any other shared schema below: an `id` hoists the schema\n// into `$defs` and leaves a `$ref` at every use site, which is exactly the wire\n// weight the published JSON schema (tools/define.ts) leaves out.\nexport const IsoDateTime = z.iso.datetime().meta({\n description: 'ISO 8601 UTC date-time string (e.g. 2024-01-15T12:00:00.000Z)',\n // `format: \"date-time\"` already pins the value; zod's ~330-char calendar\n // regex is pure wire weight. Runtime validation still runs it — this\n // suppresses the emitted keyword only.\n pattern: undefined,\n});\n\n// pattern (not z.hash('sha256')) so no `format: \"sha256_hex\"` keyword is emitted;\n// the SDK's AJV validator warns on that unknown format at every server start.\nexport const Sha256Hex = z\n .string()\n .regex(/^[0-9a-f]{64}$/)\n .meta({ description: 'SHA-256 digest as a 64-character lowercase hex string' });\n\nexport const NonNegInt = z\n .int({ message: 'Must be integer' })\n .nonnegative({ message: 'Must be ≥ 0' });\n\nexport const PositiveInt = z\n .int({ message: 'Must be integer' })\n .positive({ message: 'Must be > 0' });\n\nexport type FileType = (typeof ENTRY_TYPES)[number];\nexport const FileType = z.enum(ENTRY_TYPES);\n\nexport type FileKind = (typeof MIME_KINDS)[number];\nexport const FileKind = z.enum(MIME_KINDS);\n\nconst MAX_PATH_LENGTH = 4096;\n\nconst LINE_BREAK_RE = /[\\n\\r]/;\n\nexport const isBlank = (val: string): boolean => val.trim().length === 0;\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) ? undefined : 'Invalid glob or unsafe path (absolute/.. forbidden)',\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\n// Only the fields whose key does not already say what they hold carry a\n// description. A `.describe('Name')` on `name` costs every client tokens to\n// learn nothing.\nexport const FileInfoSchema = z.strictObject({\n name: z.string(),\n path: z.string(),\n type: FileType,\n // A directory's own inode size says nothing about what is inside it — 0 on\n // Windows, 4096 on ext4 — so say so rather than letting a caller read it as\n // \"empty\". This caveat used to live in stat's text summary, which is gone.\n size: NonNegInt.describe('Bytes; meaningless for a directory — use list for its contents'),\n tokenEstimate: NonNegInt.optional().describe('Rough token estimate; use to pre-screen read cost'),\n created: IsoDateTime,\n modified: IsoDateTime,\n accessed: IsoDateTime,\n permissions: z.string(),\n isHidden: z.boolean(),\n mimeType: z.string().optional(),\n symlinkTarget: z.string().optional().describe('Target (symlink)'),\n});\n\nexport const OperationSummarySchema = z.strictObject({\n total: NonNegInt,\n succeeded: NonNegInt,\n failed: NonNegInt,\n});\n\nexport const PerFileErrorSchema = z.strictObject({\n code: z.string(),\n message: z.string(),\n path: z.string().optional(),\n suggestion: z.string().optional(),\n});\n\n/**\n * The `{ path, value, error }` envelope every per-path tool (`delete`, `edit`,\n * `read`, `stat`, `replace_text`) wraps its results in: the requested path, the\n * tool-specific value on success, the error detail on failure.\n */\nexport function perPathEnvelope<TValue extends z.ZodType>(\n value: TValue,\n valueDescription: string,\n): z.ZodObject<\n {\n path: z.ZodString;\n value: z.ZodOptional<TValue>;\n error: z.ZodOptional<typeof PerFileErrorSchema>;\n },\n z.core.$strict\n> {\n return z.strictObject({\n path: z.string().describe('Requested path'),\n value: value.optional().describe(valueDescription),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n });\n}\n\n/** One entry of a tool's `failures[]`: the path that failed and why. */\nexport const PathFailureSchema = z.strictObject({\n path: z.string(),\n error: PerFileErrorSchema,\n});\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// `args` is the exact argument object the one producer (read's\n// buildReadContinuation) emits, not a free-form record: a bare\n// `z.record(z.string(), z.unknown())` renders as `additionalProperties: {}`,\n// which carries no validation keyword and constrains nothing — the Inspector's\n// portability check flags it, and a client cannot tell what to pass. Widen this\n// (to a union) only when a second tool starts emitting continuations.\nexport const ContinuationSchema = z.strictObject({\n tool: z.string().describe('Tool name to call for the next chunk'),\n args: z\n .strictObject({ path: z.string(), startLine: PositiveInt, endLine: PositiveInt })\n .describe('Ready-to-use arguments for the next call; pass verbatim'),\n hint: z.string().describe('One-sentence description of the data still remaining to be read'),\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\nexport const NextCursorSchema = base64urlCursor\n .optional()\n .describe('Cursor for the next page; omitted when this is the final page.');\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 { isSafeGlobSyntax } 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\nexport const isBlank = (val: string): boolean => val.trim().length === 0;\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) ? undefined : 'Invalid glob or unsafe path (absolute/.. forbidden)',\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"]}
|
package/dist/core/search.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ interface SearchResult {
|
|
|
6
6
|
/** 0-indexed column of the first occurrence on the line. */
|
|
7
7
|
column: number;
|
|
8
8
|
content: string;
|
|
9
|
-
matchCount
|
|
9
|
+
matchCount: number;
|
|
10
10
|
/** Up to `context` lines either side of the match; absent when context is 0. */
|
|
11
11
|
before?: string[];
|
|
12
12
|
after?: string[];
|
|
@@ -41,6 +41,7 @@ export declare const RE2_MAX_INPUT_BYTES: number;
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function compileRegex(pattern: string, options?: {
|
|
43
43
|
caseSensitive?: boolean;
|
|
44
|
+
multiline?: boolean;
|
|
44
45
|
}): Regex;
|
|
45
46
|
/**
|
|
46
47
|
* Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its
|
|
@@ -88,6 +89,12 @@ export interface SearchContentOptions {
|
|
|
88
89
|
skipIgnored?: boolean;
|
|
89
90
|
includeHidden?: boolean;
|
|
90
91
|
maxDepth?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Search exactly this file (absolute, already guard-validated) instead of
|
|
94
|
+
* walking `directory` with `filePattern`. Set when the caller named a file:
|
|
95
|
+
* its name must not be interpreted as a glob.
|
|
96
|
+
*/
|
|
97
|
+
explicitFile?: string;
|
|
91
98
|
/** Lines of context to carry either side of each match; 0 (default) carries none. */
|
|
92
99
|
context?: number;
|
|
93
100
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAIxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAU3C,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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"}
|
package/dist/core/search.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { stat as fsStat, readFile } from 'node:fs/promises';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
3
|
import { RE2 } from '@adguard/re2-wasm';
|
|
4
|
-
import { resolveStopReason } from "./concurrency.js";
|
|
4
|
+
import { processEntriesConcurrently, resolveStopReason } from "./concurrency.js";
|
|
5
5
|
import { globEntries } from "./glob.js";
|
|
6
|
-
import { escapeRegExp, getMaxTextFileSize, MIB } from "./util.js";
|
|
6
|
+
import { escapeRegExp, getMaxTextFileSize, MIB, PARALLEL_CONCURRENCY } from "./util.js";
|
|
7
7
|
/**
|
|
8
8
|
* A fresh, flat copy of `s`. V8 keeps a substring of a long string as a slice
|
|
9
9
|
* that references its parent, so a kept line would otherwise pin the whole
|
|
@@ -18,6 +18,10 @@ const MAX_MATCHES_PER_LINE = 100_000;
|
|
|
18
18
|
* aborts the module. 4 MiB leaves room for the copy and the pattern set.
|
|
19
19
|
*/
|
|
20
20
|
export const RE2_MAX_INPUT_BYTES = 4 * MIB;
|
|
21
|
+
const BUFFER_ANCHOR_RE = /\\[AzZ]/u;
|
|
22
|
+
/** `(?i)`, `(?-m)`, `(?s:…)`: inline flags can undo the `m` the prefilter relies on. */
|
|
23
|
+
const INLINE_FLAGS_RE = /\(\?[a-zA-Z-]+[:)]/u;
|
|
24
|
+
const CR = 13;
|
|
21
25
|
/**
|
|
22
26
|
* Compile a pattern on RE2 rather than on V8's irregexp.
|
|
23
27
|
*
|
|
@@ -40,7 +44,7 @@ export const RE2_MAX_INPUT_BYTES = 4 * MIB;
|
|
|
40
44
|
* {@link freeRegex} when it is done with it.
|
|
41
45
|
*/
|
|
42
46
|
export function compileRegex(pattern, options = {}) {
|
|
43
|
-
const flags = options.caseSensitive ? '
|
|
47
|
+
const flags = `g${options.multiline ? 'm' : ''}${options.caseSensitive ? '' : 'i'}u`;
|
|
44
48
|
try {
|
|
45
49
|
return new RE2(pattern, flags);
|
|
46
50
|
}
|
|
@@ -130,110 +134,173 @@ function findLineMatches(regex, line) {
|
|
|
130
134
|
}
|
|
131
135
|
return count > 0 ? { count, column } : undefined;
|
|
132
136
|
}
|
|
137
|
+
async function scanFile(entryPath, scanCtx) {
|
|
138
|
+
try {
|
|
139
|
+
await scanCtx.pathGuard.validateExistingPath(entryPath);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return { kind: 'inaccessible' };
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const stats = await fsStat(entryPath);
|
|
146
|
+
if (stats.size > scanCtx.maxFileSize)
|
|
147
|
+
return { kind: 'tooLarge' };
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return { kind: 'inaccessible' };
|
|
151
|
+
}
|
|
152
|
+
let buffer;
|
|
153
|
+
try {
|
|
154
|
+
buffer = await readFile(entryPath, { signal: scanCtx.signal });
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (scanCtx.signal?.aborted)
|
|
158
|
+
throw error;
|
|
159
|
+
return { kind: 'inaccessible' };
|
|
160
|
+
}
|
|
161
|
+
// A NUL byte marks a binary file, as it does for `read` and `grep -I`.
|
|
162
|
+
// Non-UTF-8 text is still searched: a lossy decode only affects the
|
|
163
|
+
// bytes a pattern could not have matched anyway.
|
|
164
|
+
if (buffer.includes(0))
|
|
165
|
+
return { kind: 'binary' };
|
|
166
|
+
const content = buffer.toString('utf-8');
|
|
167
|
+
if (canPrefilter(scanCtx.source, buffer)) {
|
|
168
|
+
scanCtx.prefilter.lastIndex = 0;
|
|
169
|
+
if (scanCtx.prefilter.exec(content) === null)
|
|
170
|
+
return { kind: 'scanned', matches: [] };
|
|
171
|
+
}
|
|
172
|
+
const lines = content.split(/\r?\n/u);
|
|
173
|
+
// A trailing newline splits into a phantom empty last element, and an
|
|
174
|
+
// empty file splits into one empty element: neither is a line the
|
|
175
|
+
// file has, for matching or for context.
|
|
176
|
+
const lineCount = content.length === 0 ? 0 : content.endsWith('\n') ? lines.length - 1 : lines.length;
|
|
177
|
+
const matches = [];
|
|
178
|
+
for (let i = 0; i < lineCount; i++) {
|
|
179
|
+
const line = lines[i];
|
|
180
|
+
if (line === undefined)
|
|
181
|
+
continue;
|
|
182
|
+
// One scan per line: the helper resets lastIndex itself, so it
|
|
183
|
+
// doubles as the "does this line match" test.
|
|
184
|
+
const found = findLineMatches(scanCtx.regex, line);
|
|
185
|
+
if (found) {
|
|
186
|
+
matches.push({
|
|
187
|
+
file: entryPath,
|
|
188
|
+
line: i + 1,
|
|
189
|
+
column: found.column,
|
|
190
|
+
content: own(line),
|
|
191
|
+
matchCount: found.count,
|
|
192
|
+
...(scanCtx.context > 0
|
|
193
|
+
? {
|
|
194
|
+
before: lines.slice(Math.max(0, i - scanCtx.context), i).map(own),
|
|
195
|
+
after: lines.slice(i + 1, Math.min(lineCount, i + 1 + scanCtx.context)).map(own),
|
|
196
|
+
}
|
|
197
|
+
: {}),
|
|
198
|
+
});
|
|
199
|
+
if (matches.length >= scanCtx.maxResults)
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { kind: 'scanned', matches };
|
|
204
|
+
}
|
|
133
205
|
export async function searchContent(directory, pattern, options, pathGuard) {
|
|
134
|
-
const
|
|
206
|
+
const source = options.isRegex ? pattern || '' : escapeRegExp(pattern || '');
|
|
207
|
+
const regex = compileRegex(source, {
|
|
208
|
+
caseSensitive: Boolean(options.caseSensitive),
|
|
209
|
+
});
|
|
210
|
+
// Whole-buffer test: `m` makes ^/$ line-relative so a per-line hit is always a
|
|
211
|
+
// buffer hit (the converse can be false — a false positive just falls through
|
|
212
|
+
// to the per-line scan). \A/\z/\Z are buffer-relative even under `m`, inline
|
|
213
|
+
// flag groups can undo `m`, and $ cannot see past a \r, so those inputs skip
|
|
214
|
+
// the prefilter (see canPrefilter).
|
|
215
|
+
const prefilter = compileRegex(source, {
|
|
135
216
|
caseSensitive: Boolean(options.caseSensitive),
|
|
217
|
+
multiline: true,
|
|
136
218
|
});
|
|
137
219
|
try {
|
|
138
220
|
const matches = [];
|
|
139
221
|
const maxResults = options.maxResults ?? 100;
|
|
140
222
|
const maxFileSize = getMaxTextFileSize();
|
|
141
223
|
const context = options.context ?? 0;
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
224
|
+
const ceiling = pathGuard.allowedRootContaining(directory);
|
|
225
|
+
const entries = options.explicitFile
|
|
226
|
+
? [{ path: options.explicitFile }]
|
|
227
|
+
: globEntries({
|
|
228
|
+
cwd: directory,
|
|
229
|
+
pattern: options.filePattern ?? '**/*',
|
|
230
|
+
// Same rule as replace_text, so a search with the same glob previews the
|
|
231
|
+
// files a replace would touch: a slash-free glob matches at any depth.
|
|
232
|
+
baseNameMatch: true,
|
|
233
|
+
includeHidden: Boolean(options.includeHidden),
|
|
234
|
+
skipIgnored: Boolean(options.skipIgnored),
|
|
235
|
+
...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),
|
|
236
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
237
|
+
...(options.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {}),
|
|
238
|
+
suppressErrors: true,
|
|
239
|
+
});
|
|
154
240
|
let filesScanned = 0;
|
|
155
241
|
let filesMatched = 0;
|
|
156
242
|
let matchingLines = 0;
|
|
157
243
|
let skippedTooLarge = 0;
|
|
158
244
|
let skippedBinary = 0;
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
245
|
+
const scanCtx = {
|
|
246
|
+
pathGuard,
|
|
247
|
+
signal: options.signal,
|
|
248
|
+
maxFileSize,
|
|
249
|
+
maxResults,
|
|
250
|
+
regex,
|
|
251
|
+
prefilter,
|
|
252
|
+
source,
|
|
253
|
+
context,
|
|
254
|
+
};
|
|
255
|
+
const perFile = new Map();
|
|
256
|
+
let dispatched = 0;
|
|
257
|
+
let collected = 0;
|
|
258
|
+
const stopped = await processEntriesConcurrently(entries, {
|
|
259
|
+
signal: options.signal,
|
|
260
|
+
concurrency: PARALLEL_CONCURRENCY,
|
|
261
|
+
shouldStop: () => collected >= maxResults,
|
|
262
|
+
onEntry: () => {
|
|
263
|
+
dispatched++;
|
|
264
|
+
},
|
|
265
|
+
onError: () => undefined,
|
|
266
|
+
runEntry: async (entryPath) => {
|
|
267
|
+
const index = dispatched - 1;
|
|
268
|
+
const scan = await scanFile(entryPath, scanCtx);
|
|
269
|
+
perFile.set(index, scan);
|
|
270
|
+
if (scan.kind === 'scanned')
|
|
271
|
+
collected += scan.matches.length;
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
let skippedInaccessible = 0;
|
|
275
|
+
for (let i = 0; i < dispatched; i++) {
|
|
276
|
+
const scan = perFile.get(i);
|
|
277
|
+
if (!scan)
|
|
174
278
|
continue;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
279
|
+
switch (scan.kind) {
|
|
280
|
+
case 'inaccessible':
|
|
281
|
+
skippedInaccessible++;
|
|
282
|
+
break;
|
|
283
|
+
case 'tooLarge':
|
|
284
|
+
skippedTooLarge++;
|
|
285
|
+
break;
|
|
286
|
+
case 'binary':
|
|
287
|
+
filesScanned++;
|
|
183
288
|
skippedBinary++;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// file has, for matching or for context.
|
|
191
|
-
const lineCount = content.length === 0 ? 0 : content.endsWith('\n') ? lines.length - 1 : lines.length;
|
|
192
|
-
let matchedFile = false;
|
|
193
|
-
for (let i = 0; i < lineCount; i++) {
|
|
194
|
-
const line = lines[i];
|
|
195
|
-
if (line === undefined)
|
|
196
|
-
continue;
|
|
197
|
-
// One scan per line: findLineMatches resets lastIndex itself, so it
|
|
198
|
-
// doubles as the "does this line match" test.
|
|
199
|
-
const found = findLineMatches(regex, line);
|
|
200
|
-
if (found) {
|
|
201
|
-
matchedFile = true;
|
|
202
|
-
matchingLines++;
|
|
203
|
-
matches.push({
|
|
204
|
-
file: entry.path,
|
|
205
|
-
line: i + 1,
|
|
206
|
-
column: found.column,
|
|
207
|
-
content: own(line),
|
|
208
|
-
matchCount: found.count,
|
|
209
|
-
...(context > 0
|
|
210
|
-
? {
|
|
211
|
-
before: lines.slice(Math.max(0, i - context), i).map(own),
|
|
212
|
-
after: lines.slice(i + 1, Math.min(lineCount, i + 1 + context)).map(own),
|
|
213
|
-
}
|
|
214
|
-
: {}),
|
|
215
|
-
});
|
|
289
|
+
break;
|
|
290
|
+
case 'scanned':
|
|
291
|
+
filesScanned++;
|
|
292
|
+
if (scan.matches.length > 0)
|
|
293
|
+
filesMatched++;
|
|
294
|
+
for (const match of scan.matches) {
|
|
216
295
|
if (matches.length >= maxResults)
|
|
217
296
|
break;
|
|
297
|
+
matches.push(match);
|
|
298
|
+
matchingLines++;
|
|
218
299
|
}
|
|
219
|
-
}
|
|
220
|
-
if (matchedFile)
|
|
221
|
-
filesMatched++;
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
// A read failure while the signal is aborted IS the abort, not an
|
|
225
|
-
// unreadable file — stop rather than spend another iteration and then
|
|
226
|
-
// report a cut-short scan as complete.
|
|
227
|
-
if (options.signal?.aborted) {
|
|
228
|
-
counters.stoppedByAbort = true;
|
|
229
300
|
break;
|
|
230
|
-
}
|
|
231
|
-
// unreadable mid-scan (deleted, permission changed): skip the file
|
|
232
301
|
}
|
|
233
302
|
}
|
|
234
|
-
|
|
235
|
-
// same iteration (see the summary type's narrowing note).
|
|
236
|
-
const stoppedReason = resolveStopReason(matches.length >= maxResults, counters.stoppedByAbort);
|
|
303
|
+
const stoppedReason = resolveStopReason(matches.length >= maxResults, stopped === 'timeout' || Boolean(options.signal?.aborted));
|
|
237
304
|
return {
|
|
238
305
|
basePath: directory,
|
|
239
306
|
matches,
|
|
@@ -242,7 +309,7 @@ export async function searchContent(directory, pattern, options, pathGuard) {
|
|
|
242
309
|
filesScanned,
|
|
243
310
|
filesMatched,
|
|
244
311
|
truncated: stoppedReason !== undefined,
|
|
245
|
-
skippedInaccessible
|
|
312
|
+
skippedInaccessible,
|
|
246
313
|
skippedTooLarge,
|
|
247
314
|
skippedBinary,
|
|
248
315
|
...(stoppedReason ? { stoppedReason } : {}),
|
|
@@ -250,38 +317,61 @@ export async function searchContent(directory, pattern, options, pathGuard) {
|
|
|
250
317
|
};
|
|
251
318
|
}
|
|
252
319
|
finally {
|
|
320
|
+
freeRegex(prefilter);
|
|
253
321
|
freeRegex(regex);
|
|
254
322
|
}
|
|
255
323
|
}
|
|
324
|
+
/** Whether one whole-buffer RE2 test is a sound "cannot match" check for this file. */
|
|
325
|
+
function canPrefilter(source, buffer) {
|
|
326
|
+
if (buffer.length > RE2_MAX_INPUT_BYTES)
|
|
327
|
+
return false;
|
|
328
|
+
if (BUFFER_ANCHOR_RE.test(source))
|
|
329
|
+
return false;
|
|
330
|
+
if (INLINE_FLAGS_RE.test(source))
|
|
331
|
+
return false;
|
|
332
|
+
if (source.includes('$') && buffer.includes(CR))
|
|
333
|
+
return false;
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
256
336
|
async function* guardedEntries(entries, pathGuard, signal, counters) {
|
|
257
337
|
// The signal carries both client cancellation and the tool's search timeout,
|
|
258
338
|
// so an abort means "return what we have, marked incomplete" rather than
|
|
259
|
-
// throw — but it must never be reported as a finished scan.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
339
|
+
// throw — but it must never be reported as a finished scan. The walk itself
|
|
340
|
+
// throws when the signal stops it before a single entry is yielded.
|
|
341
|
+
try {
|
|
342
|
+
for await (const entry of entries) {
|
|
343
|
+
if (signal?.aborted) {
|
|
344
|
+
counters.stoppedByAbort = true;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await pathGuard.validateExistingPath(entry.path);
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
counters.skippedInaccessible++;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
yield entry;
|
|
271
355
|
}
|
|
272
|
-
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (!signal?.aborted)
|
|
359
|
+
throw error;
|
|
360
|
+
counters.stoppedByAbort = true;
|
|
273
361
|
}
|
|
274
362
|
}
|
|
275
363
|
export async function searchFiles(directory, pattern, options, pathGuard) {
|
|
276
364
|
const maxResults = options.maxResults ?? 100;
|
|
365
|
+
const ceiling = pathGuard.allowedRootContaining(directory);
|
|
277
366
|
const entries = globEntries({
|
|
278
367
|
cwd: directory,
|
|
279
368
|
pattern,
|
|
280
369
|
baseNameMatch: true,
|
|
281
370
|
includeHidden: Boolean(options.includeHidden),
|
|
282
371
|
skipIgnored: Boolean(options.skipIgnored),
|
|
372
|
+
...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),
|
|
283
373
|
...(options.signal ? { signal: options.signal } : {}),
|
|
284
|
-
maxDepth: options.maxDepth
|
|
374
|
+
...(options.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {}),
|
|
285
375
|
suppressErrors: true,
|
|
286
376
|
});
|
|
287
377
|
const results = [];
|