@j0hanz/filesystem-mcp 2.6.0 → 2.6.2

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.
@@ -21,24 +21,38 @@ export declare function buildFileResourceUri(validPath: string): string;
21
21
  * disagree hand the client two subscriptions for one file.
22
22
  */
23
23
  export declare function buildFileResourceLinkFor(uri: string, name: string, mimeType: string, size: number): ContentBlock;
24
- export declare function buildFileResourceLink(validPath: string, mimeType: string, size: number): ContentBlock;
25
24
  export declare function extractPath(uri: string): string | undefined;
26
25
  export interface WrittenFileMeta {
27
26
  size: number;
28
27
  lineCount: number;
29
28
  mimeType: string;
30
29
  kind: FileKind;
31
- /**
32
- * Undefined when the resulting file exceeds the text-size cap: the store
33
- * serves the URI via readRaw, which would reject it with TOO_LARGE.
34
- */
30
+ /** Undefined unless {@link writtenFileLinks} allows it: see there for when. */
35
31
  resourceUri: string | undefined;
36
- /** Undefined when no resource store is configured — nothing to link into. */
32
+ /** Undefined unless {@link writtenFileLinks} allows it: see there for when. */
37
33
  resourceLink: ContentBlock | undefined;
38
34
  }
39
35
  /**
40
- * The five-step block every write tool runs on its post-write content: size,
41
- * line count, MIME, the file's resource URI, and the store-gated link block.
36
+ * The URI and link a write tool may advertise for the file it wrote. None on a
37
+ * dry run — nothing was written, so the file on disk is still the one the
38
+ * caller already has. None when the size is unknown or over the text-size cap —
39
+ * the store serves the URI via readRaw, which would reject it with TOO_LARGE.
40
+ * No link without a store to link into.
42
41
  */
43
- export declare function buildWrittenFileMeta(validPath: string, content: string, resourceStore: ResourceStore | undefined): WrittenFileMeta;
42
+ export declare function writtenFileLinks(validPath: string, mimeType: string, size: number | undefined, options: {
43
+ resourceStore: ResourceStore | undefined;
44
+ dryRun?: boolean | undefined;
45
+ }): Pick<WrittenFileMeta, 'resourceUri' | 'resourceLink'>;
46
+ /**
47
+ * The block every write tool reports for the content it wrote (or, with
48
+ * `dryRun`, would have written): size, line count, MIME, and whatever
49
+ * {@link writtenFileLinks} allows it to advertise. Named fields, because
50
+ * `validPath` and `content` are both strings and a positional swap type-checks.
51
+ */
52
+ export declare function buildWrittenFileMeta(options: {
53
+ validPath: string;
54
+ content: string;
55
+ resourceStore: ResourceStore | undefined;
56
+ dryRun?: boolean | undefined;
57
+ }): WrittenFileMeta;
44
58
  //# sourceMappingURL=file-uri.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-uri.d.ts","sourceRoot":"","sources":["../../src/core/file-uri.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAMjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAOhD,eAAO,MAAM,4BAA4B,kCAAkC,CAAC;AAE5E;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,YAAY,CASd;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,YAAY,CAOd;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI3D;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf;;;OAGG;IACH,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,6EAA6E;IAC7E,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;CACxC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,aAAa,GAAG,SAAS,GACvC,eAAe,CAiBjB"}
1
+ {"version":3,"file":"file-uri.d.ts","sourceRoot":"","sources":["../../src/core/file-uri.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAMjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAOhD,eAAO,MAAM,4BAA4B,kCAAkC,CAAC;AAE5E;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,YAAY,CASd;AAWD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI3D;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,+EAA+E;IAC/E,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,+EAA+E;IAC/E,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE;IAAE,aAAa,EAAE,aAAa,GAAG,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,GAClF,IAAI,CAAC,eAAe,EAAE,aAAa,GAAG,cAAc,CAAC,CAUvD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,aAAa,GAAG,SAAS,CAAC;IACzC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B,GAAG,eAAe,CAWlB"}
@@ -46,7 +46,7 @@ export function buildFileResourceLinkFor(uri, name, mimeType, size) {
46
46
  annotations: { audience: ['user', 'assistant'] },
47
47
  };
48
48
  }
49
- export function buildFileResourceLink(validPath, mimeType, size) {
49
+ function buildFileResourceLink(validPath, mimeType, size) {
50
50
  return buildFileResourceLinkFor(buildFileResourceUri(validPath), basename(validPath), mimeType, size);
51
51
  }
52
52
  export function extractPath(uri) {
@@ -56,24 +56,39 @@ export function extractPath(uri) {
56
56
  return decodeFileUriPath(url.pathname.slice(1));
57
57
  }
58
58
  /**
59
- * The five-step block every write tool runs on its post-write content: size,
60
- * line count, MIME, the file's resource URI, and the store-gated link block.
59
+ * The URI and link a write tool may advertise for the file it wrote. None on a
60
+ * dry run — nothing was written, so the file on disk is still the one the
61
+ * caller already has. None when the size is unknown or over the text-size cap —
62
+ * the store serves the URI via readRaw, which would reject it with TOO_LARGE.
63
+ * No link without a store to link into.
61
64
  */
62
- export function buildWrittenFileMeta(validPath, content, resourceStore) {
65
+ export function writtenFileLinks(validPath, mimeType, size, options) {
66
+ if (options.dryRun || size === undefined || size > getMaxTextFileSize()) {
67
+ return { resourceUri: undefined, resourceLink: undefined };
68
+ }
69
+ return {
70
+ resourceUri: buildFileResourceUri(validPath),
71
+ resourceLink: options.resourceStore
72
+ ? buildFileResourceLink(validPath, mimeType, size)
73
+ : undefined,
74
+ };
75
+ }
76
+ /**
77
+ * The block every write tool reports for the content it wrote (or, with
78
+ * `dryRun`, would have written): size, line count, MIME, and whatever
79
+ * {@link writtenFileLinks} allows it to advertise. Named fields, because
80
+ * `validPath` and `content` are both strings and a positional swap type-checks.
81
+ */
82
+ export function buildWrittenFileMeta(options) {
83
+ const { validPath, content } = options;
63
84
  const size = Buffer.byteLength(content, 'utf-8');
64
- const mimeInfo = detectMimeFromContent(validPath, content);
65
- // An edit or patch can push a readable file past the text-size cap; the
66
- // store serves the URI via readRaw, which would reject it with TOO_LARGE.
67
- const servable = size <= getMaxTextFileSize();
85
+ const { mimeType, kind } = detectMimeFromContent(validPath, content);
68
86
  return {
69
87
  size,
70
88
  lineCount: countLines(content),
71
- mimeType: mimeInfo.mimeType,
72
- kind: mimeInfo.kind,
73
- resourceUri: servable ? buildFileResourceUri(validPath) : undefined,
74
- resourceLink: resourceStore && servable
75
- ? buildFileResourceLink(validPath, mimeInfo.mimeType, size)
76
- : undefined,
89
+ mimeType,
90
+ kind,
91
+ ...writtenFileLinks(validPath, mimeType, size, options),
77
92
  };
78
93
  }
79
94
  //# sourceMappingURL=file-uri.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-uri.js","sourceRoot":"","sources":["../../src/core/file-uri.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,iFAAiF;AACjF,+EAA+E;AAC/E,6EAA6E;AAE7E,MAAM,CAAC,MAAM,4BAA4B,GAAG,+BAA+B,CAAC;AAE5E;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO,yBAAyB,iBAAiB,CAAC,SAAS,CAAC,EAAE,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,GAAW,EACX,IAAY,EACZ,QAAgB,EAChB,IAAY;IAEZ,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,GAAG;QACH,IAAI;QACJ,QAAQ;QACR,IAAI;QACJ,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE;KACjD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,SAAiB,EACjB,QAAgB,EAChB,IAAY;IAEZ,OAAO,wBAAwB,CAC7B,oBAAoB,CAAC,SAAS,CAAC,EAC/B,QAAQ,CAAC,SAAS,CAAC,EACnB,QAAQ,EACR,IAAI,CACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,GAAG,EAAE,QAAQ,KAAK,iBAAiB,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IACjF,OAAO,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAgBD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAiB,EACjB,OAAe,EACf,aAAwC;IAExC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC3D,wEAAwE;IACxE,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,IAAI,IAAI,kBAAkB,EAAE,CAAC;IAC9C,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC;QAC9B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;QACnE,YAAY,EACV,aAAa,IAAI,QAAQ;YACvB,CAAC,CAAC,qBAAqB,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;YAC3D,CAAC,CAAC,SAAS;KAChB,CAAC;AACJ,CAAC","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename } from 'node:path';\n\nimport { detectMimeFromContent } from './mime.ts';\nimport { countLines } from './read.ts';\nimport type { FileKind } from './schema.ts';\nimport type { ResourceStore } from './store.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\n// Single owner of the `filesystem-mcp://file/` URI scheme — the template string,\n// the path→URI encoder, the URI→path decoder, the link blocks built from them,\n// and the post-write metadata block every write tool reports alongside them.\n\nexport const FILESYSTEM_FILE_URI_TEMPLATE = 'filesystem-mcp://file/{+path}';\n\n/**\n * The `{+path}` template-variable form of a path — what `completion/complete`\n * must return, since a client expands a suggestion into the template verbatim.\n * Unescaped, a '#' or '?' in a filename truncates the URI into a fragment or\n * query (silently naming a different path) and a '%' makes the decode throw.\n * Separators are restored so the value still reads as a path.\n */\nexport function encodeFileUriPath(validPath: string): string {\n const posix = validPath.replace(/\\\\/g, '/');\n return encodeURIComponent(posix).replace(/%2F/gi, '/');\n}\n\n/** Inverse of {@link encodeFileUriPath}; `undefined` on invalid percent-encoding. */\nexport function decodeFileUriPath(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n return undefined;\n }\n}\n\nexport function buildFileResourceUri(validPath: string): string {\n return `filesystem-mcp://file/${encodeFileUriPath(validPath)}`;\n}\n\n/**\n * The link block for a URI already built by `buildFileResourceUri`. Callers that\n * hold the URI must use this rather than rebuilding from a path: a path that\n * differs only in case (the drive letter, on Windows) yields a *different* URI\n * string, and watchers key on that string — a link and a `resourceUri` that\n * disagree hand the client two subscriptions for one file.\n */\nexport function buildFileResourceLinkFor(\n uri: string,\n name: string,\n mimeType: string,\n size: number,\n): ContentBlock {\n return {\n type: 'resource_link',\n uri,\n name,\n mimeType,\n size,\n annotations: { audience: ['user', 'assistant'] },\n };\n}\n\nexport function buildFileResourceLink(\n validPath: string,\n mimeType: string,\n size: number,\n): ContentBlock {\n return buildFileResourceLinkFor(\n buildFileResourceUri(validPath),\n basename(validPath),\n mimeType,\n size,\n );\n}\n\nexport function extractPath(uri: string): string | undefined {\n const url = URL.parse(uri);\n if (url?.protocol !== 'filesystem-mcp:' || url.host !== 'file') return undefined;\n return decodeFileUriPath(url.pathname.slice(1));\n}\n\nexport interface WrittenFileMeta {\n size: number;\n lineCount: number;\n mimeType: string;\n kind: FileKind;\n /**\n * Undefined when the resulting file exceeds the text-size cap: the store\n * serves the URI via readRaw, which would reject it with TOO_LARGE.\n */\n resourceUri: string | undefined;\n /** Undefined when no resource store is configured — nothing to link into. */\n resourceLink: ContentBlock | undefined;\n}\n\n/**\n * The five-step block every write tool runs on its post-write content: size,\n * line count, MIME, the file's resource URI, and the store-gated link block.\n */\nexport function buildWrittenFileMeta(\n validPath: string,\n content: string,\n resourceStore: ResourceStore | undefined,\n): WrittenFileMeta {\n const size = Buffer.byteLength(content, 'utf-8');\n const mimeInfo = detectMimeFromContent(validPath, content);\n // An edit or patch can push a readable file past the text-size cap; the\n // store serves the URI via readRaw, which would reject it with TOO_LARGE.\n const servable = size <= getMaxTextFileSize();\n return {\n size,\n lineCount: countLines(content),\n mimeType: mimeInfo.mimeType,\n kind: mimeInfo.kind,\n resourceUri: servable ? buildFileResourceUri(validPath) : undefined,\n resourceLink:\n resourceStore && servable\n ? buildFileResourceLink(validPath, mimeInfo.mimeType, size)\n : undefined,\n };\n}\n"]}
1
+ {"version":3,"file":"file-uri.js","sourceRoot":"","sources":["../../src/core/file-uri.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename } from 'node:path';\n\nimport { detectMimeFromContent } from './mime.ts';\nimport { countLines } from './read.ts';\nimport type { FileKind } from './schema.ts';\nimport type { ResourceStore } from './store.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\n// Single owner of the `filesystem-mcp://file/` URI scheme — the template string,\n// the path→URI encoder, the URI→path decoder, the link blocks built from them,\n// and the post-write metadata block every write tool reports alongside them.\n\nexport const FILESYSTEM_FILE_URI_TEMPLATE = 'filesystem-mcp://file/{+path}';\n\n/**\n * The `{+path}` template-variable form of a path — what `completion/complete`\n * must return, since a client expands a suggestion into the template verbatim.\n * Unescaped, a '#' or '?' in a filename truncates the URI into a fragment or\n * query (silently naming a different path) and a '%' makes the decode throw.\n * Separators are restored so the value still reads as a path.\n */\nexport function encodeFileUriPath(validPath: string): string {\n const posix = validPath.replace(/\\\\/g, '/');\n return encodeURIComponent(posix).replace(/%2F/gi, '/');\n}\n\n/** Inverse of {@link encodeFileUriPath}; `undefined` on invalid percent-encoding. */\nexport function decodeFileUriPath(encoded: string): string | undefined {\n try {\n return decodeURIComponent(encoded);\n } catch {\n return undefined;\n }\n}\n\nexport function buildFileResourceUri(validPath: string): string {\n return `filesystem-mcp://file/${encodeFileUriPath(validPath)}`;\n}\n\n/**\n * The link block for a URI already built by `buildFileResourceUri`. Callers that\n * hold the URI must use this rather than rebuilding from a path: a path that\n * differs only in case (the drive letter, on Windows) yields a *different* URI\n * string, and watchers key on that string — a link and a `resourceUri` that\n * disagree hand the client two subscriptions for one file.\n */\nexport function buildFileResourceLinkFor(\n uri: string,\n name: string,\n mimeType: string,\n size: number,\n): ContentBlock {\n return {\n type: 'resource_link',\n uri,\n name,\n mimeType,\n size,\n annotations: { audience: ['user', 'assistant'] },\n };\n}\n\nfunction buildFileResourceLink(validPath: string, mimeType: string, size: number): ContentBlock {\n return buildFileResourceLinkFor(\n buildFileResourceUri(validPath),\n basename(validPath),\n mimeType,\n size,\n );\n}\n\nexport function extractPath(uri: string): string | undefined {\n const url = URL.parse(uri);\n if (url?.protocol !== 'filesystem-mcp:' || url.host !== 'file') return undefined;\n return decodeFileUriPath(url.pathname.slice(1));\n}\n\nexport interface WrittenFileMeta {\n size: number;\n lineCount: number;\n mimeType: string;\n kind: FileKind;\n /** Undefined unless {@link writtenFileLinks} allows it: see there for when. */\n resourceUri: string | undefined;\n /** Undefined unless {@link writtenFileLinks} allows it: see there for when. */\n resourceLink: ContentBlock | undefined;\n}\n\n/**\n * The URI and link a write tool may advertise for the file it wrote. None on a\n * dry run — nothing was written, so the file on disk is still the one the\n * caller already has. None when the size is unknown or over the text-size cap —\n * the store serves the URI via readRaw, which would reject it with TOO_LARGE.\n * No link without a store to link into.\n */\nexport function writtenFileLinks(\n validPath: string,\n mimeType: string,\n size: number | undefined,\n options: { resourceStore: ResourceStore | undefined; dryRun?: boolean | undefined },\n): Pick<WrittenFileMeta, 'resourceUri' | 'resourceLink'> {\n if (options.dryRun || size === undefined || size > getMaxTextFileSize()) {\n return { resourceUri: undefined, resourceLink: undefined };\n }\n return {\n resourceUri: buildFileResourceUri(validPath),\n resourceLink: options.resourceStore\n ? buildFileResourceLink(validPath, mimeType, size)\n : undefined,\n };\n}\n\n/**\n * The block every write tool reports for the content it wrote (or, with\n * `dryRun`, would have written): size, line count, MIME, and whatever\n * {@link writtenFileLinks} allows it to advertise. Named fields, because\n * `validPath` and `content` are both strings and a positional swap type-checks.\n */\nexport function buildWrittenFileMeta(options: {\n validPath: string;\n content: string;\n resourceStore: ResourceStore | undefined;\n dryRun?: boolean | undefined;\n}): WrittenFileMeta {\n const { validPath, content } = options;\n const size = Buffer.byteLength(content, 'utf-8');\n const { mimeType, kind } = detectMimeFromContent(validPath, content);\n return {\n size,\n lineCount: countLines(content),\n mimeType,\n kind,\n ...writtenFileLinks(validPath, mimeType, size, options),\n };\n}\n"]}
@@ -3,7 +3,7 @@ import { basename } from 'node:path';
3
3
  import { RE2 } from '@adguard/re2-wasm';
4
4
  import { resolveStopReason } from "./concurrency.js";
5
5
  import { globEntries } from "./glob.js";
6
- import { getMaxTextFileSize, MIB } from "./util.js";
6
+ import { escapeRegExp, getMaxTextFileSize, MIB } from "./util.js";
7
7
  /**
8
8
  * A fresh, flat copy of `s`. V8 keeps a substring of a long string as a slice
9
9
  * that references its parent, so a kept line would otherwise pin the whole
@@ -131,7 +131,7 @@ function findLineMatches(regex, line) {
131
131
  return count > 0 ? { count, column } : undefined;
132
132
  }
133
133
  export async function searchContent(directory, pattern, options, pathGuard) {
134
- const regex = compileRegex(options.isRegex ? pattern || '' : RegExp.escape(pattern || ''), {
134
+ const regex = compileRegex(options.isRegex ? pattern || '' : escapeRegExp(pattern || ''), {
135
135
  caseSensitive: Boolean(options.caseSensitive),
136
136
  });
137
137
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { resolveStopReason } from './concurrency.ts';\nimport { globEntries, type GlobEntry } from './glob.ts';\nimport type { PathGuard } from './path.ts';\nimport { getMaxTextFileSize, MIB } from './util.ts';\n\n/**\n * A fresh, flat copy of `s`. V8 keeps a substring of a long string as a slice\n * that references its parent, so a kept line would otherwise pin the whole\n * file's text for as long as the match list lives (60 s in the page store).\n */\nconst own = (s: string): string => Buffer.from(s, 'utf8').toString('utf8');\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n /** Up to `context` lines either side of the match; absent when context is 0. */\n before?: string[];\n after?: string[];\n}\n\nexport type Regex = RE2;\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Largest input one RE2 call may scan as a single string. The input is copied\n * into the same fixed 16 MB wasm heap as the patterns; about 10 MB of input\n * aborts the module. 4 MiB leaves room for the copy and the pattern set.\n */\nexport const RE2_MAX_INPUT_BYTES = 4 * MIB;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: { caseSensitive?: boolean } = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nconst codePointLength = (text: string): number =>\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- code points are the intended unit (re2-wasm offsets count code points); grapheme segmentation would change the count, not preserve it.\n [...text].length;\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n let count = 0;\n let column = -1;\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n skipIgnored?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n /** Lines of context to carry either side of each match; 0 (default) carries none. */\n context?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /** Files skipped because they contain a NUL byte (binary). */\n skippedBinary: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both\n * resolve the stop through `resolveStopReason` (concurrency.ts) — never\n * `maxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n): Promise<SearchContentOutcome> {\n const regex = compileRegex(options.isRegex ? pattern || '' : RegExp.escape(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = getMaxTextFileSize();\n const context = options.context ?? 0;\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n // Same rule as replace_text, so a search with the same glob previews the\n // files a replace would touch: a slash-free glob matches at any depth.\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n let skippedBinary = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const buffer = await readFile(entry.path, { signal: options.signal });\n // A NUL byte marks a binary file, as it does for `read` and `grep -I`.\n // Non-UTF-8 text is still searched: a lossy decode only affects the\n // bytes a pattern could not have matched anyway.\n if (buffer.includes(0)) {\n skippedBinary++;\n continue;\n }\n const content = buffer.toString('utf-8');\n const lines = content.split(/\\r?\\n/u);\n // A trailing newline splits into a phantom empty last element, and an\n // empty file splits into one empty element: neither is a line the\n // file has, for matching or for context.\n const lineCount =\n content.length === 0 ? 0 : content.endsWith('\\n') ? lines.length - 1 : lines.length;\n let matchedFile = false;\n for (let i = 0; i < lineCount; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: own(line),\n matchCount: found.count,\n ...(context > 0\n ? {\n before: lines.slice(Math.max(0, i - context), i).map(own),\n after: lines.slice(i + 1, Math.min(lineCount, i + 1 + context)).map(own),\n }\n : {}),\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // unreadable mid-scan (deleted, permission changed): skip the file\n }\n }\n\n // The result cap is the definite cause even when the abort fired on the\n // same iteration (see the summary type's narrowing note).\n const stoppedReason = resolveStopReason(matches.length >= maxResults, counters.stoppedByAbort);\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n skippedBinary,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n skipIgnored?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never `maxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n // Same precedence as above: the result cap wins over a same-iteration abort.\n const stoppedReason = resolveStopReason(results.length >= maxResults, counters.stoppedByAbort);\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { resolveStopReason } from './concurrency.ts';\nimport { globEntries, type GlobEntry } from './glob.ts';\nimport type { PathGuard } from './path.ts';\nimport { escapeRegExp, getMaxTextFileSize, MIB } from './util.ts';\n\n/**\n * A fresh, flat copy of `s`. V8 keeps a substring of a long string as a slice\n * that references its parent, so a kept line would otherwise pin the whole\n * file's text for as long as the match list lives (60 s in the page store).\n */\nconst own = (s: string): string => Buffer.from(s, 'utf8').toString('utf8');\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n /** Up to `context` lines either side of the match; absent when context is 0. */\n before?: string[];\n after?: string[];\n}\n\nexport type Regex = RE2;\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Largest input one RE2 call may scan as a single string. The input is copied\n * into the same fixed 16 MB wasm heap as the patterns; about 10 MB of input\n * aborts the module. 4 MiB leaves room for the copy and the pattern set.\n */\nexport const RE2_MAX_INPUT_BYTES = 4 * MIB;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: { caseSensitive?: boolean } = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nconst codePointLength = (text: string): number =>\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- code points are the intended unit (re2-wasm offsets count code points); grapheme segmentation would change the count, not preserve it.\n [...text].length;\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n let count = 0;\n let column = -1;\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n skipIgnored?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n /** Lines of context to carry either side of each match; 0 (default) carries none. */\n context?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /** Files skipped because they contain a NUL byte (binary). */\n skippedBinary: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both\n * resolve the stop through `resolveStopReason` (concurrency.ts) — never\n * `maxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n): Promise<SearchContentOutcome> {\n const regex = compileRegex(options.isRegex ? pattern || '' : escapeRegExp(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = getMaxTextFileSize();\n const context = options.context ?? 0;\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n // Same rule as replace_text, so a search with the same glob previews the\n // files a replace would touch: a slash-free glob matches at any depth.\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n let skippedBinary = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const buffer = await readFile(entry.path, { signal: options.signal });\n // A NUL byte marks a binary file, as it does for `read` and `grep -I`.\n // Non-UTF-8 text is still searched: a lossy decode only affects the\n // bytes a pattern could not have matched anyway.\n if (buffer.includes(0)) {\n skippedBinary++;\n continue;\n }\n const content = buffer.toString('utf-8');\n const lines = content.split(/\\r?\\n/u);\n // A trailing newline splits into a phantom empty last element, and an\n // empty file splits into one empty element: neither is a line the\n // file has, for matching or for context.\n const lineCount =\n content.length === 0 ? 0 : content.endsWith('\\n') ? lines.length - 1 : lines.length;\n let matchedFile = false;\n for (let i = 0; i < lineCount; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: own(line),\n matchCount: found.count,\n ...(context > 0\n ? {\n before: lines.slice(Math.max(0, i - context), i).map(own),\n after: lines.slice(i + 1, Math.min(lineCount, i + 1 + context)).map(own),\n }\n : {}),\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // unreadable mid-scan (deleted, permission changed): skip the file\n }\n }\n\n // The result cap is the definite cause even when the abort fired on the\n // same iteration (see the summary type's narrowing note).\n const stoppedReason = resolveStopReason(matches.length >= maxResults, counters.stoppedByAbort);\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n skippedBinary,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n skipIgnored?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never `maxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n // Same precedence as above: the result cap wins over a same-iteration abort.\n const stoppedReason = resolveStopReason(results.length >= maxResults, counters.stoppedByAbort);\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"AA0UA,iHAAiH;AACjH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAQA;AAID,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;gBAK/B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE;IAanE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAcvC"}
1
+ {"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"AA2UA,iHAAiH;AACjH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAQA;AAID,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;gBAK/B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE;IAanE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAcvC"}
@@ -7,6 +7,7 @@
7
7
  import { normalize, posix, sep } from 'node:path';
8
8
  import { cli } from "./config.js";
9
9
  import { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from "./path-utils.js";
10
+ import { escapeRegExp } from "./util.js";
10
11
  const CHAR_COLON = 58;
11
12
  function normalizeForMatch(input) {
12
13
  // Always lowercase for case-insensitive denylist matching on all platforms.
@@ -149,11 +150,11 @@ function globToRegExp(glob) {
149
150
  j = close;
150
151
  }
151
152
  else {
152
- source += RegExp.escape(char);
153
+ source += escapeRegExp(char);
153
154
  }
154
155
  }
155
156
  else {
156
- source += RegExp.escape(char);
157
+ source += escapeRegExp(char);
157
158
  }
158
159
  }
159
160
  if (!last)
@@ -1 +1 @@
1
- {"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"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","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// path-utils.ts, not the allowed-directory assembly. path.ts and\n// path-completer.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.ts';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './path-utils.ts';\n\nconst CHAR_COLON = 58;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n // normalize() preserves the double leading slash of UNC paths\n // (//server/share/...) on Windows; collapse it to one so the compiled\n // patterns — which carry no leading slash — still match their segments.\n return toPosixPath(normalize(input)).toLowerCase().replace(/^\\/+/u, '/');\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly RegExp[];\n nameGlobs: readonly RegExp[];\n}\n\n// posix.matchesGlob runs with dot:false semantics and does not honor a dot\n// option at all (verified on Node 24): '*' and '**' never match dot-leading\n// path segments, so a deny of `secrets/**` silently let `secrets/.env`\n// through — fail-open against exactly the hidden files this list exists to\n// guard. Compile our own matcher instead. Supported metacharacters: '*'\n// (any run within a segment), '**' (any run of whole segments), '?' (one\n// char), '[...]' classes, and '{a,b}' alternation; everything else is\n// literal, and dot-leading segments match like any other.\n\n// Character classes can carry '{', '}' and ',' as members, so every scanner\n// below must skip a class's interior. classEnd returns the index of the ']'\n// closing the class opened at openIdx, or -1 when unclosed (then '[' is a\n// literal, matching globToRegExp's fallback). A ']' right after '[' or the\n// '!' negation is a literal member, per glob syntax.\nfunction classEnd(glob: string, openIdx: number): number {\n let i = openIdx + 1;\n if (glob[i] === '!') i++;\n if (glob[i] === ']') i++;\n while (i < glob.length && glob[i] !== ']') i++;\n return i < glob.length ? i : -1;\n}\n\nfunction expandBraces(glob: string): string[] {\n // First '{' outside any [...] class.\n let open = -1;\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) i = end;\n } else if (char === '{') {\n open = i;\n break;\n }\n }\n if (open === -1) return [glob];\n // Pair the outer '{' with ITS matching '}' — indexOf would grab the inner\n // group's closer and produce garbage like 'data/x}/secret'. Then split the\n // body on depth-0 commas so nested groups stay intact for the recursion.\n let depth = 0;\n let close = -1;\n for (let i = open; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return [glob];\n const prefix = glob.slice(0, open);\n const suffix = glob.slice(close + 1);\n const body = glob.slice(open + 1, close);\n const parts: string[] = [];\n let start = 0;\n depth = 0;\n for (let i = 0; i < body.length; i++) {\n const char = body[i] ?? '';\n if (char === '[') {\n const end = classEnd(body, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(body.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(body.slice(start));\n return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`));\n}\n\nfunction globToRegExp(glob: string): RegExp {\n const segments = glob.split('/');\n let source = '';\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? '';\n const last = i === segments.length - 1;\n if (segment === '**') {\n source += last ? '(?:.*)?' : '(?:[^/]+/)*';\n continue;\n }\n for (let j = 0; j < segment.length; j++) {\n const char = segment[j] ?? '';\n if (char === '*') {\n while (segment[j + 1] === '*') j++;\n source += '[^/]*';\n } else if (char === '?') {\n source += '[^/]';\n } else if (char === '[') {\n // Same classEnd rule the scanners use: a ']' right after '[' (or\n // after the '!' negation) is a literal member, not the closer.\n const close = classEnd(segment, j);\n if (close !== -1) {\n // Pass the class through, flipping glob '!' negation to '^' and\n // escaping backslashes so no body can make new RegExp throw. A\n // leading ']' must be escaped or the 'u' flag parses '[]a]' as an\n // empty class followed by stray literals; a leading '^' is a\n // literal member (glob negation is '!' only), unescaped it would\n // negate the class and relieve more than the operator named.\n let body = segment.slice(j + 1, close).replace(/\\\\/gu, '\\\\\\\\');\n const negated = body.startsWith('!');\n if (negated) body = body.slice(1);\n if (body.startsWith(']') || body.startsWith('^')) body = `\\\\${body}`;\n source += `[${negated ? '^' : ''}${body}]`;\n j = close;\n } else {\n source += RegExp.escape(char);\n }\n } else {\n source += RegExp.escape(char);\n }\n }\n if (!last) source += '/';\n }\n // Candidates are absolute paths but patterns carry no leading '/', so the\n // anchor optionally eats the root slash — `**/` then reaches from any\n // parent, exactly as matchesGlob's globstar did. '$' is strict in\n // JavaScript (it matches end-of-input only, unlike PCRE's, which also\n // matches before a final newline), so an exact-name pattern can never\n // match a '.env\\n' lookalike. The 's' flag makes the terminal-`**` `.`\n // match newlines too: filenames can contain them, and a deny ending in\n // '/**' must not fail open on 'secrets/plan\\nnotes'.\n return new RegExp(`^/?${source}$`, 'su');\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n // A leading '/' (POSIX-absolute) is rooted like a Windows drive path:\n // aliasing '/abs/secret' as '**/abs/secret' would also match\n // 'other/abs/secret', and on the allow side that is over-relief. The\n // drive-letter regex is safe on [a-z] because normalizeForMatch lowercased.\n const isRooted =\n normalizedPattern.startsWith('/') ||\n normalizedPattern.startsWith('**/') ||\n /^[a-z]:\\//u.test(normalizedPattern);\n const withoutRoot = isRooted ? '' : normalizedPattern.replace(/^\\/+/, '');\n return withoutRoot ? [normalizedPattern, `**/${withoutRoot}`] : [normalizedPattern];\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<RegExp>();\n const nameGlobs = new Set<RegExp>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n const globs = matchesPath ? compilePatternGlobs(normalized) : [normalized];\n for (const glob of globs) {\n for (const expanded of expandBraces(glob)) {\n target.add(globToRegExp(expanded));\n }\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly RegExp[], candidate: string): boolean {\n return globs.some((glob) => glob.test(candidate));\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.netrc',\n '_netrc',\n '.git-credentials',\n '.pgpass',\n '.aws/credentials',\n '.aws/config',\n '.docker/config.json',\n '.kube/config',\n 'application_default_credentials.json',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n '*id_ecdsa*',\n '*id_ed25519*',\n] as const;\n\n// Built-ins and operator-supplied deny entries live in separate tiers because\n// they answer to different relief switches: an allow entry (FS_ALLOWLIST /\n// --allow) can lift a built-in hit, but never an explicit deny.\n\n// Split an env pattern list on commas and newlines at brace depth 0 only and\n// outside any [...] class, so documented syntax like '*.{pem,key}' or\n// 'x[1,2].env' survives as one pattern instead of tearing apart.\nfunction splitPatternList(value: string): string[] {\n const naive = (): string[] =>\n value\n .split(/,|\\n/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let unbalanced = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i] ?? '';\n if (char === '[') {\n const end = classEnd(value, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n if (depth === 0) unbalanced = true;\n else depth--;\n } else if (depth === 0 && (char === ',' || char === '\\n')) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n // An unbalanced brace would swallow every separator behind it into one\n // bogus literal pattern and silently drop the real entries — '{bad,\n // secrets/**' must not lose 'secrets/**'. A typo'd brace costs the brace\n // pattern only: fall back to the naive split.\n if (unbalanced || depth !== 0) return naive();\n parts.push(value.slice(start));\n return parts.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\nfunction buildDenyTiers(): { builtin: readonly string[]; operator: readonly string[] } {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envDenylist = process.env['FS_DENYLIST']\n ? splitPatternList(process.env['FS_DENYLIST'])\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return {\n builtin: allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS,\n operator: [...new Set([...envDenylist, ...flagDenylist])],\n };\n}\n\nfunction buildAllowPatterns(): readonly string[] {\n const envAllowlist = process.env['FS_ALLOWLIST']\n ? splitPatternList(process.env['FS_ALLOWLIST'])\n : [];\n const flagAllowlist = cli.allowPatterns ?? [];\n return [...new Set([...envAllowlist, ...flagAllowlist])];\n}\n\n/** The three pattern tiers as configured, for `--print-config`. Read-only view; the matcher compiles its own. */\nexport function describeSensitivePolicy(): {\n allowSensitive: boolean;\n builtinDeny: readonly string[];\n operatorDeny: readonly string[];\n allow: readonly string[];\n} {\n const tiers = buildDenyTiers();\n return {\n allowSensitive: tiers.builtin.length === 0,\n builtinDeny: tiers.builtin,\n operatorDeny: tiers.operator,\n allow: buildAllowPatterns(),\n };\n}\n\nconst EMPTY_PATTERN_SET: CompiledPatternSet = { pathGlobs: [], nameGlobs: [] };\n\nexport class SensitiveMatcher {\n private readonly builtin: CompiledPatternSet;\n private readonly operator: CompiledPatternSet;\n private readonly allow: CompiledPatternSet;\n\n // An explicit pattern list (tests, custom guards) is one relievable tier;\n // the default construction splits built-ins from operator-supplied\n // FS_DENYLIST/--deny entries, which allow entries can never lift.\n constructor(patterns?: readonly string[], allow?: readonly string[]) {\n if (patterns !== undefined || allow !== undefined) {\n this.builtin = toPatternSet(patterns ?? []);\n this.operator = EMPTY_PATTERN_SET;\n this.allow = toPatternSet(allow ?? []);\n } else {\n const tiers = buildDenyTiers();\n this.builtin = toPatternSet(tiers.builtin);\n this.operator = toPatternSet(tiers.operator);\n this.allow = toPatternSet(buildAllowPatterns());\n }\n }\n\n isSensitive(filePath: string): boolean {\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n const name = posix.basename(normalizedPath);\n const matches = (set: CompiledPatternSet): boolean =>\n matchesAnyGlob(set.pathGlobs, normalizedPath) || matchesAnyGlob(set.nameGlobs, name);\n\n // Explicit deny entries (FS_DENYLIST/--deny) always apply.\n if (matches(this.operator)) return true;\n if (!matches(this.builtin)) return false;\n // A built-in hit is lifted only by an allow entry that matches the same\n // normalized candidate — a pattern that matches nothing relieves nothing.\n return !matches(this.allow);\n }\n}\n"]}
1
+ {"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,iEAAiE;AACjE,kFAAkF;AAClF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,SAAS,iBAAiB,CAAC,KAAa;IACtC,4EAA4E;IAC5E,8DAA8D;IAC9D,sEAAsE;IACtE,wEAAwE;IACxE,OAAO,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAC3E,CAAC;AAOD,2EAA2E;AAC3E,4EAA4E;AAC5E,uEAAuE;AACvE,2EAA2E;AAC3E,wEAAwE;AACxE,yEAAyE;AACzE,sEAAsE;AACtE,0DAA0D;AAE1D,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,qDAAqD;AACrD,SAAS,QAAQ,CAAC,IAAY,EAAE,OAAe;IAC7C,IAAI,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;IACpB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IACzB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IACzB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IAC/C,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,qCAAqC;IACrC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,CAAC,GAAG,GAAG,CAAC;QAC1B,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBACf,CAAC,GAAG,GAAG,CAAC;gBACR,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACrB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACtB,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,KAAK,GAAG,CAAC,CAAC;gBACV,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBACf,CAAC,GAAG,GAAG,CAAC;gBACR,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACrB,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aAC1B,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YACjC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,CAAC,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QACvC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,OAAO,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG;oBAAE,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,OAAO,CAAC;YACpB,CAAC;iBAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACxB,MAAM,IAAI,MAAM,CAAC;YACnB,CAAC;iBAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACxB,iEAAiE;gBACjE,+DAA+D;gBAC/D,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACnC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;oBACjB,gEAAgE;oBAChE,+DAA+D;oBAC/D,kEAAkE;oBAClE,6DAA6D;oBAC7D,iEAAiE;oBACjE,6DAA6D;oBAC7D,IAAI,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;oBACrC,IAAI,OAAO;wBAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAClC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;wBAAE,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;oBACrE,MAAM,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC;oBAC3C,CAAC,GAAG,KAAK,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;gBAC/B,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,GAAG,CAAC;IAC3B,CAAC;IACD,0EAA0E;IAC1E,sEAAsE;IACtE,kEAAkE;IAClE,sEAAsE;IACtE,sEAAsE;IACtE,uEAAuE;IACvE,uEAAuE;IACvE,qDAAqD;IACrD,OAAO,IAAI,MAAM,CAAC,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,mBAAmB,CAAC,iBAAyB;IACpD,sEAAsE;IACtE,6DAA6D;IAC7D,qEAAqE;IACrE,4EAA4E;IAC5E,MAAM,QAAQ,GACZ,iBAAiB,CAAC,UAAU,CAAC,GAAG,CAAC;QACjC,iBAAiB,CAAC,UAAU,CAAC,KAAK,CAAC;QACnC,YAAY,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1E,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,YAAY,CAAC,QAA2B;IAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACnD,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC3E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,MAAM,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1C,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC;QACzB,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAwB,EAAE,SAAiB;IACjE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,4EAA4E;AAC5E,gFAAgF;AAChF,0EAA0E;AAC1E,6EAA6E;AAC7E,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,SAAS,yBAAyB,CAAC,QAAgB;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QACxC,IACE,CAAC,KAAK,CAAC;YACP,OAAO,CAAC,MAAM,KAAK,CAAC;YACpB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC9B,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,EACpC,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,aAAa,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAC7E,sEAAsE;QACtE,0EAA0E;QAC1E,yEAAyE;QACzE,OAAO,yBAAyB,CAAC,aAAa,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,yBAAyB,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAE7F,MAAM,0BAA0B,GAAG;IACjC,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,kBAAkB;IAClB,SAAS;IACT,kBAAkB;IAClB,aAAa;IACb,qBAAqB;IACrB,cAAc;IACd,sCAAsC;IACtC,sBAAsB;IACtB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;IACV,YAAY;IACZ,cAAc;CACN,CAAC;AAEX,8EAA8E;AAC9E,2EAA2E;AAC3E,gEAAgE;AAEhE,6EAA6E;AAC7E,sEAAsE;AACtE,iEAAiE;AACjE,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,KAAK,GAAG,GAAa,EAAE,CAC3B,KAAK;SACF,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC/B,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBACf,CAAC,GAAG,GAAG,CAAC;gBACR,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACrB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACtB,IAAI,KAAK,KAAK,CAAC;gBAAE,UAAU,GAAG,IAAI,CAAC;;gBAC9B,KAAK,EAAE,CAAC;QACf,CAAC;aAAM,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YAC1D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YAClC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,uEAAuE;IACvE,oEAAoE;IACpE,yEAAyE;IACzE,8CAA8C;IAC9C,IAAI,UAAU,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,EAAE,CAAC;IAC9C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,cAAc;IACrB,MAAM,cAAc,GAClB,GAAG,CAAC,cAAc,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAClG,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QAC5C,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC9C,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IAC5C,8EAA8E;IAC9E,sEAAsE;IACtE,OAAO;QACL,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,0BAA0B;QACzD,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QAC9C,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/C,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,aAAa,GAAG,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC;IAC9C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,uBAAuB;IAMrC,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,OAAO;QACL,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QAC1C,WAAW,EAAE,KAAK,CAAC,OAAO;QAC1B,YAAY,EAAE,KAAK,CAAC,QAAQ;QAC5B,KAAK,EAAE,kBAAkB,EAAE;KAC5B,CAAC;AACJ,CAAC;AAED,MAAM,iBAAiB,GAAuB,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AAE/E,MAAM,OAAO,gBAAgB;IACV,OAAO,CAAqB;IAC5B,QAAQ,CAAqB;IAC7B,KAAK,CAAqB;IAE3C,0EAA0E;IAC1E,mEAAmE;IACnE,kEAAkE;IAClE,YAAY,QAA4B,EAAE,KAAyB;QACjE,IAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAClD,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;YAC5C,IAAI,CAAC,QAAQ,GAAG,iBAAiB,CAAC;YAClC,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;YAC/B,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7C,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,WAAW,CAAC,QAAgB;QAC1B,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAChF,MAAM,cAAc,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,CAAC,GAAuB,EAAW,EAAE,CACnD,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAEvF,2DAA2D;QAC3D,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,wEAAwE;QACxE,0EAA0E;QAC1E,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;CACF","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// path-utils.ts, not the allowed-directory assembly. path.ts and\n// path-completer.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.ts';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './path-utils.ts';\nimport { escapeRegExp } from './util.ts';\n\nconst CHAR_COLON = 58;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n // normalize() preserves the double leading slash of UNC paths\n // (//server/share/...) on Windows; collapse it to one so the compiled\n // patterns — which carry no leading slash — still match their segments.\n return toPosixPath(normalize(input)).toLowerCase().replace(/^\\/+/u, '/');\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly RegExp[];\n nameGlobs: readonly RegExp[];\n}\n\n// posix.matchesGlob runs with dot:false semantics and does not honor a dot\n// option at all (verified on Node 24): '*' and '**' never match dot-leading\n// path segments, so a deny of `secrets/**` silently let `secrets/.env`\n// through — fail-open against exactly the hidden files this list exists to\n// guard. Compile our own matcher instead. Supported metacharacters: '*'\n// (any run within a segment), '**' (any run of whole segments), '?' (one\n// char), '[...]' classes, and '{a,b}' alternation; everything else is\n// literal, and dot-leading segments match like any other.\n\n// Character classes can carry '{', '}' and ',' as members, so every scanner\n// below must skip a class's interior. classEnd returns the index of the ']'\n// closing the class opened at openIdx, or -1 when unclosed (then '[' is a\n// literal, matching globToRegExp's fallback). A ']' right after '[' or the\n// '!' negation is a literal member, per glob syntax.\nfunction classEnd(glob: string, openIdx: number): number {\n let i = openIdx + 1;\n if (glob[i] === '!') i++;\n if (glob[i] === ']') i++;\n while (i < glob.length && glob[i] !== ']') i++;\n return i < glob.length ? i : -1;\n}\n\nfunction expandBraces(glob: string): string[] {\n // First '{' outside any [...] class.\n let open = -1;\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) i = end;\n } else if (char === '{') {\n open = i;\n break;\n }\n }\n if (open === -1) return [glob];\n // Pair the outer '{' with ITS matching '}' — indexOf would grab the inner\n // group's closer and produce garbage like 'data/x}/secret'. Then split the\n // body on depth-0 commas so nested groups stay intact for the recursion.\n let depth = 0;\n let close = -1;\n for (let i = open; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return [glob];\n const prefix = glob.slice(0, open);\n const suffix = glob.slice(close + 1);\n const body = glob.slice(open + 1, close);\n const parts: string[] = [];\n let start = 0;\n depth = 0;\n for (let i = 0; i < body.length; i++) {\n const char = body[i] ?? '';\n if (char === '[') {\n const end = classEnd(body, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(body.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(body.slice(start));\n return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`));\n}\n\nfunction globToRegExp(glob: string): RegExp {\n const segments = glob.split('/');\n let source = '';\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? '';\n const last = i === segments.length - 1;\n if (segment === '**') {\n source += last ? '(?:.*)?' : '(?:[^/]+/)*';\n continue;\n }\n for (let j = 0; j < segment.length; j++) {\n const char = segment[j] ?? '';\n if (char === '*') {\n while (segment[j + 1] === '*') j++;\n source += '[^/]*';\n } else if (char === '?') {\n source += '[^/]';\n } else if (char === '[') {\n // Same classEnd rule the scanners use: a ']' right after '[' (or\n // after the '!' negation) is a literal member, not the closer.\n const close = classEnd(segment, j);\n if (close !== -1) {\n // Pass the class through, flipping glob '!' negation to '^' and\n // escaping backslashes so no body can make new RegExp throw. A\n // leading ']' must be escaped or the 'u' flag parses '[]a]' as an\n // empty class followed by stray literals; a leading '^' is a\n // literal member (glob negation is '!' only), unescaped it would\n // negate the class and relieve more than the operator named.\n let body = segment.slice(j + 1, close).replace(/\\\\/gu, '\\\\\\\\');\n const negated = body.startsWith('!');\n if (negated) body = body.slice(1);\n if (body.startsWith(']') || body.startsWith('^')) body = `\\\\${body}`;\n source += `[${negated ? '^' : ''}${body}]`;\n j = close;\n } else {\n source += escapeRegExp(char);\n }\n } else {\n source += escapeRegExp(char);\n }\n }\n if (!last) source += '/';\n }\n // Candidates are absolute paths but patterns carry no leading '/', so the\n // anchor optionally eats the root slash — `**/` then reaches from any\n // parent, exactly as matchesGlob's globstar did. '$' is strict in\n // JavaScript (it matches end-of-input only, unlike PCRE's, which also\n // matches before a final newline), so an exact-name pattern can never\n // match a '.env\\n' lookalike. The 's' flag makes the terminal-`**` `.`\n // match newlines too: filenames can contain them, and a deny ending in\n // '/**' must not fail open on 'secrets/plan\\nnotes'.\n return new RegExp(`^/?${source}$`, 'su');\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n // A leading '/' (POSIX-absolute) is rooted like a Windows drive path:\n // aliasing '/abs/secret' as '**/abs/secret' would also match\n // 'other/abs/secret', and on the allow side that is over-relief. The\n // drive-letter regex is safe on [a-z] because normalizeForMatch lowercased.\n const isRooted =\n normalizedPattern.startsWith('/') ||\n normalizedPattern.startsWith('**/') ||\n /^[a-z]:\\//u.test(normalizedPattern);\n const withoutRoot = isRooted ? '' : normalizedPattern.replace(/^\\/+/, '');\n return withoutRoot ? [normalizedPattern, `**/${withoutRoot}`] : [normalizedPattern];\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<RegExp>();\n const nameGlobs = new Set<RegExp>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n const globs = matchesPath ? compilePatternGlobs(normalized) : [normalized];\n for (const glob of globs) {\n for (const expanded of expandBraces(glob)) {\n target.add(globToRegExp(expanded));\n }\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly RegExp[], candidate: string): boolean {\n return globs.some((glob) => glob.test(candidate));\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.netrc',\n '_netrc',\n '.git-credentials',\n '.pgpass',\n '.aws/credentials',\n '.aws/config',\n '.docker/config.json',\n '.kube/config',\n 'application_default_credentials.json',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n '*id_ecdsa*',\n '*id_ed25519*',\n] as const;\n\n// Built-ins and operator-supplied deny entries live in separate tiers because\n// they answer to different relief switches: an allow entry (FS_ALLOWLIST /\n// --allow) can lift a built-in hit, but never an explicit deny.\n\n// Split an env pattern list on commas and newlines at brace depth 0 only and\n// outside any [...] class, so documented syntax like '*.{pem,key}' or\n// 'x[1,2].env' survives as one pattern instead of tearing apart.\nfunction splitPatternList(value: string): string[] {\n const naive = (): string[] =>\n value\n .split(/,|\\n/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let unbalanced = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i] ?? '';\n if (char === '[') {\n const end = classEnd(value, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n if (depth === 0) unbalanced = true;\n else depth--;\n } else if (depth === 0 && (char === ',' || char === '\\n')) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n // An unbalanced brace would swallow every separator behind it into one\n // bogus literal pattern and silently drop the real entries — '{bad,\n // secrets/**' must not lose 'secrets/**'. A typo'd brace costs the brace\n // pattern only: fall back to the naive split.\n if (unbalanced || depth !== 0) return naive();\n parts.push(value.slice(start));\n return parts.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\nfunction buildDenyTiers(): { builtin: readonly string[]; operator: readonly string[] } {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envDenylist = process.env['FS_DENYLIST']\n ? splitPatternList(process.env['FS_DENYLIST'])\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return {\n builtin: allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS,\n operator: [...new Set([...envDenylist, ...flagDenylist])],\n };\n}\n\nfunction buildAllowPatterns(): readonly string[] {\n const envAllowlist = process.env['FS_ALLOWLIST']\n ? splitPatternList(process.env['FS_ALLOWLIST'])\n : [];\n const flagAllowlist = cli.allowPatterns ?? [];\n return [...new Set([...envAllowlist, ...flagAllowlist])];\n}\n\n/** The three pattern tiers as configured, for `--print-config`. Read-only view; the matcher compiles its own. */\nexport function describeSensitivePolicy(): {\n allowSensitive: boolean;\n builtinDeny: readonly string[];\n operatorDeny: readonly string[];\n allow: readonly string[];\n} {\n const tiers = buildDenyTiers();\n return {\n allowSensitive: tiers.builtin.length === 0,\n builtinDeny: tiers.builtin,\n operatorDeny: tiers.operator,\n allow: buildAllowPatterns(),\n };\n}\n\nconst EMPTY_PATTERN_SET: CompiledPatternSet = { pathGlobs: [], nameGlobs: [] };\n\nexport class SensitiveMatcher {\n private readonly builtin: CompiledPatternSet;\n private readonly operator: CompiledPatternSet;\n private readonly allow: CompiledPatternSet;\n\n // An explicit pattern list (tests, custom guards) is one relievable tier;\n // the default construction splits built-ins from operator-supplied\n // FS_DENYLIST/--deny entries, which allow entries can never lift.\n constructor(patterns?: readonly string[], allow?: readonly string[]) {\n if (patterns !== undefined || allow !== undefined) {\n this.builtin = toPatternSet(patterns ?? []);\n this.operator = EMPTY_PATTERN_SET;\n this.allow = toPatternSet(allow ?? []);\n } else {\n const tiers = buildDenyTiers();\n this.builtin = toPatternSet(tiers.builtin);\n this.operator = toPatternSet(tiers.operator);\n this.allow = toPatternSet(buildAllowPatterns());\n }\n }\n\n isSensitive(filePath: string): boolean {\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n const name = posix.basename(normalizedPath);\n const matches = (set: CompiledPatternSet): boolean =>\n matchesAnyGlob(set.pathGlobs, normalizedPath) || matchesAnyGlob(set.nameGlobs, name);\n\n // Explicit deny entries (FS_DENYLIST/--deny) always apply.\n if (matches(this.operator)) return true;\n if (!matches(this.builtin)) return false;\n // A built-in hit is lifted only by an allow entry that matches the same\n // normalized candidate — a pattern that matches nothing relieves nothing.\n return !matches(this.allow);\n }\n}\n"]}
@@ -7,6 +7,13 @@ export declare function parseEnvInt(envVar: string, defaultValue: number, min: n
7
7
  * here, so they all agree on what "configured" means.
8
8
  */
9
9
  export declare function splitCsvList(value: string | undefined): string[];
10
+ /** The TC39 `RegExp.escape` algorithm, for runtimes that lack it. */
11
+ export declare function escapeRegExpFallback(input: string): string;
12
+ /**
13
+ * `RegExp.escape` shipped in Node 24. Older runtimes still load the published
14
+ * package (npm only warns on `engines`), so fall back rather than crash.
15
+ */
16
+ export declare const escapeRegExp: (input: string) => string;
10
17
  export declare const PARALLEL_CONCURRENCY: number;
11
18
  export declare const ROOTS_TIMEOUT_MS = 5000;
12
19
  export declare function getMaxTextFileSize(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,GAAG,QAAa,CAAC;AAE9B,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKhE;AAED,eAAO,MAAM,oBAAoB,QAAoD,CAAC;AAEtF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,QAAY,CAAC;AAEnD,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AACxC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,GAAG,QAAa,CAAC;AAE9B,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKhE;AA6BD,qEAAqE;AACrE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS1D;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MACgD,CAAC;AAE/F,eAAO,MAAM,oBAAoB,QAAoD,CAAC;AAEtF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,QAAY,CAAC;AAEnD,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AACxC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
package/dist/core/util.js CHANGED
@@ -35,6 +35,49 @@ export function splitCsvList(value) {
35
35
  .map((entry) => entry.trim())
36
36
  .filter(Boolean);
37
37
  }
38
+ const REGEXP_SYNTAX = /[\\^$.*+?()[\]{}|/]/;
39
+ const REGEXP_OTHER_PUNCTUATORS = /[,\-=<>#&!%:;@~'`"]/;
40
+ const REGEXP_CONTROL_ESCAPES = {
41
+ '\t': 't',
42
+ '\n': 'n',
43
+ '\v': 'v',
44
+ '\f': 'f',
45
+ '\r': 'r',
46
+ };
47
+ const hexEscape = (code, width) => `\\${width === 2 ? 'x' : 'u'}${code.toString(16).padStart(width, '0')}`;
48
+ function encodeForRegExpEscape(ch) {
49
+ if (REGEXP_SYNTAX.test(ch))
50
+ return `\\${ch}`;
51
+ const control = REGEXP_CONTROL_ESCAPES[ch];
52
+ if (control !== undefined)
53
+ return `\\${control}`;
54
+ const code = ch.codePointAt(0) ?? 0;
55
+ const loneSurrogate = ch.length === 1 && code >= 0xd800 && code <= 0xdfff;
56
+ if (!REGEXP_OTHER_PUNCTUATORS.test(ch) && !/\s/.test(ch) && !loneSurrogate)
57
+ return ch;
58
+ if (code <= 0xff)
59
+ return hexEscape(code, 2);
60
+ return ch
61
+ .split('')
62
+ .map((unit) => hexEscape(unit.charCodeAt(0), 4))
63
+ .join('');
64
+ }
65
+ /** The TC39 `RegExp.escape` algorithm, for runtimes that lack it. */
66
+ export function escapeRegExpFallback(input) {
67
+ let out = '';
68
+ for (const ch of input) {
69
+ out +=
70
+ out === '' && /[0-9A-Za-z]/.test(ch)
71
+ ? hexEscape(ch.charCodeAt(0), 2)
72
+ : encodeForRegExpEscape(ch);
73
+ }
74
+ return out;
75
+ }
76
+ /**
77
+ * `RegExp.escape` shipped in Node 24. Older runtimes still load the published
78
+ * package (npm only warns on `engines`), so fall back rather than crash.
79
+ */
80
+ export const escapeRegExp = typeof RegExp.escape === 'function' ? (input) => RegExp.escape(input) : escapeRegExpFallback;
38
81
  export const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);
39
82
  export const ROOTS_TIMEOUT_MS = 5000;
40
83
  export function getMaxTextFileSize() {
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,GAAG,GAAG,IAAI,CAAC;AACjB,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAE9B,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,YAAoB,EACpB,GAAW,EACX,GAAW;IAEX,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,uFAAuF;AACvF,SAAS,eAAe,CACtB,IAAY,EACZ,KAAyB,EACzB,YAAoB,EACpB,GAAW,EACX,GAAW;IAEX,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,IACE,OAAO,KAAK,EAAE;QACd,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACpB,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QACzB,MAAM,GAAG,GAAG;QACZ,MAAM,GAAG,GAAG,EACZ,CAAC;QACD,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAC/E,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAyB;IACpD,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACjB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAEtF,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,CACpB,kBAAkB,EAClB,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAClD,EAAE,GAAG,GAAG,EACR,GAAG,EACH,GAAG,GAAG,GAAG,CACV,CAAC;AACJ,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,GAAG,GAAG,CAAC;AAEnD,sFAAsF;AACtF,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,CAAC;AAEnD,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAE9C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAEzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACxC,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAC1C,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAEpC,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC","sourcesContent":["import { availableParallelism } from 'node:os';\n\nimport { cli } from './config.ts';\nimport { warnInvalidSetting } from './path-utils.ts';\n\nconst KIB = 1024;\nexport const MIB = 1024 * KIB;\n\nexport function parseEnvInt(\n envVar: string,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);\n}\n\n/** Validate a raw integer setting (env var or CLI override) with a logged fallback. */\nfunction parseIntSetting(\n name: string,\n value: string | undefined,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n if (!value) {\n return defaultValue;\n }\n\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (\n trimmed === '' ||\n Number.isNaN(parsed) ||\n !Number.isInteger(parsed) ||\n parsed < min ||\n parsed > max\n ) {\n warnInvalidSetting(name, value, `${String(min)}-${String(max)}`, defaultValue);\n return defaultValue;\n }\n return parsed;\n}\n\n/**\n * Split a comma-separated config value into trimmed, non-empty entries. Empty\n * entries are dropped so \",\" or \" \" reads as unset rather than as a list of\n * blanks. Every comma-separated env var this server accepts parses through\n * here, so they all agree on what \"configured\" means.\n */\nexport function splitCsvList(value: string | undefined): string[] {\n return (value ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nexport const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);\n\nexport const ROOTS_TIMEOUT_MS = 5000;\n\nexport function getMaxTextFileSize(): number {\n return parseIntSetting(\n 'FS_MAX_FILE_SIZE',\n cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'],\n 10 * MIB,\n MIB,\n 100 * MIB,\n );\n}\n\n/** Combined byte budget for one batched `read`. */\nexport const READ_MANY_MAX_TOTAL_BYTES = 512 * KIB;\n\n/** Default line chunk size for read continuation when no explicit range was given. */\nexport const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;\n\nexport const DEFAULT_SEARCH_TIMEOUT_MS = 5000;\n\nexport const MAX_TREE_DEPTH = 50;\nexport const DEFAULT_TREE_ENTRIES = 1000;\n\nexport const MAX_LIST_ENTRIES = 20000;\n\nexport const MAX_SEARCH_RESULTS = 10000;\nexport const DEFAULT_SEARCH_RESULTS = 100;\nexport const MAX_SEARCH_DEPTH = 100;\n\nexport const DEFAULT_SEARCH_CONTENT_RESULTS = 500;\n"]}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"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","sourcesContent":["import { availableParallelism } from 'node:os';\n\nimport { cli } from './config.ts';\nimport { warnInvalidSetting } from './path-utils.ts';\n\nconst KIB = 1024;\nexport const MIB = 1024 * KIB;\n\nexport function parseEnvInt(\n envVar: string,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);\n}\n\n/** Validate a raw integer setting (env var or CLI override) with a logged fallback. */\nfunction parseIntSetting(\n name: string,\n value: string | undefined,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n if (!value) {\n return defaultValue;\n }\n\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (\n trimmed === '' ||\n Number.isNaN(parsed) ||\n !Number.isInteger(parsed) ||\n parsed < min ||\n parsed > max\n ) {\n warnInvalidSetting(name, value, `${String(min)}-${String(max)}`, defaultValue);\n return defaultValue;\n }\n return parsed;\n}\n\n/**\n * Split a comma-separated config value into trimmed, non-empty entries. Empty\n * entries are dropped so \",\" or \" \" reads as unset rather than as a list of\n * blanks. Every comma-separated env var this server accepts parses through\n * here, so they all agree on what \"configured\" means.\n */\nexport function splitCsvList(value: string | undefined): string[] {\n return (value ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nconst REGEXP_SYNTAX = /[\\\\^$.*+?()[\\]{}|/]/;\nconst REGEXP_OTHER_PUNCTUATORS = /[,\\-=<>#&!%:;@~'`\"]/;\nconst REGEXP_CONTROL_ESCAPES: Readonly<Record<string, string>> = {\n '\\t': 't',\n '\\n': 'n',\n '\\v': 'v',\n '\\f': 'f',\n '\\r': 'r',\n};\n\nconst hexEscape = (code: number, width: 2 | 4): string =>\n `\\\\${width === 2 ? 'x' : 'u'}${code.toString(16).padStart(width, '0')}`;\n\nfunction encodeForRegExpEscape(ch: string): string {\n if (REGEXP_SYNTAX.test(ch)) return `\\\\${ch}`;\n const control = REGEXP_CONTROL_ESCAPES[ch];\n if (control !== undefined) return `\\\\${control}`;\n const code = ch.codePointAt(0) ?? 0;\n const loneSurrogate = ch.length === 1 && code >= 0xd800 && code <= 0xdfff;\n if (!REGEXP_OTHER_PUNCTUATORS.test(ch) && !/\\s/.test(ch) && !loneSurrogate) return ch;\n if (code <= 0xff) return hexEscape(code, 2);\n return ch\n .split('')\n .map((unit) => hexEscape(unit.charCodeAt(0), 4))\n .join('');\n}\n\n/** The TC39 `RegExp.escape` algorithm, for runtimes that lack it. */\nexport function escapeRegExpFallback(input: string): string {\n let out = '';\n for (const ch of input) {\n out +=\n out === '' && /[0-9A-Za-z]/.test(ch)\n ? hexEscape(ch.charCodeAt(0), 2)\n : encodeForRegExpEscape(ch);\n }\n return out;\n}\n\n/**\n * `RegExp.escape` shipped in Node 24. Older runtimes still load the published\n * package (npm only warns on `engines`), so fall back rather than crash.\n */\nexport const escapeRegExp: (input: string) => string =\n typeof RegExp.escape === 'function' ? (input) => RegExp.escape(input) : escapeRegExpFallback;\n\nexport const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);\n\nexport const ROOTS_TIMEOUT_MS = 5000;\n\nexport function getMaxTextFileSize(): number {\n return parseIntSetting(\n 'FS_MAX_FILE_SIZE',\n cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'],\n 10 * MIB,\n MIB,\n 100 * MIB,\n );\n}\n\n/** Combined byte budget for one batched `read`. */\nexport const READ_MANY_MAX_TOTAL_BYTES = 512 * KIB;\n\n/** Default line chunk size for read continuation when no explicit range was given. */\nexport const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;\n\nexport const DEFAULT_SEARCH_TIMEOUT_MS = 5000;\n\nexport const MAX_TREE_DEPTH = 50;\nexport const DEFAULT_TREE_ENTRIES = 1000;\n\nexport const MAX_LIST_ENTRIES = 20000;\n\nexport const MAX_SEARCH_RESULTS = 10000;\nexport const DEFAULT_SEARCH_RESULTS = 100;\nexport const MAX_SEARCH_DEPTH = 100;\n\nexport const DEFAULT_SEARCH_CONTENT_RESULTS = 500;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AAmHA,eAAO,MAAM,MAAM,mCAwNjB,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AA8GA,eAAO,MAAM,MAAM,mCAqNjB,CAAC"}