@j0hanz/filesystem-mcp 2.1.7 → 2.2.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 (145) hide show
  1. package/README.md +8 -9
  2. package/dist/cli.d.ts +1 -2
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +29 -34
  5. package/dist/cli.js.map +1 -1
  6. package/dist/core/concurrency.d.ts +4 -7
  7. package/dist/core/concurrency.d.ts.map +1 -1
  8. package/dist/core/concurrency.js +4 -12
  9. package/dist/core/concurrency.js.map +1 -1
  10. package/dist/core/cursor.d.ts +11 -0
  11. package/dist/core/cursor.d.ts.map +1 -1
  12. package/dist/core/cursor.js +18 -20
  13. package/dist/core/cursor.js.map +1 -1
  14. package/dist/core/fmt.d.ts +2 -1
  15. package/dist/core/fmt.d.ts.map +1 -1
  16. package/dist/core/fmt.js +8 -15
  17. package/dist/core/fmt.js.map +1 -1
  18. package/dist/core/fs.d.ts +0 -14
  19. package/dist/core/fs.d.ts.map +1 -1
  20. package/dist/core/fs.js +1 -0
  21. package/dist/core/fs.js.map +1 -1
  22. package/dist/core/glob.d.ts +4 -16
  23. package/dist/core/glob.d.ts.map +1 -1
  24. package/dist/core/glob.js +16 -33
  25. package/dist/core/glob.js.map +1 -1
  26. package/dist/core/input-required.d.ts +10 -2
  27. package/dist/core/input-required.d.ts.map +1 -1
  28. package/dist/core/input-required.js +10 -0
  29. package/dist/core/input-required.js.map +1 -1
  30. package/dist/core/observability.d.ts.map +1 -1
  31. package/dist/core/observability.js +2 -1
  32. package/dist/core/observability.js.map +1 -1
  33. package/dist/core/path-completer.d.ts.map +1 -1
  34. package/dist/core/path-completer.js +4 -27
  35. package/dist/core/path-completer.js.map +1 -1
  36. package/dist/core/path-utils.d.ts +1 -1
  37. package/dist/core/path-utils.d.ts.map +1 -1
  38. package/dist/core/path-utils.js +2 -2
  39. package/dist/core/path-utils.js.map +1 -1
  40. package/dist/core/path.d.ts +7 -9
  41. package/dist/core/path.d.ts.map +1 -1
  42. package/dist/core/path.js +24 -42
  43. package/dist/core/path.js.map +1 -1
  44. package/dist/core/primitives.d.ts +7 -0
  45. package/dist/core/primitives.d.ts.map +1 -1
  46. package/dist/core/primitives.js +15 -8
  47. package/dist/core/primitives.js.map +1 -1
  48. package/dist/core/read.d.ts.map +1 -1
  49. package/dist/core/read.js +6 -23
  50. package/dist/core/read.js.map +1 -1
  51. package/dist/core/schema.d.ts.map +1 -1
  52. package/dist/core/schema.js +19 -74
  53. package/dist/core/schema.js.map +1 -1
  54. package/dist/core/search.d.ts +4 -11
  55. package/dist/core/search.d.ts.map +1 -1
  56. package/dist/core/search.js +11 -18
  57. package/dist/core/search.js.map +1 -1
  58. package/dist/core/store.d.ts.map +1 -1
  59. package/dist/core/store.js +6 -17
  60. package/dist/core/store.js.map +1 -1
  61. package/dist/core/util.d.ts.map +1 -1
  62. package/dist/core/util.js +2 -11
  63. package/dist/core/util.js.map +1 -1
  64. package/dist/index.js +15 -42
  65. package/dist/index.js.map +1 -1
  66. package/dist/instructions.js +11 -11
  67. package/dist/instructions.js.map +1 -1
  68. package/dist/prompts.d.ts.map +1 -1
  69. package/dist/prompts.js +17 -25
  70. package/dist/prompts.js.map +1 -1
  71. package/dist/resources.d.ts +4 -4
  72. package/dist/resources.d.ts.map +1 -1
  73. package/dist/resources.js +7 -17
  74. package/dist/resources.js.map +1 -1
  75. package/dist/server.d.ts +1 -12
  76. package/dist/server.d.ts.map +1 -1
  77. package/dist/server.js +15 -30
  78. package/dist/server.js.map +1 -1
  79. package/dist/tools/define.d.ts.map +1 -1
  80. package/dist/tools/define.js +39 -65
  81. package/dist/tools/define.js.map +1 -1
  82. package/dist/tools/delete.d.ts +2 -0
  83. package/dist/tools/delete.d.ts.map +1 -0
  84. package/dist/tools/{delete-file.js → delete.js} +35 -40
  85. package/dist/tools/delete.js.map +1 -0
  86. package/dist/tools/find-files.d.ts +2 -0
  87. package/dist/tools/find-files.d.ts.map +1 -0
  88. package/dist/tools/{search-files.js → find-files.js} +14 -19
  89. package/dist/tools/find-files.js.map +1 -0
  90. package/dist/tools/index.d.ts +6 -6
  91. package/dist/tools/index.d.ts.map +1 -1
  92. package/dist/tools/index.js +15 -15
  93. package/dist/tools/index.js.map +1 -1
  94. package/dist/tools/list-roots.d.ts +2 -0
  95. package/dist/tools/list-roots.d.ts.map +1 -0
  96. package/dist/tools/{roots.js → list-roots.js} +2 -2
  97. package/dist/tools/list-roots.js.map +1 -0
  98. package/dist/tools/list.d.ts.map +1 -1
  99. package/dist/tools/list.js +29 -37
  100. package/dist/tools/list.js.map +1 -1
  101. package/dist/tools/move.d.ts.map +1 -1
  102. package/dist/tools/move.js +5 -7
  103. package/dist/tools/move.js.map +1 -1
  104. package/dist/tools/progress.d.ts +1 -1
  105. package/dist/tools/progress.d.ts.map +1 -1
  106. package/dist/tools/progress.js.map +1 -1
  107. package/dist/tools/read.d.ts +1 -1
  108. package/dist/tools/read.d.ts.map +1 -1
  109. package/dist/tools/read.js +37 -43
  110. package/dist/tools/read.js.map +1 -1
  111. package/dist/tools/replace-text.d.ts +2 -0
  112. package/dist/tools/replace-text.d.ts.map +1 -0
  113. package/dist/tools/{replace-in-files.js → replace-text.js} +9 -23
  114. package/dist/tools/replace-text.js.map +1 -0
  115. package/dist/tools/search-text.d.ts +2 -0
  116. package/dist/tools/search-text.d.ts.map +1 -0
  117. package/dist/tools/{search-content.js → search-text.js} +10 -37
  118. package/dist/tools/search-text.js.map +1 -0
  119. package/dist/tools/stat.d.ts +1 -1
  120. package/dist/tools/stat.d.ts.map +1 -1
  121. package/dist/tools/stat.js +10 -38
  122. package/dist/tools/stat.js.map +1 -1
  123. package/dist/transport/http-policy.d.ts +18 -15
  124. package/dist/transport/http-policy.d.ts.map +1 -1
  125. package/dist/transport/http-policy.js +29 -39
  126. package/dist/transport/http-policy.js.map +1 -1
  127. package/dist/transport/http.d.ts.map +1 -1
  128. package/dist/transport/http.js +29 -39
  129. package/dist/transport/http.js.map +1 -1
  130. package/package.json +4 -7
  131. package/dist/tools/delete-file.d.ts +0 -2
  132. package/dist/tools/delete-file.d.ts.map +0 -1
  133. package/dist/tools/delete-file.js.map +0 -1
  134. package/dist/tools/replace-in-files.d.ts +0 -2
  135. package/dist/tools/replace-in-files.d.ts.map +0 -1
  136. package/dist/tools/replace-in-files.js.map +0 -1
  137. package/dist/tools/roots.d.ts +0 -2
  138. package/dist/tools/roots.d.ts.map +0 -1
  139. package/dist/tools/roots.js.map +0 -1
  140. package/dist/tools/search-content.d.ts +0 -2
  141. package/dist/tools/search-content.d.ts.map +0 -1
  142. package/dist/tools/search-content.js.map +0 -1
  143. package/dist/tools/search-files.d.ts +0 -2
  144. package/dist/tools/search-files.d.ts.map +0 -1
  145. package/dist/tools/search-files.js.map +0 -1
@@ -34,47 +34,28 @@ export const FileKind = z.enum(FILE_KINDS);
34
34
  const MAX_PATH_LENGTH = 4096;
35
35
  const SHELL_METACHAR_RE = /[\n\r;|`]/;
36
36
  export const isBlank = (val) => val.trim().length === 0;
37
+ function refineSafeText(label, specific) {
38
+ return (val, ctx) => {
39
+ if (val.length === 0)
40
+ return;
41
+ const issue = isBlank(val)
42
+ ? `${label} cannot be empty or whitespace-only`
43
+ : val.includes('\0')
44
+ ? `${label} cannot contain null bytes`
45
+ : (specific(val) ??
46
+ (SHELL_METACHAR_RE.test(val)
47
+ ? `${label} contains prohibited characters (newlines or shell metacharacters)`
48
+ : undefined));
49
+ if (issue !== undefined) {
50
+ ctx.addIssue({ code: 'custom', message: issue, fatal: true });
51
+ }
52
+ };
53
+ }
37
54
  const PathBase = z
38
55
  .string()
39
56
  .min(1, { message: 'Path required' })
40
57
  .max(MAX_PATH_LENGTH, { message: `Path too long (max ${MAX_PATH_LENGTH} chars)` })
41
- .superRefine((val, ctx) => {
42
- if (val.length === 0) {
43
- return;
44
- }
45
- if (val.trim().length === 0) {
46
- ctx.addIssue({
47
- code: 'custom',
48
- message: 'Path cannot be empty or whitespace-only',
49
- fatal: true,
50
- });
51
- return z.NEVER;
52
- }
53
- if (val.includes('\0')) {
54
- ctx.addIssue({
55
- code: 'custom',
56
- message: 'Path cannot contain null bytes',
57
- fatal: true,
58
- });
59
- return z.NEVER;
60
- }
61
- if (val.includes('..')) {
62
- ctx.addIssue({
63
- code: 'custom',
64
- message: 'Directory traversal sequences ("..") are forbidden',
65
- fatal: true,
66
- });
67
- return z.NEVER;
68
- }
69
- if (SHELL_METACHAR_RE.test(val)) {
70
- ctx.addIssue({
71
- code: 'custom',
72
- message: 'Path contains prohibited characters (newlines or shell metacharacters)',
73
- fatal: true,
74
- });
75
- return z.NEVER;
76
- }
77
- })
58
+ .superRefine(refineSafeText('Path', (val) => val.includes('..') ? 'Directory traversal sequences ("..") are forbidden' : undefined))
78
59
  .describe('File or directory path inside an allowed workspace root.');
79
60
  export const OptionalPath = PathBase.optional();
80
61
  export const RequiredPath = PathBase;
@@ -82,43 +63,7 @@ export const SafeGlobPattern = z
82
63
  .string()
83
64
  .min(1, { message: 'Pattern required' })
84
65
  .max(1000, { message: 'Max 1000 chars' })
85
- .superRefine((val, ctx) => {
86
- if (val.length === 0) {
87
- return;
88
- }
89
- if (val.trim().length === 0) {
90
- ctx.addIssue({
91
- code: 'custom',
92
- message: 'Pattern cannot be empty or whitespace-only',
93
- fatal: true,
94
- });
95
- return z.NEVER;
96
- }
97
- if (val.includes('\0')) {
98
- ctx.addIssue({
99
- code: 'custom',
100
- message: 'Pattern cannot contain null bytes',
101
- fatal: true,
102
- });
103
- return z.NEVER;
104
- }
105
- if (!isSafeGlobSyntax(val)) {
106
- ctx.addIssue({
107
- code: 'custom',
108
- message: 'Invalid glob or unsafe path (absolute/.. forbidden)',
109
- fatal: true,
110
- });
111
- return z.NEVER;
112
- }
113
- if (SHELL_METACHAR_RE.test(val)) {
114
- ctx.addIssue({
115
- code: 'custom',
116
- message: 'Pattern contains prohibited characters (newlines or shell metacharacters)',
117
- fatal: true,
118
- });
119
- return z.NEVER;
120
- }
121
- })
66
+ .superRefine(refineSafeText('Pattern', (val) => isSafeGlobSyntax(val) ? undefined : 'Invalid glob or unsafe path (absolute/.. forbidden)'))
122
67
  .describe('Relative glob pattern under the search root (e.g. "**/*.ts", "src/**/*.js").')
123
68
  .meta({ examples: ['**/*.ts', 'src/**/*.js', '*.{ts,tsx}'] });
124
69
  // Only the fields whose key does not already say what they hold carry a
@@ -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.js';\nimport { MIME_KINDS } from './mime.js';\nimport { ENTRY_TYPES } from './primitives.js';\nimport { MAX_SEARCH_DEPTH } from './util.js';\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 `toDraft202012` (tools/define.ts) publishes without.\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\nconst FILE_TYPES = ENTRY_TYPES;\nexport type FileType = (typeof FILE_TYPES)[number];\nexport const FileType = z.enum(FILE_TYPES);\n\nconst FILE_KINDS = MIME_KINDS;\nexport type FileKind = (typeof FILE_KINDS)[number];\nexport const FileKind = z.enum(FILE_KINDS);\n\nconst MAX_PATH_LENGTH = 4096;\n\nconst SHELL_METACHAR_RE = /[\\n\\r;|`]/;\n\nexport const isBlank = (val: string): boolean => val.trim().length === 0;\n\nconst PathBase = 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((val, ctx) => {\n if (val.length === 0) {\n return;\n }\n if (val.trim().length === 0) {\n ctx.addIssue({\n code: 'custom',\n message: 'Path cannot be empty or whitespace-only',\n fatal: true,\n });\n return z.NEVER;\n }\n if (val.includes('\\0')) {\n ctx.addIssue({\n code: 'custom',\n message: 'Path cannot contain null bytes',\n fatal: true,\n });\n return z.NEVER;\n }\n if (val.includes('..')) {\n ctx.addIssue({\n code: 'custom',\n message: 'Directory traversal sequences (\"..\") are forbidden',\n fatal: true,\n });\n return z.NEVER;\n }\n if (SHELL_METACHAR_RE.test(val)) {\n ctx.addIssue({\n code: 'custom',\n message: 'Path contains prohibited characters (newlines or shell metacharacters)',\n fatal: true,\n });\n return z.NEVER;\n }\n })\n .describe('File or directory path inside an allowed workspace root.');\n\nexport const OptionalPath = PathBase.optional();\nexport const RequiredPath = PathBase;\n\nexport const SafeGlobPattern = z\n .string()\n .min(1, { message: 'Pattern required' })\n .max(1000, { message: 'Max 1000 chars' })\n .superRefine((val, ctx) => {\n if (val.length === 0) {\n return;\n }\n if (val.trim().length === 0) {\n ctx.addIssue({\n code: 'custom',\n message: 'Pattern cannot be empty or whitespace-only',\n fatal: true,\n });\n return z.NEVER;\n }\n if (val.includes('\\0')) {\n ctx.addIssue({\n code: 'custom',\n message: 'Pattern cannot contain null bytes',\n fatal: true,\n });\n return z.NEVER;\n }\n if (!isSafeGlobSyntax(val)) {\n ctx.addIssue({\n code: 'custom',\n message: 'Invalid glob or unsafe path (absolute/.. forbidden)',\n fatal: true,\n });\n return z.NEVER;\n }\n if (SHELL_METACHAR_RE.test(val)) {\n ctx.addIssue({\n code: 'custom',\n message: 'Pattern contains prohibited characters (newlines or shell metacharacters)',\n fatal: true,\n });\n return z.NEVER;\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/** 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 includeHiddenField = (): z.ZodDefault<z.ZodBoolean> =>\n defaultFalseBoolean('Include hidden items (starting with .)');\nexport const includeIgnoredField = (): z.ZodDefault<z.ZodBoolean> =>\n defaultFalseBoolean('Include ignored items (node_modules, .git, etc).');\nexport const maxDepthField = (): z.ZodOptional<z.ZodNumber> =>\n 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.js';\nimport { MIME_KINDS } from './mime.js';\nimport { ENTRY_TYPES } from './primitives.js';\nimport { MAX_SEARCH_DEPTH } from './util.js';\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 `toDraft202012` (tools/define.ts) publishes without.\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\nconst FILE_TYPES = ENTRY_TYPES;\nexport type FileType = (typeof FILE_TYPES)[number];\nexport const FileType = z.enum(FILE_TYPES);\n\nconst FILE_KINDS = MIME_KINDS;\nexport type FileKind = (typeof FILE_KINDS)[number];\nexport const FileKind = z.enum(FILE_KINDS);\n\nconst MAX_PATH_LENGTH = 4096;\n\nconst SHELL_METACHAR_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 (SHELL_METACHAR_RE.test(val)\n ? `${label} contains prohibited characters (newlines or shell metacharacters)`\n : undefined));\n if (issue !== undefined) {\n ctx.addIssue({ code: 'custom', message: issue, fatal: true });\n }\n };\n}\n\nconst PathBase = 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 val.includes('..') ? 'Directory traversal sequences (\"..\") are forbidden' : undefined,\n ),\n )\n .describe('File or directory path inside an allowed workspace root.');\n\nexport const OptionalPath = PathBase.optional();\nexport const RequiredPath = PathBase;\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/** 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 includeHiddenField = (): z.ZodDefault<z.ZodBoolean> =>\n defaultFalseBoolean('Include hidden items (starting with .)');\nexport const includeIgnoredField = (): z.ZodDefault<z.ZodBoolean> =>\n defaultFalseBoolean('Include ignored items (node_modules, .git, etc).');\nexport const maxDepthField = (): z.ZodOptional<z.ZodNumber> =>\n 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"]}
@@ -77,11 +77,9 @@ export interface SearchContentOptions {
77
77
  isRegex?: boolean;
78
78
  maxResults?: number;
79
79
  filePattern?: string;
80
- excludePatterns?: string[];
81
- respectGitignore?: boolean;
80
+ skipIgnored?: boolean;
82
81
  includeHidden?: boolean;
83
82
  maxDepth?: number;
84
- maxFileSize?: number;
85
83
  signal?: AbortSignal;
86
84
  }
87
85
  export interface SearchContentOutcome {
@@ -109,17 +107,12 @@ export interface SearchContentOutcome {
109
107
  stoppedReason?: 'maxResults' | 'timeout';
110
108
  };
111
109
  }
112
- /**
113
- * Owns the compiled pattern's lifetime. Pass `precompiled` to skip compiling
114
- * here — the caller compiled the same pattern with the same flags and keeps
115
- * owning the {@link freeRegex} call in that case.
116
- */
117
- export declare function searchContent(directory: string, pattern: string, options: SearchContentOptions, pathGuard: PathGuard, precompiled?: Regex): Promise<SearchContentOutcome>;
118
- export declare function searchFiles(directory: string, pattern: string, excludePatterns: string[], options: {
110
+ export declare function searchContent(directory: string, pattern: string, options: SearchContentOptions, pathGuard: PathGuard): Promise<SearchContentOutcome>;
111
+ export declare function searchFiles(directory: string, pattern: string, options: {
119
112
  maxResults?: number;
120
113
  includeHidden?: boolean;
121
114
  sortBy?: 'name' | 'path';
122
- respectGitignore?: boolean;
115
+ skipIgnored?: boolean;
123
116
  maxDepth?: number;
124
117
  signal?: AbortSignal;
125
118
  }, pathGuard: PathGuard): Promise<{
@@ -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;AAI3C,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,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,KAAK,GAAG,GAAG,CAAC;AACxB,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,KAAK,CAWtF;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,IAAI,CAOxD;AAED,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,MAAM,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC/B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;CACb;AAcD;;;;;;;;;;;;;;;GAeG;AACH,wBAAiB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CA2BzF;AAoBD,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,OAAO,EAAE;QACP;;;;WAIG;QACH,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,4DAA4D;QAC5D,mBAAmB,EAAE,MAAM,CAAC;QAC5B,4DAA4D;QAC5D,eAAe,EAAE,MAAM,CAAC;QACxB;;;;WAIG;QACH,aAAa,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;KAC1C,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,SAAS,EACpB,WAAW,CAAC,EAAE,KAAK,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAwG/B;AA0BD,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,EAAE,EACzB,OAAO,EAAE;IACP,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,EACD,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5B,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,OAAO,CAAC;QACnB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,4EAA4E;QAC5E,aAAa,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;KAC1C,CAAC;CACH,CAAC,CA+CD"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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"}
@@ -126,26 +126,20 @@ function findLineMatches(regex, line) {
126
126
  }
127
127
  return count > 0 ? { count, column } : undefined;
128
128
  }
129
- /**
130
- * Owns the compiled pattern's lifetime. Pass `precompiled` to skip compiling
131
- * here — the caller compiled the same pattern with the same flags and keeps
132
- * owning the {@link freeRegex} call in that case.
133
- */
134
- export async function searchContent(directory, pattern, options, pathGuard, precompiled) {
135
- const regex = precompiled ??
136
- compileRegex(options.isRegex ? pattern || '' : escapeRegexLiteral(pattern || ''), {
137
- caseSensitive: Boolean(options.caseSensitive),
138
- });
129
+ export async function searchContent(directory, pattern, options, pathGuard) {
130
+ const regex = compileRegex(options.isRegex ? pattern || '' : escapeRegexLiteral(pattern || ''), {
131
+ caseSensitive: Boolean(options.caseSensitive),
132
+ });
139
133
  try {
140
134
  const matches = [];
141
135
  const maxResults = options.maxResults ?? 100;
142
- const maxFileSize = options.maxFileSize ?? getMaxTextFileSize();
136
+ const maxFileSize = getMaxTextFileSize();
143
137
  const entries = globEntries({
144
138
  cwd: directory,
145
139
  pattern: options.filePattern ?? '**/*',
146
- excludePatterns: options.excludePatterns ?? [],
147
140
  includeHidden: Boolean(options.includeHidden),
148
- respectGitignore: Boolean(options.respectGitignore),
141
+ skipIgnored: Boolean(options.skipIgnored),
142
+ ...(options.signal ? { signal: options.signal } : {}),
149
143
  maxDepth: options.maxDepth ?? 100,
150
144
  suppressErrors: true,
151
145
  });
@@ -233,8 +227,7 @@ export async function searchContent(directory, pattern, options, pathGuard, prec
233
227
  };
234
228
  }
235
229
  finally {
236
- if (precompiled === undefined)
237
- freeRegex(regex);
230
+ freeRegex(regex);
238
231
  }
239
232
  }
240
233
  async function* guardedEntries(entries, pathGuard, signal, counters) {
@@ -256,14 +249,14 @@ async function* guardedEntries(entries, pathGuard, signal, counters) {
256
249
  yield entry;
257
250
  }
258
251
  }
259
- export async function searchFiles(directory, pattern, excludePatterns, options, pathGuard) {
252
+ export async function searchFiles(directory, pattern, options, pathGuard) {
260
253
  const maxResults = options.maxResults ?? 100;
261
254
  const entries = globEntries({
262
255
  cwd: directory,
263
256
  pattern,
264
- excludePatterns,
265
257
  includeHidden: Boolean(options.includeHidden),
266
- respectGitignore: Boolean(options.respectGitignore),
258
+ skipIgnored: Boolean(options.skipIgnored),
259
+ ...(options.signal ? { signal: options.signal } : {}),
267
260
  maxDepth: options.maxDepth ?? 100,
268
261
  suppressErrors: true,
269
262
  });
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAkB,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAgB/C,+EAA+E;AAC/E,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,UAA+B,EAAE;IAC7E,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;YAAE,MAAM,KAAK,CAAC;QACjD,MAAM,IAAI,WAAW,CACnB,GAAG,KAAK,CAAC,OAAO,iEAAiE,EACjF,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,KAAwB;IAChD,MAAM,MAAM,GAAI,KAAsE,EAAE,OAAO,CAAC;IAChG,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAeD,qFAAqF;AACrF,SAAS,eAAe,CAAC,IAAY;IACnC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC;YAC7F,CAAC,EAAE,CAAC;QACN,CAAC;QACD,MAAM,EAAE,CAAC;IACX,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,SAAS,CAAC,CAAC,WAAW,CAAC,KAAY,EAAE,IAAY;IACrD,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;IACpB,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,KAA0B,CAAC;IAC/B,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,OAAO,QAAQ,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACzD,MAAM,MAAM,GACV,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM;gBAChD,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;YACvD,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,QAAQ,EAAE,CAAC;QACb,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,SAAS,CAAC;QACxB,MAAM;YACJ,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YACtB,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,KAAK;YACL,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM;SAC5B,CAAC;QACF,4EAA4E;QAC5E,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CACtB,KAAY,EACZ,IAAY;IAEZ,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,MAAM,KAAK,CAAC,CAAC;YAAE,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;QACxC,KAAK,EAAE,CAAC;QACR,IAAI,KAAK,IAAI,oBAAoB;YAAE,MAAM;IAC3C,CAAC;IACD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACnD,CAAC;AAyCD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,OAAe,EACf,OAA6B,EAC7B,SAAoB,EACpB,WAAmB;IAEnB,MAAM,KAAK,GACT,WAAW;QACX,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE;YAChF,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;SAC9C,CAAC,CAAC;IACL,IAAI,CAAC;QACH,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;QAC7C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,EAAE,CAAC;QAEhE,MAAM,OAAO,GAAG,WAAW,CAAC;YAC1B,GAAG,EAAE,SAAS;YACd,OAAO,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM;YACtC,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,EAAE;YAC9C,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;YAC7C,gBAAgB,EAAE,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;YACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG;YACjC,cAAc,EAAE,IAAI;SACrB,CAAC,CAAC;QAEH,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,MAAM,QAAQ,GAAG,EAAE,mBAAmB,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QAEnE,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;YACvF,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;gBAAE,MAAM;YAExC,2EAA2E;YAC3E,0EAA0E;YAC1E,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;oBAC7B,eAAe,EAAE,CAAC;oBAClB,SAAS;gBACX,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,CAAC,mBAAmB,EAAE,CAAC;gBAC/B,SAAS;YACX,CAAC;YAED,YAAY,EAAE,CAAC;YAEf,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC1F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAClC,IAAI,WAAW,GAAG,KAAK,CAAC;gBACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACtB,IAAI,IAAI,KAAK,SAAS;wBAAE,SAAS;oBACjC,oEAAoE;oBACpE,8CAA8C;oBAC9C,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBAC3C,IAAI,KAAK,EAAE,CAAC;wBACV,WAAW,GAAG,IAAI,CAAC;wBACnB,aAAa,EAAE,CAAC;wBAChB,OAAO,CAAC,IAAI,CAAC;4BACX,IAAI,EAAE,KAAK,CAAC,IAAI;4BAChB,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,MAAM,EAAE,KAAK,CAAC,MAAM;4BACpB,OAAO,EAAE,IAAI;4BACb,UAAU,EAAE,KAAK,CAAC,KAAK;yBACxB,CAAC,CAAC;wBACH,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;4BAAE,MAAM;oBAC1C,CAAC;gBACH,CAAC;gBACD,IAAI,WAAW;oBAAE,YAAY,EAAE,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,kEAAkE;gBAClE,sEAAsE;gBACtE,uCAAuC;gBACvC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;oBAC5B,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC;oBAC/B,MAAM;gBACR,CAAC;gBACD,yCAAyC;YAC3C,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;YAAE,OAAO,CAAC,aAAa,EAAE,CAAC;QAC1D,IAAI,QAAQ,CAAC,cAAc;YAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;QAChD,qEAAqE;QACrE,oEAAoE;QACpE,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAA0C,CAAC;QAEhF,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,OAAO;YACP,OAAO,EAAE;gBACP,aAAa;gBACb,YAAY;gBACZ,YAAY;gBACZ,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;gBACjD,eAAe;gBACf,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5C;SACF,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,WAAW,KAAK,SAAS;YAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,cAAc,CAC5B,OAAiC,EACjC,SAAoB,EACpB,MAA+B,EAC/B,QAAkE;IAElE,6EAA6E;IAC7E,yEAAyE;IACzE,4DAA4D;IAC5D,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,mBAAmB,EAAE,CAAC;YAC/B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAAiB,EACjB,OAAe,EACf,eAAyB,EACzB,OAOC,EACD,SAAoB;IAapB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;IAC7C,MAAM,OAAO,GAAG,WAAW,CAAC;QAC1B,GAAG,EAAE,SAAS;QACd,OAAO;QACP,eAAe;QACf,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAC7C,gBAAgB,EAAE,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG;QACjC,cAAc,EAAE,IAAI;KACrB,CAAC,CAAC;IACH,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,MAAM,QAAQ,GAAG,EAAE,mBAAmB,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IAEnE,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QACvF,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;QACxC,YAAY,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,yEAAyE;IACzE,mEAAmE;IACnE,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;QAAE,OAAO,CAAC,aAAa,EAAE,CAAC;IAC1D,IAAI,QAAQ,CAAC,cAAc;QAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;IAChD,qEAAqE;IACrE,oEAAoE;IACpE,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAA0C,CAAC;IAEhF,OAAO;QACL,QAAQ,EAAE,SAAS;QACnB,OAAO;QACP,OAAO,EAAE;YACP,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,YAAY;YACZ,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;YACjD,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { StopReasonTracker } from './concurrency.js';\nimport { globEntries, type GlobEntry } from './glob.js';\nimport type { PathGuard } from './path.js';\nimport { escapeRegexLiteral } from './primitives.js';\nimport { getMaxTextFileSize } from './util.js';\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n}\n\nexport type Regex = RE2;\nexport interface RegexCompileOptions {\n caseSensitive?: boolean;\n}\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: RegexCompileOptions = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nfunction codePointLength(text: string): number {\n let length = 0;\n for (let i = 0; i < text.length; i++) {\n if ((text.charCodeAt(i) & 0xfc00) === 0xd800 && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n i++;\n }\n length++;\n }\n return length;\n}\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n let count = 0;\n let column = -1;\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n excludePatterns?: string[];\n respectGitignore?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n maxFileSize?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both call\n * only `hitMaxResults`/`hitAbort` (see concurrency.ts) — never\n * `hitMaxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\n/**\n * Owns the compiled pattern's lifetime. Pass `precompiled` to skip compiling\n * here — the caller compiled the same pattern with the same flags and keeps\n * owning the {@link freeRegex} call in that case.\n */\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n precompiled?: Regex,\n): Promise<SearchContentOutcome> {\n const regex =\n precompiled ??\n compileRegex(options.isRegex ? pattern || '' : escapeRegexLiteral(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = options.maxFileSize ?? getMaxTextFileSize();\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n excludePatterns: options.excludePatterns ?? [],\n includeHidden: Boolean(options.includeHidden),\n respectGitignore: Boolean(options.respectGitignore),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const content = await readFile(entry.path, { encoding: 'utf-8', signal: options.signal });\n const lines = content.split('\\n');\n let matchedFile = false;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: line,\n matchCount: found.count,\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // ignore read errors (e.g. binary files)\n }\n }\n\n const tracker = new StopReasonTracker();\n if (matches.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n if (precompiled === undefined) freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n excludePatterns: string[],\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n respectGitignore?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never hit `hitMaxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n excludePatterns,\n includeHidden: Boolean(options.includeHidden),\n respectGitignore: Boolean(options.respectGitignore),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n const tracker = new StopReasonTracker();\n if (results.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { StopReasonTracker } from './concurrency.js';\nimport { globEntries, type GlobEntry } from './glob.js';\nimport type { PathGuard } from './path.js';\nimport { escapeRegexLiteral } from './primitives.js';\nimport { getMaxTextFileSize } from './util.js';\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n}\n\nexport type Regex = RE2;\nexport interface RegexCompileOptions {\n caseSensitive?: boolean;\n}\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: RegexCompileOptions = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nfunction codePointLength(text: string): number {\n let length = 0;\n for (let i = 0; i < text.length; i++) {\n if ((text.charCodeAt(i) & 0xfc00) === 0xd800 && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n i++;\n }\n length++;\n }\n return length;\n}\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n let count = 0;\n let column = -1;\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n skipIgnored?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both call\n * only `hitMaxResults`/`hitAbort` (see concurrency.ts) — never\n * `hitMaxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n): Promise<SearchContentOutcome> {\n const regex = compileRegex(options.isRegex ? pattern || '' : escapeRegexLiteral(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = getMaxTextFileSize();\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const content = await readFile(entry.path, { encoding: 'utf-8', signal: options.signal });\n const lines = content.split('\\n');\n let matchedFile = false;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: line,\n matchCount: found.count,\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // ignore read errors (e.g. binary files)\n }\n }\n\n const tracker = new StopReasonTracker();\n if (matches.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n skipIgnored?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never hit `hitMaxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n const tracker = new StopReasonTracker();\n if (results.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAQjE,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,WAAW,CAAK;IAExB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;gBAE7C,aAAa,CAAC,EAAE,MAAM,IAAI;IAItC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,aAAa;IAerB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAuBpC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa;IAoBjF;;;;;;;OAOG;IACH,IAAI,IAAI,MAAM,EAAE;CAIjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EAIZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,OAAO,EAAE,GAClD,kBAAkB,CAuBpB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAOjE,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,WAAW,CAAK;IAExB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;gBAE7C,aAAa,CAAC,EAAE,MAAM,IAAI;IAItC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,aAAa;IASrB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAuBpC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa;IAoBjF;;;;;;;OAOG;IACH,IAAI,IAAI,MAAM,EAAE;CAIjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EAIZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,OAAO,EAAE,GAClD,kBAAkB,CAuBpB"}
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { ErrorCode, FsError } from './errors.js';
3
- import { Logger } from './observability.js';
4
3
  import { MIB } from './util.js';
5
4
  const MAX_ENTRIES = 64;
6
5
  const MAX_TOTAL_BYTES = 25 * MIB;
@@ -48,26 +47,16 @@ export class ResourceStore {
48
47
  this.removeEntry(first.value.uri);
49
48
  }
50
49
  pruneExpiredEntries(now = Date.now()) {
51
- const toRemove = [];
50
+ const before = this.byUri.size;
52
51
  for (const entry of this.byUri.values()) {
53
- if (isExpired(entry, now)) {
54
- toRemove.push(entry.uri);
55
- }
52
+ if (isExpired(entry, now))
53
+ this.removeEntry(entry.uri);
56
54
  }
57
- for (const uri of toRemove) {
58
- this.removeEntry(uri);
59
- }
60
- return toRemove.length > 0;
55
+ return this.byUri.size !== before;
61
56
  }
62
57
  enforceLimits() {
63
- while (this.byUri.size > MAX_ENTRIES) {
64
- this.evictOldest();
65
- }
66
- while (this._totalBytes > MAX_TOTAL_BYTES) {
67
- if (this.byUri.size === 0) {
68
- Logger.error(`[resource-store] enforceLimits invariant violation: totalBytes=${this._totalBytes} but entryCount=0`);
69
- break;
70
- }
58
+ while (this.byUri.size > 0 &&
59
+ (this.byUri.size > MAX_ENTRIES || this._totalBytes > MAX_TOTAL_BYTES)) {
71
60
  this.evictOldest();
72
61
  }
73
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.js';\nimport { Logger } from './observability.js';\nimport { MIB } from './util.js';\n\ninterface ResourceEntry {\n uri: string;\n name: string;\n mimeType: string;\n size: number;\n expiresAt: string;\n text: string;\n}\n\nconst MAX_ENTRIES = 64;\nconst MAX_TOTAL_BYTES = 25 * MIB;\nconst MAX_ENTRY_BYTES = 10 * MIB;\nconst ENTRY_TTL_MS = 60 * 1000;\n\nfunction isExpired(entry: ResourceEntry, now = Date.now()): boolean {\n const expiresAt = Date.parse(entry.expiresAt);\n if (!Number.isFinite(expiresAt)) {\n return true;\n }\n return expiresAt <= now;\n}\n\nexport class ResourceStore {\n private readonly byUri = new Map<string, ResourceEntry>();\n private _totalBytes = 0;\n\n private readonly onListChanged: (() => void) | undefined;\n\n constructor(onListChanged?: () => void) {\n this.onListChanged = onListChanged;\n }\n\n private emitListChanged(changed: boolean): void {\n if (changed) this.onListChanged?.();\n }\n\n private bumpLru(uri: string, entry: ResourceEntry): void {\n if (!this.byUri.has(uri)) {\n return;\n }\n this.byUri.delete(uri);\n this.byUri.set(uri, entry);\n }\n\n private removeEntry(uri: string): ResourceEntry | undefined {\n const existing = this.byUri.get(uri);\n if (!existing) {\n return undefined;\n }\n this._totalBytes -= existing.size;\n this.byUri.delete(uri);\n return existing;\n }\n\n private evictOldest(): void {\n const first = this.byUri.values().next();\n if (first.done) {\n return;\n }\n this.removeEntry(first.value.uri);\n }\n\n private pruneExpiredEntries(now = Date.now()): boolean {\n const toRemove: string[] = [];\n for (const entry of this.byUri.values()) {\n if (isExpired(entry, now)) {\n toRemove.push(entry.uri);\n }\n }\n for (const uri of toRemove) {\n this.removeEntry(uri);\n }\n return toRemove.length > 0;\n }\n\n private enforceLimits(): void {\n while (this.byUri.size > MAX_ENTRIES) {\n this.evictOldest();\n }\n while (this._totalBytes > MAX_TOTAL_BYTES) {\n if (this.byUri.size === 0) {\n Logger.error(\n `[resource-store] enforceLimits invariant violation: totalBytes=${this._totalBytes} but entryCount=0`,\n );\n break;\n }\n this.evictOldest();\n }\n }\n\n getEntry(uri: string): ResourceEntry {\n const existing = this.byUri.get(uri);\n\n if (!existing) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource not found: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n if (isExpired(existing)) {\n this.removeEntry(uri);\n this.emitListChanged(true);\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource expired: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n this.bumpLru(uri, existing);\n return existing;\n }\n\n private checkBeforePut(entryBytes: number): void {\n const changed = this.pruneExpiredEntries();\n if (entryBytes > MAX_ENTRY_BYTES) {\n this.emitListChanged(changed);\n throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);\n }\n }\n\n putText(params: { name: string; mimeType?: string; text: string }): ResourceEntry {\n const entryBytes = Buffer.byteLength(params.text, 'utf8');\n this.checkBeforePut(entryBytes);\n\n const storedAt = new Date();\n const entry: ResourceEntry = {\n uri: `filesystem-mcp://result/${randomUUID()}`,\n name: params.name,\n mimeType: params.mimeType ?? 'text/plain',\n size: entryBytes,\n expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),\n text: params.text,\n };\n this.byUri.set(entry.uri, entry);\n this._totalBytes += entryBytes;\n this.enforceLimits();\n this.emitListChanged(true);\n return entry;\n }\n\n /**\n * Returns live URIs currently in the store.\n * Side effect: prunes expired entries before returning.\n *\n * Deliberately silent: this is the read path `resources/list` runs through,\n * and the caller is already receiving the post-prune list. Emitting here\n * would tell the client its list changed while handing it that same list.\n */\n keys(): string[] {\n this.pruneExpiredEntries();\n return Array.from(this.byUri.keys());\n }\n}\n\nexport interface JsonResourceResult {\n entry: {\n uri: string;\n size: number;\n mimeType: string;\n expiresAt: string;\n };\n link: ContentBlock;\n}\n\n/**\n * Owner of the externalize-a-payload rule: a tool whose inline response is\n * truncated publishes the full value to the resource store as pretty-printed\n * JSON and hands back the URI to reach it by, plus the link block that offers\n * it to the user.\n */\nexport function putJsonResource(\n store: ResourceStore,\n name: string,\n // `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:\n // JSON.stringify returns undefined for undefined and for functions, and `null`\n // is excluded so non-string payloads cannot enter the store's text entry.\n value: Record<string, unknown> | readonly unknown[],\n): JsonResourceResult {\n const entry = store.putText({\n name,\n mimeType: 'application/json',\n text: JSON.stringify(value, null, 2),\n });\n\n return {\n entry: {\n uri: entry.uri,\n size: entry.size,\n mimeType: entry.mimeType,\n expiresAt: entry.expiresAt,\n },\n link: {\n type: 'resource_link',\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n annotations: { audience: ['user'] },\n },\n };\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.js';\nimport { MIB } from './util.js';\n\ninterface ResourceEntry {\n uri: string;\n name: string;\n mimeType: string;\n size: number;\n expiresAt: string;\n text: string;\n}\n\nconst MAX_ENTRIES = 64;\nconst MAX_TOTAL_BYTES = 25 * MIB;\nconst MAX_ENTRY_BYTES = 10 * MIB;\nconst ENTRY_TTL_MS = 60 * 1000;\n\nfunction isExpired(entry: ResourceEntry, now = Date.now()): boolean {\n const expiresAt = Date.parse(entry.expiresAt);\n if (!Number.isFinite(expiresAt)) {\n return true;\n }\n return expiresAt <= now;\n}\n\nexport class ResourceStore {\n private readonly byUri = new Map<string, ResourceEntry>();\n private _totalBytes = 0;\n\n private readonly onListChanged: (() => void) | undefined;\n\n constructor(onListChanged?: () => void) {\n this.onListChanged = onListChanged;\n }\n\n private emitListChanged(changed: boolean): void {\n if (changed) this.onListChanged?.();\n }\n\n private bumpLru(uri: string, entry: ResourceEntry): void {\n if (!this.byUri.has(uri)) {\n return;\n }\n this.byUri.delete(uri);\n this.byUri.set(uri, entry);\n }\n\n private removeEntry(uri: string): ResourceEntry | undefined {\n const existing = this.byUri.get(uri);\n if (!existing) {\n return undefined;\n }\n this._totalBytes -= existing.size;\n this.byUri.delete(uri);\n return existing;\n }\n\n private evictOldest(): void {\n const first = this.byUri.values().next();\n if (first.done) {\n return;\n }\n this.removeEntry(first.value.uri);\n }\n\n private pruneExpiredEntries(now = Date.now()): boolean {\n const before = this.byUri.size;\n for (const entry of this.byUri.values()) {\n if (isExpired(entry, now)) this.removeEntry(entry.uri);\n }\n return this.byUri.size !== before;\n }\n\n private enforceLimits(): void {\n while (\n this.byUri.size > 0 &&\n (this.byUri.size > MAX_ENTRIES || this._totalBytes > MAX_TOTAL_BYTES)\n ) {\n this.evictOldest();\n }\n }\n\n getEntry(uri: string): ResourceEntry {\n const existing = this.byUri.get(uri);\n\n if (!existing) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource not found: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n if (isExpired(existing)) {\n this.removeEntry(uri);\n this.emitListChanged(true);\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource expired: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n this.bumpLru(uri, existing);\n return existing;\n }\n\n private checkBeforePut(entryBytes: number): void {\n const changed = this.pruneExpiredEntries();\n if (entryBytes > MAX_ENTRY_BYTES) {\n this.emitListChanged(changed);\n throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);\n }\n }\n\n putText(params: { name: string; mimeType?: string; text: string }): ResourceEntry {\n const entryBytes = Buffer.byteLength(params.text, 'utf8');\n this.checkBeforePut(entryBytes);\n\n const storedAt = new Date();\n const entry: ResourceEntry = {\n uri: `filesystem-mcp://result/${randomUUID()}`,\n name: params.name,\n mimeType: params.mimeType ?? 'text/plain',\n size: entryBytes,\n expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),\n text: params.text,\n };\n this.byUri.set(entry.uri, entry);\n this._totalBytes += entryBytes;\n this.enforceLimits();\n this.emitListChanged(true);\n return entry;\n }\n\n /**\n * Returns live URIs currently in the store.\n * Side effect: prunes expired entries before returning.\n *\n * Deliberately silent: this is the read path `resources/list` runs through,\n * and the caller is already receiving the post-prune list. Emitting here\n * would tell the client its list changed while handing it that same list.\n */\n keys(): string[] {\n this.pruneExpiredEntries();\n return Array.from(this.byUri.keys());\n }\n}\n\nexport interface JsonResourceResult {\n entry: {\n uri: string;\n size: number;\n mimeType: string;\n expiresAt: string;\n };\n link: ContentBlock;\n}\n\n/**\n * Owner of the externalize-a-payload rule: a tool whose inline response is\n * truncated publishes the full value to the resource store as pretty-printed\n * JSON and hands back the URI to reach it by, plus the link block that offers\n * it to the user.\n */\nexport function putJsonResource(\n store: ResourceStore,\n name: string,\n // `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:\n // JSON.stringify returns undefined for undefined and for functions, and `null`\n // is excluded so non-string payloads cannot enter the store's text entry.\n value: Record<string, unknown> | readonly unknown[],\n): JsonResourceResult {\n const entry = store.putText({\n name,\n mimeType: 'application/json',\n text: JSON.stringify(value, null, 2),\n });\n\n return {\n entry: {\n uri: entry.uri,\n size: entry.size,\n mimeType: entry.mimeType,\n expiresAt: entry.expiresAt,\n },\n link: {\n type: 'resource_link',\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n annotations: { audience: ['user'] },\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,GAAG,QAAa,CAAC;AAoB9B,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKhE;AAED,eAAO,MAAM,oBAAoB,QAAoD,CAAC;AAEtF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,eAAO,MAAM,yBAAyB,QAAwD,CAAC;AAE/F,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AACxC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,GAAG,QAAa,CAAC;AAE9B,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKhE;AAED,eAAO,MAAM,oBAAoB,QAAoD,CAAC;AAEtF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,eAAO,MAAM,yBAAyB,QAAwD,CAAC;AAE/F,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AACxC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
package/dist/core/util.js CHANGED
@@ -1,17 +1,8 @@
1
1
  import { availableParallelism } from 'node:os';
2
2
  import { cli } from './config.js';
3
- import { Logger } from './observability.js';
3
+ import { warnInvalidSetting } from './primitives.js';
4
4
  const KIB = 1024;
5
5
  export const MIB = 1024 * KIB;
6
- const loggedWarns = new Set();
7
- function logInvalidEnvValue(envVar, value, expected, defaultValue) {
8
- const key = `${envVar}:${value}:${expected}`;
9
- if (loggedWarns.has(key)) {
10
- return;
11
- }
12
- loggedWarns.add(key);
13
- Logger.warn(`Invalid ${envVar} value: ${value} (must be ${expected}). Using default: ${String(defaultValue)}`);
14
- }
15
6
  export function parseEnvInt(envVar, defaultValue, min, max) {
16
7
  return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);
17
8
  }
@@ -27,7 +18,7 @@ function parseIntSetting(name, value, defaultValue, min, max) {
27
18
  !Number.isInteger(parsed) ||
28
19
  parsed < min ||
29
20
  parsed > max) {
30
- logInvalidEnvValue(name, value, `${String(min)}-${String(max)}`, defaultValue);
21
+ warnInvalidSetting(name, value, `${String(min)}-${String(max)}`, defaultValue);
31
22
  return defaultValue;
32
23
  }
33
24
  return parsed;
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"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","sourcesContent":["import { availableParallelism } from 'node:os';\n\nimport { cli } from './config.js';\nimport { Logger } from './observability.js';\n\nconst KIB = 1024;\nexport const MIB = 1024 * KIB;\n\nconst loggedWarns = new Set<string>();\n\nfunction logInvalidEnvValue(\n envVar: string,\n value: string,\n expected: string,\n defaultValue: number | boolean,\n): void {\n const key = `${envVar}:${value}:${expected}`;\n if (loggedWarns.has(key)) {\n return;\n }\n loggedWarns.add(key);\n Logger.warn(\n `Invalid ${envVar} value: ${value} (must be ${expected}). Using default: ${String(defaultValue)}`,\n );\n}\n\nexport function parseEnvInt(\n envVar: string,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);\n}\n\n/** Validate a raw integer setting (env var or CLI override) with a logged fallback. */\nfunction parseIntSetting(\n name: string,\n value: string | undefined,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n if (!value) {\n return defaultValue;\n }\n\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (\n trimmed === '' ||\n Number.isNaN(parsed) ||\n !Number.isInteger(parsed) ||\n parsed < min ||\n parsed > max\n ) {\n logInvalidEnvValue(name, value, `${String(min)}-${String(max)}`, defaultValue);\n return defaultValue;\n }\n return parsed;\n}\n\n/**\n * Split a comma-separated config value into trimmed, non-empty entries. Empty\n * entries are dropped so \",\" or \" \" reads as unset rather than as a list of\n * blanks. Every comma-separated env var this server accepts parses through\n * here, so they all agree on what \"configured\" means.\n */\nexport function splitCsvList(value: string | undefined): string[] {\n return (value ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nexport const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);\n\nexport const ROOTS_TIMEOUT_MS = 5000;\n\nexport function getMaxTextFileSize(): number {\n return parseIntSetting(\n 'FS_MAX_FILE_SIZE',\n cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'],\n 10 * MIB,\n MIB,\n 100 * MIB,\n );\n}\n\nexport function getDefaultReadManyMaxTotalSize(): number {\n return parseEnvInt('FS_MAX_READ_MANY_BYTES', 512 * KIB, 10 * KIB, 100 * MIB);\n}\n\n/** Default line chunk size for read continuation when no explicit range was given. */\nexport const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;\n\nexport const DEFAULT_SEARCH_TIMEOUT_MS = parseEnvInt('FS_SEARCH_TIMEOUT_MS', 5000, 100, 60000);\n\nexport const MAX_TREE_DEPTH = 50;\nexport const DEFAULT_TREE_ENTRIES = 1000;\n\nexport const MAX_LIST_ENTRIES = 20000;\n\nexport const MAX_SEARCH_RESULTS = 10000;\nexport const DEFAULT_SEARCH_RESULTS = 100;\nexport const MAX_SEARCH_DEPTH = 100;\n\nexport const DEFAULT_SEARCH_CONTENT_RESULTS = 500;\n"]}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"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","sourcesContent":["import { availableParallelism } from 'node:os';\n\nimport { cli } from './config.js';\nimport { warnInvalidSetting } from './primitives.js';\n\nconst KIB = 1024;\nexport const MIB = 1024 * KIB;\n\nexport function parseEnvInt(\n envVar: string,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);\n}\n\n/** Validate a raw integer setting (env var or CLI override) with a logged fallback. */\nfunction parseIntSetting(\n name: string,\n value: string | undefined,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n if (!value) {\n return defaultValue;\n }\n\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (\n trimmed === '' ||\n Number.isNaN(parsed) ||\n !Number.isInteger(parsed) ||\n parsed < min ||\n parsed > max\n ) {\n warnInvalidSetting(name, value, `${String(min)}-${String(max)}`, defaultValue);\n return defaultValue;\n }\n return parsed;\n}\n\n/**\n * Split a comma-separated config value into trimmed, non-empty entries. Empty\n * entries are dropped so \",\" or \" \" reads as unset rather than as a list of\n * blanks. Every comma-separated env var this server accepts parses through\n * here, so they all agree on what \"configured\" means.\n */\nexport function splitCsvList(value: string | undefined): string[] {\n return (value ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nexport const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);\n\nexport const ROOTS_TIMEOUT_MS = 5000;\n\nexport function getMaxTextFileSize(): number {\n return parseIntSetting(\n 'FS_MAX_FILE_SIZE',\n cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'],\n 10 * MIB,\n MIB,\n 100 * MIB,\n );\n}\n\nexport function getDefaultReadManyMaxTotalSize(): number {\n return parseEnvInt('FS_MAX_READ_MANY_BYTES', 512 * KIB, 10 * KIB, 100 * MIB);\n}\n\n/** Default line chunk size for read continuation when no explicit range was given. */\nexport const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;\n\nexport const DEFAULT_SEARCH_TIMEOUT_MS = parseEnvInt('FS_SEARCH_TIMEOUT_MS', 5000, 100, 60000);\n\nexport const MAX_TREE_DEPTH = 50;\nexport const DEFAULT_TREE_ENTRIES = 1000;\n\nexport const MAX_LIST_ENTRIES = 20000;\n\nexport const MAX_SEARCH_RESULTS = 10000;\nexport const DEFAULT_SEARCH_RESULTS = 100;\nexport const MAX_SEARCH_DEPTH = 100;\n\nexport const DEFAULT_SEARCH_CONTENT_RESULTS = 500;\n"]}