@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,10 +25,10 @@ The last two columns are the ones that matter when choosing. **"Resolves with ze
25
25
  | Subpath | Contents | Node builtins reached | Third-party | Resolves with zero deps installed? |
26
26
  |---|---|---|---|---|
27
27
  | `./path` | `safePath`, `toForwardSlash`, `toForwardSlashAnyPlatform`, `toNfc`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `relativeEscapesRoot`, `isSingleFsSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
28
- | `./text` | `decodeTextContent` — the one bytes-to-text seam: BOM-announced UTF-8/UTF-16LE/UTF-16BE/UTF-32LE/UTF-32BE, BOM stripped, UTF-8 assumed otherwise; reports the encoding, whether it was a BOM fact or an assumption, and how many U+FFFD the decode substituted | **none** | — | **yes** |
28
+ | `./text` | `decodeTextContent` — the one bytes-to-text seam: BOM-announced UTF-8/UTF-16LE/UTF-16BE/UTF-32LE/UTF-32BE, BOM stripped, UTF-8 assumed otherwise; reports the encoding, whether it was a BOM fact or an assumption, and how many U+FFFD the decode substituted. `TextTooLargeError` (`TEXT_TOO_LARGE`) when the engine refuses a string that long | **none** | — | **yes** |
29
29
  | `./zod` | `ZodTypeNames`, `getZodTypeName`, `isZodType`, `unwrapZodType`, `isZodOptional`, `isZodNullable` | **none** | — | **yes** |
30
30
  | `./glob` | `isGlob`, static base extraction, magic remainder | `path` only | — | **yes** |
31
- | `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `fillPathSpellings`, `pathSpellingFrom`, `DirectorySpellingIndex`, `spellingWalkRoot`, `fillRealpaths`, `realpathFrom`, `FsLookupCache`, `readTextContent`, `readTextContentSync` | `fs`, `fs/promises`, `os`, `path`, `url`, `util` | — | **yes** |
31
+ | `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `fillPathSpellings`, `pathSpellingFrom`, `DirectorySpellingIndex`, `spellingWalkRoot`, `fillRealpaths`, `realpathFrom`, `FsLookupCache`, `readTextContent`, `readTextContentSync`, `readDecodableBytes` (refuses a file past `MAX_DECODABLE_BYTES` by `fstat`, before reading it), `exceedsDecodableLength`, `MAX_DECODABLE_BYTES`, `TextTooLargeError` | `buffer`, `fs`, `fs/promises`, `os`, `path`, `url`, `util` | — | **yes** |
32
32
  | `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite`, `removeScratchDir`, `createTempDir`, `removeTempDir`, `tempDirTracker`, `CANNOT_DENY_READS`, `buildHostileTree`, `HOSTILE_NAMES`, `symlinkCapability`, `createSymlink`, `createSymlinkAsync` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
33
33
  | `./asset` | `resolveAssetReference` — paths and npm bare specifiers | `fs`, `module`, `os`, `path`, `url` | — | **yes** |
34
34
  | `./yaml` | `updateYamlIn`, `verifyConfinedYamlEdit` — byte-surgical YAML edits | **none** | `yaml` | no — needs `yaml` |
@@ -20,21 +20,27 @@
20
20
  * needs it rather than speculatively. That second package arrived:
21
21
  * `packages/cli`'s `projectionStoreSelected()` reads the same `VAT_CACHE` the
22
22
  * `resources` parse cache does, and two independent readings of one variable is
23
- * the defect, not the fix. The four consumers today:
23
+ * the defect, not the fix. The consumers today:
24
24
  *
25
25
  * | Caller | Variable | Reads `undefined` as | Why |
26
26
  * |---|---|---|---|
27
- * | `link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
27
+ * | `resources/link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
28
28
  * | `resources/parse-cache.ts` | `VAT_CACHE` | cache stays on | gates a cache — an unreadable value must not silently change behaviour |
29
29
  * | `cli/utils/projection-store.ts` | `VAT_CACHE` | not a veto | same variable, same reading, one implementation |
30
30
  * | `cli/utils/projection-store.ts` | `VAT_PROJECTION_STORE` | not a veto | the projection store is ON by default; only an explicit off spelling (or an empty value, which that call site handles before asking) turns it off |
31
+ * | `resources/parse-dispatcher.ts` | `VAT_PARSE_POOL` | pool stays on | gates a measured speed-up — only an explicit off spelling disables it; a typo must not silently cost it |
31
32
  *
32
33
  * Same parser, different safe sides, each chosen at its own call site. That is
33
34
  * the contract; do not move a default in here.
34
35
  *
35
- * 🔑 The last two rows are the interesting pair: one variable defaults OFF and
36
- * the other defaults ON, and both are still read through this one function,
37
- * because what it answers is not "is this on" but "did the operator say no".
36
+ * 🔑 The two `projection-store.ts` rows are the interesting pair: one variable
37
+ * defaults OFF and the other defaults ON, and both are still read through this
38
+ * one function, because what it answers is not "is this on" but "did the
39
+ * operator say no".
40
+ *
41
+ * The table is asserted both ways, per (file, variable), against every
42
+ * `parseEnvBoolean` call (aliased imports included) in every package's `src` by
43
+ * `test/env-flag.test.ts`.
38
44
  */
39
45
  /**
40
46
  * Parse an env value as a boolean.
@@ -1 +1 @@
1
- {"version":3,"file":"env-flag.d.ts","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM5E"}
1
+ {"version":3,"file":"env-flag.d.ts","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM5E"}
package/dist/env-flag.js CHANGED
@@ -20,21 +20,27 @@
20
20
  * needs it rather than speculatively. That second package arrived:
21
21
  * `packages/cli`'s `projectionStoreSelected()` reads the same `VAT_CACHE` the
22
22
  * `resources` parse cache does, and two independent readings of one variable is
23
- * the defect, not the fix. The four consumers today:
23
+ * the defect, not the fix. The consumers today:
24
24
  *
25
25
  * | Caller | Variable | Reads `undefined` as | Why |
26
26
  * |---|---|---|---|
27
- * | `link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
27
+ * | `resources/link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
28
28
  * | `resources/parse-cache.ts` | `VAT_CACHE` | cache stays on | gates a cache — an unreadable value must not silently change behaviour |
29
29
  * | `cli/utils/projection-store.ts` | `VAT_CACHE` | not a veto | same variable, same reading, one implementation |
30
30
  * | `cli/utils/projection-store.ts` | `VAT_PROJECTION_STORE` | not a veto | the projection store is ON by default; only an explicit off spelling (or an empty value, which that call site handles before asking) turns it off |
31
+ * | `resources/parse-dispatcher.ts` | `VAT_PARSE_POOL` | pool stays on | gates a measured speed-up — only an explicit off spelling disables it; a typo must not silently cost it |
31
32
  *
32
33
  * Same parser, different safe sides, each chosen at its own call site. That is
33
34
  * the contract; do not move a default in here.
34
35
  *
35
- * 🔑 The last two rows are the interesting pair: one variable defaults OFF and
36
- * the other defaults ON, and both are still read through this one function,
37
- * because what it answers is not "is this on" but "did the operator say no".
36
+ * 🔑 The two `projection-store.ts` rows are the interesting pair: one variable
37
+ * defaults OFF and the other defaults ON, and both are still read through this
38
+ * one function, because what it answers is not "is this on" but "did the
39
+ * operator say no".
40
+ *
41
+ * The table is asserted both ways, per (file, variable), against every
42
+ * `parseEnvBoolean` call (aliased imports included) in every package's `src` by
43
+ * `test/env-flag.test.ts`.
38
44
  */
39
45
  const TRUE_SPELLINGS = new Set(['1', 'true', 'yes', 'y', 'on']);
40
46
  const FALSE_SPELLINGS = new Set(['0', 'false', 'no', 'n', 'off']);
@@ -1 +1 @@
1
- {"version":3,"file":"env-flag.js","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AACrF,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAEvF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"env-flag.js","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AACrF,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAEvF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,SAAS,CAAC;AACnB,CAAC"}
package/dist/fs.d.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  * `@vibe-agent-toolkit/utils/path` instead — it is dramatically cheaper.
9
9
  */
10
10
  export { normalizePath, normalizedTmpdir, mkdirSyncReal, resolveFromImportMeta, dynamicImportPath, } from './path-utils.js';
11
- export { readTextContent, readTextContentSync } from './text-file.js';
11
+ export { exceedsDecodableLength, MAX_DECODABLE_BYTES, readDecodableBytes, readTextContent, readTextContentSync, } from './text-file.js';
12
+ export { TextTooLargeError } from './text-content.js';
12
13
  export type { DecodedText, EncodingSource, TextEncoding, TextProvenance } from './text-content.js';
13
14
  export { isFilesystemAccessError, isPathAbsentError } from './errors/errno.js';
14
15
  export { copyDirectory, DirectorySpellingIndex, fillPathSpellings, fillRealpaths, FsLookupCache, pathSpellingFrom, realpathFrom, spellingWalkRoot, } from './fs-utils.js';
package/dist/fs.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKnG,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,cAAc,EACd,aAAa,EACb,SAAS,EACT,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,GACd,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKnG,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,cAAc,EACd,aAAa,EACb,SAAS,EACT,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,GACd,MAAM,eAAe,CAAC"}
package/dist/fs.js CHANGED
@@ -11,7 +11,8 @@ export { normalizePath, normalizedTmpdir, mkdirSyncReal, resolveFromImportMeta,
11
11
  // Read a file and decode it through the one content-decoding seam. The DECISION
12
12
  // about what the bytes say is pure and lives on `./text`; these two are that
13
13
  // plus a `readFile`, which is why they are here and not there.
14
- export { readTextContent, readTextContentSync } from './text-file.js';
14
+ export { exceedsDecodableLength, MAX_DECODABLE_BYTES, readDecodableBytes, readTextContent, readTextContentSync, } from './text-file.js';
15
+ export { TextTooLargeError } from './text-content.js';
15
16
  // Two materialized columns, each a fill+judge pair and nothing else. In both the
16
17
  // row IS the answer, so the row lookup — `pathSpellingFrom`, `realpathFrom` — is
17
18
  // itself the judge and is exported. See `index.ts` for the full reasoning.
package/dist/fs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAEzB,gFAAgF;AAChF,6EAA6E;AAC7E,+DAA+D;AAC/D,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGtE,iFAAiF;AACjF,iFAAiF;AACjF,2EAA2E;AAC3E,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAEzB,gFAAgF;AAChF,6EAA6E;AAC7E,+DAA+D;AAC/D,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,iFAAiF;AACjF,iFAAiF;AACjF,2EAA2E;AAC3E,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
@@ -34,6 +34,21 @@
34
34
  * inside the root. A create sink proceeds; a delete sink has nothing to do.
35
35
  */
36
36
  export type Containment = 'inside' | 'outside' | 'absent';
37
+ /**
38
+ * The canonical (realpath) spelling of `target`, whether or not it exists.
39
+ *
40
+ * The same canonicalization {@link isUnderRoot} applies to both of its sides,
41
+ * exposed for a caller that must COMPARE two spellings rather than ask a
42
+ * containment question — a root discovered from the physical `process.cwd()`
43
+ * against a path the operator typed through a symlink (macOS `/tmp` →
44
+ * `/private/tmp`). A missing target is canonicalized from its deepest existing
45
+ * ancestor with the missing tail re-appended.
46
+ *
47
+ * @param target - Absolute, or relative to cwd; either separator
48
+ * @returns The canonical spelling, forward-slashed
49
+ * @throws When the OS refuses to examine the path for any reason but absence
50
+ */
51
+ export declare function canonicalPath(target: string): string;
37
52
  /**
38
53
  * Is `candidate` strictly under `root`, as the filesystem judges it?
39
54
  *
@@ -1 +1 @@
1
- {"version":3,"file":"path-containment.d.ts","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAkD1D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAQxE"}
1
+ {"version":3,"file":"path-containment.d.ts","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAuC1D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAQxE"}
@@ -61,6 +61,23 @@ function canonicalFromAncestor(target) {
61
61
  candidate = parent;
62
62
  }
63
63
  }
64
+ /**
65
+ * The canonical (realpath) spelling of `target`, whether or not it exists.
66
+ *
67
+ * The same canonicalization {@link isUnderRoot} applies to both of its sides,
68
+ * exposed for a caller that must COMPARE two spellings rather than ask a
69
+ * containment question — a root discovered from the physical `process.cwd()`
70
+ * against a path the operator typed through a symlink (macOS `/tmp` →
71
+ * `/private/tmp`). A missing target is canonicalized from its deepest existing
72
+ * ancestor with the missing tail re-appended.
73
+ *
74
+ * @param target - Absolute, or relative to cwd; either separator
75
+ * @returns The canonical spelling, forward-slashed
76
+ * @throws When the OS refuses to examine the path for any reason but absence
77
+ */
78
+ export function canonicalPath(target) {
79
+ return canonicalFromAncestor(target).canonical;
80
+ }
64
81
  /** Whether an entry (a file, a directory, or a link — dangling or not) is at `p`. */
65
82
  function entryExists(p) {
66
83
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"path-containment.js","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAgB1E;;;;;;;;;;;;;;GAcG;AACH,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,SAAS,CAAC;QACR,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC;gBAC9E,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;aAC7B,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1C,SAAS,GAAG,MAAM,CAAC;IACrB,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC;QACH,SAAS,CAAC,CAAC,CAAC,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAiB;IACzD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"path-containment.js","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAgB1E;;;;;;;;;;;;;;GAcG;AACH,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,SAAS,CAAC;QACR,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC;gBAC9E,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;aAC7B,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1C,SAAS,GAAG,MAAM,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC;QACH,SAAS,CAAC,CAAC,CAAC,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAiB;IACzD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7C,CAAC"}
@@ -130,6 +130,34 @@
130
130
  * NOT shift is the content key, which is computed over the raw bytes; see
131
131
  * `resources`' `content-key.ts`.
132
132
  */
133
+ import { VatError } from './errors/vat-error.js';
134
+ /**
135
+ * Thrown when bytes are too many to become one JS string — the engine's refusal,
136
+ * made a coded error so a caller can treat it as a fact about a file.
137
+ *
138
+ * The limit is the engine's, not VAT's: V8 caps a string at
139
+ * `buffer.constants.MAX_STRING_LENGTH` (0x1fffffe8) UTF-16 code units, and
140
+ * decoding past it throws. One near-gigabyte CSV in a corpus used to abort a
141
+ * whole projection population on that throw.
142
+ *
143
+ * Two places raise it, and they agree by construction rather than by a shared
144
+ * literal. The `./fs` read path (`readDecodableBytes` in `text-file.ts`) refuses
145
+ * by `stat` BEFORE reading, against `MAX_DECODABLE_BYTES`, so half a gigabyte is
146
+ * never pulled into memory only to be refused. {@link decodeTextContent} — pure,
147
+ * so it cannot ask `node:buffer` for the limit (`subpath-purity.test.ts`) —
148
+ * translates the engine's own refusal for bytes that arrive by any other route.
149
+ * Dispatch with `isVatError(error, TextTooLargeError.code)`, never on the message.
150
+ */
151
+ export declare class TextTooLargeError extends VatError {
152
+ readonly byteLength: number;
153
+ /** The code every instance carries, for `isVatError(error, TextTooLargeError.code)`. */
154
+ static readonly code = "TEXT_TOO_LARGE";
155
+ /**
156
+ * @param byteLength - How many bytes the refused content holds
157
+ * @param options - `cause`, when an engine refusal is being translated
158
+ */
159
+ constructor(byteLength: number, options?: ErrorOptions);
160
+ }
133
161
  /** An encoding this module can decode. */
134
162
  export type TextEncoding = 'utf-8' | 'utf-16le' | 'utf-16be' | 'utf-32le' | 'utf-32be';
135
163
  /**
@@ -184,6 +212,7 @@ export interface DecodedText extends TextProvenance {
184
212
  * @param bytes - The exact bytes read from disk
185
213
  * @returns The decoded text, the encoding used, whether that was a fact, and how
186
214
  * many characters the decode had to replace
215
+ * @throws {@link TextTooLargeError} when the engine refuses a string that long
187
216
  *
188
217
  * @example
189
218
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"text-content.d.ts","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmIG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkLD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAiChE"}
1
+ {"version":3,"file":"text-content.d.ts","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAkB,SAAQ,QAAQ;IAS3C,QAAQ,CAAC,UAAU,EAAE,MAAM;IAR7B,wFAAwF;IACxF,MAAM,CAAC,QAAQ,CAAC,IAAI,oBAAoB;IAExC;;;OAGG;gBAEQ,UAAU,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE,YAAY;CAQzB;AAkBD,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkLD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAOhE"}
@@ -130,6 +130,51 @@
130
130
  * NOT shift is the content key, which is computed over the raw bytes; see
131
131
  * `resources`' `content-key.ts`.
132
132
  */
133
+ import { VatError } from './errors/vat-error.js';
134
+ /**
135
+ * Thrown when bytes are too many to become one JS string — the engine's refusal,
136
+ * made a coded error so a caller can treat it as a fact about a file.
137
+ *
138
+ * The limit is the engine's, not VAT's: V8 caps a string at
139
+ * `buffer.constants.MAX_STRING_LENGTH` (0x1fffffe8) UTF-16 code units, and
140
+ * decoding past it throws. One near-gigabyte CSV in a corpus used to abort a
141
+ * whole projection population on that throw.
142
+ *
143
+ * Two places raise it, and they agree by construction rather than by a shared
144
+ * literal. The `./fs` read path (`readDecodableBytes` in `text-file.ts`) refuses
145
+ * by `stat` BEFORE reading, against `MAX_DECODABLE_BYTES`, so half a gigabyte is
146
+ * never pulled into memory only to be refused. {@link decodeTextContent} — pure,
147
+ * so it cannot ask `node:buffer` for the limit (`subpath-purity.test.ts`) —
148
+ * translates the engine's own refusal for bytes that arrive by any other route.
149
+ * Dispatch with `isVatError(error, TextTooLargeError.code)`, never on the message.
150
+ */
151
+ export class TextTooLargeError extends VatError {
152
+ byteLength;
153
+ /** The code every instance carries, for `isVatError(error, TextTooLargeError.code)`. */
154
+ static code = 'TEXT_TOO_LARGE';
155
+ /**
156
+ * @param byteLength - How many bytes the refused content holds
157
+ * @param options - `cause`, when an engine refusal is being translated
158
+ */
159
+ constructor(byteLength, options) {
160
+ super(TextTooLargeError.code, `${byteLength} bytes is too large to decode into a JavaScript string`, options);
161
+ this.byteLength = byteLength;
162
+ }
163
+ }
164
+ /**
165
+ * The code Node's `TextDecoder` attaches when the decoded string would pass the
166
+ * engine's string-length limit. Matched as a `code`, never as its message.
167
+ */
168
+ const ENGINE_STRING_TOO_LONG = 'ERR_STRING_TOO_LONG';
169
+ /**
170
+ * Whether `error` is the engine refusing to build a string that long.
171
+ *
172
+ * @param error - What a decode threw
173
+ * @returns True for Node's `ERR_STRING_TOO_LONG`
174
+ */
175
+ function isEngineStringTooLong(error) {
176
+ return error instanceof Error && error.code === ENGINE_STRING_TOO_LONG;
177
+ }
133
178
  /**
134
179
  * Byte-order marks, **longest first**.
135
180
  *
@@ -308,6 +353,7 @@ function countReplacementCharacters(text) {
308
353
  * @param bytes - The exact bytes read from disk
309
354
  * @returns The decoded text, the encoding used, whether that was a fact, and how
310
355
  * many characters the decode had to replace
356
+ * @throws {@link TextTooLargeError} when the engine refuses a string that long
311
357
  *
312
358
  * @example
313
359
  * ```typescript
@@ -318,6 +364,24 @@ function countReplacementCharacters(text) {
318
364
  * ```
319
365
  */
320
366
  export function decodeTextContent(bytes) {
367
+ try {
368
+ return decodeWithinEngineLimits(bytes);
369
+ }
370
+ catch (error) {
371
+ if (isEngineStringTooLong(error))
372
+ throw new TextTooLargeError(bytes.byteLength, { cause: error });
373
+ throw error;
374
+ }
375
+ }
376
+ /**
377
+ * {@link decodeTextContent} without the translation of the engine's
378
+ * string-length refusal — split out so the translation wraps BOTH decodes, the
379
+ * fatal first pass and the lenient retry, either of which can reach the limit.
380
+ *
381
+ * @param bytes - The exact bytes read from disk
382
+ * @returns The decoded text and its provenance
383
+ */
384
+ function decodeWithinEngineLimits(bytes) {
321
385
  const bom = bomAt(bytes);
322
386
  const encoding = bom?.encoding ?? 'utf-8';
323
387
  const body = bom === null ? bytes : bytes.subarray(bom.length);
@@ -1 +1 @@
1
- {"version":3,"file":"text-content.js","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"text-content.js","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"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"}
@@ -14,10 +14,10 @@
14
14
  * it worse rather than more consistent.
15
15
  *
16
16
  * A caller that needs the RAW bytes as well as the text — to hash them, key
17
- * them, or report `stat().size` — must not use these: read the bytes once itself
18
- * and call `decodeTextContent` on them, so the digest and the characters come
19
- * from the same read. `readContentWithKey` in `@vibe-agent-toolkit/resources` is
20
- * that caller and is shaped exactly that way.
17
+ * them, or report `stat().size` — must not use these: read the bytes once with
18
+ * {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
19
+ * and the characters come from the same read. `readContentWithKey` in
20
+ * `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
21
21
  */
22
22
  import { type DecodedText } from './text-content.js';
23
23
  /**
@@ -25,7 +25,8 @@ import { type DecodedText } from './text-content.js';
25
25
  *
26
26
  * @param filePath - Path to read
27
27
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
28
- * @throws Whatever `readFile` throws — callers decide whether that is fatal
28
+ * @throws Whatever `readFile` throws — callers decide whether that is fatal — and
29
+ * {@link TextTooLargeError} for a file too large to decode, before reading it
29
30
  *
30
31
  * @example
31
32
  * ```typescript
@@ -39,7 +40,45 @@ export declare function readTextContent(filePath: string): Promise<DecodedText>;
39
40
  *
40
41
  * @param filePath - Path to read
41
42
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
42
- * @throws Whatever `readFileSync` throws
43
+ * @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
43
44
  */
44
45
  export declare function readTextContentSync(filePath: string): DecodedText;
46
+ /**
47
+ * The most bytes that can be decoded into one JS string: the engine's own
48
+ * string-length limit, read from `node:buffer` rather than written down.
49
+ *
50
+ * A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
51
+ * (a UTF-8 byte never yields more than one code unit) and conservative for
52
+ * UTF-16/UTF-32, whose bytes yield half or a quarter as many.
53
+ */
54
+ export declare const MAX_DECODABLE_BYTES: number;
55
+ /**
56
+ * Whether content of this many bytes is past what can be decoded to a string.
57
+ *
58
+ * @param byteLength - The content's size in bytes, as `stat` reports it
59
+ * @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
60
+ */
61
+ export declare function exceedsDecodableLength(byteLength: number): boolean;
62
+ /**
63
+ * Read a file's raw bytes for decoding — refusing, by `stat` and before reading
64
+ * a byte, one too large to decode at all.
65
+ *
66
+ * The one read path for "bytes that are about to go through
67
+ * {@link decodeTextContent}": {@link readTextContent} uses it, and so does
68
+ * `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
69
+ * bytes as well to key them. Without the stat, a 966 MB file is read whole and
70
+ * only then refused by the decoder, and past 2 GiB `readFile` fails first with
71
+ * its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
72
+ *
73
+ * The size comes from `fstat` on the handle that is then read, not from a
74
+ * separate `stat` of the path, so a rename between the two cannot substitute
75
+ * another file. A file that GROWS past the limit after the `fstat` still reaches
76
+ * the decoder, which raises the same {@link TextTooLargeError}.
77
+ *
78
+ * @param filePath - Path to read
79
+ * @returns The file's bytes
80
+ * @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
81
+ * and whatever `open`/`readFile` throw otherwise
82
+ */
83
+ export declare function readDecodableBytes(filePath: string): Promise<Buffer>;
45
84
  //# sourceMappingURL=text-file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-file.d.ts","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAEjE"}
1
+ {"version":3,"file":"text-file.d.ts","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAQjE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAoC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAElE;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1E"}
package/dist/text-file.js CHANGED
@@ -14,20 +14,22 @@
14
14
  * it worse rather than more consistent.
15
15
  *
16
16
  * A caller that needs the RAW bytes as well as the text — to hash them, key
17
- * them, or report `stat().size` — must not use these: read the bytes once itself
18
- * and call `decodeTextContent` on them, so the digest and the characters come
19
- * from the same read. `readContentWithKey` in `@vibe-agent-toolkit/resources` is
20
- * that caller and is shaped exactly that way.
17
+ * them, or report `stat().size` — must not use these: read the bytes once with
18
+ * {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
19
+ * and the characters come from the same read. `readContentWithKey` in
20
+ * `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
21
21
  */
22
- import { readFileSync } from 'node:fs';
23
- import { readFile } from 'node:fs/promises';
24
- import { decodeTextContent } from './text-content.js';
22
+ import { constants } from 'node:buffer';
23
+ import { closeSync, fstatSync, openSync, readFileSync } from 'node:fs';
24
+ import { open } from 'node:fs/promises';
25
+ import { decodeTextContent, TextTooLargeError } from './text-content.js';
25
26
  /**
26
27
  * Read a file and decode it through {@link decodeTextContent}.
27
28
  *
28
29
  * @param filePath - Path to read
29
30
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
30
- * @throws Whatever `readFile` throws — callers decide whether that is fatal
31
+ * @throws Whatever `readFile` throws — callers decide whether that is fatal — and
32
+ * {@link TextTooLargeError} for a file too large to decode, before reading it
31
33
  *
32
34
  * @example
33
35
  * ```typescript
@@ -36,16 +38,81 @@ import { decodeTextContent } from './text-content.js';
36
38
  * ```
37
39
  */
38
40
  export async function readTextContent(filePath) {
39
- return decodeTextContent(await readFile(filePath));
41
+ return decodeTextContent(await readDecodableBytes(filePath));
40
42
  }
41
43
  /**
42
44
  * {@link readTextContent}, synchronously.
43
45
  *
44
46
  * @param filePath - Path to read
45
47
  * @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
46
- * @throws Whatever `readFileSync` throws
48
+ * @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
47
49
  */
48
50
  export function readTextContentSync(filePath) {
49
- return decodeTextContent(readFileSync(filePath));
51
+ const handle = openSync(filePath, 'r');
52
+ try {
53
+ refuseUndecodableSize(fstatSync(handle).size);
54
+ return decodeTextContent(readFileSync(handle));
55
+ }
56
+ finally {
57
+ closeSync(handle);
58
+ }
59
+ }
60
+ /**
61
+ * The most bytes that can be decoded into one JS string: the engine's own
62
+ * string-length limit, read from `node:buffer` rather than written down.
63
+ *
64
+ * A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
65
+ * (a UTF-8 byte never yields more than one code unit) and conservative for
66
+ * UTF-16/UTF-32, whose bytes yield half or a quarter as many.
67
+ */
68
+ export const MAX_DECODABLE_BYTES = constants.MAX_STRING_LENGTH;
69
+ /**
70
+ * Whether content of this many bytes is past what can be decoded to a string.
71
+ *
72
+ * @param byteLength - The content's size in bytes, as `stat` reports it
73
+ * @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
74
+ */
75
+ export function exceedsDecodableLength(byteLength) {
76
+ return byteLength > MAX_DECODABLE_BYTES;
77
+ }
78
+ /**
79
+ * Throw {@link TextTooLargeError} for a size no decode can hold.
80
+ *
81
+ * @param byteLength - The content's size in bytes
82
+ */
83
+ function refuseUndecodableSize(byteLength) {
84
+ if (exceedsDecodableLength(byteLength))
85
+ throw new TextTooLargeError(byteLength);
86
+ }
87
+ /**
88
+ * Read a file's raw bytes for decoding — refusing, by `stat` and before reading
89
+ * a byte, one too large to decode at all.
90
+ *
91
+ * The one read path for "bytes that are about to go through
92
+ * {@link decodeTextContent}": {@link readTextContent} uses it, and so does
93
+ * `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
94
+ * bytes as well to key them. Without the stat, a 966 MB file is read whole and
95
+ * only then refused by the decoder, and past 2 GiB `readFile` fails first with
96
+ * its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
97
+ *
98
+ * The size comes from `fstat` on the handle that is then read, not from a
99
+ * separate `stat` of the path, so a rename between the two cannot substitute
100
+ * another file. A file that GROWS past the limit after the `fstat` still reaches
101
+ * the decoder, which raises the same {@link TextTooLargeError}.
102
+ *
103
+ * @param filePath - Path to read
104
+ * @returns The file's bytes
105
+ * @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
106
+ * and whatever `open`/`readFile` throw otherwise
107
+ */
108
+ export async function readDecodableBytes(filePath) {
109
+ const handle = await open(filePath, 'r');
110
+ try {
111
+ refuseUndecodableSize((await handle.stat()).size);
112
+ return await handle.readFile();
113
+ }
114
+ finally {
115
+ await handle.close();
116
+ }
50
117
  }
51
118
  //# sourceMappingURL=text-file.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-file.js","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAExE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,OAAO,iBAAiB,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,OAAO,iBAAiB,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;AACnD,CAAC"}
1
+ {"version":3,"file":"text-file.js","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,OAAO,iBAAiB,CAAC,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,iBAAiB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAW,SAAS,CAAC,iBAAiB,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,OAAO,UAAU,GAAG,mBAAmB,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,UAAkB;IAC/C,IAAI,sBAAsB,CAAC,UAAU,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,qBAAqB,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;IACjC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
package/dist/text.d.ts CHANGED
@@ -12,5 +12,5 @@
12
12
  * `readTextContent` from `@vibe-agent-toolkit/utils/fs`, which is this plus a
13
13
  * `readFile`.
14
14
  */
15
- export { decodeTextContent, type DecodedText, type EncodingSource, type TextEncoding, type TextProvenance, } from './text-content.js';
15
+ export { decodeTextContent, type DecodedText, type EncodingSource, type TextEncoding, type TextProvenance, TextTooLargeError, } from './text-content.js';
16
16
  //# sourceMappingURL=text.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
package/dist/text.js CHANGED
@@ -12,5 +12,5 @@
12
12
  * `readTextContent` from `@vibe-agent-toolkit/utils/fs`, which is this plus a
13
13
  * `readFile`.
14
14
  */
15
- export { decodeTextContent, } from './text-content.js';
15
+ export { decodeTextContent, TextTooLargeError, } from './text-content.js';
16
16
  //# sourceMappingURL=text.js.map
package/dist/text.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,GAKlB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EAKjB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
@@ -20,17 +20,69 @@
20
20
  * segment boundary via `exempt-path-matcher.cjs`; give the full repo-relative
21
21
  * path, not a bare basename.
22
22
  *
23
+ * ## Option `derived` — the code is DERIVED from the document, not chosen
24
+ *
25
+ * Naming a member is not enough. `process.exit(refused ? ExitCode.FINDINGS :
26
+ * ExitCode.OK)` names members and still re-decides, beside the document, what
27
+ * the document means — and one outcome shipped with different codes in
28
+ * different verbs that way (a budget kill 1-or-2 by timing; an unreadable root
29
+ * 2 in one verb and 1 in another). Under `derived.paths`:
30
+ *
31
+ * - `ExitCode.FINDINGS` may not be NAMED at all. "What it examined failed its
32
+ * gate" is a claim about a published document, so it comes from
33
+ * `exitCodeForReport(document)` (`@vibe-agent-toolkit/schema`) and nowhere else.
34
+ * - `process.exit(x)` / `process.exitCode = x` takes `ExitCode.OK`,
35
+ * `ExitCode.ERROR`, or a call to one of `derived.calls` (the derivations:
36
+ * `exitCodeForReport`, `exitCodeOfChild`, …). A ternary, a variable or a
37
+ * forwarded field is a code decided somewhere this rule cannot see.
38
+ * - `derived.legacy` names the files not yet migrated. It is a RATCHET asserted
39
+ * both ways: a listed file that no longer decides a code by hand is itself an
40
+ * error (`staleLegacy`), so the list can only shrink and never holds a dead entry.
41
+ *
42
+ * ⚠️ What this cannot see: a document verb ending on a bare `ExitCode.OK` over a
43
+ * document that says `error`. That is a runtime fact, pinned by the exit-code
44
+ * matrix system test, not a syntactic one.
45
+ *
23
46
  * @example
24
47
  * // BAD — 1 means what, exactly?
25
48
  * process.exit(1);
26
49
  *
27
- * // GOOD — the meaning is the code
28
- * process.exit(ExitCode.FINDINGS);
50
+ * // BAD under `derived` — the call site decides what the document means
51
+ * process.exit(errors > 0 ? ExitCode.FINDINGS : ExitCode.OK);
52
+ *
53
+ * // GOOD — derived from what was published
54
+ * process.exit(exitCodeForReport(report));
29
55
  */
30
56
 
31
57
  'use strict';
32
58
 
33
- const { createExemptPathMatcher } = require('./exempt-path-matcher.cjs');
59
+ const { createExemptDirectoryMatcher, createExemptPathMatcher, isTestFile } = require('./exempt-path-matcher.cjs');
60
+
61
+ /** The members a call site may name outright: neither is a claim about a document. */
62
+ const UNDERIVED_MEMBERS = new Set(['OK', 'ERROR']);
63
+
64
+ /** Whether `node` is `ExitCode.<name>` (non-computed). */
65
+ function isExitCodeMember(node, name) {
66
+ return (
67
+ node.type === 'MemberExpression' &&
68
+ !node.computed &&
69
+ node.object.type === 'Identifier' &&
70
+ node.object.name === 'ExitCode' &&
71
+ node.property.type === 'Identifier' &&
72
+ (name === undefined ? UNDERIVED_MEMBERS.has(node.property.name) : node.property.name === name)
73
+ );
74
+ }
75
+
76
+ /** The called function's own name — `f(…)` or `x.f(…)` — or null. */
77
+ function calleeName(node) {
78
+ if (node.type !== 'CallExpression') return null;
79
+ const { callee } = node;
80
+ if (callee.type === 'Identifier') return callee.name;
81
+ if (callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier') {
82
+ return callee.property.name;
83
+ }
84
+ return null;
85
+ }
34
86
 
35
87
  /** Whether `node` is the member expression `process.<name>` (non-computed). */
36
88
  function isProcessMember(node, name) {
@@ -84,6 +136,16 @@ module.exports = {
84
136
  type: 'object',
85
137
  properties: {
86
138
  allow: { type: 'array', items: { type: 'string' }, uniqueItems: true },
139
+ derived: {
140
+ type: 'object',
141
+ properties: {
142
+ paths: { type: 'array', items: { type: 'string' }, uniqueItems: true },
143
+ calls: { type: 'array', items: { type: 'string' }, uniqueItems: true },
144
+ legacy: { type: 'array', items: { type: 'string' }, uniqueItems: true },
145
+ },
146
+ required: ['paths', 'calls', 'legacy'],
147
+ additionalProperties: false,
148
+ },
87
149
  },
88
150
  additionalProperties: false,
89
151
  },
@@ -96,34 +158,73 @@ module.exports = {
96
158
  literalExitCode:
97
159
  'process.exitCode = {{literal}} re-decides the exit contract at this site. Assign an ' +
98
160
  'ExitCode member from @vibe-agent-toolkit/schema instead.',
161
+ findingsNotDerived:
162
+ 'ExitCode.FINDINGS is a claim about a published document ("what it examined failed its ' +
163
+ 'gate"). Derive it: process.exit(exitCodeForReport(document)) — never name it at a call site.',
164
+ exitNotDerived:
165
+ 'This exit code is decided here, beside the document, where it can disagree with it. Exit ' +
166
+ 'with ExitCode.OK, ExitCode.ERROR, or a derivation: {{calls}}.',
167
+ staleLegacy:
168
+ 'This file is listed in no-literal-process-exit derived.legacy but decides no exit code by ' +
169
+ 'hand any more. Remove it from the list — the ratchet only shrinks.',
99
170
  },
100
171
  },
101
172
 
102
173
  create(context) {
103
174
  const allow = context.options?.[0]?.allow ?? [];
175
+ const derived = context.options?.[0]?.derived;
104
176
  const filename = context.filename ?? context.getFilename();
105
177
  if (createExemptPathMatcher(allow)(filename)) {
106
178
  return {};
107
179
  }
108
180
 
181
+ const inDerived = derived !== undefined
182
+ && !isTestFile(filename)
183
+ && createExemptDirectoryMatcher(derived.paths)(filename);
184
+ const isLegacy = inDerived && createExemptPathMatcher(derived.legacy)(filename);
185
+ const derivations = new Set(derived?.calls ?? []);
186
+ let handDecided = 0;
187
+
188
+ /** Report a derivation violation — or, in a legacy file, only count it. */
189
+ function reportDerived(node, messageId) {
190
+ handDecided += 1;
191
+ if (isLegacy) return;
192
+ context.report({ node, messageId, data: { calls: [...derivations].join(', ') } });
193
+ }
194
+
195
+ /** @returns {boolean} Whether a literal was reported (so nothing else is). */
109
196
  function reportLiteral(node, expr, messageId) {
110
197
  const leaf = numericLiteralLeaf(expr);
111
- if (leaf !== null) {
112
- context.report({ node, messageId, data: { literal: String(leaf.raw ?? leaf.value) } });
113
- }
198
+ if (leaf === null) return false;
199
+ context.report({ node, messageId, data: { literal: String(leaf.raw ?? leaf.value) } });
200
+ return true;
201
+ }
202
+
203
+ function checkExitValue(node, expr, literalMessageId) {
204
+ if (reportLiteral(node, expr, literalMessageId) || !inDerived) return;
205
+ if (isExitCodeMember(expr) || derivations.has(calleeName(expr))) return;
206
+ // `ExitCode.FINDINGS` itself is reported by the member visitor below.
207
+ if (isExitCodeMember(expr, 'FINDINGS')) return;
208
+ reportDerived(node, 'exitNotDerived');
114
209
  }
115
210
 
116
211
  return {
117
212
  CallExpression(node) {
118
213
  if (isProcessMember(node.callee, 'exit') && node.arguments.length > 0) {
119
- reportLiteral(node, node.arguments[0], 'literalExit');
214
+ checkExitValue(node, node.arguments[0], 'literalExit');
120
215
  }
121
216
  },
122
217
  AssignmentExpression(node) {
123
218
  if (isProcessMember(node.left, 'exitCode')) {
124
- reportLiteral(node, node.right, 'literalExitCode');
219
+ checkExitValue(node, node.right, 'literalExitCode');
125
220
  }
126
221
  },
222
+ MemberExpression(node) {
223
+ if (inDerived && isExitCodeMember(node, 'FINDINGS')) reportDerived(node, 'findingsNotDerived');
224
+ },
225
+ 'Program:exit'(node) {
226
+ if (isLegacy && handDecided === 0) context.report({ node, messageId: 'staleLegacy' });
227
+ },
127
228
  };
128
229
  },
129
230
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-agent-toolkit/utils",
3
- "version": "0.2.0-rc.10",
3
+ "version": "0.2.0-rc.11",
4
4
  "type": "module",
5
5
  "description": "Core utility functions shared across the vibe-agent-toolkit packages",
6
6
  "sideEffects": [