@j0hanz/filesystem-mcp 2.5.1 → 2.6.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 (142) hide show
  1. package/README.md +26 -19
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -3
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/concurrency.d.ts +7 -4
  6. package/dist/core/concurrency.d.ts.map +1 -1
  7. package/dist/core/concurrency.js +9 -4
  8. package/dist/core/concurrency.js.map +1 -1
  9. package/dist/core/config.d.ts +1 -1
  10. package/dist/core/config.d.ts.map +1 -1
  11. package/dist/core/config.js.map +1 -1
  12. package/dist/core/cursor.d.ts.map +1 -1
  13. package/dist/core/cursor.js +11 -1
  14. package/dist/core/cursor.js.map +1 -1
  15. package/dist/core/diff.d.ts +15 -2
  16. package/dist/core/diff.d.ts.map +1 -1
  17. package/dist/core/diff.js +38 -7
  18. package/dist/core/diff.js.map +1 -1
  19. package/dist/core/errors.d.ts +4 -5
  20. package/dist/core/errors.d.ts.map +1 -1
  21. package/dist/core/errors.js +14 -14
  22. package/dist/core/errors.js.map +1 -1
  23. package/dist/core/fmt.d.ts +3 -1
  24. package/dist/core/fmt.d.ts.map +1 -1
  25. package/dist/core/fmt.js +3 -3
  26. package/dist/core/fmt.js.map +1 -1
  27. package/dist/core/fs.d.ts +2 -8
  28. package/dist/core/fs.d.ts.map +1 -1
  29. package/dist/core/fs.js +32 -20
  30. package/dist/core/fs.js.map +1 -1
  31. package/dist/core/glob.d.ts +1 -1
  32. package/dist/core/glob.d.ts.map +1 -1
  33. package/dist/core/glob.js +77 -80
  34. package/dist/core/glob.js.map +1 -1
  35. package/dist/core/mime.d.ts +1 -0
  36. package/dist/core/mime.d.ts.map +1 -1
  37. package/dist/core/mime.js +1 -1
  38. package/dist/core/mime.js.map +1 -1
  39. package/dist/core/observability.js +1 -1
  40. package/dist/core/observability.js.map +1 -1
  41. package/dist/core/path-completer.d.ts +2 -1
  42. package/dist/core/path-completer.d.ts.map +1 -1
  43. package/dist/core/path-completer.js +14 -13
  44. package/dist/core/path-completer.js.map +1 -1
  45. package/dist/core/path-discovery.d.ts.map +1 -1
  46. package/dist/core/path-discovery.js +29 -21
  47. package/dist/core/path-discovery.js.map +1 -1
  48. package/dist/core/path-utils.d.ts +8 -0
  49. package/dist/core/path-utils.d.ts.map +1 -1
  50. package/dist/core/path-utils.js +24 -17
  51. package/dist/core/path-utils.js.map +1 -1
  52. package/dist/core/path.d.ts +13 -1
  53. package/dist/core/path.d.ts.map +1 -1
  54. package/dist/core/path.js +27 -11
  55. package/dist/core/path.js.map +1 -1
  56. package/dist/core/read.d.ts +1 -0
  57. package/dist/core/read.d.ts.map +1 -1
  58. package/dist/core/read.js +23 -12
  59. package/dist/core/read.js.map +1 -1
  60. package/dist/core/schema.d.ts +10 -0
  61. package/dist/core/schema.d.ts.map +1 -1
  62. package/dist/core/schema.js +20 -6
  63. package/dist/core/schema.js.map +1 -1
  64. package/dist/core/search.d.ts +12 -4
  65. package/dist/core/search.d.ts.map +1 -1
  66. package/dist/core/search.js +42 -26
  67. package/dist/core/search.js.map +1 -1
  68. package/dist/core/sensitive.d.ts +7 -0
  69. package/dist/core/sensitive.d.ts.map +1 -1
  70. package/dist/core/sensitive.js +21 -2
  71. package/dist/core/sensitive.js.map +1 -1
  72. package/dist/core/watcher-registry.d.ts.map +1 -1
  73. package/dist/core/watcher-registry.js +24 -9
  74. package/dist/core/watcher-registry.js.map +1 -1
  75. package/dist/instructions.js +4 -4
  76. package/dist/instructions.js.map +1 -1
  77. package/dist/resources.d.ts.map +1 -1
  78. package/dist/resources.js +21 -3
  79. package/dist/resources.js.map +1 -1
  80. package/dist/server.d.ts +1 -1
  81. package/dist/server.d.ts.map +1 -1
  82. package/dist/server.js +16 -3
  83. package/dist/server.js.map +1 -1
  84. package/dist/tools/batch.d.ts +2 -5
  85. package/dist/tools/batch.d.ts.map +1 -1
  86. package/dist/tools/batch.js +1 -2
  87. package/dist/tools/batch.js.map +1 -1
  88. package/dist/tools/create.d.ts.map +1 -1
  89. package/dist/tools/create.js +21 -6
  90. package/dist/tools/create.js.map +1 -1
  91. package/dist/tools/define.d.ts +1 -1
  92. package/dist/tools/define.d.ts.map +1 -1
  93. package/dist/tools/define.js.map +1 -1
  94. package/dist/tools/delete.d.ts.map +1 -1
  95. package/dist/tools/delete.js +2 -7
  96. package/dist/tools/delete.js.map +1 -1
  97. package/dist/tools/diff.d.ts.map +1 -1
  98. package/dist/tools/diff.js +8 -5
  99. package/dist/tools/diff.js.map +1 -1
  100. package/dist/tools/edit.d.ts.map +1 -1
  101. package/dist/tools/edit.js +74 -30
  102. package/dist/tools/edit.js.map +1 -1
  103. package/dist/tools/find-files.d.ts.map +1 -1
  104. package/dist/tools/find-files.js +1 -2
  105. package/dist/tools/find-files.js.map +1 -1
  106. package/dist/tools/list-roots.d.ts.map +1 -1
  107. package/dist/tools/list-roots.js +1 -3
  108. package/dist/tools/list-roots.js.map +1 -1
  109. package/dist/tools/list.js +4 -4
  110. package/dist/tools/list.js.map +1 -1
  111. package/dist/tools/move.d.ts +2 -0
  112. package/dist/tools/move.d.ts.map +1 -1
  113. package/dist/tools/move.js +10 -4
  114. package/dist/tools/move.js.map +1 -1
  115. package/dist/tools/patch.js +1 -1
  116. package/dist/tools/patch.js.map +1 -1
  117. package/dist/tools/read.d.ts.map +1 -1
  118. package/dist/tools/read.js +7 -29
  119. package/dist/tools/read.js.map +1 -1
  120. package/dist/tools/replace-text.d.ts.map +1 -1
  121. package/dist/tools/replace-text.js +13 -13
  122. package/dist/tools/replace-text.js.map +1 -1
  123. package/dist/tools/search-text.d.ts.map +1 -1
  124. package/dist/tools/search-text.js +3 -1
  125. package/dist/tools/search-text.js.map +1 -1
  126. package/dist/tools/stat.d.ts.map +1 -1
  127. package/dist/tools/stat.js +4 -9
  128. package/dist/tools/stat.js.map +1 -1
  129. package/dist/transport/http-policy.d.ts +3 -3
  130. package/dist/transport/http-policy.d.ts.map +1 -1
  131. package/dist/transport/http-policy.js +10 -9
  132. package/dist/transport/http-policy.js.map +1 -1
  133. package/dist/transport/http.d.ts.map +1 -1
  134. package/dist/transport/http.js +38 -14
  135. package/dist/transport/http.js.map +1 -1
  136. package/dist/transport/shared.d.ts +2 -2
  137. package/dist/transport/shared.d.ts.map +1 -1
  138. package/dist/transport/shared.js.map +1 -1
  139. package/dist/transport/stdio.d.ts +1 -1
  140. package/dist/transport/stdio.js +1 -1
  141. package/dist/transport/stdio.js.map +1 -1
  142. package/package.json +8 -8
package/README.md CHANGED
@@ -252,7 +252,7 @@ All tools are scoped to the configured roots. Call `list_roots` first to discove
252
252
  | Tool | Description |
253
253
  | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
254
254
  | `create` | Create one or more files, creating parent directories as needed. An existing file prompts the user to confirm the overwrite; `overwrite: true` on an entry skips the prompt, `append: true` adds to the end instead. |
255
- | `edit` | Apply sequential literal string replacements to one or more files (max 5 per call). |
255
+ | `edit` | Apply sequential literal string replacements to one or more files (up to 5 files per call, 100 edits per file). |
256
256
  | `move` | Move, rename, or copy (`copy: true`) one or more files/directories to explicit destinations. |
257
257
  | `delete` | Permanently delete one or more files or directories. This action is irreversible. |
258
258
  | `replace_text` | Bulk search-and-replace across files matching a glob pattern. |
@@ -276,30 +276,37 @@ All tools are scoped to the configured roots. Call `list_roots` first to discove
276
276
 
277
277
  ```text
278
278
  filesystem-mcp/
279
- ├── __tests__/ Test suites
279
+ ├── __tests__/ Test suites (node --test) and shared helpers
280
+ ├── docs/adr/ Architecture decision records
281
+ ├── mcpb/manifest.json Claude Desktop extension manifest
282
+ ├── scripts/ Release-path scripts (MCPB pack, Smithery publish)
280
283
  ├── src/
281
- │ ├── core/ Path guarding, filesystem abstraction, concurrency, observability
282
- │ ├── tools/ Tool definitions and registration
283
- │ ├── index.ts Process entrypoint and transport selection
284
- │ ├── server.ts Server factory and registrar composition
285
- │ ├── transport/ stdio and Streamable HTTP transport setup
286
- │ ├── prompts.ts Prompt definitions and registration
287
- │ └── resources.ts Resource definitions and registration
288
- └── Dockerfile Multi-stage alpine build, non-root user
284
+ │ ├── core/ Path guarding, filesystem facade, search, stores, watchers
285
+ │ ├── tools/ One file per tool, plus define.ts (registration) and batch.ts
286
+ │ ├── transport/ stdio.ts, http.ts, http-policy.ts (auth, Origin, rate limit), shared.ts
287
+ │ ├── cli.ts Argument parsing and --print-config
288
+ │ ├── cli-help.ts --help / --version text
289
+ │ ├── index.ts Process entrypoint, shutdown, transport selection
290
+ │ ├── instructions.ts Server instructions sent to every client
291
+ │ ├── prompts.ts Prompt definitions and registration
292
+ │ ├── resources.ts Resource definitions, subscriptions, completion
293
+ │ ├── server.ts Server factory and registrar composition
294
+ │ └── transport.ts Facade re-exporting startServer / startHttpServer
295
+ └── Dockerfile Multi-stage alpine build, non-root user
289
296
  ```
290
297
 
291
- Runtime composition flows from `src/index.ts` to `src/transport.ts`, then to
298
+ Runtime composition flows from `src/index.ts` to `src/transport/` (stdio or HTTP), then to
292
299
  `src/server.ts`, the registrars, and finally `src/core/`. Each registrar owns
293
300
  the narrow dependency contract it consumes.
294
301
 
295
- | Path | Purpose |
296
- | :-------------------- | :------------------------------------------------------------- |
297
- | `src/core/path.ts` | `PathGuard` — validates every path against allowed roots |
298
- | `src/core/fs.ts` | `GuardedFileSystem` — guarded filesystem facade |
299
- | `src/tools/define.ts` | Tool registration and execution framework |
300
- | `src/tools/batch.ts` | Batch helpers (runOverPaths, isTotalFailure) |
301
- | `src/server.ts` | Builds shared dependencies and invokes the three registrars |
302
- | `src/transport.ts` | Owns stdio and Streamable HTTP setup around the server factory |
302
+ | Path | Purpose |
303
+ | :-------------------- | :------------------------------------------------------------------------------ |
304
+ | `src/core/path.ts` | `PathGuard` — validates every path against allowed roots |
305
+ | `src/core/fs.ts` | `GuardedFileSystem` — guarded filesystem facade |
306
+ | `src/tools/define.ts` | Tool registration and execution framework |
307
+ | `src/tools/batch.ts` | Batch helpers (runOverPaths, isTotalFailure) |
308
+ | `src/server.ts` | Builds shared dependencies and invokes the three registrars |
309
+ | `src/transport/` | stdio (`stdio.ts`), Streamable HTTP (`http.ts`), HTTP policy (`http-policy.ts`) |
303
310
 
304
311
  ## Configuration
305
312
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAqBA,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAyFD,wBAAsB,SAAS,IAAI,OAAO,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC,CAsED;AAMD,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAuBA,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAyFD,wBAAsB,SAAS,IAAI,OAAO,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC,CAsED;AAMD,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkChB"}
package/dist/cli.js CHANGED
@@ -3,8 +3,9 @@ import { parseArgs as utilParseArgs } from 'node:util';
3
3
  import { printHelpAndExit, printVersionAndExit } from "./cli-help.js";
4
4
  import { cli } from "./core/config.js";
5
5
  import { formatUnknownErrorMessage } from "./core/errors.js";
6
- import { getReservedDeviceNameForPath, isWindowsDriveRelativePath, normalizePath, parseTrueEnvFlag, } from "./core/path-utils.js";
6
+ import { getReservedDeviceNameForPath, IS_WINDOWS, isWindowsDriveRelativePath, normalizePath, parseTrueEnvFlag, } from "./core/path-utils.js";
7
7
  import { PathGuard } from "./core/path.js";
8
+ import { describeSensitivePolicy } from "./core/sensitive.js";
8
9
  import { getMaxTextFileSize } from "./core/util.js";
9
10
  import { registeredTools } from "./tools/index.js";
10
11
  // ════════════════════════════════════════════════════════════
@@ -23,7 +24,7 @@ function validateCliPath(inputPath) {
23
24
  if (isWindowsDriveRelativePath(inputPath)) {
24
25
  throw new CliExitError('Windows drive-relative paths are not allowed. Use C:\\path or C:/path instead of C:path.');
25
26
  }
26
- const reserved = getReservedDeviceNameForPath(inputPath);
27
+ const reserved = IS_WINDOWS ? getReservedDeviceNameForPath(inputPath) : undefined;
27
28
  if (reserved) {
28
29
  throw new CliExitError(`Windows reserved device name not allowed: ${reserved}.`);
29
30
  }
@@ -170,7 +171,7 @@ export async function runPrintConfig(options) {
170
171
  cliAllowedDirs: options.allowedDirs,
171
172
  });
172
173
  await pathGuard.recomputeAllowedDirectories();
173
- const allowedRoots = pathGuard.getAllowedDirectories();
174
+ const allowedRoots = pathGuard.getRoots();
174
175
  const tools = registeredTools(options.readOnly).map((t) => t.name);
175
176
  // Derived, never assumed: `--print-config --port 3000` reports the HTTP bind
176
177
  // that `--port` would actually have started, not the stdio default.
@@ -183,6 +184,14 @@ export async function runPrintConfig(options) {
183
184
  tools,
184
185
  apiKey: options.apiKey ? '***' : null,
185
186
  limits: { maxFileSizeBytes: getMaxTextFileSize() },
187
+ // Everything that can produce ACCESS_DENIED, read through the same
188
+ // sources the guard and the matcher use (flag beats env).
189
+ policy: {
190
+ ...describeSensitivePolicy(),
191
+ rootBoundary: cli.rootBoundary ?? process.env['FS_ROOT_BOUNDARY'] ?? null,
192
+ allowMissingRoots: cli.allowMissingRoots ??
193
+ parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS'),
194
+ },
186
195
  };
187
196
  process.stdout.write(`${JSON.stringify(config, null, 2)}\n`);
188
197
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,aAAa,EACb,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,+DAA+D;AAC/D,2DAA2D;AAC3D,+DAA+D;AAE/D,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,SAAS,eAAe,CAAC,SAAiB;IACxC,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,YAAY,CAAC,2BAA2B,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,0BAA0B,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,YAAY,CACpB,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,4BAA4B,CAAC,SAAS,CAAC,CAAC;IACzD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,6CAA6C,QAAQ,GAAG,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,YAAY,GAAG,KAAK;IAC1E,MAAM,UAAU,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAE5C,IAAI,KAAY,CAAC;IACjB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,YAAY;YAAE,OAAO,UAAU,CAAC;QACpC,8DAA8D;QAC9D,MAAM,IAAI,KAAK,CAAC,2BAA2B,SAAS,KAAK,yBAAyB,CAAC,KAAK,CAAC,EAAE,EAAE;YAC3F,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,qBAAqB,CAAC,CAAC;IAC7E,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,gFAAgF;AAChF,4EAA4E;AAC5E,iBAAiB;AACjB,KAAK,UAAU,wBAAwB,CACrC,KAAwB,EACxB,YAAY,GAAG,KAAK;IAEpB,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,UAAU,CAAC,IAAI,CAAC,MAAM,qBAAqB,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc;IAC7C,MAAM,UAAU,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,UAAU,EAAE,CAAC;AAC/E,CAAC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,6EAA6E;IAC7E,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;QAC/C,MAAM,IAAI,YAAY,CAAC,gEAAgE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,iBAAiB,GAAG;IACxB,OAAO,EAAE;QACP,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAChD,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAChD,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACnD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;QACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;QACxC,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC/B,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC/B,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC7B,iBAAiB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACtD,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACnC,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACnC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;QACxC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;QACzC,qBAAqB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;KAC3D;IACD,MAAM,EAAE,IAAI;IACZ,gBAAgB,EAAE,IAAI;CACd,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,SAAS;IAS7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC;QAEhD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACvB,gBAAgB,EAAE,CAAC;QACrB,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC1B,mBAAmB,EAAE,CAAC;QACxB,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YAC5C,eAAe,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;QAED,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;QACxB,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,qEAAqE;QACrE,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACzD,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;QAChE,IAAI,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;QAC3E,IAAI,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;QAC5E,IAAI,CAAC,CAAC,iBAAiB,CAAC;YAAE,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC;QACpD,IAAI,CAAC,CAAC,UAAU,CAAC;YAAE,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3C,IAAI,CAAC,CAAC,qBAAqB,CAAC;YAAE,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC3D,MAAM,WAAW,GAAG,CAAC,OAAsC,EAAqB,EAAE,CAAC;YACjF,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;SACzE,CAAC;QACF,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,YAAY,GAAG,YAAY,CAAC;QAC7D,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,aAAa,GAAG,aAAa,CAAC;QAEhE,MAAM,QAAQ,GACZ,CAAC,CAAC,WAAW,CAAC;YACd,CAAC,CAAC,UAAU,CAAC;YACb,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;QAChC,MAAM,WAAW,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/D,MAAM,iBAAiB,GACrB,CAAC,CAAC,qBAAqB,CAAC;YACxB,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAEpF,MAAM,WAAW,GACf,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;YAC3B,CAAC,CAAC,MAAM,wBAAwB,CAAC,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC;YACvE,CAAC,CAAC,EAAE,CAAC;QAET,OAAO;YACL,WAAW;YACX,QAAQ;YACR,IAAI;YACJ,QAAQ;YACR,WAAW;YACX,QAAQ;YACR,MAAM;SACP,CAAC;IACJ,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,IAAI,YAAY,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,yEAAyE;AACzE,+DAA+D;AAE/D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAQpC;IACC,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC;QAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,cAAc,EAAE,OAAO,CAAC,WAAW;KACpC,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,2BAA2B,EAAE,CAAC;IAC9C,MAAM,YAAY,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC;IAEvD,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEnE,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,MAAM,GAAG;QACb,SAAS,EACP,OAAO,CAAC,IAAI,KAAK,SAAS;YACxB,CAAC,CAAC,UAAU,OAAO,CAAC,QAAQ,IAAI,WAAW,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACrE,CAAC,CAAC,OAAO;QACb,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY;QACZ,KAAK;QACL,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QACrC,MAAM,EAAE,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,EAAE;KACnD,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["import type { Stats } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { parseArgs as utilParseArgs } from 'node:util';\n\nimport { printHelpAndExit, printVersionAndExit } from './cli-help.ts';\nimport { cli } from './core/config.ts';\nimport { formatUnknownErrorMessage } from './core/errors.ts';\nimport {\n getReservedDeviceNameForPath,\n isWindowsDriveRelativePath,\n normalizePath,\n parseTrueEnvFlag,\n} from './core/path-utils.ts';\nimport { PathGuard } from './core/path.ts';\nimport { getMaxTextFileSize } from './core/util.ts';\nimport { registeredTools } from './tools/index.ts';\n\n// ════════════════════════════════════════════════════════════\n// Path & Config Utilities — pure functions and error types\n// ════════════════════════════════════════════════════════════\n\nexport class CliExitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CliExitError';\n }\n}\n\nfunction validateCliPath(inputPath: string): void {\n if (inputPath.includes('\\0')) {\n throw new CliExitError('Path contains null bytes.');\n }\n\n if (isWindowsDriveRelativePath(inputPath)) {\n throw new CliExitError(\n 'Windows drive-relative paths are not allowed. Use C:\\\\path or C:/path instead of C:path.',\n );\n }\n\n const reserved = getReservedDeviceNameForPath(inputPath);\n if (reserved) {\n throw new CliExitError(`Windows reserved device name not allowed: ${reserved}.`);\n }\n}\n\nasync function validateDirectoryPath(inputPath: string, allowMissing = false): Promise<string> {\n const normalized = normalizePath(inputPath);\n\n let stats: Stats;\n try {\n stats = await stat(normalized);\n } catch (error) {\n if (allowMissing) return normalized;\n // Node's own message already names the syscall and the errno.\n throw new Error(`Cannot access directory ${inputPath}: ${formatUnknownErrorMessage(error)}`, {\n cause: error,\n });\n }\n\n if (!stats.isDirectory()) throw new Error(`${inputPath} is not a directory`);\n return normalized;\n}\n\n// No dedupe here: PathGuard's `normalizeAllowedDirectories` already Set-dedupes\n// the normalized set, and containment checks are case-insensitive where the\n// filesystem is.\nasync function normalizeAndValidateDirs(\n paths: readonly string[],\n allowMissing = false,\n): Promise<string[]> {\n const normalized: string[] = [];\n for (const p of paths) {\n normalized.push(await validateDirectoryPath(p, allowMissing));\n }\n return normalized;\n}\n\nfunction normalizeCliExitMessage(error: unknown): string {\n const rawMessage = formatUnknownErrorMessage(error);\n return rawMessage.startsWith('Error:') ? rawMessage : `Error: ${rawMessage}`;\n}\n\nfunction parsePortOption(raw: unknown): number | undefined {\n // '' reads as unset: FS_PORT set-but-empty (compose templating) means stdio.\n if (raw === undefined || raw === '') return undefined;\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new CliExitError(`Error: --port / FS_PORT must be an integer between 1 and 65535`);\n }\n return n;\n}\n\nconst CLI_PARSER_CONFIG = {\n options: {\n 'allow-cwd': { type: 'boolean', default: false },\n 'read-only': { type: 'boolean', default: false },\n 'print-config': { type: 'boolean', default: false },\n port: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n 'log-level': { type: 'string' },\n 'http-host': { type: 'string' },\n 'api-key': { type: 'string' },\n 'allow-sensitive': { type: 'boolean', default: false },\n 'root-boundary': { type: 'string' },\n 'max-file-size': { type: 'string' },\n 'walk-cwd': { type: 'boolean', default: false },\n deny: { type: 'string', multiple: true },\n allow: { type: 'string', multiple: true },\n 'allow-missing-roots': { type: 'boolean', default: false },\n },\n strict: true,\n allowPositionals: true,\n} as const;\n\nexport async function parseArgs(): Promise<{\n allowedDirs: string[];\n allowCwd: boolean;\n port: number | undefined;\n readOnly: boolean;\n printConfig: boolean;\n httpHost: string | undefined;\n apiKey: string | undefined;\n}> {\n try {\n const parsed = utilParseArgs(CLI_PARSER_CONFIG);\n\n if (parsed.values.help) {\n printHelpAndExit();\n }\n\n if (parsed.values.version) {\n printVersionAndExit();\n }\n\n for (const positional of parsed.positionals) {\n validateCliPath(positional);\n }\n\n const v = parsed.values;\n // `--http-host` and `--api-key` are returned to the caller and handed to\n // the server as config. Every other flag lands in the CLI-override store\n // (core/config.ts) — the one owner of the flag-beats-env rule — which deep\n // core readers (path, sensitive, observability, util) consult before\n // falling back to the operator's environment. Nothing writes process.env.\n const httpHost = v['http-host'] ?? process.env['FS_HTTP_HOST'];\n const apiKey = v['api-key'] ?? process.env['FS_API_KEY'];\n if (v['log-level'] !== undefined) cli.logLevel = v['log-level'];\n if (v['max-file-size'] !== undefined) cli.maxFileSize = v['max-file-size'];\n if (v['root-boundary'] !== undefined) cli.rootBoundary = v['root-boundary'];\n if (v['allow-sensitive']) cli.allowSensitive = true;\n if (v['walk-cwd']) cli.allowCwdWalk = true;\n if (v['allow-missing-roots']) cli.allowMissingRoots = true;\n const patternList = (entries: readonly string[] | undefined): readonly string[] => [\n ...new Set((entries ?? []).map((entry) => entry.trim()).filter(Boolean)),\n ];\n const denyPatterns = patternList(v.deny);\n if (denyPatterns.length > 0) cli.denyPatterns = denyPatterns;\n const allowPatterns = patternList(v.allow);\n if (allowPatterns.length > 0) cli.allowPatterns = allowPatterns;\n\n const allowCwd =\n v['allow-cwd'] ||\n v['walk-cwd'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');\n const readOnly = v['read-only'];\n const printConfig = v['print-config'];\n const port = parsePortOption(v.port ?? process.env['FS_PORT']);\n const allowMissingRoots =\n v['allow-missing-roots'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');\n\n const allowedDirs =\n parsed.positionals.length > 0\n ? await normalizeAndValidateDirs(parsed.positionals, allowMissingRoots)\n : [];\n\n return {\n allowedDirs,\n allowCwd,\n port,\n readOnly,\n printConfig,\n httpHost,\n apiKey,\n };\n } catch (error: unknown) {\n if (error instanceof CliExitError) {\n throw error;\n }\n\n throw new CliExitError(normalizeCliExitMessage(error));\n }\n}\n\n// ════════════════════════════════════════════════════════════\n// Effective config reporting — prints the resolved server config as JSON\n// ════════════════════════════════════════════════════════════\n\nexport async function runPrintConfig(options: {\n allowedDirs: string[];\n allowCwd: boolean;\n readOnly: boolean;\n /** Resolved `--port`. Present means the launch this reports on is an HTTP bind. */\n port?: number;\n httpHost?: string;\n apiKey?: string;\n}): Promise<void> {\n const pathGuard = new PathGuard({\n allowCwd: options.allowCwd,\n cliAllowedDirs: options.allowedDirs,\n });\n await pathGuard.recomputeAllowedDirectories();\n const allowedRoots = pathGuard.getAllowedDirectories();\n\n const tools = registeredTools(options.readOnly).map((t) => t.name);\n\n // Derived, never assumed: `--print-config --port 3000` reports the HTTP bind\n // that `--port` would actually have started, not the stdio default.\n const config = {\n transport:\n options.port !== undefined\n ? `http://${options.httpHost ?? '127.0.0.1'}:${String(options.port)}`\n : 'stdio',\n readOnly: options.readOnly,\n allowedRoots,\n tools,\n apiKey: options.apiKey ? '***' : null,\n limits: { maxFileSizeBytes: getMaxTextFileSize() },\n };\n\n process.stdout.write(`${JSON.stringify(config, null, 2)}\\n`);\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,4BAA4B,EAC5B,UAAU,EACV,0BAA0B,EAC1B,aAAa,EACb,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,+DAA+D;AAC/D,2DAA2D;AAC3D,+DAA+D;AAE/D,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,SAAS,eAAe,CAAC,SAAiB;IACxC,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,YAAY,CAAC,2BAA2B,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,0BAA0B,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,YAAY,CACpB,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAClF,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,6CAA6C,QAAQ,GAAG,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,YAAY,GAAG,KAAK;IAC1E,MAAM,UAAU,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAE5C,IAAI,KAAY,CAAC;IACjB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,YAAY;YAAE,OAAO,UAAU,CAAC;QACpC,8DAA8D;QAC9D,MAAM,IAAI,KAAK,CAAC,2BAA2B,SAAS,KAAK,yBAAyB,CAAC,KAAK,CAAC,EAAE,EAAE;YAC3F,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,qBAAqB,CAAC,CAAC;IAC7E,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,gFAAgF;AAChF,4EAA4E;AAC5E,iBAAiB;AACjB,KAAK,UAAU,wBAAwB,CACrC,KAAwB,EACxB,YAAY,GAAG,KAAK;IAEpB,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,UAAU,CAAC,IAAI,CAAC,MAAM,qBAAqB,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc;IAC7C,MAAM,UAAU,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,UAAU,EAAE,CAAC;AAC/E,CAAC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,6EAA6E;IAC7E,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;QAC/C,MAAM,IAAI,YAAY,CAAC,gEAAgE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,iBAAiB,GAAG;IACxB,OAAO,EAAE;QACP,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAChD,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAChD,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACnD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;QACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;QACxC,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC/B,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC/B,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC7B,iBAAiB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACtD,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACnC,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACnC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;QACxC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;QACzC,qBAAqB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;KAC3D;IACD,MAAM,EAAE,IAAI;IACZ,gBAAgB,EAAE,IAAI;CACd,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,SAAS;IAS7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC;QAEhD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACvB,gBAAgB,EAAE,CAAC;QACrB,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC1B,mBAAmB,EAAE,CAAC;QACxB,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YAC5C,eAAe,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;QAED,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;QACxB,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,qEAAqE;QACrE,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACzD,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;QAChE,IAAI,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;QAC3E,IAAI,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;QAC5E,IAAI,CAAC,CAAC,iBAAiB,CAAC;YAAE,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC;QACpD,IAAI,CAAC,CAAC,UAAU,CAAC;YAAE,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3C,IAAI,CAAC,CAAC,qBAAqB,CAAC;YAAE,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC3D,MAAM,WAAW,GAAG,CAAC,OAAsC,EAAqB,EAAE,CAAC;YACjF,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;SACzE,CAAC;QACF,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,YAAY,GAAG,YAAY,CAAC;QAC7D,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,aAAa,GAAG,aAAa,CAAC;QAEhE,MAAM,QAAQ,GACZ,CAAC,CAAC,WAAW,CAAC;YACd,CAAC,CAAC,UAAU,CAAC;YACb,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;QAChC,MAAM,WAAW,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/D,MAAM,iBAAiB,GACrB,CAAC,CAAC,qBAAqB,CAAC;YACxB,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAEpF,MAAM,WAAW,GACf,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;YAC3B,CAAC,CAAC,MAAM,wBAAwB,CAAC,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC;YACvE,CAAC,CAAC,EAAE,CAAC;QAET,OAAO;YACL,WAAW;YACX,QAAQ;YACR,IAAI;YACJ,QAAQ;YACR,WAAW;YACX,QAAQ;YACR,MAAM;SACP,CAAC;IACJ,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,IAAI,YAAY,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,yEAAyE;AACzE,+DAA+D;AAE/D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAQpC;IACC,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC;QAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,cAAc,EAAE,OAAO,CAAC,WAAW;KACpC,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,2BAA2B,EAAE,CAAC;IAC9C,MAAM,YAAY,GAAG,SAAS,CAAC,QAAQ,EAAE,CAAC;IAE1C,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEnE,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,MAAM,GAAG;QACb,SAAS,EACP,OAAO,CAAC,IAAI,KAAK,SAAS;YACxB,CAAC,CAAC,UAAU,OAAO,CAAC,QAAQ,IAAI,WAAW,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACrE,CAAC,CAAC,OAAO;QACb,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY;QACZ,KAAK;QACL,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QACrC,MAAM,EAAE,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,EAAE;QAClD,mEAAmE;QACnE,0DAA0D;QAC1D,MAAM,EAAE;YACN,GAAG,uBAAuB,EAAE;YAC5B,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,IAAI;YACzE,iBAAiB,EACf,GAAG,CAAC,iBAAiB;gBACrB,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;SACpF;KACF,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["import type { Stats } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { parseArgs as utilParseArgs } from 'node:util';\n\nimport { printHelpAndExit, printVersionAndExit } from './cli-help.ts';\nimport { cli } from './core/config.ts';\nimport { formatUnknownErrorMessage } from './core/errors.ts';\nimport {\n getReservedDeviceNameForPath,\n IS_WINDOWS,\n isWindowsDriveRelativePath,\n normalizePath,\n parseTrueEnvFlag,\n} from './core/path-utils.ts';\nimport { PathGuard } from './core/path.ts';\nimport { describeSensitivePolicy } from './core/sensitive.ts';\nimport { getMaxTextFileSize } from './core/util.ts';\nimport { registeredTools } from './tools/index.ts';\n\n// ════════════════════════════════════════════════════════════\n// Path & Config Utilities — pure functions and error types\n// ════════════════════════════════════════════════════════════\n\nexport class CliExitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CliExitError';\n }\n}\n\nfunction validateCliPath(inputPath: string): void {\n if (inputPath.includes('\\0')) {\n throw new CliExitError('Path contains null bytes.');\n }\n\n if (isWindowsDriveRelativePath(inputPath)) {\n throw new CliExitError(\n 'Windows drive-relative paths are not allowed. Use C:\\\\path or C:/path instead of C:path.',\n );\n }\n\n const reserved = IS_WINDOWS ? getReservedDeviceNameForPath(inputPath) : undefined;\n if (reserved) {\n throw new CliExitError(`Windows reserved device name not allowed: ${reserved}.`);\n }\n}\n\nasync function validateDirectoryPath(inputPath: string, allowMissing = false): Promise<string> {\n const normalized = normalizePath(inputPath);\n\n let stats: Stats;\n try {\n stats = await stat(normalized);\n } catch (error) {\n if (allowMissing) return normalized;\n // Node's own message already names the syscall and the errno.\n throw new Error(`Cannot access directory ${inputPath}: ${formatUnknownErrorMessage(error)}`, {\n cause: error,\n });\n }\n\n if (!stats.isDirectory()) throw new Error(`${inputPath} is not a directory`);\n return normalized;\n}\n\n// No dedupe here: PathGuard's `normalizeAllowedDirectories` already Set-dedupes\n// the normalized set, and containment checks are case-insensitive where the\n// filesystem is.\nasync function normalizeAndValidateDirs(\n paths: readonly string[],\n allowMissing = false,\n): Promise<string[]> {\n const normalized: string[] = [];\n for (const p of paths) {\n normalized.push(await validateDirectoryPath(p, allowMissing));\n }\n return normalized;\n}\n\nfunction normalizeCliExitMessage(error: unknown): string {\n const rawMessage = formatUnknownErrorMessage(error);\n return rawMessage.startsWith('Error:') ? rawMessage : `Error: ${rawMessage}`;\n}\n\nfunction parsePortOption(raw: unknown): number | undefined {\n // '' reads as unset: FS_PORT set-but-empty (compose templating) means stdio.\n if (raw === undefined || raw === '') return undefined;\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new CliExitError(`Error: --port / FS_PORT must be an integer between 1 and 65535`);\n }\n return n;\n}\n\nconst CLI_PARSER_CONFIG = {\n options: {\n 'allow-cwd': { type: 'boolean', default: false },\n 'read-only': { type: 'boolean', default: false },\n 'print-config': { type: 'boolean', default: false },\n port: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n 'log-level': { type: 'string' },\n 'http-host': { type: 'string' },\n 'api-key': { type: 'string' },\n 'allow-sensitive': { type: 'boolean', default: false },\n 'root-boundary': { type: 'string' },\n 'max-file-size': { type: 'string' },\n 'walk-cwd': { type: 'boolean', default: false },\n deny: { type: 'string', multiple: true },\n allow: { type: 'string', multiple: true },\n 'allow-missing-roots': { type: 'boolean', default: false },\n },\n strict: true,\n allowPositionals: true,\n} as const;\n\nexport async function parseArgs(): Promise<{\n allowedDirs: string[];\n allowCwd: boolean;\n port: number | undefined;\n readOnly: boolean;\n printConfig: boolean;\n httpHost: string | undefined;\n apiKey: string | undefined;\n}> {\n try {\n const parsed = utilParseArgs(CLI_PARSER_CONFIG);\n\n if (parsed.values.help) {\n printHelpAndExit();\n }\n\n if (parsed.values.version) {\n printVersionAndExit();\n }\n\n for (const positional of parsed.positionals) {\n validateCliPath(positional);\n }\n\n const v = parsed.values;\n // `--http-host` and `--api-key` are returned to the caller and handed to\n // the server as config. Every other flag lands in the CLI-override store\n // (core/config.ts) — the one owner of the flag-beats-env rule — which deep\n // core readers (path, sensitive, observability, util) consult before\n // falling back to the operator's environment. Nothing writes process.env.\n const httpHost = v['http-host'] ?? process.env['FS_HTTP_HOST'];\n const apiKey = v['api-key'] ?? process.env['FS_API_KEY'];\n if (v['log-level'] !== undefined) cli.logLevel = v['log-level'];\n if (v['max-file-size'] !== undefined) cli.maxFileSize = v['max-file-size'];\n if (v['root-boundary'] !== undefined) cli.rootBoundary = v['root-boundary'];\n if (v['allow-sensitive']) cli.allowSensitive = true;\n if (v['walk-cwd']) cli.allowCwdWalk = true;\n if (v['allow-missing-roots']) cli.allowMissingRoots = true;\n const patternList = (entries: readonly string[] | undefined): readonly string[] => [\n ...new Set((entries ?? []).map((entry) => entry.trim()).filter(Boolean)),\n ];\n const denyPatterns = patternList(v.deny);\n if (denyPatterns.length > 0) cli.denyPatterns = denyPatterns;\n const allowPatterns = patternList(v.allow);\n if (allowPatterns.length > 0) cli.allowPatterns = allowPatterns;\n\n const allowCwd =\n v['allow-cwd'] ||\n v['walk-cwd'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');\n const readOnly = v['read-only'];\n const printConfig = v['print-config'];\n const port = parsePortOption(v.port ?? process.env['FS_PORT']);\n const allowMissingRoots =\n v['allow-missing-roots'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');\n\n const allowedDirs =\n parsed.positionals.length > 0\n ? await normalizeAndValidateDirs(parsed.positionals, allowMissingRoots)\n : [];\n\n return {\n allowedDirs,\n allowCwd,\n port,\n readOnly,\n printConfig,\n httpHost,\n apiKey,\n };\n } catch (error: unknown) {\n if (error instanceof CliExitError) {\n throw error;\n }\n\n throw new CliExitError(normalizeCliExitMessage(error));\n }\n}\n\n// ════════════════════════════════════════════════════════════\n// Effective config reporting — prints the resolved server config as JSON\n// ════════════════════════════════════════════════════════════\n\nexport async function runPrintConfig(options: {\n allowedDirs: string[];\n allowCwd: boolean;\n readOnly: boolean;\n /** Resolved `--port`. Present means the launch this reports on is an HTTP bind. */\n port?: number;\n httpHost?: string;\n apiKey?: string;\n}): Promise<void> {\n const pathGuard = new PathGuard({\n allowCwd: options.allowCwd,\n cliAllowedDirs: options.allowedDirs,\n });\n await pathGuard.recomputeAllowedDirectories();\n const allowedRoots = pathGuard.getRoots();\n\n const tools = registeredTools(options.readOnly).map((t) => t.name);\n\n // Derived, never assumed: `--print-config --port 3000` reports the HTTP bind\n // that `--port` would actually have started, not the stdio default.\n const config = {\n transport:\n options.port !== undefined\n ? `http://${options.httpHost ?? '127.0.0.1'}:${String(options.port)}`\n : 'stdio',\n readOnly: options.readOnly,\n allowedRoots,\n tools,\n apiKey: options.apiKey ? '***' : null,\n limits: { maxFileSizeBytes: getMaxTextFileSize() },\n // Everything that can produce ACCESS_DENIED, read through the same\n // sources the guard and the matcher use (flag beats env).\n policy: {\n ...describeSensitivePolicy(),\n rootBoundary: cli.rootBoundary ?? process.env['FS_ROOT_BOUNDARY'] ?? null,\n allowMissingRoots:\n cli.allowMissingRoots ??\n parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS'),\n },\n };\n\n process.stdout.write(`${JSON.stringify(config, null, 2)}\\n`);\n}\n"]}
@@ -6,15 +6,18 @@ export declare const StoppedReasonSchema: z.ZodOptional<z.ZodEnum<{
6
6
  timeout: "timeout";
7
7
  }>>;
8
8
  /**
9
- * The subset `search_text` and `find_files` can actually emit. Their scans call
10
- * only `hitMaxResults` and `hitAbort` (search.ts), never `hitMaxFiles`, so the
11
- * three-value enum published a value no response can carry and disagreed with
12
- * both tools' own descriptions.
9
+ * The subset `search_text` and `find_files` can actually emit. Both scans
10
+ * resolve the stop through `resolveStopReason` below, which answers only
11
+ * `maxResults` or `timeout` — never `maxFiles` — so the three-value enum
12
+ * published a value no response can carry and disagreed with both tools' own
13
+ * descriptions.
13
14
  */
14
15
  export declare const SearchStoppedReasonSchema: z.ZodOptional<z.ZodEnum<{
15
16
  maxResults: "maxResults";
16
17
  timeout: "timeout";
17
18
  }>>;
19
+ /** The one precedence both scans share: the result cap wins over an abort that fired the same iteration. */
20
+ export declare function resolveStopReason(hitCap: boolean, aborted: boolean): 'maxResults' | 'timeout' | undefined;
18
21
  interface ParallelResult<R> {
19
22
  results: {
20
23
  index: number;
@@ -1 +1 @@
1
- {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAK5B,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;GAA2D,CAAC;AAE5F;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;GAA+C,CAAC;AAEtF,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,EAAE,CAAC;IACvC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAC1C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACjD,WAAW,GAAE,MAA6B,EAC1C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAgE5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACrE,OAAO,EAAE;IACP,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD,GACA,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CA0DpC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAmBlF"}
1
+ {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAK5B,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;GAA2D,CAAC;AAE5F;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB;;;GAA+C,CAAC;AAEtF,4GAA4G;AAC5G,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,OAAO,GACf,YAAY,GAAG,SAAS,GAAG,SAAS,CAEtC;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,EAAE,CAAC;IACvC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAC1C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACjD,WAAW,GAAE,MAA6B,EAC1C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAgE5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACrE,OAAO,EAAE;IACP,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD,GACA,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CA0DpC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAmBlF"}
@@ -3,12 +3,17 @@ import { normalizeUnknownError } from "./errors.js";
3
3
  import { PARALLEL_CONCURRENCY } from "./util.js";
4
4
  export const StoppedReasonSchema = z.enum(['maxResults', 'maxFiles', 'timeout']).optional();
5
5
  /**
6
- * The subset `search_text` and `find_files` can actually emit. Their scans call
7
- * only `hitMaxResults` and `hitAbort` (search.ts), never `hitMaxFiles`, so the
8
- * three-value enum published a value no response can carry and disagreed with
9
- * both tools' own descriptions.
6
+ * The subset `search_text` and `find_files` can actually emit. Both scans
7
+ * resolve the stop through `resolveStopReason` below, which answers only
8
+ * `maxResults` or `timeout` — never `maxFiles` — so the three-value enum
9
+ * published a value no response can carry and disagreed with both tools' own
10
+ * descriptions.
10
11
  */
11
12
  export const SearchStoppedReasonSchema = z.enum(['maxResults', 'timeout']).optional();
13
+ /** The one precedence both scans share: the result cap wins over an abort that fired the same iteration. */
14
+ export function resolveStopReason(hitCap, aborted) {
15
+ return hitCap ? 'maxResults' : aborted ? 'timeout' : undefined;
16
+ }
12
17
  export async function processInParallel(items, processor, concurrency = PARALLEL_CONCURRENCY, signal) {
13
18
  const itemCount = items.length;
14
19
  if (itemCount === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from 'zod/v4';\n\nimport { normalizeUnknownError } from './errors.ts';\nimport { PARALLEL_CONCURRENCY } from './util.ts';\n\nexport type StoppedReason = 'maxResults' | 'maxFiles' | 'timeout';\n\nexport const StoppedReasonSchema = z.enum(['maxResults', 'maxFiles', 'timeout']).optional();\n\n/**\n * The subset `search_text` and `find_files` can actually emit. Their scans call\n * only `hitMaxResults` and `hitAbort` (search.ts), never `hitMaxFiles`, so the\n * three-value enum published a value no response can carry and disagreed with\n * both tools' own descriptions.\n */\nexport const SearchStoppedReasonSchema = z.enum(['maxResults', 'timeout']).optional();\n\ninterface ParallelResult<R> {\n results: { index: number; value: R }[];\n errors: { index: number; error: Error }[];\n}\n\nexport async function processInParallel<T, R>(\n items: readonly T[],\n processor: (item: T, index: number) => Promise<R>,\n concurrency: number = PARALLEL_CONCURRENCY,\n signal?: AbortSignal,\n): Promise<ParallelResult<R>> {\n const itemCount = items.length;\n if (itemCount === 0) return { results: [], errors: [] };\n\n const results: { index: number; value: R }[] = [];\n const errors: { index: number; error: Error }[] = [];\n\n signal?.throwIfAborted();\n\n let nextIndex = 0;\n // Set when a worker saw the signal fire before an item ran — the only\n // shape in which the batch is genuinely incomplete. The annotation\n // matters: without it control-flow analysis keeps the `false` narrowing\n // from the initializer and cannot see the closure assignment.\n let truncated = false as boolean;\n\n const next = async (): Promise<void> => {\n while (nextIndex < itemCount) {\n if (signal?.aborted) {\n truncated = true;\n signal.throwIfAborted();\n }\n\n const index = nextIndex;\n nextIndex += 1;\n const item = items[index];\n if (item === undefined) {\n continue;\n }\n\n try {\n // No abort check after the processor: once an item has run, its\n // result is real work that a write caller may have committed to\n // disk. Discarding it here would report a finished append as\n // failed, and a client retry would append twice.\n const value = await processor(item, index);\n results.push({ index, value });\n } catch (error) {\n errors.push({\n index,\n error: normalizeUnknownError(error),\n });\n }\n }\n };\n\n const workerCount = Math.min(itemCount, concurrency);\n const workers: Promise<void>[] = new Array<Promise<void>>(workerCount);\n for (let index = 0; index < workerCount; index += 1) {\n workers[index] = next();\n }\n\n await Promise.allSettled(workers);\n // A deadline (AbortSignal.timeout) hit during the run surfaces here as\n // signal.reason — a TimeoutError — rather than a fresh AbortError, so\n // callers see TIMEOUT, not CANCELLED. Only a run that actually skipped\n // items reports the abort: a deadline firing after the last item\n // finished leaves a complete batch, and returning it beats telling the\n // caller that committed writes failed. The per-item throws above are\n // swallowed by allSettled.\n if (truncated) signal?.throwIfAborted();\n\n results.sort((left, right) => left.index - right.index);\n return { results, errors };\n}\n\n/**\n * The streaming sibling of {@link processInParallel}: bounded-concurrency\n * dispatch over an AsyncIterable that may be far larger than the caller wants\n * to walk, so it stops early and reports why.\n *\n * `maxEntries` caps how many entries are dispatched (maxFiles); `shouldStop`\n * lets the caller stop on its own accumulating result count (maxResults). Both\n * are checked before dispatch, so the returned reason is exactly one — the\n * loop breaks on the first that fires. `undefined` means every entry was\n * dispatched.\n */\nexport async function processEntriesConcurrently(\n entries: AsyncIterable<{ path: string }> | Iterable<{ path: string }>,\n options: {\n signal: AbortSignal | undefined;\n concurrency: number;\n maxEntries?: number;\n shouldStop?: () => boolean;\n onEntry: () => void;\n onError?: (entryPath: string, err: unknown) => void;\n runEntry: (entryPath: string) => Promise<void>;\n },\n): Promise<StoppedReason | undefined> {\n const pending = new Set<Promise<void>>();\n const { signal, concurrency, maxEntries, shouldStop, onEntry, onError, runEntry } = options;\n let stoppedReason: StoppedReason | undefined;\n let dispatched = 0;\n\n const waitForSlot = async (): Promise<void> => {\n if (pending.size < concurrency) return;\n await Promise.race(pending);\n };\n\n for await (const entry of entries) {\n // The signal is cancellation OR the caller's timeout: stop dispatching and\n // let the caller report the run as incomplete rather than as a full sweep.\n if (signal?.aborted) {\n stoppedReason = 'timeout';\n break;\n }\n if (maxEntries !== undefined && dispatched >= maxEntries) {\n stoppedReason = 'maxFiles';\n break;\n }\n // Check the result cap before waiting for a slot so an in-flight task that\n // already crossed the cap stops dispatch without an extra wait...\n if (shouldStop?.()) {\n stoppedReason = 'maxResults';\n break;\n }\n await waitForSlot();\n // ...and again after the slot frees, since tasks settle concurrently. The\n // cap can still be exceeded by at most `concurrency - 1` already-dispatched\n // tasks that are mid-flight; that overrun is inherent to concurrent dispatch.\n if (shouldStop?.()) {\n stoppedReason = 'maxResults';\n break;\n }\n onEntry();\n dispatched++;\n\n // Track a non-rejecting wrapper so a rejected task can never propagate out of\n // Promise.race(pending) in waitForSlot() and abort the loop before the final\n // drain below (which would silently abandon other in-flight tasks).\n // runEntry is expected to catch its own errors; if it unexpectedly throws,\n // record it as a failure rather than silently dropping it.\n const tracked = runEntry(entry.path).catch((err: unknown) => {\n onError?.(entry.path, err);\n });\n pending.add(tracked);\n void tracked.finally(() => {\n pending.delete(tracked);\n });\n }\n\n if (pending.size > 0) {\n await Promise.allSettled([...pending]);\n }\n\n return stoppedReason;\n}\n\nexport function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) {\n return promise;\n }\n signal.throwIfAborted();\n let onAbort: (() => void) | undefined;\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n onAbort = () => {\n reject(signal.reason instanceof Error ? signal.reason : new Error('Operation aborted'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ]).finally(() => {\n if (onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n });\n}\n"]}
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from 'zod/v4';\n\nimport { normalizeUnknownError } from './errors.ts';\nimport { PARALLEL_CONCURRENCY } from './util.ts';\n\nexport type StoppedReason = 'maxResults' | 'maxFiles' | 'timeout';\n\nexport const StoppedReasonSchema = z.enum(['maxResults', 'maxFiles', 'timeout']).optional();\n\n/**\n * The subset `search_text` and `find_files` can actually emit. Both scans\n * resolve the stop through `resolveStopReason` below, which answers only\n * `maxResults` or `timeout` — never `maxFiles` — so the three-value enum\n * published a value no response can carry and disagreed with both tools' own\n * descriptions.\n */\nexport const SearchStoppedReasonSchema = z.enum(['maxResults', 'timeout']).optional();\n\n/** The one precedence both scans share: the result cap wins over an abort that fired the same iteration. */\nexport function resolveStopReason(\n hitCap: boolean,\n aborted: boolean,\n): 'maxResults' | 'timeout' | undefined {\n return hitCap ? 'maxResults' : aborted ? 'timeout' : undefined;\n}\n\ninterface ParallelResult<R> {\n results: { index: number; value: R }[];\n errors: { index: number; error: Error }[];\n}\n\nexport async function processInParallel<T, R>(\n items: readonly T[],\n processor: (item: T, index: number) => Promise<R>,\n concurrency: number = PARALLEL_CONCURRENCY,\n signal?: AbortSignal,\n): Promise<ParallelResult<R>> {\n const itemCount = items.length;\n if (itemCount === 0) return { results: [], errors: [] };\n\n const results: { index: number; value: R }[] = [];\n const errors: { index: number; error: Error }[] = [];\n\n signal?.throwIfAborted();\n\n let nextIndex = 0;\n // Set when a worker saw the signal fire before an item ran — the only\n // shape in which the batch is genuinely incomplete. The annotation\n // matters: without it control-flow analysis keeps the `false` narrowing\n // from the initializer and cannot see the closure assignment.\n let truncated = false as boolean;\n\n const next = async (): Promise<void> => {\n while (nextIndex < itemCount) {\n if (signal?.aborted) {\n truncated = true;\n signal.throwIfAborted();\n }\n\n const index = nextIndex;\n nextIndex += 1;\n const item = items[index];\n if (item === undefined) {\n continue;\n }\n\n try {\n // No abort check after the processor: once an item has run, its\n // result is real work that a write caller may have committed to\n // disk. Discarding it here would report a finished append as\n // failed, and a client retry would append twice.\n const value = await processor(item, index);\n results.push({ index, value });\n } catch (error) {\n errors.push({\n index,\n error: normalizeUnknownError(error),\n });\n }\n }\n };\n\n const workerCount = Math.min(itemCount, concurrency);\n const workers: Promise<void>[] = new Array<Promise<void>>(workerCount);\n for (let index = 0; index < workerCount; index += 1) {\n workers[index] = next();\n }\n\n await Promise.allSettled(workers);\n // A deadline (AbortSignal.timeout) hit during the run surfaces here as\n // signal.reason — a TimeoutError — rather than a fresh AbortError, so\n // callers see TIMEOUT, not CANCELLED. Only a run that actually skipped\n // items reports the abort: a deadline firing after the last item\n // finished leaves a complete batch, and returning it beats telling the\n // caller that committed writes failed. The per-item throws above are\n // swallowed by allSettled.\n if (truncated) signal?.throwIfAborted();\n\n results.sort((left, right) => left.index - right.index);\n return { results, errors };\n}\n\n/**\n * The streaming sibling of {@link processInParallel}: bounded-concurrency\n * dispatch over an AsyncIterable that may be far larger than the caller wants\n * to walk, so it stops early and reports why.\n *\n * `maxEntries` caps how many entries are dispatched (maxFiles); `shouldStop`\n * lets the caller stop on its own accumulating result count (maxResults). Both\n * are checked before dispatch, so the returned reason is exactly one — the\n * loop breaks on the first that fires. `undefined` means every entry was\n * dispatched.\n */\nexport async function processEntriesConcurrently(\n entries: AsyncIterable<{ path: string }> | Iterable<{ path: string }>,\n options: {\n signal: AbortSignal | undefined;\n concurrency: number;\n maxEntries?: number;\n shouldStop?: () => boolean;\n onEntry: () => void;\n onError?: (entryPath: string, err: unknown) => void;\n runEntry: (entryPath: string) => Promise<void>;\n },\n): Promise<StoppedReason | undefined> {\n const pending = new Set<Promise<void>>();\n const { signal, concurrency, maxEntries, shouldStop, onEntry, onError, runEntry } = options;\n let stoppedReason: StoppedReason | undefined;\n let dispatched = 0;\n\n const waitForSlot = async (): Promise<void> => {\n if (pending.size < concurrency) return;\n await Promise.race(pending);\n };\n\n for await (const entry of entries) {\n // The signal is cancellation OR the caller's timeout: stop dispatching and\n // let the caller report the run as incomplete rather than as a full sweep.\n if (signal?.aborted) {\n stoppedReason = 'timeout';\n break;\n }\n if (maxEntries !== undefined && dispatched >= maxEntries) {\n stoppedReason = 'maxFiles';\n break;\n }\n // Check the result cap before waiting for a slot so an in-flight task that\n // already crossed the cap stops dispatch without an extra wait...\n if (shouldStop?.()) {\n stoppedReason = 'maxResults';\n break;\n }\n await waitForSlot();\n // ...and again after the slot frees, since tasks settle concurrently. The\n // cap can still be exceeded by at most `concurrency - 1` already-dispatched\n // tasks that are mid-flight; that overrun is inherent to concurrent dispatch.\n if (shouldStop?.()) {\n stoppedReason = 'maxResults';\n break;\n }\n onEntry();\n dispatched++;\n\n // Track a non-rejecting wrapper so a rejected task can never propagate out of\n // Promise.race(pending) in waitForSlot() and abort the loop before the final\n // drain below (which would silently abandon other in-flight tasks).\n // runEntry is expected to catch its own errors; if it unexpectedly throws,\n // record it as a failure rather than silently dropping it.\n const tracked = runEntry(entry.path).catch((err: unknown) => {\n onError?.(entry.path, err);\n });\n pending.add(tracked);\n void tracked.finally(() => {\n pending.delete(tracked);\n });\n }\n\n if (pending.size > 0) {\n await Promise.allSettled([...pending]);\n }\n\n return stoppedReason;\n}\n\nexport function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) {\n return promise;\n }\n signal.throwIfAborted();\n let onAbort: (() => void) | undefined;\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n onAbort = () => {\n reject(signal.reason instanceof Error ? signal.reason : new Error('Operation aborted'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ]).finally(() => {\n if (onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n });\n}\n"]}
@@ -18,7 +18,7 @@ export interface CliOverrides {
18
18
  allowCwdWalk?: boolean;
19
19
  /** `--allow-missing-roots` */
20
20
  allowMissingRoots?: boolean;
21
- /** `--deny` entries (merged with DENYLIST by the reader) */
21
+ /** `--deny` entries (merged with FS_DENYLIST by the reader) */
22
22
  denyPatterns?: readonly string[];
23
23
  /** `--allow` entries (merged with FS_ALLOWLIST by the reader; relieve built-ins only) */
24
24
  allowPatterns?: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,+NACqL,CAAC;AAE/N,MAAM,WAAW,YAAY;IAC3B,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8BAA8B;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,yFAAyF;IACzF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,iFAAiF;AACjF,eAAO,MAAM,GAAG,EAAE,YAAiB,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,+NACqL,CAAC;AAE/N,MAAM,WAAW,YAAY;IAC3B,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8BAA8B;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,yFAAyF;IACzF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,iFAAiF;AACjF,eAAO,MAAM,GAAG,EAAE,YAAiB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,kDAAkD;AAElD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GACvC,4NAA4N,CAAC;AAqB/N,iFAAiF;AACjF,MAAM,CAAC,MAAM,GAAG,GAAiB,EAAE,CAAC","sourcesContent":["// Resolved CLI-flag overrides — the one owner of the flag-beats-env rule.\n//\n// `parseArgs` (cli.ts) used to write flag values back into `process.env` so\n// deep core readers (path, sensitive, observability, util) would pick them up.\n// That made the environment a hidden mutable config bus. Instead, cli.ts\n// assigns onto `cli` once at startup and each reader consults it first, falling\n// back to the operator's environment. This module deliberately has zero imports\n// so any core module can read it without a cycle.\n\n/**\n * How an operator gives this server a root. Lives here rather than beside its\n * readers because both ends of the repo need it — `index.ts` prints it at\n * startup, `list_roots` returns it when it has nothing to list — and this\n * module is the one place either can import without a cycle.\n */\nexport const NO_POSITIONAL_ROOTS_GUIDANCE =\n 'No positional directories specified. Configure roots with directory arguments, FS_ALLOWED_DIRS, or --allow-cwd. Modern clients with elicitation can also call a tool with a concrete path and approve the requested grant.';\n\nexport interface CliOverrides {\n /** `--log-level` */\n logLevel?: string;\n /** `--max-file-size` (raw string; validated by the reader like the env var) */\n maxFileSize?: string;\n /** `--root-boundary` */\n rootBoundary?: string;\n /** `--allow-sensitive` */\n allowSensitive?: boolean;\n /** `--walk-cwd` */\n allowCwdWalk?: boolean;\n /** `--allow-missing-roots` */\n allowMissingRoots?: boolean;\n /** `--deny` entries (merged with DENYLIST by the reader) */\n denyPatterns?: readonly string[];\n /** `--allow` entries (merged with FS_ALLOWLIST by the reader; relieve built-ins only) */\n allowPatterns?: readonly string[];\n}\n\n/** Written once by cli.ts; an absent key means \"not set on the command line\". */\nexport const cli: CliOverrides = {};\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,kDAAkD;AAElD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GACvC,4NAA4N,CAAC;AAqB/N,iFAAiF;AACjF,MAAM,CAAC,MAAM,GAAG,GAAiB,EAAE,CAAC","sourcesContent":["// Resolved CLI-flag overrides — the one owner of the flag-beats-env rule.\n//\n// `parseArgs` (cli.ts) used to write flag values back into `process.env` so\n// deep core readers (path, sensitive, observability, util) would pick them up.\n// That made the environment a hidden mutable config bus. Instead, cli.ts\n// assigns onto `cli` once at startup and each reader consults it first, falling\n// back to the operator's environment. This module deliberately has zero imports\n// so any core module can read it without a cycle.\n\n/**\n * How an operator gives this server a root. Lives here rather than beside its\n * readers because both ends of the repo need it — `index.ts` prints it at\n * startup, `list_roots` returns it when it has nothing to list — and this\n * module is the one place either can import without a cycle.\n */\nexport const NO_POSITIONAL_ROOTS_GUIDANCE =\n 'No positional directories specified. Configure roots with directory arguments, FS_ALLOWED_DIRS, or --allow-cwd. Modern clients with elicitation can also call a tool with a concrete path and approve the requested grant.';\n\nexport interface CliOverrides {\n /** `--log-level` */\n logLevel?: string;\n /** `--max-file-size` (raw string; validated by the reader like the env var) */\n maxFileSize?: string;\n /** `--root-boundary` */\n rootBoundary?: string;\n /** `--allow-sensitive` */\n allowSensitive?: boolean;\n /** `--walk-cwd` */\n allowCwdWalk?: boolean;\n /** `--allow-missing-roots` */\n allowMissingRoots?: boolean;\n /** `--deny` entries (merged with FS_DENYLIST by the reader) */\n denyPatterns?: readonly string[];\n /** `--allow` entries (merged with FS_ALLOWLIST by the reader; relieve built-ins only) */\n allowPatterns?: readonly string[];\n}\n\n/** Written once by cli.ts; an absent key means \"not set on the command line\". */\nexport const cli: CliOverrides = {};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAgClE;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;IACjB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACxD,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE;IAC9C,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC;CACrE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAwBlC"}
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAgClE;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;IACjB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACxD,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE;IAC9C,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC;CACrE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CA+BlC"}
@@ -1,3 +1,4 @@
1
+ import { ErrorCode, isFsError } from "./errors.js";
1
2
  import { invalidCursor } from "./store.js";
2
3
  function encodePageCursor(cursor) {
3
4
  return Buffer.from(JSON.stringify(cursor)).toString('base64url');
@@ -46,7 +47,16 @@ export async function paginate(params) {
46
47
  }), 0, params.pageSize, { items, metadata });
47
48
  if (!incomplete || params.externalize === undefined)
48
49
  return first;
49
- return { ...first, resource: params.externalize(produced.items, produced.metadata) };
50
+ try {
51
+ return { ...first, resource: params.externalize(produced.items, produced.metadata) };
52
+ }
53
+ catch (error) {
54
+ // The store refuses an entry over its byte cap. The page itself is
55
+ // fine; the caller simply gets no full-set resource for this query.
56
+ if (isFsError(error) && error.code === ErrorCode.TOO_LARGE)
57
+ return first;
58
+ throw error;
59
+ }
50
60
  }
51
61
  function decodePageCursor(cursor) {
52
62
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PageSnapshot, PageSnapshotStore } from './store.ts';\nimport { invalidCursor } from './store.ts';\n\ninterface PageCursor {\n readonly snapshotId: string;\n readonly offset: number;\n}\n\nfunction encodePageCursor(cursor: PageCursor): string {\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction pageResult<T, M>(\n snapshotId: string,\n offset: number,\n pageSize: number,\n snapshot: PageSnapshot<T, M>,\n): Page<T, M> {\n if (offset >= snapshot.items.length) throw invalidCursor();\n const page = snapshot.items.slice(offset, offset + pageSize);\n const nextOffset = offset + page.length;\n return {\n page,\n metadata: snapshot.metadata,\n offset,\n nextCursor:\n nextOffset < snapshot.items.length\n ? encodePageCursor({ snapshotId, offset: nextOffset })\n : undefined,\n };\n}\n\n/**\n * The full result set a paged tool produced, before slicing. `truncated` is the\n * engine's own stop state — the hard result cap or a timeout cut the set — and\n * says nothing about page size.\n */\nexport interface ProducedPage<T, M> {\n readonly items: readonly T[];\n readonly metadata: M;\n readonly truncated: boolean;\n}\n\ninterface Page<T, M> {\n readonly page: readonly T[];\n readonly metadata: M;\n readonly nextCursor: string | undefined;\n /**\n * Index of this page's first item in the full set. A tool that reports\n * progress in its text needs it: without it every page can only say how many\n * rows it holds, which is the same number on page 1 and page 9.\n */\n readonly offset: number;\n}\n\nexport interface PaginatedPage<T, M, R> extends Page<T, M> {\n /** The externalized full set. First page only, and only when incomplete. */\n readonly resource?: R;\n}\n\n/**\n * The one replay-or-produce branch every paged tool runs. A cursor replays a\n * stored snapshot; without one, `produce` runs the query and its result is\n * snapshotted for later pages.\n *\n * The response is incomplete when the set spans more than one page or the\n * engine truncated it. Then — and only on the first page — `externalize`\n * stores the full set and its handle rides back as `resource`. Replayed pages\n * never carry it: the stored entry expires on the resource store's own TTL and\n * LRU, not this snapshot's, so replaying its URI would hand back a dead\n * pointer. Nor do they mint a new one; the set was already stored whole.\n */\nexport async function paginate<T, M, R>(params: {\n store: PageSnapshotStore;\n queryKey: string;\n cursor: string | undefined;\n pageSize: number;\n produce: () => Promise<ProducedPage<T, M>>;\n externalize?: ((items: readonly T[], metadata: M) => R) | undefined;\n}): Promise<PaginatedPage<T, M, R>> {\n if (params.cursor !== undefined) {\n const decoded = decodePageCursor(params.cursor);\n const snapshot = params.store.read<T, M>(decoded.snapshotId, params.queryKey);\n return pageResult(decoded.snapshotId, decoded.offset, params.pageSize, snapshot);\n }\n const produced = await params.produce();\n const { items, metadata } = produced;\n const incomplete = items.length > params.pageSize || produced.truncated;\n const first: Page<T, M> =\n items.length <= params.pageSize\n ? { page: items, metadata, nextCursor: undefined, offset: 0 }\n : pageResult(\n params.store.create({\n queryKey: params.queryKey,\n items,\n metadata,\n }),\n 0,\n params.pageSize,\n { items, metadata },\n );\n if (!incomplete || params.externalize === undefined) return first;\n return { ...first, resource: params.externalize(produced.items, produced.metadata) };\n}\n\nfunction decodePageCursor(cursor: string): PageCursor {\n try {\n const text = Buffer.from(cursor, 'base64url').toString('utf-8');\n const parsed = JSON.parse(text) as { snapshotId?: unknown; offset?: unknown };\n if (\n typeof parsed.snapshotId === 'string' &&\n parsed.snapshotId.length > 0 &&\n typeof parsed.offset === 'number' &&\n Number.isInteger(parsed.offset) &&\n parsed.offset >= 0\n ) {\n return { snapshotId: parsed.snapshotId, offset: parsed.offset };\n }\n throw new Error('Invalid page cursor');\n } catch {\n throw invalidCursor();\n }\n}\n"]}
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"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","sourcesContent":["import { ErrorCode, isFsError } from './errors.ts';\nimport type { PageSnapshot, PageSnapshotStore } from './store.ts';\nimport { invalidCursor } from './store.ts';\n\ninterface PageCursor {\n readonly snapshotId: string;\n readonly offset: number;\n}\n\nfunction encodePageCursor(cursor: PageCursor): string {\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction pageResult<T, M>(\n snapshotId: string,\n offset: number,\n pageSize: number,\n snapshot: PageSnapshot<T, M>,\n): Page<T, M> {\n if (offset >= snapshot.items.length) throw invalidCursor();\n const page = snapshot.items.slice(offset, offset + pageSize);\n const nextOffset = offset + page.length;\n return {\n page,\n metadata: snapshot.metadata,\n offset,\n nextCursor:\n nextOffset < snapshot.items.length\n ? encodePageCursor({ snapshotId, offset: nextOffset })\n : undefined,\n };\n}\n\n/**\n * The full result set a paged tool produced, before slicing. `truncated` is the\n * engine's own stop state — the hard result cap or a timeout cut the set — and\n * says nothing about page size.\n */\nexport interface ProducedPage<T, M> {\n readonly items: readonly T[];\n readonly metadata: M;\n readonly truncated: boolean;\n}\n\ninterface Page<T, M> {\n readonly page: readonly T[];\n readonly metadata: M;\n readonly nextCursor: string | undefined;\n /**\n * Index of this page's first item in the full set. A tool that reports\n * progress in its text needs it: without it every page can only say how many\n * rows it holds, which is the same number on page 1 and page 9.\n */\n readonly offset: number;\n}\n\nexport interface PaginatedPage<T, M, R> extends Page<T, M> {\n /** The externalized full set. First page only, and only when incomplete. */\n readonly resource?: R;\n}\n\n/**\n * The one replay-or-produce branch every paged tool runs. A cursor replays a\n * stored snapshot; without one, `produce` runs the query and its result is\n * snapshotted for later pages.\n *\n * The response is incomplete when the set spans more than one page or the\n * engine truncated it. Then — and only on the first page — `externalize`\n * stores the full set and its handle rides back as `resource`. Replayed pages\n * never carry it: the stored entry expires on the resource store's own TTL and\n * LRU, not this snapshot's, so replaying its URI would hand back a dead\n * pointer. Nor do they mint a new one; the set was already stored whole.\n */\nexport async function paginate<T, M, R>(params: {\n store: PageSnapshotStore;\n queryKey: string;\n cursor: string | undefined;\n pageSize: number;\n produce: () => Promise<ProducedPage<T, M>>;\n externalize?: ((items: readonly T[], metadata: M) => R) | undefined;\n}): Promise<PaginatedPage<T, M, R>> {\n if (params.cursor !== undefined) {\n const decoded = decodePageCursor(params.cursor);\n const snapshot = params.store.read<T, M>(decoded.snapshotId, params.queryKey);\n return pageResult(decoded.snapshotId, decoded.offset, params.pageSize, snapshot);\n }\n const produced = await params.produce();\n const { items, metadata } = produced;\n const incomplete = items.length > params.pageSize || produced.truncated;\n const first: Page<T, M> =\n items.length <= params.pageSize\n ? { page: items, metadata, nextCursor: undefined, offset: 0 }\n : pageResult(\n params.store.create({\n queryKey: params.queryKey,\n items,\n metadata,\n }),\n 0,\n params.pageSize,\n { items, metadata },\n );\n if (!incomplete || params.externalize === undefined) return first;\n try {\n return { ...first, resource: params.externalize(produced.items, produced.metadata) };\n } catch (error) {\n // The store refuses an entry over its byte cap. The page itself is\n // fine; the caller simply gets no full-set resource for this query.\n if (isFsError(error) && error.code === ErrorCode.TOO_LARGE) return first;\n throw error;\n }\n}\n\nfunction decodePageCursor(cursor: string): PageCursor {\n try {\n const text = Buffer.from(cursor, 'base64url').toString('utf-8');\n const parsed = JSON.parse(text) as { snapshotId?: unknown; offset?: unknown };\n if (\n typeof parsed.snapshotId === 'string' &&\n parsed.snapshotId.length > 0 &&\n typeof parsed.offset === 'number' &&\n Number.isInteger(parsed.offset) &&\n parsed.offset >= 0\n ) {\n return { snapshotId: parsed.snapshotId, offset: parsed.offset };\n }\n throw new Error('Invalid page cursor');\n } catch {\n throw invalidCursor();\n }\n}\n"]}
@@ -1,6 +1,19 @@
1
- export declare function computeDiffStats(original: string, modified: string): {
1
+ /**
2
+ * Wall-clock bound for one Myers diff. The algorithm is synchronous and
3
+ * quadratic in differing lines (8k changed lines ≈ 11 s), and neither the tool
4
+ * timeout nor the abort signal can interrupt it. Past the deadline the `diff`
5
+ * package returns `undefined`; callers degrade (no preview / no counts) instead
6
+ * of freezing the process.
7
+ */
8
+ export declare const DIFF_TIMEOUT_MS = 1000;
9
+ export declare function computeDiffStats(original: string, modified: string, timeoutMs?: number): {
10
+ linesAdded: number;
11
+ linesRemoved: number;
12
+ } | undefined;
13
+ export declare function unifiedPatch(label: string, original: string, modified: string, timeoutMs?: number): string | undefined;
14
+ /** Added/removed line counts read off a unified patch — no second diff pass. */
15
+ export declare function diffStatsFromPatch(patch: string): {
2
16
  linesAdded: number;
3
17
  linesRemoved: number;
4
18
  };
5
- export declare function unifiedPatch(label: string, original: string, modified: string): string;
6
19
  //# sourceMappingURL=diff.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/core/diff.ts"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAS9C;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtF"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/core/diff.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAkB,GAC1B;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAU1D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAkB,GAC1B,MAAM,GAAG,SAAS,CAIpB;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAgB9F"}
package/dist/core/diff.js CHANGED
@@ -1,11 +1,21 @@
1
1
  import { createTwoFilesPatch, diffLines } from 'diff';
2
2
  // Unified-diff computation over two text buffers. Kept out of `fmt.ts`, which
3
3
  // formats terminal output and has no business pulling in the `diff` package.
4
- export function computeDiffStats(original, modified) {
5
- // diffLines returns the change list synchronously on diff v9.
4
+ /**
5
+ * Wall-clock bound for one Myers diff. The algorithm is synchronous and
6
+ * quadratic in differing lines (8k changed lines ≈ 11 s), and neither the tool
7
+ * timeout nor the abort signal can interrupt it. Past the deadline the `diff`
8
+ * package returns `undefined`; callers degrade (no preview / no counts) instead
9
+ * of freezing the process.
10
+ */
11
+ export const DIFF_TIMEOUT_MS = 1000;
12
+ export function computeDiffStats(original, modified, timeoutMs = DIFF_TIMEOUT_MS) {
13
+ const parts = diffLines(original, modified, { timeout: timeoutMs });
14
+ if (parts === undefined)
15
+ return undefined;
6
16
  let linesAdded = 0;
7
17
  let linesRemoved = 0;
8
- for (const part of diffLines(original, modified)) {
18
+ for (const part of parts) {
9
19
  if (part.added)
10
20
  linesAdded += part.count;
11
21
  else if (part.removed)
@@ -13,9 +23,30 @@ export function computeDiffStats(original, modified) {
13
23
  }
14
24
  return { linesAdded, linesRemoved };
15
25
  }
16
- // createTwoFilesPatch returns the unified diff string synchronously on diff v9
17
- // (the { callback } option fires via setTimeout and returns undefined).
18
- export function unifiedPatch(label, original, modified) {
19
- return createTwoFilesPatch(label, label, original, modified, 'Original', 'Modified');
26
+ export function unifiedPatch(label, original, modified, timeoutMs = DIFF_TIMEOUT_MS) {
27
+ return createTwoFilesPatch(label, label, original, modified, 'Original', 'Modified', {
28
+ timeout: timeoutMs,
29
+ });
30
+ }
31
+ /** Added/removed line counts read off a unified patch — no second diff pass. */
32
+ export function diffStatsFromPatch(patch) {
33
+ let linesAdded = 0;
34
+ let linesRemoved = 0;
35
+ // The file header (---/+++) precedes the first hunk; only hunk bodies count,
36
+ // so a content line that itself starts with -- or ++ is never mistaken for it.
37
+ let inHunk = false;
38
+ for (const line of patch.split('\n')) {
39
+ if (line.startsWith('@@')) {
40
+ inHunk = true;
41
+ continue;
42
+ }
43
+ if (!inHunk)
44
+ continue;
45
+ if (line.startsWith('+'))
46
+ linesAdded++;
47
+ else if (line.startsWith('-'))
48
+ linesRemoved++;
49
+ }
50
+ return { linesAdded, linesRemoved };
20
51
  }
21
52
  //# sourceMappingURL=diff.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/core/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtD,8EAA8E;AAC9E,6EAA6E;AAE7E,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,QAAgB;IAEhB,8DAA8D;IAC9D,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;QACjD,IAAI,IAAI,CAAC,KAAK;YAAE,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC;aACpC,IAAI,IAAI,CAAC,OAAO;YAAE,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC;IACpD,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;AACtC,CAAC;AAED,+EAA+E;AAC/E,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAAgB,EAAE,QAAgB;IAC5E,OAAO,mBAAmB,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AACvF,CAAC","sourcesContent":["import { createTwoFilesPatch, diffLines } from 'diff';\n\n// Unified-diff computation over two text buffers. Kept out of `fmt.ts`, which\n// formats terminal output and has no business pulling in the `diff` package.\n\nexport function computeDiffStats(\n original: string,\n modified: string,\n): { linesAdded: number; linesRemoved: number } {\n // diffLines returns the change list synchronously on diff v9.\n let linesAdded = 0;\n let linesRemoved = 0;\n for (const part of diffLines(original, modified)) {\n if (part.added) linesAdded += part.count;\n else if (part.removed) linesRemoved += part.count;\n }\n return { linesAdded, linesRemoved };\n}\n\n// createTwoFilesPatch returns the unified diff string synchronously on diff v9\n// (the { callback } option fires via setTimeout and returns undefined).\nexport function unifiedPatch(label: string, original: string, modified: string): string {\n return createTwoFilesPatch(label, label, original, modified, 'Original', 'Modified');\n}\n"]}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/core/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtD,8EAA8E;AAC9E,6EAA6E;AAE7E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,QAAgB,EAChB,SAAS,GAAG,eAAe;IAE3B,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IACpE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK;YAAE,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC;aACpC,IAAI,IAAI,CAAC,OAAO;YAAE,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC;IACpD,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,KAAa,EACb,QAAgB,EAChB,QAAgB,EAChB,SAAS,GAAG,eAAe;IAE3B,OAAO,mBAAmB,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE;QACnF,OAAO,EAAE,SAAS;KACnB,CAAC,CAAC;AACL,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,6EAA6E;IAC7E,+EAA+E;IAC/E,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,GAAG,IAAI,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,UAAU,EAAE,CAAC;aAClC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,YAAY,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;AACtC,CAAC","sourcesContent":["import { createTwoFilesPatch, diffLines } from 'diff';\n\n// Unified-diff computation over two text buffers. Kept out of `fmt.ts`, which\n// formats terminal output and has no business pulling in the `diff` package.\n\n/**\n * Wall-clock bound for one Myers diff. The algorithm is synchronous and\n * quadratic in differing lines (8k changed lines ≈ 11 s), and neither the tool\n * timeout nor the abort signal can interrupt it. Past the deadline the `diff`\n * package returns `undefined`; callers degrade (no preview / no counts) instead\n * of freezing the process.\n */\nexport const DIFF_TIMEOUT_MS = 1000;\n\nexport function computeDiffStats(\n original: string,\n modified: string,\n timeoutMs = DIFF_TIMEOUT_MS,\n): { linesAdded: number; linesRemoved: number } | undefined {\n const parts = diffLines(original, modified, { timeout: timeoutMs });\n if (parts === undefined) return undefined;\n let linesAdded = 0;\n let linesRemoved = 0;\n for (const part of parts) {\n if (part.added) linesAdded += part.count;\n else if (part.removed) linesRemoved += part.count;\n }\n return { linesAdded, linesRemoved };\n}\n\nexport function unifiedPatch(\n label: string,\n original: string,\n modified: string,\n timeoutMs = DIFF_TIMEOUT_MS,\n): string | undefined {\n return createTwoFilesPatch(label, label, original, modified, 'Original', 'Modified', {\n timeout: timeoutMs,\n });\n}\n\n/** Added/removed line counts read off a unified patch — no second diff pass. */\nexport function diffStatsFromPatch(patch: string): { linesAdded: number; linesRemoved: number } {\n let linesAdded = 0;\n let linesRemoved = 0;\n // The file header (---/+++) precedes the first hunk; only hunk bodies count,\n // so a content line that itself starts with -- or ++ is never mistaken for it.\n let inHunk = false;\n for (const line of patch.split('\\n')) {\n if (line.startsWith('@@')) {\n inHunk = true;\n continue;\n }\n if (!inHunk) continue;\n if (line.startsWith('+')) linesAdded++;\n else if (line.startsWith('-')) linesRemoved++;\n }\n return { linesAdded, linesRemoved };\n}\n"]}
@@ -1,4 +1,3 @@
1
- import { ProtocolErrorCode } from '@modelcontextprotocol/server';
2
1
  export declare const ErrorCode: {
3
2
  readonly ACCESS_DENIED: "ACCESS_DENIED";
4
3
  readonly NOT_FOUND: "NOT_FOUND";
@@ -32,15 +31,15 @@ export declare const Problem: {
32
31
  readonly fromUnknown: (error: unknown, defaultCode: ErrorCode, path?: string) => Problem;
33
32
  readonly toText: (error: unknown, defaultCode: ErrorCode) => string;
34
33
  };
35
- export declare const ERRNO_MAP: Readonly<Record<string, ErrorCode>>;
36
34
  export declare const SKIPPABLE_FS_CODES: ReadonlySet<ErrorCode>;
37
- export declare const SKIPPABLE_ERRNOS: ReadonlySet<string>;
35
+ /** The scan's facts as a Problem: an abort wins, then a timeout, then errno. */
36
+ export declare function classifyCauseChain(error: unknown): Problem;
38
37
  export declare function isFsError(error: unknown): error is FsError;
39
- /** FsError traces to a caller-supplied argument; anything else is server-side. */
40
- export declare function fsErrorCode(error: unknown): ProtocolErrorCode;
41
38
  export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
42
39
  /** True when `error` is a Node errno error with code `ENOENT` (path not found). */
43
40
  export declare function isNotFoundErrno(error: unknown): error is NodeJS.ErrnoException;
41
+ /** True when `error` is a Node errno error whose code is in `SKIPPABLE_ERRNOS`. */
42
+ export declare function isSkippableErrno(error: unknown): error is NodeJS.ErrnoException;
44
43
  /**
45
44
  * Rethrow when `error` reads as cancelled. Answers the question directly rather
46
45
  * than through `classify`, which allocated a Problem — and ran
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,eAAO,MAAM,SAAS;;;;;;;;;;;;;;CAcZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,qBAAqB;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,eAAO,MAAM,OAAO;iCACE,MAAM,MAAM,qBAAqB,KAAG,OAAO;iCAE3C,MAAM,MAAM,qBAAqB,KAAG,OAAO;8BAE9C,MAAM,MAAM,qBAAqB,KAAG,OAAO;kCAEzC,OAAO,eAAe,SAAS,SAAS,MAAM,KAAG,OAAO;6BAe7D,OAAO,eAAe,SAAS,KAAG,MAAM;CAG9C,CAAC;AAcX,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAezD,CAAC;AAOF,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,SAAS,CAOpD,CAAC;AAEH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA0C,CAAC;AAsD5F,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAE7D;AAmBD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAK1E;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAE9E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAMrD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQhE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAE3D;AAeD,qBAAa,OAAQ,SAAQ,KAAK;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;IAY5E,IAAI,IAAI,IAAI,SAAS,CAEpB;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS;;;;;;;;;;;;;;CAcZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,qBAAqB;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,eAAO,MAAM,OAAO;iCACE,MAAM,MAAM,qBAAqB,KAAG,OAAO;iCAE3C,MAAM,MAAM,qBAAqB,KAAG,OAAO;8BAE9C,MAAM,MAAM,qBAAqB,KAAG,OAAO;kCAEzC,OAAO,eAAe,SAAS,SAAS,MAAM,KAAG,OAAO;6BAe7D,OAAO,eAAe,SAAS,KAAG,MAAM;CAG9C,CAAC;AAoCX,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,SAAS,CAOpD,CAAC;AA0CH,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW1D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAoBD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAK1E;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAE9E;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAE/E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAMrD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQhE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAE3D;AAeD,qBAAa,OAAQ,SAAQ,KAAK;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;IAY5E,IAAI,IAAI,IAAI,SAAS,CAEpB;CACF"}