@vibe-agent-toolkit/utils 0.1.41 → 0.1.42-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +146 -31
  2. package/dist/asset.d.ts +9 -0
  3. package/dist/asset.d.ts.map +1 -0
  4. package/dist/asset.js +9 -0
  5. package/dist/asset.js.map +1 -0
  6. package/dist/crawl.d.ts +19 -0
  7. package/dist/crawl.d.ts.map +1 -0
  8. package/dist/crawl.js +19 -0
  9. package/dist/crawl.js.map +1 -0
  10. package/dist/file-crawler.d.ts +19 -2
  11. package/dist/file-crawler.d.ts.map +1 -1
  12. package/dist/file-crawler.js +19 -1
  13. package/dist/file-crawler.js.map +1 -1
  14. package/dist/fs-utils.d.ts +60 -3
  15. package/dist/fs-utils.d.ts.map +1 -1
  16. package/dist/fs-utils.js +88 -10
  17. package/dist/fs-utils.js.map +1 -1
  18. package/dist/fs.d.ts +7 -6
  19. package/dist/fs.d.ts.map +1 -1
  20. package/dist/fs.js +7 -6
  21. package/dist/fs.js.map +1 -1
  22. package/dist/git-utils.d.ts +2 -0
  23. package/dist/git-utils.d.ts.map +1 -1
  24. package/dist/git-utils.js +16 -0
  25. package/dist/git-utils.js.map +1 -1
  26. package/dist/git.d.ts +23 -0
  27. package/dist/git.d.ts.map +1 -0
  28. package/dist/git.js +23 -0
  29. package/dist/git.js.map +1 -0
  30. package/dist/gitignore-checker.d.ts +0 -9
  31. package/dist/gitignore-checker.d.ts.map +1 -1
  32. package/dist/gitignore-checker.js +0 -12
  33. package/dist/gitignore-checker.js.map +1 -1
  34. package/dist/glob/glob-pattern.js +1 -1
  35. package/dist/glob/glob-pattern.js.map +1 -1
  36. package/dist/glob.d.ts +8 -0
  37. package/dist/glob.d.ts.map +1 -0
  38. package/dist/glob.js +8 -0
  39. package/dist/glob.js.map +1 -0
  40. package/dist/path-core.d.ts +196 -0
  41. package/dist/path-core.d.ts.map +1 -0
  42. package/dist/path-core.js +251 -0
  43. package/dist/path-core.js.map +1 -0
  44. package/dist/path-utils.d.ts +12 -183
  45. package/dist/path-utils.d.ts.map +1 -1
  46. package/dist/path-utils.js +13 -237
  47. package/dist/path-utils.js.map +1 -1
  48. package/dist/path.d.ts +12 -0
  49. package/dist/path.d.ts.map +1 -0
  50. package/dist/path.js +12 -0
  51. package/dist/path.js.map +1 -0
  52. package/dist/process.d.ts +7 -4
  53. package/dist/process.d.ts.map +1 -1
  54. package/dist/process.js +7 -4
  55. package/dist/process.js.map +1 -1
  56. package/dist/project.d.ts +31 -0
  57. package/dist/project.d.ts.map +1 -0
  58. package/dist/project.js +31 -0
  59. package/dist/project.js.map +1 -0
  60. package/dist/safe-exec.d.ts.map +1 -1
  61. package/dist/safe-exec.js +8 -4
  62. package/dist/safe-exec.js.map +1 -1
  63. package/dist/spawn-hardened.d.ts.map +1 -1
  64. package/dist/spawn-hardened.js +4 -17
  65. package/dist/spawn-hardened.js.map +1 -1
  66. package/dist/template-entry.d.ts +10 -0
  67. package/dist/template-entry.d.ts.map +1 -0
  68. package/dist/template-entry.js +10 -0
  69. package/dist/template-entry.js.map +1 -0
  70. package/dist/testing.d.ts +8 -0
  71. package/dist/testing.d.ts.map +1 -0
  72. package/dist/testing.js +8 -0
  73. package/dist/testing.js.map +1 -0
  74. package/dist/windows-shell.d.ts +140 -8
  75. package/dist/windows-shell.d.ts.map +1 -1
  76. package/dist/windows-shell.js +218 -11
  77. package/dist/windows-shell.js.map +1 -1
  78. package/dist/yaml.d.ts +9 -0
  79. package/dist/yaml.d.ts.map +1 -0
  80. package/dist/yaml.js +9 -0
  81. package/dist/yaml.js.map +1 -0
  82. package/dist/zod.d.ts +10 -0
  83. package/dist/zod.d.ts.map +1 -0
  84. package/dist/zod.js +10 -0
  85. package/dist/zod.js.map +1 -0
  86. package/eslint/README.md +191 -0
  87. package/eslint/index.cjs +161 -0
  88. package/eslint/index.d.cts +60 -0
  89. package/eslint/rules/eslint-rule-factory.cjs +241 -0
  90. package/eslint/rules/exempt-path-matcher.cjs +265 -0
  91. package/eslint/rules/no-bare-dynamic-import-path.cjs +142 -0
  92. package/eslint/rules/no-child-process-execSync.cjs +23 -0
  93. package/eslint/rules/no-command-direct-factory.cjs +246 -0
  94. package/eslint/rules/no-file-url-string-concat.cjs +77 -0
  95. package/eslint/rules/no-fs-mkdirSync.cjs +23 -0
  96. package/eslint/rules/no-fs-promises-cp.cjs +36 -0
  97. package/eslint/rules/no-fs-realpathSync.cjs +23 -0
  98. package/eslint/rules/no-hardcoded-path-split.cjs +141 -0
  99. package/eslint/rules/no-manual-path-normalize.cjs +130 -0
  100. package/eslint/rules/no-os-tmpdir.cjs +24 -0
  101. package/eslint/rules/no-path-join.cjs +14 -0
  102. package/eslint/rules/no-path-operations-in-comparisons.cjs +148 -0
  103. package/eslint/rules/no-path-relative.cjs +14 -0
  104. package/eslint/rules/no-path-resolve.cjs +14 -0
  105. package/eslint/rules/no-path-sep-in-strings.cjs +130 -0
  106. package/eslint/rules/no-path-startswith.cjs +139 -0
  107. package/eslint/rules/no-test-scoped-functions.cjs +134 -0
  108. package/eslint/rules/no-unix-shell-commands.cjs +152 -0
  109. package/eslint/rules/no-unsafe-root-join.cjs +85 -0
  110. package/eslint/rules/no-url-pathname-for-fs.cjs +107 -0
  111. package/eslint/rules/path-function-rule-factory.cjs +216 -0
  112. package/eslint/rules/prefer-startswith-over-regex.cjs +111 -0
  113. package/eslint/rules/require-justified-skip.cjs +348 -0
  114. package/eslint/rules/safe-import.cjs +141 -0
  115. package/package.json +65 -7
package/README.md CHANGED
@@ -1,12 +1,10 @@
1
1
  # @vibe-agent-toolkit/utils
2
2
 
3
- Core shared utilities with no dependencies on other packages.
3
+ Cross-platform primitives for Node tooling that has to run correctly on both Windows and Linux — safe command execution, hardened process spawning, path normalization, and git introspection.
4
4
 
5
- ## Philosophy
5
+ Projects building skills and agent tooling with the vibe-agent-toolkit write exactly this kind of Node code and hit exactly these platform potholes: `.cmd` shims that need a shell on Windows, 8.3 short paths from `tmpdir()`, backslash-versus-forward-slash comparisons, and `import()` of an absolute path failing on Windows without a `file://` URL. This package is the shared answer.
6
6
 
7
- This package provides utilities that are needed by multiple packages in the toolkit. Utilities are **added as real needs arise**, not speculatively.
8
-
9
- If you need a utility function and multiple packages would benefit from it, add it here. Otherwise, keep it local to the package that needs it.
7
+ **Node-only.** Requires Node >= 22. See [Runtime support](#runtime-support).
10
8
 
11
9
  ## Installation
12
10
 
@@ -14,19 +12,62 @@ If you need a utility function and multiple packages would benefit from it, add
14
12
  bun add @vibe-agent-toolkit/utils
15
13
  ```
16
14
 
17
- ## Usage
15
+ ## Import narrowly
16
+
17
+ Every area has its own subpath. Import the one you need.
18
+
19
+ The package sets `"sideEffects": false`, so a modern bundler will tree-shake unused code out of the `.` barrel — importing `safePath` from `.` and from `./path` produce near-identical bundles. **Subpaths are not primarily a size optimization.** What they control is what your build has to *resolve* and what your module graph *reaches*: the `.` barrel reaches `yaml`, `handlebars`, and `node:fs` no matter what you destructure from it, so it cannot be bundled for a browser target and requires every dependency to be installed. A narrow entry reaches only what it needs.
20
+
21
+ The last two columns are the ones that matter when choosing. **"Resolves with zero deps installed?"** is the sharper of the two: it separates an entry that is merely *heavy* from one that is *unbuildable* in an environment where the package's third-party dependencies are absent or unresolvable.
22
+
23
+ | Subpath | Contents | Node builtins reached | Third-party | Resolves with zero deps installed? |
24
+ |---|---|---|---|---|
25
+ | `./path` | `safePath`, `toForwardSlash`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
26
+ | `./zod` | `ZodTypeNames`, `getZodTypeName`, `isZodType`, `unwrapZodType`, `isZodOptional`, `isZodNullable` | **none** | — | **yes** |
27
+ | `./glob` | `isGlob`, static base extraction, magic remainder | `path` only | — | **yes** |
28
+ | `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `verifyCaseSensitiveFilename`, `FsLookupCache` | `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
29
+ | `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
30
+ | `./asset` | `resolveAssetReference` — paths and npm bare specifiers | `fs`, `module`, `os`, `path`, `url` | — | **yes** |
31
+ | `./yaml` | `updateYamlIn`, `verifyConfinedYamlEdit` — byte-surgical YAML edits | **none** | `yaml` | no — needs `yaml` |
32
+ | `./template` | `renderTemplate` — cached Handlebars | **none** | `handlebars` | no — needs `handlebars` |
33
+ | `./process` | `safeExecSync`, `safeExecResult`, `safeExecFromString`, `isToolAvailable`, `getToolVersion`, `hasShellSyntax`, `CommandExecutionError`, `spawnHardened`, `shouldUseShell`, `windowsShellQuote`, `buildWindowsShellLine`, `resolveShellCommandToken`, `isPathLike`, `makeStdioBlocking`, `describeStdioBlocking` | `child_process`, `path` | `which` | no — needs `which` |
34
+ | `./git` | `gitFindRoot`, `gitLsFiles`, `isGitIgnored`, `loadGitignoreRules`, `GitTracker`, `parseGitUrl`, `isGitUrl`, `nonInteractiveGitOverrides` | `child_process`, `fs`, `os`, `path`, `url` | `ignore`, `which` | no — needs `which`, `ignore` |
35
+ | `./crawl` | `crawlDirectory`, `crawlDirectorySync`, `NEVER_CRAWL_GLOBS`, `BUILD_OUTPUT_GLOBS` | `child_process`, `fs`, `os`, `path`, `url` | `picomatch`, `which` | no — needs `picomatch`, `which` |
36
+ | `./project` | `findProjectRoot`, `findConfigFile`, `findNodeWorkspaceRoot`, `resetProjectRootCaches` | `fs`, `path` | — | **yes** |
37
+ | `./eslint` | the 21 ESLint rules that enforce everything above — see [ESLint rules](#eslint-rules--vibe-agent-toolkitutilseslint) | **none** | — | **yes** |
38
+ | `.` | every runtime entry above (not `./eslint`) | all of the above, plus `stream` | `handlebars`, `ignore`, `picomatch`, `which`, `yaml` | no — needs all of them |
39
+ | `./package.json` | the manifest itself, for version reporting and resolution assertions | — | — | **yes** |
40
+
41
+ Note `./zod` reaches nothing at all: it detects Zod types by duck-typing `_def.typeName` rather than importing Zod, which is exactly why it works across Zod v3 and v4.
42
+
43
+ `./crawl` is the only *subpath* that reaches `picomatch` (the `.` barrel also reaches it, via linkAuth's host-pattern matching), and it is deliberately *not* folded into `./glob` — `./glob` is guarded as portable (`node:path`, no third-party), and directory crawling would break both halves of that guarantee.
18
44
 
19
45
  ```typescript
20
- import {
21
- safeExecSync,
22
- toForwardSlash,
23
- crawlDirectory,
24
- isGitIgnored,
25
- getGitRootDir,
26
- setupTestTempDir,
27
- } from '@vibe-agent-toolkit/utils';
46
+ // Reaches node:path and nothing else
47
+ import { safePath, toForwardSlash } from '@vibe-agent-toolkit/utils/path';
48
+ import { safeExecSync, spawnHardened } from '@vibe-agent-toolkit/utils/process';
49
+
50
+ // Reaches yaml, handlebars, and node:fs regardless of what you destructure
51
+ import { safePath } from '@vibe-agent-toolkit/utils';
28
52
  ```
29
53
 
54
+ `./package.json` is exported as well, so `require('@vibe-agent-toolkit/utils/package.json')` (or a `with { type: 'json' }` import) works for version reporting and "which build am I on?" resolution assertions instead of failing with `ERR_PACKAGE_PATH_NOT_EXPORTED`.
55
+
56
+ Consumers who prefer a single seam over direct dependencies can re-export the subpaths they want from their own internal module. That pattern works well — just wrap the subpaths rather than the `.` barrel, or the narrowing is lost.
57
+
58
+ ## Runtime support
59
+
60
+ This package targets **Node >= 22** and is not published for browsers. Most entry points reach `node:fs`, `node:child_process`, or `node:os`.
61
+
62
+ A guard test in `test/subpath-purity.test.ts` walks each entry's transitive source graph and enforces **both** of the table's last two columns:
63
+
64
+ - **Third-party reach** — the "Resolves with zero deps installed?" column. Every entry's expected third-party set is asserted exactly, so every **yes** row above is a tested claim rather than a documented intention, and adding a dependency to any entry is a deliberate, reviewed edit.
65
+ - **Builtin reach** — five entries are held to a stricter contract still: `./zod`, `./yaml`, `./template` reach **no Node builtin at all**, and `./path`, `./glob` reach **`node:path` and nothing else** — the one builtin every bundler shims.
66
+
67
+ That is an enforced invariant, not a browser-support commitment: there are no browser export conditions and no browser test lane. The guard exists so the property can't regress silently — it fails loudly if it cannot resolve a module, so it can't pass vacuously; `test/fixtures/dangling-import/` exercises that failure so the guarantee is demonstrated, not just claimed. If you add a new entry, add it to that test or nothing protects it.
68
+
69
+ `./eslint` is hand-written CommonJS rather than compiled TypeScript, so that walker cannot see it; `test/eslint/subpath-purity.test.ts` holds it to the same contract by walking its `require()` graph instead — no builtin, no third-party, and in particular never `eslint` itself.
70
+
30
71
  ## Available Utilities
31
72
 
32
73
  ### Zod Type Introspection (Version-Agnostic)
@@ -37,7 +78,7 @@ Uses duck typing via `_def.typeName` instead of `instanceof` checks, which fail
37
78
 
38
79
  **Quick Example**:
39
80
  ```typescript
40
- import { getZodTypeName, isZodType, ZodTypeNames } from '@vibe-agent-toolkit/utils';
81
+ import { getZodTypeName, isZodType, ZodTypeNames } from '@vibe-agent-toolkit/utils/zod';
41
82
  import { z } from 'zod';
42
83
 
43
84
  const schema = z.string().optional();
@@ -67,31 +108,105 @@ UNION, INTERSECTION, TUPLE, RECORD, MAP, SET,
67
108
  FUNCTION, LAZY, PROMISE, and more...
68
109
  ```
69
110
 
70
- **See**: [docs/zod-compatibility.md](../../docs/zod-compatibility.md) for complete guide
111
+ **See**: [docs/zod-compatibility.md](https://github.com/jdutton/vibe-agent-toolkit/blob/main/docs/zod-compatibility.md) for the complete guide
71
112
 
72
113
  **Peer Dependency**: Requires `zod ^3.25.0 || ^4.0.0`
73
114
 
74
115
  ---
75
116
 
76
- ### Process Spawning
77
- - `safeExecSync()` - Cross-platform secure command execution without shell
117
+ ### Path strings — `@vibe-agent-toolkit/utils/path`
118
+
119
+ These always return forward slashes on every platform, so they are safe for comparisons, `Map` keys, globs, and display.
120
+
121
+ - `safePath.join()` / `.resolve()` / `.relative()` - forward-slash equivalents of the `node:path` functions
122
+ - `toForwardSlash()` - explicit converter for any path string
123
+ - `toAbsolutePath()` - resolve a path relative to a base directory
124
+ - `getRelativePath()` - relative path between two absolute paths
125
+ - `isAbsolutePath()` / `isAbsoluteAnyPlatform()` - absolute-path predicates
126
+ - `hasParentTraversalSegment()` - detect `..` segments before using a caller-supplied path
127
+ - `issueLocation()` - format a `file:line`-style location relative to a project root
128
+
129
+ ### Filesystem — `@vibe-agent-toolkit/utils/fs`
130
+
131
+ These return **OS-native** separators, because they resolve real filesystem identity via `realpathSync.native()` (which is what resolves Windows 8.3 short names). Wrap with `toForwardSlash()` if you need forward slashes.
132
+
133
+ ⚠️ **`normalizePath()` has an input-dependent split personality.** It lives on `./fs` (rather than `./path`) for the right reason — it calls `realpathSync.native()` — but which of two different things it does depends on its argument: given a single *relative* path it is pure string work, equivalent to `path.normalize`, touching no filesystem; given anything else it resolves to absolute and performs a filesystem `realpath`, so it follows symlinks, resolves 8.3 short names, and — when the path does not exist — silently falls back to the merely-resolved path. Two semantics under one name: check which case you are in before relying on either.
134
+
135
+ - `normalizePath()` - resolve 8.3 short names and return the real path (see the warning above)
136
+ - `normalizedTmpdir()` - `os.tmpdir()` with short names resolved
137
+ - `mkdirSyncReal()` - create a directory and return its real path
138
+ - `resolveFromImportMeta()` - resolve paths relative to an `import.meta.url`
139
+ - `dynamicImportPath()` - `import()` an absolute path (works on Windows, which rejects bare absolute paths)
140
+ - `copyDirectory()`
141
+ - `verifyCaseSensitiveFilename(filePath, fsCache)` - case-exact existence check; takes a per-run `FsLookupCache`
142
+ - `FsLookupCache` - per-run memo for `realpath`/`readdir`, sharing in-flight promises. Construct one per
143
+ validation run and let it die with the run — never a module-level singleton, or a long-lived process
144
+ answers from a stale directory listing.
145
+
146
+ ### Processes — `@vibe-agent-toolkit/utils/process`
147
+
148
+ - `safeExecSync()` / `safeExecResult()` / `safeExecFromString()` - cross-platform command execution with no shell injection
149
+ - `spawnHardened()` - async spawn with streaming stdio and correct Windows `.cmd`/`.bat` launching
150
+ - `shouldUseShell()` / `windowsShellQuote()` / `buildWindowsShellLine()` - Windows shell-invocation helpers
151
+ - `isToolAvailable()` / `getToolVersion()` / `hasShellSyntax()`
152
+ - `makeStdioBlocking()` - stop `process.exit()` truncating output in a published bin
153
+
154
+ ### Git — `@vibe-agent-toolkit/utils/git`
155
+
156
+ - `gitFindRoot()` - find the repository root from a starting directory
157
+ - `gitLsFiles()` - enumerate tracked files
158
+ - `isGitIgnored()` - check whether a path is gitignored (outside a repository it answers from the filesystem, spawning nothing)
159
+ - `loadGitignoreRules()` - load a repository's ignore rules
160
+ - `GitTracker` - cached tracked-file lookups for repeated checks
161
+ - `parseGitUrl()` / `isGitUrl()` - recognize and decompose git URLs, including `owner/repo` shorthand and `#ref:subpath` fragments
162
+ - `nonInteractiveGitOverrides()` - env and `git -c` overrides that stop a clone blocking on a credential prompt
163
+
164
+ **One root finder, on purpose.** `gitFindRoot()` is the only one in the package. There used to be a second name, `findGitRoot()`, whose entire body was `return gitFindRoot(startDir)`; two names for one function guarantees half of all callers pick each. Keeping it off this entry was not enough — it stayed on the `.` barrel, so both names were still one import away — so it has been removed outright. Replace any `findGitRoot(` with `gitFindRoot(`; the behavior is identical.
165
+
166
+ ### Test helpers — `@vibe-agent-toolkit/utils/testing`
167
+
168
+ - `setupAsyncTempDirSuite()` / `setupSyncTempDirSuite()` - per-suite temp directories with cleanup
169
+ - `getTestOutputDir()` / `getTestOutputBase()` - isolated test output paths
170
+
171
+ ### Project roots — `@vibe-agent-toolkit/utils` (barrel only)
172
+
173
+ - `findProjectRoot()` - the VAT project root: nearest `vibe-agent-toolkit.config.yaml`, else nearest `.git/`, else `null`
174
+ - `findConfigFile()` / `findNodeWorkspaceRoot()` - the narrower individual probes
175
+ - `resetProjectRootCaches()` - invalidate the module-level walk-up cache (long-lived processes and tests that mutate fixtures)
176
+
177
+ These are CLI-boundary functions: inner libraries should take a root as a parameter rather than discovering one. They return `string | null` with no internal fallback, so a caller with no root has to decide one rather than silently landing on an absolute path.
178
+
179
+ **These four are VAT-shaped — read this before reaching for them.** `findProjectRoot()` looks for `vibe-agent-toolkit.config.yaml` and then `.git/`; if your repo's notion of "root" is a `pnpm-workspace.yaml`, a `turbo.json`, or a lockfile, that ladder is not your ladder — and for a *published* package, keying anything on `.git/` is a bug, since it will not be there at install time. `findNodeWorkspaceRoot()` is narrower still: it needs a `package.json` carrying a `"workspaces"` key, which pnpm and Bun workspaces do not have. `findConfigFile()` hardcodes VAT's config filename. If you want a git root, take `gitFindRoot()` from [`./git`](#git--vibe-agent-toolkitutilsgit); if you want your own marker, a six-line walk-up is more honest than a helper whose ladder you have to work around.
180
+
181
+ They are nonetheless on their own [`./project`](#import-narrowly) entry rather than the barrel alone. The entry was briefly withdrawn on the grounds that the functions fit few repos — which is true, and is what the paragraph above says — but that answered the wrong question. What decides whether an *entry* exists is how heavy the only remaining door is: barrel-only, these four cost `handlebars`, `yaml`, `picomatch`, `ignore` and `which` to reach, while their own code imports nothing but `node:fs` and `node:path`. Publishing the entry is not a claim that the ladder fits you — only that finding out shouldn't cost five dependencies.
182
+
183
+ ### Directory crawling — `@vibe-agent-toolkit/utils/crawl`
184
+
185
+ - `crawlDirectory()` / `crawlDirectorySync()` - gitignore-aware directory walks
186
+ - `NEVER_CRAWL_GLOBS` / `BUILD_OUTPUT_GLOBS` - the standard exclusion sets
187
+
188
+ Glob *pattern inspection* is a separate entry, `./glob`, and stays that way: `./glob` is dependency-free and reaches only `node:path`, whereas crawling reaches the filesystem, `git`, and `picomatch`.
189
+
190
+ ### ESLint rules — `@vibe-agent-toolkit/utils/eslint`
191
+
192
+ A safety helper is only as good as its enforcement: `safePath.join()` prevents a class of Windows bug precisely once — the moment someone writes `path.join()` instead, the helper's existence has bought nothing. So the 21 rules that direct code to these helpers ship with them, on their own subpath:
193
+
194
+ ```js
195
+ // eslint.config.js
196
+ import vat from '@vibe-agent-toolkit/utils/eslint';
197
+
198
+ export default [
199
+ vat.configs.recommended,
200
+ ];
201
+ ```
78
202
 
79
- ### Path Utilities
80
- - `toForwardSlash()` - Convert paths to forward slashes (Windows/Unix compatibility)
81
- - `getRelativePath()` - Get relative path between two absolute paths
82
- - `normalizeFilePath()` - Normalize file paths for consistent comparisons
203
+ `configs.recommended` registers the rules under the `@vibe-agent-toolkit` namespace and turns on the cross-platform safety core (18 of the 21 — three rules are opt-in). Most rules auto-fix, and every message names the replacement and the subpath it lives on. Rules that ban a primitive take an `exemptFiles` option naming the file that implements *your* wrapper; there are deliberately no built-in exemptions.
83
204
 
84
- ### File System
85
- - `crawlDirectory()` - Recursively crawl directories with pattern filtering
86
- - `readFileContent()` - Read file content with encoding detection
205
+ **[Full rule table, severities, and exemption semantics →](./eslint/README.md)**
87
206
 
88
- ### Git Integration
89
- - `isGitIgnored()` - Check if file is gitignored (cached per directory)
90
- - `getGitRootDir()` - Find git repository root directory
91
- - `ensureGitRepository()` - Verify current directory is in a git repo
207
+ Requires ESLint 9+ (flat config). `eslint` is an **optional** peer dependency and adds nothing to the entries above: an ESLint plugin is data, not code that runs — the rule modules export plain objects and none of them `require('eslint')` — so this subpath reaches no Node builtin and no third-party package at all, and installing `utils` for `safePath.join()` alone pulls in nothing extra.
92
208
 
93
- ### Test Helpers
94
- - `setupTestTempDir()` - Create temp directory for tests with cleanup
209
+ Shipping them here rather than as a separate `eslint-plugin` package is deliberate: one install, one version, and no way for a rule to name a helper signature the installed `utils` no longer has.
95
210
 
96
211
  ## License
97
212
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @vibe-agent-toolkit/utils/asset
3
+ *
4
+ * Asset reference resolution: accepts both filesystem paths (relative to a
5
+ * base directory, or absolute) and npm bare specifiers (`@scope/pkg/subpath`),
6
+ * honoring the target package's `exports` map. Node-only.
7
+ */
8
+ export * from './asset-reference.js';
9
+ //# sourceMappingURL=asset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset.d.ts","sourceRoot":"","sources":["../src/asset.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,sBAAsB,CAAC"}
package/dist/asset.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @vibe-agent-toolkit/utils/asset
3
+ *
4
+ * Asset reference resolution: accepts both filesystem paths (relative to a
5
+ * base directory, or absolute) and npm bare specifiers (`@scope/pkg/subpath`),
6
+ * honoring the target package's `exports` map. Node-only.
7
+ */
8
+ export * from './asset-reference.js';
9
+ //# sourceMappingURL=asset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset.js","sourceRoot":"","sources":["../src/asset.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @vibe-agent-toolkit/utils/crawl
3
+ *
4
+ * Directory crawling (`crawlDirectory`, `crawlDirectorySync`) plus the
5
+ * never-crawl / build-output glob constants. Node-only — reads the filesystem
6
+ * and shells out to `git` for the gitignore-aware fast path.
7
+ *
8
+ * This is the only *subpath* entry that reaches `picomatch` — `./crawl` is
9
+ * therefore the narrow entry that makes `picomatch` a required install. It is
10
+ * not the package's only consumer of it: `link-auth/select-provider.ts` uses
11
+ * `picomatch.isMatch` for host-pattern matching, and that is reachable from the
12
+ * `.` barrel.
13
+ *
14
+ * Deliberately NOT folded into `./glob`. `./glob` is guarded as portable —
15
+ * `node:path` and no third-party dependency (see `test/subpath-purity.test.ts`)
16
+ * — and file-crawler would break both halves of that guarantee.
17
+ */
18
+ export * from './file-crawler.js';
19
+ //# sourceMappingURL=crawl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../src/crawl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,mBAAmB,CAAC"}
package/dist/crawl.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @vibe-agent-toolkit/utils/crawl
3
+ *
4
+ * Directory crawling (`crawlDirectory`, `crawlDirectorySync`) plus the
5
+ * never-crawl / build-output glob constants. Node-only — reads the filesystem
6
+ * and shells out to `git` for the gitignore-aware fast path.
7
+ *
8
+ * This is the only *subpath* entry that reaches `picomatch` — `./crawl` is
9
+ * therefore the narrow entry that makes `picomatch` a required install. It is
10
+ * not the package's only consumer of it: `link-auth/select-provider.ts` uses
11
+ * `picomatch.isMatch` for host-pattern matching, and that is reachable from the
12
+ * `.` barrel.
13
+ *
14
+ * Deliberately NOT folded into `./glob`. `./glob` is guarded as portable —
15
+ * `node:path` and no third-party dependency (see `test/subpath-purity.test.ts`)
16
+ * — and file-crawler would break both halves of that guarantee.
17
+ */
18
+ export * from './file-crawler.js';
19
+ //# sourceMappingURL=crawl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawl.js","sourceRoot":"","sources":["../src/crawl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,mBAAmB,CAAC"}
@@ -48,10 +48,27 @@ export interface CrawlOptions {
48
48
  * worktrees at `.claude/worktrees/`, inside a dot-directory that `dot: true`
49
49
  * (see {@link PICOMATCH_OPTIONS}) deliberately makes reachable.
50
50
  *
51
+ * `.turbo` is here rather than in {@link BUILD_OUTPUT_GLOBS}, and the placement
52
+ * is the decision. The line between the two lists is not "who produced it" —
53
+ * `coverage/` is tool output too — it is *does any lane exist precisely to look
54
+ * at it*. Something does walk `dist/`; nothing walks `.turbo`, which holds
55
+ * `turbo-<task>.log` telemetry plus, when `cacheDir` points inside it,
56
+ * hash-keyed cache entries that are COPIES of package build output. That is the
57
+ * `.worktrees` failure two lines up, not the `dist` one: a crawl that descends
58
+ * into it reports the same file twice under two paths. Filing it as build
59
+ * output would have it exactly backwards, since a lane spreading only this list
60
+ * is by definition a lane that wants to see built output — and so is precisely
61
+ * the lane that must not walk a cache of copies. (Turborepo is a common enough
62
+ * monorepo layout that this is not hypothetical: every package in THIS repo has
63
+ * a `.turbo/`.)
64
+ *
51
65
  * Note these patterns only bite when `respectGitignore` is false; the fast
52
- * `git ls-files` path never sees ignored directories in the first place.
66
+ * `git ls-files` path never sees ignored directories in the first place. For
67
+ * `.turbo` that is the only path it could bite on — it is gitignored by every
68
+ * turborepo setup — which is the same position `coverage/` and `.test-output/`
69
+ * are in.
53
70
  */
54
- export declare const NEVER_CRAWL_GLOBS: readonly ["**/node_modules/**", "**/.git/**", "**/coverage/**", "**/.test-output/**", "**/.worktrees/**", "**/.claude/worktrees/**"];
71
+ export declare const NEVER_CRAWL_GLOBS: readonly ["**/node_modules/**", "**/.git/**", "**/coverage/**", "**/.test-output/**", "**/.worktrees/**", "**/.claude/worktrees/**", "**/.turbo/**"];
55
72
  /**
56
73
  * Build output — excluded by DEFAULT, but deliberately NOT part of
57
74
  * {@link NEVER_CRAWL_GLOBS}.
@@ -1 +1 @@
1
- {"version":3,"file":"file-crawler.d.ts","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkBD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,sIAOpB,CAAC;AAEX;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,yBAA0B,CAAC;AAI1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CA0KlE"}
1
+ {"version":3,"file":"file-crawler.d.ts","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,iBAAiB,sJAQpB,CAAC;AAEX;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,yBAA0B,CAAC;AAI1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CA0KlE"}
@@ -29,8 +29,25 @@ const PICOMATCH_OPTIONS = { dot: true };
29
29
  * worktrees at `.claude/worktrees/`, inside a dot-directory that `dot: true`
30
30
  * (see {@link PICOMATCH_OPTIONS}) deliberately makes reachable.
31
31
  *
32
+ * `.turbo` is here rather than in {@link BUILD_OUTPUT_GLOBS}, and the placement
33
+ * is the decision. The line between the two lists is not "who produced it" —
34
+ * `coverage/` is tool output too — it is *does any lane exist precisely to look
35
+ * at it*. Something does walk `dist/`; nothing walks `.turbo`, which holds
36
+ * `turbo-<task>.log` telemetry plus, when `cacheDir` points inside it,
37
+ * hash-keyed cache entries that are COPIES of package build output. That is the
38
+ * `.worktrees` failure two lines up, not the `dist` one: a crawl that descends
39
+ * into it reports the same file twice under two paths. Filing it as build
40
+ * output would have it exactly backwards, since a lane spreading only this list
41
+ * is by definition a lane that wants to see built output — and so is precisely
42
+ * the lane that must not walk a cache of copies. (Turborepo is a common enough
43
+ * monorepo layout that this is not hypothetical: every package in THIS repo has
44
+ * a `.turbo/`.)
45
+ *
32
46
  * Note these patterns only bite when `respectGitignore` is false; the fast
33
- * `git ls-files` path never sees ignored directories in the first place.
47
+ * `git ls-files` path never sees ignored directories in the first place. For
48
+ * `.turbo` that is the only path it could bite on — it is gitignored by every
49
+ * turborepo setup — which is the same position `coverage/` and `.test-output/`
50
+ * are in.
34
51
  */
35
52
  export const NEVER_CRAWL_GLOBS = [
36
53
  '**/node_modules/**', // Dependencies (40K+ files), never user content
@@ -39,6 +56,7 @@ export const NEVER_CRAWL_GLOBS = [
39
56
  '**/.test-output/**', // Test artifacts
40
57
  '**/.worktrees/**', // Git worktrees are full repo copies — never traverse
41
58
  '**/.claude/worktrees/**', // Claude Code worktrees, same reason
59
+ '**/.turbo/**', // turborepo task logs + cache — see below
42
60
  ];
43
61
  /**
44
62
  * Build output — excluded by DEFAULT, but deliberately NOT part of
@@ -1 +1 @@
1
- {"version":3,"file":"file-crawler.js","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-crawler.js","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,51 @@
1
1
  /**
2
2
  * Filesystem utilities
3
3
  */
4
+ /**
5
+ * Per-run memo for the two filesystem lookups that validation repeats on values
6
+ * which are constant for the whole run: `realpath` of roots, and `readdir` of the
7
+ * directories link targets live in.
8
+ *
9
+ * A markdown corpus resolves thousands of links into a few hundred directories, so
10
+ * the uncached form is an N+1: measured at 9,963 `readdir` calls on a 3,437-document
11
+ * tree and 7,443 on a 1,132-document monorepo. Concurrent callers share the
12
+ * in-flight promise rather than each starting their own syscall.
13
+ *
14
+ * **Instance-based on purpose — never make this a module-level singleton.** The
15
+ * cache holds a *snapshot* of directory contents, and a long-lived process (watch
16
+ * mode, a language server, a daemon) would then answer from a listing taken
17
+ * arbitrarily long ago. The intended lifetime is one instance per validation run,
18
+ * constructed as a local and collected with the run.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const fsCache = new FsLookupCache(); // one per run
23
+ * for (const link of links) {
24
+ * await verifyCaseSensitiveFilename(link.target, fsCache);
25
+ * }
26
+ * ```
27
+ */
28
+ export declare class FsLookupCache {
29
+ #private;
30
+ /**
31
+ * Canonical path for `targetPath`, falling back to `safePath.resolve()` when the
32
+ * path does not exist or cannot be resolved (a non-existent file has no realpath,
33
+ * and callers comparing paths still need an answer).
34
+ *
35
+ * @param targetPath - Path to canonicalize
36
+ * @returns Canonical path with forward slashes on every platform
37
+ */
38
+ realpath(targetPath: string): Promise<string>;
39
+ /**
40
+ * Entry names of `dirPath`, or `null` when it cannot be read (missing directory,
41
+ * no permission). The unreadable answer is cached too — re-asking is the same
42
+ * failed syscall.
43
+ *
44
+ * @param dirPath - Directory to list
45
+ * @returns Entry names, or `null` if the directory could not be read
46
+ */
47
+ readdir(dirPath: string): Promise<string[] | null>;
48
+ }
4
49
  /**
5
50
  * Recursively copy a directory
6
51
  *
@@ -18,20 +63,32 @@ export declare function copyDirectory(src: string, dest: string): Promise<void>;
18
63
  * the case doesn't match. This function checks that the actual filename on disk
19
64
  * matches the requested path exactly (case-sensitive).
20
65
  *
66
+ * Answering requires listing the target's parent directory. Callers checking many
67
+ * paths (every link in a corpus) hit the same handful of directories over and over,
68
+ * so the listing comes from a caller-supplied {@link FsLookupCache}.
69
+ *
70
+ * The cache parameter is **required rather than defaulted on purpose**: a default
71
+ * would let an unmigrated call site silently keep the un-memoized behaviour, which
72
+ * is a no-op wearing the shape of a fix. `new FsLookupCache()` per call reproduces
73
+ * the old behaviour exactly, so migrating is mechanical — but it has to be a
74
+ * decision someone made.
75
+ *
21
76
  * @param filePath - Absolute path to the file to verify
77
+ * @param fsCache - Per-run lookup cache (one instance per validation run)
22
78
  * @returns Object with exists flag and actual filename (or null if not found)
23
79
  *
24
80
  * @example
25
81
  * ```typescript
26
82
  * // On case-insensitive filesystem with file "README.md"
27
- * const result1 = await verifyCaseSensitiveFilename('/project/README.md');
83
+ * const fsCache = new FsLookupCache();
84
+ * const result1 = await verifyCaseSensitiveFilename('/project/README.md', fsCache);
28
85
  * // { exists: true, actualName: 'README.md' }
29
86
  *
30
- * const result2 = await verifyCaseSensitiveFilename('/project/readme.md');
87
+ * const result2 = await verifyCaseSensitiveFilename('/project/readme.md', fsCache);
31
88
  * // { exists: false, actualName: 'README.md' } - case mismatch!
32
89
  * ```
33
90
  */
34
- export declare function verifyCaseSensitiveFilename(filePath: string): Promise<{
91
+ export declare function verifyCaseSensitiveFilename(filePath: string, fsCache: FsLookupCache): Promise<{
35
92
  exists: boolean;
36
93
  actualName: string | null;
37
94
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAmCzD"}
1
+ {"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,aAAa;;IAOxB;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAe7C;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;CASnD;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAgCzD"}
package/dist/fs-utils.js CHANGED
@@ -3,7 +3,77 @@
3
3
  */
4
4
  import fs from 'node:fs/promises';
5
5
  import path from 'node:path';
6
+ import { toForwardSlash } from './path-core.js';
6
7
  import { safePath } from './path-utils.js';
8
+ /**
9
+ * Per-run memo for the two filesystem lookups that validation repeats on values
10
+ * which are constant for the whole run: `realpath` of roots, and `readdir` of the
11
+ * directories link targets live in.
12
+ *
13
+ * A markdown corpus resolves thousands of links into a few hundred directories, so
14
+ * the uncached form is an N+1: measured at 9,963 `readdir` calls on a 3,437-document
15
+ * tree and 7,443 on a 1,132-document monorepo. Concurrent callers share the
16
+ * in-flight promise rather than each starting their own syscall.
17
+ *
18
+ * **Instance-based on purpose — never make this a module-level singleton.** The
19
+ * cache holds a *snapshot* of directory contents, and a long-lived process (watch
20
+ * mode, a language server, a daemon) would then answer from a listing taken
21
+ * arbitrarily long ago. The intended lifetime is one instance per validation run,
22
+ * constructed as a local and collected with the run.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const fsCache = new FsLookupCache(); // one per run
27
+ * for (const link of links) {
28
+ * await verifyCaseSensitiveFilename(link.target, fsCache);
29
+ * }
30
+ * ```
31
+ */
32
+ export class FsLookupCache {
33
+ /** Directory path → its entry names, or `null` when the directory is unreadable. */
34
+ #listings = new Map();
35
+ /** Path → its canonical path, falling back to the resolved path. */
36
+ #realpaths = new Map();
37
+ /**
38
+ * Canonical path for `targetPath`, falling back to `safePath.resolve()` when the
39
+ * path does not exist or cannot be resolved (a non-existent file has no realpath,
40
+ * and callers comparing paths still need an answer).
41
+ *
42
+ * @param targetPath - Path to canonicalize
43
+ * @returns Canonical path with forward slashes on every platform
44
+ */
45
+ realpath(targetPath) {
46
+ const cached = this.#realpaths.get(targetPath);
47
+ if (cached !== undefined)
48
+ return cached;
49
+ // Stored before the first `await` anywhere can run, so concurrent callers
50
+ // reaching this method share the one in-flight promise.
51
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- caller-validated path
52
+ const pending = fs
53
+ .realpath(targetPath)
54
+ .then(toForwardSlash)
55
+ .catch(() => safePath.resolve(targetPath));
56
+ this.#realpaths.set(targetPath, pending);
57
+ return pending;
58
+ }
59
+ /**
60
+ * Entry names of `dirPath`, or `null` when it cannot be read (missing directory,
61
+ * no permission). The unreadable answer is cached too — re-asking is the same
62
+ * failed syscall.
63
+ *
64
+ * @param dirPath - Directory to list
65
+ * @returns Entry names, or `null` if the directory could not be read
66
+ */
67
+ readdir(dirPath) {
68
+ const cached = this.#listings.get(dirPath);
69
+ if (cached !== undefined)
70
+ return cached;
71
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- caller-validated path
72
+ const pending = fs.readdir(dirPath).catch(() => null);
73
+ this.#listings.set(dirPath, pending);
74
+ return pending;
75
+ }
76
+ }
7
77
  /**
8
78
  * Recursively copy a directory
9
79
  *
@@ -36,31 +106,39 @@ export async function copyDirectory(src, dest) {
36
106
  * the case doesn't match. This function checks that the actual filename on disk
37
107
  * matches the requested path exactly (case-sensitive).
38
108
  *
109
+ * Answering requires listing the target's parent directory. Callers checking many
110
+ * paths (every link in a corpus) hit the same handful of directories over and over,
111
+ * so the listing comes from a caller-supplied {@link FsLookupCache}.
112
+ *
113
+ * The cache parameter is **required rather than defaulted on purpose**: a default
114
+ * would let an unmigrated call site silently keep the un-memoized behaviour, which
115
+ * is a no-op wearing the shape of a fix. `new FsLookupCache()` per call reproduces
116
+ * the old behaviour exactly, so migrating is mechanical — but it has to be a
117
+ * decision someone made.
118
+ *
39
119
  * @param filePath - Absolute path to the file to verify
120
+ * @param fsCache - Per-run lookup cache (one instance per validation run)
40
121
  * @returns Object with exists flag and actual filename (or null if not found)
41
122
  *
42
123
  * @example
43
124
  * ```typescript
44
125
  * // On case-insensitive filesystem with file "README.md"
45
- * const result1 = await verifyCaseSensitiveFilename('/project/README.md');
126
+ * const fsCache = new FsLookupCache();
127
+ * const result1 = await verifyCaseSensitiveFilename('/project/README.md', fsCache);
46
128
  * // { exists: true, actualName: 'README.md' }
47
129
  *
48
- * const result2 = await verifyCaseSensitiveFilename('/project/readme.md');
130
+ * const result2 = await verifyCaseSensitiveFilename('/project/readme.md', fsCache);
49
131
  * // { exists: false, actualName: 'README.md' } - case mismatch!
50
132
  * ```
51
133
  */
52
- export async function verifyCaseSensitiveFilename(filePath) {
134
+ export async function verifyCaseSensitiveFilename(filePath, fsCache) {
53
135
  // Get parent directory and expected filename
54
136
  const parentDir = path.dirname(filePath);
55
137
  const expectedName = path.basename(filePath);
56
138
  // Read actual directory entries
57
- let entries;
58
- try {
59
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- parentDir from validated path
60
- entries = await fs.readdir(parentDir);
61
- }
62
- catch {
63
- // Parent directory doesn't exist
139
+ const entries = await fsCache.readdir(parentDir);
140
+ if (entries === null) {
141
+ // Parent directory doesn't exist (or can't be read)
64
142
  return { exists: false, actualName: null };
65
143
  }
66
144
  // Find the actual filename (case-sensitive exact match)
@@ -1 +1 @@
1
- {"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,IAAY;IAC3D,mGAAmG;IACnG,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,mGAAmG;IACnG,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAEjD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,QAAgB;IAEhB,6CAA6C;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAE7C,gCAAgC;IAChC,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,oGAAoG;QACpG,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;QACjC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,wDAAwD;IACxD,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC;IAEjE,IAAI,UAAU,EAAE,CAAC;QACf,yDAAyD;QACzD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;IAClD,CAAC;IAED,oDAAoD;IACpD,MAAM,oBAAoB,GAAG,OAAO,CAAC,IAAI,CACvC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,YAAY,CAAC,WAAW,EAAE,CAC5D,CAAC;IAEF,iBAAiB;IACjB,oFAAoF;IACpF,sDAAsD;IACtD,OAAO;QACL,MAAM,EAAE,KAAK;QACb,UAAU,EAAE,oBAAoB,IAAI,IAAI;KACzC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"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"}