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

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 (270) hide show
  1. package/README.md +69 -23
  2. package/dist/asset-reference.js +3 -11
  3. package/dist/asset-reference.js.map +1 -1
  4. package/dist/compare-code-units.d.ts +28 -0
  5. package/dist/compare-code-units.d.ts.map +1 -0
  6. package/dist/compare-code-units.js +33 -0
  7. package/dist/compare-code-units.js.map +1 -0
  8. package/dist/crawl-timing.d.ts +657 -0
  9. package/dist/crawl-timing.d.ts.map +1 -0
  10. package/dist/crawl-timing.js +765 -0
  11. package/dist/crawl-timing.js.map +1 -0
  12. package/dist/crawl.d.ts +2 -4
  13. package/dist/crawl.d.ts.map +1 -1
  14. package/dist/crawl.js +2 -4
  15. package/dist/crawl.js.map +1 -1
  16. package/dist/dirent-kind.d.ts +83 -0
  17. package/dist/dirent-kind.d.ts.map +1 -0
  18. package/dist/dirent-kind.js +129 -0
  19. package/dist/dirent-kind.js.map +1 -0
  20. package/dist/entrypoint.d.ts +54 -0
  21. package/dist/entrypoint.d.ts.map +1 -0
  22. package/dist/entrypoint.js +71 -0
  23. package/dist/entrypoint.js.map +1 -0
  24. package/dist/env-flag.d.ts +51 -0
  25. package/dist/env-flag.d.ts.map +1 -0
  26. package/dist/env-flag.js +62 -0
  27. package/dist/env-flag.js.map +1 -0
  28. package/dist/errors/errno.d.ts +51 -0
  29. package/dist/errors/errno.d.ts.map +1 -0
  30. package/dist/errors/errno.js +112 -0
  31. package/dist/errors/errno.js.map +1 -0
  32. package/dist/errors/vat-error.d.ts +60 -0
  33. package/dist/errors/vat-error.d.ts.map +1 -0
  34. package/dist/errors/vat-error.js +86 -0
  35. package/dist/errors/vat-error.js.map +1 -0
  36. package/dist/file-crawler.d.ts +60 -0
  37. package/dist/file-crawler.d.ts.map +1 -1
  38. package/dist/file-crawler.js +133 -29
  39. package/dist/file-crawler.js.map +1 -1
  40. package/dist/fs/file-hash.d.ts.map +1 -1
  41. package/dist/fs/file-hash.js +0 -1
  42. package/dist/fs/file-hash.js.map +1 -1
  43. package/dist/fs-utils.d.ts +422 -191
  44. package/dist/fs-utils.d.ts.map +1 -1
  45. package/dist/fs-utils.js +534 -204
  46. package/dist/fs-utils.js.map +1 -1
  47. package/dist/fs.d.ts +5 -2
  48. package/dist/fs.d.ts.map +1 -1
  49. package/dist/fs.js +9 -7
  50. package/dist/fs.js.map +1 -1
  51. package/dist/git-run.d.ts +167 -0
  52. package/dist/git-run.d.ts.map +1 -0
  53. package/dist/git-run.js +154 -0
  54. package/dist/git-run.js.map +1 -0
  55. package/dist/git-snapshot.d.ts +199 -0
  56. package/dist/git-snapshot.d.ts.map +1 -0
  57. package/dist/git-snapshot.js +250 -0
  58. package/dist/git-snapshot.js.map +1 -0
  59. package/dist/git-tracker.d.ts +166 -3
  60. package/dist/git-tracker.d.ts.map +1 -1
  61. package/dist/git-tracker.js +250 -22
  62. package/dist/git-tracker.js.map +1 -1
  63. package/dist/git-utils.d.ts +84 -1
  64. package/dist/git-utils.d.ts.map +1 -1
  65. package/dist/git-utils.js +208 -82
  66. package/dist/git-utils.js.map +1 -1
  67. package/dist/git.d.ts +13 -4
  68. package/dist/git.d.ts.map +1 -1
  69. package/dist/git.js +12 -4
  70. package/dist/git.js.map +1 -1
  71. package/dist/gitignore-checker.d.ts.map +1 -1
  72. package/dist/gitignore-checker.js +15 -6
  73. package/dist/gitignore-checker.js.map +1 -1
  74. package/dist/glob/glob-pattern.js +5 -5
  75. package/dist/glob/glob-pattern.js.map +1 -1
  76. package/dist/index.d.ts +32 -20
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +95 -50
  79. package/dist/index.js.map +1 -1
  80. package/dist/listing-refusal.d.ts +112 -0
  81. package/dist/listing-refusal.d.ts.map +1 -0
  82. package/dist/listing-refusal.js +105 -0
  83. package/dist/listing-refusal.js.map +1 -0
  84. package/dist/path-containment.d.ts +64 -0
  85. package/dist/path-containment.d.ts.map +1 -0
  86. package/dist/path-containment.js +111 -0
  87. package/dist/path-containment.js.map +1 -0
  88. package/dist/path-core.d.ts +113 -18
  89. package/dist/path-core.d.ts.map +1 -1
  90. package/dist/path-core.js +139 -23
  91. package/dist/path-core.js.map +1 -1
  92. package/dist/path-utils.d.ts.map +1 -1
  93. package/dist/path-utils.js +42 -47
  94. package/dist/path-utils.js.map +1 -1
  95. package/dist/path.d.ts +1 -1
  96. package/dist/path.d.ts.map +1 -1
  97. package/dist/path.js +1 -1
  98. package/dist/path.js.map +1 -1
  99. package/dist/process.d.ts +7 -0
  100. package/dist/process.d.ts.map +1 -1
  101. package/dist/process.js +7 -0
  102. package/dist/process.js.map +1 -1
  103. package/dist/project-utils.d.ts.map +1 -1
  104. package/dist/project-utils.js +15 -9
  105. package/dist/project-utils.js.map +1 -1
  106. package/dist/project.d.ts +7 -5
  107. package/dist/project.d.ts.map +1 -1
  108. package/dist/project.js +7 -5
  109. package/dist/project.js.map +1 -1
  110. package/dist/safe-exec.d.ts +40 -11
  111. package/dist/safe-exec.d.ts.map +1 -1
  112. package/dist/safe-exec.js +111 -25
  113. package/dist/safe-exec.js.map +1 -1
  114. package/dist/skill-targets.d.ts +2 -2
  115. package/dist/skill-targets.js +2 -2
  116. package/dist/skill-test/auth-resolver.d.ts +10 -2
  117. package/dist/skill-test/auth-resolver.d.ts.map +1 -1
  118. package/dist/skill-test/auth-resolver.js +26 -11
  119. package/dist/skill-test/auth-resolver.js.map +1 -1
  120. package/dist/skill-test/index.d.ts +1 -1
  121. package/dist/skill-test/index.d.ts.map +1 -1
  122. package/dist/skill-test/index.js +1 -1
  123. package/dist/skill-test/index.js.map +1 -1
  124. package/dist/skill-test/spawn-claude.d.ts +30 -1
  125. package/dist/skill-test/spawn-claude.d.ts.map +1 -1
  126. package/dist/skill-test/spawn-claude.js +71 -5
  127. package/dist/skill-test/spawn-claude.js.map +1 -1
  128. package/dist/skill-test/transcript.d.ts +41 -2
  129. package/dist/skill-test/transcript.d.ts.map +1 -1
  130. package/dist/skill-test/transcript.js +55 -10
  131. package/dist/skill-test/transcript.js.map +1 -1
  132. package/dist/spawn-hardened.js +0 -1
  133. package/dist/spawn-hardened.js.map +1 -1
  134. package/dist/stdio-blocking.js +13 -14
  135. package/dist/stdio-blocking.js.map +1 -1
  136. package/dist/test-helpers.d.ts +135 -100
  137. package/dist/test-helpers.d.ts.map +1 -1
  138. package/dist/test-helpers.js +224 -146
  139. package/dist/test-helpers.js.map +1 -1
  140. package/dist/testing/executables.d.ts +32 -0
  141. package/dist/testing/executables.d.ts.map +1 -0
  142. package/dist/testing/executables.js +70 -0
  143. package/dist/testing/executables.js.map +1 -0
  144. package/dist/testing/hostile-tree.d.ts +79 -0
  145. package/dist/testing/hostile-tree.d.ts.map +1 -0
  146. package/dist/testing/hostile-tree.js +164 -0
  147. package/dist/testing/hostile-tree.js.map +1 -0
  148. package/dist/testing/platform-gates.d.ts +31 -0
  149. package/dist/testing/platform-gates.d.ts.map +1 -0
  150. package/dist/testing/platform-gates.js +31 -0
  151. package/dist/testing/platform-gates.js.map +1 -0
  152. package/dist/testing/temp-dir.d.ts +255 -0
  153. package/dist/testing/temp-dir.d.ts.map +1 -0
  154. package/dist/testing/temp-dir.js +363 -0
  155. package/dist/testing/temp-dir.js.map +1 -0
  156. package/dist/testing.d.ts +94 -3
  157. package/dist/testing.d.ts.map +1 -1
  158. package/dist/testing.js +119 -3
  159. package/dist/testing.js.map +1 -1
  160. package/dist/text-content.d.ts +197 -0
  161. package/dist/text-content.d.ts.map +1 -0
  162. package/dist/text-content.js +354 -0
  163. package/dist/text-content.js.map +1 -0
  164. package/dist/text-file.d.ts +45 -0
  165. package/dist/text-file.d.ts.map +1 -0
  166. package/dist/text-file.js +51 -0
  167. package/dist/text-file.js.map +1 -0
  168. package/dist/text.d.ts +16 -0
  169. package/dist/text.d.ts.map +1 -0
  170. package/dist/text.js +16 -0
  171. package/dist/text.js.map +1 -0
  172. package/dist/timing-dump.d.ts +136 -0
  173. package/dist/timing-dump.d.ts.map +1 -0
  174. package/dist/timing-dump.js +229 -0
  175. package/dist/timing-dump.js.map +1 -0
  176. package/dist/windows-shell.d.ts +0 -1
  177. package/dist/windows-shell.d.ts.map +1 -1
  178. package/dist/windows-shell.js +0 -1
  179. package/dist/windows-shell.js.map +1 -1
  180. package/dist/zod-introspection.js +2 -2
  181. package/dist/zod-introspection.js.map +1 -1
  182. package/eslint/README.md +223 -49
  183. package/eslint/index.cjs +111 -97
  184. package/eslint/index.d.cts +16 -6
  185. package/eslint/rules/callee-name.cjs +23 -0
  186. package/eslint/rules/commands-import-boundary.cjs +148 -0
  187. package/eslint/rules/dead-import.cjs +1 -1
  188. package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
  189. package/eslint/rules/eslint-rule-factory.cjs +5 -2
  190. package/eslint/rules/exempt-path-matcher.cjs +2 -1
  191. package/eslint/rules/explicit-zod-strictness.cjs +120 -0
  192. package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
  193. package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
  194. package/eslint/rules/no-bare-symlink-in-tests.cjs +213 -0
  195. package/eslint/rules/no-blind-catch.cjs +171 -0
  196. package/eslint/rules/no-child-process-execSync.cjs +8 -0
  197. package/eslint/rules/no-decaying-referent.cjs +217 -0
  198. package/eslint/rules/no-dotdot-containment.cjs +160 -0
  199. package/eslint/rules/no-file-url-string-concat.cjs +4 -1
  200. package/eslint/rules/no-fragile-entrypoint-guard.cjs +228 -0
  201. package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
  202. package/eslint/rules/no-fs-promises-cp.cjs +7 -0
  203. package/eslint/rules/no-fs-realpathSync.cjs +8 -0
  204. package/eslint/rules/no-hardcoded-path-split.cjs +29 -18
  205. package/eslint/rules/no-io-in-unit-tier.cjs +129 -0
  206. package/eslint/rules/no-literal-process-exit.cjs +129 -0
  207. package/eslint/rules/no-manual-path-normalize.cjs +136 -107
  208. package/eslint/rules/no-os-tmpdir.cjs +8 -0
  209. package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
  210. package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
  211. package/eslint/rules/no-path-startswith.cjs +7 -2
  212. package/eslint/rules/no-process-exit-in-phase.cjs +127 -0
  213. package/eslint/rules/no-raw-node-path.cjs +563 -0
  214. package/eslint/rules/no-raw-text-decode.cjs +305 -0
  215. package/eslint/rules/no-registry-count-pin.cjs +255 -0
  216. package/eslint/rules/no-self-package-import.cjs +168 -0
  217. package/eslint/rules/no-test-scoped-functions.cjs +10 -3
  218. package/eslint/rules/no-unix-shell-commands.cjs +6 -2
  219. package/eslint/rules/no-unsafe-root-join.cjs +23 -2
  220. package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
  221. package/eslint/rules/no-version-literal.cjs +151 -0
  222. package/eslint/rules/prefer-startswith-over-regex.cjs +9 -0
  223. package/eslint/rules/require-justified-skip.cjs +8 -2
  224. package/eslint/rules/safe-import.cjs +1 -1
  225. package/package.json +16 -16
  226. package/dist/link-auth/build-headers.d.ts +0 -34
  227. package/dist/link-auth/build-headers.d.ts.map +0 -1
  228. package/dist/link-auth/build-headers.js +0 -58
  229. package/dist/link-auth/build-headers.js.map +0 -1
  230. package/dist/link-auth/expand-macro.d.ts +0 -38
  231. package/dist/link-auth/expand-macro.d.ts.map +0 -1
  232. package/dist/link-auth/expand-macro.js +0 -133
  233. package/dist/link-auth/expand-macro.js.map +0 -1
  234. package/dist/link-auth/macros.yaml +0 -50
  235. package/dist/link-auth/resolve-token.d.ts +0 -83
  236. package/dist/link-auth/resolve-token.d.ts.map +0 -1
  237. package/dist/link-auth/resolve-token.js +0 -101
  238. package/dist/link-auth/resolve-token.js.map +0 -1
  239. package/dist/link-auth/resolve.d.ts +0 -102
  240. package/dist/link-auth/resolve.d.ts.map +0 -1
  241. package/dist/link-auth/resolve.js +0 -66
  242. package/dist/link-auth/resolve.js.map +0 -1
  243. package/dist/link-auth/rewrite.d.ts +0 -52
  244. package/dist/link-auth/rewrite.d.ts.map +0 -1
  245. package/dist/link-auth/rewrite.js +0 -102
  246. package/dist/link-auth/rewrite.js.map +0 -1
  247. package/dist/link-auth/select-provider.d.ts +0 -30
  248. package/dist/link-auth/select-provider.d.ts.map +0 -1
  249. package/dist/link-auth/select-provider.js +0 -55
  250. package/dist/link-auth/select-provider.js.map +0 -1
  251. package/dist/link-auth/template.d.ts +0 -40
  252. package/dist/link-auth/template.d.ts.map +0 -1
  253. package/dist/link-auth/template.js +0 -89
  254. package/dist/link-auth/template.js.map +0 -1
  255. package/dist/link-auth/transforms.d.ts +0 -46
  256. package/dist/link-auth/transforms.d.ts.map +0 -1
  257. package/dist/link-auth/transforms.js +0 -52
  258. package/dist/link-auth/transforms.js.map +0 -1
  259. package/dist/template-entry.d.ts +0 -10
  260. package/dist/template-entry.d.ts.map +0 -1
  261. package/dist/template-entry.js +0 -10
  262. package/dist/template-entry.js.map +0 -1
  263. package/dist/template.d.ts +0 -7
  264. package/dist/template.d.ts.map +0 -1
  265. package/dist/template.js +0 -18
  266. package/dist/template.js.map +0 -1
  267. package/eslint/rules/no-path-join.cjs +0 -14
  268. package/eslint/rules/no-path-relative.cjs +0 -14
  269. package/eslint/rules/no-path-resolve.cjs +0 -14
  270. package/eslint/rules/path-function-rule-factory.cjs +0 -496
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostile-tree.d.ts","sourceRoot":"","sources":["../../src/testing/hostile-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAYH,8EAA8E;AAC9E,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAW1C,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6FAA6F;IAC7F,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iHAAiH;IACjH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gGAAgG;IAChG,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,yEAAyE;IACzE,OAAO,IAAI,IAAI,CAAC;CACjB;AAuBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA8D1D;AAED,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,WAAW,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAuBrE"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * One hostile fixture tree, built the same way for every sink.
3
+ *
4
+ * A correctness sweep found a delete, a copy and an uninstall that
5
+ * each walked out of their root, and found them by hand-building the same
6
+ * shapes — a symlink pointing out, a symlink pointing back at the root, a
7
+ * `..`-named entry, an unreadable directory, a traversal name — one throwaway
8
+ * fixture per probe. This module
9
+ * is that fixture, once, so a sink's test says `buildHostileTree(base)` and
10
+ * `it.each(HOSTILE_NAMES)` and is refusing the shapes the next sink's test is
11
+ * refusing too. A shape added here reaches every sink that uses it.
12
+ *
13
+ * ⛔ Framework-free, like everything under `testing/`: no `vitest` import, so
14
+ * the `./testing` subpath keeps the empty third-party set its purity pin
15
+ * asserts. Fields that the host cannot build are `null` — route those through
16
+ * the suite's own `skip()` so the skip is visible in the report.
17
+ */
18
+ import { chmodSync, rmSync, writeFileSync } from 'node:fs';
19
+ import { isFilesystemAccessError } from '../errors/errno.js';
20
+ import { mkdirSyncReal, safePath } from '../path-utils.js';
21
+ import { createSymlink, symlinkCapability } from '../test-helpers.js';
22
+ import { CANNOT_DENY_READS } from './platform-gates.js';
23
+ import { createTempDir, removeTempDir } from './temp-dir.js';
24
+ /** The names a sink must refuse when they arrive as "the entry to act on". */
25
+ export const HOSTILE_NAMES = [
26
+ '..',
27
+ '../victim',
28
+ '../../victim',
29
+ 'a/../../victim',
30
+ String.raw `..\victim`,
31
+ '.',
32
+ '',
33
+ '/victim',
34
+ String.raw `C:\victim`,
35
+ `nul${String.fromCodePoint(0)}byte`,
36
+ ];
37
+ /** A link, or `null` when this host cannot make one (the capability probe said so). */
38
+ function tryLink(cap, target, link, type) {
39
+ if (cap === null)
40
+ return null;
41
+ createSymlink(cap, target, link, type);
42
+ return link;
43
+ }
44
+ function tryMkdir(dir) {
45
+ try {
46
+ mkdirSyncReal(dir);
47
+ return dir;
48
+ }
49
+ catch (error) {
50
+ // Only a name the OS will not take is "cannot build" — `ENAMETOOLONG` on
51
+ // a host with a shorter limit. Anything else is a broken fixture.
52
+ if (error instanceof Error && 'code' in error && error.code === 'ENAMETOOLONG') {
53
+ return null;
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ /**
59
+ * Plant the hostile tree under `base`, which must exist and be empty enough
60
+ * to take `root`, `outside` and `root-alias` as direct children.
61
+ *
62
+ * @param base - A scratch directory the caller owns (a per-test temp dir)
63
+ * @returns The tree, with `null` for every shape this host cannot build
64
+ */
65
+ export function buildHostileTree(base) {
66
+ const root = safePath.join(base, 'root');
67
+ const member = safePath.join(root, 'member');
68
+ const dotdotNamed = safePath.join(root, '..cache');
69
+ const outside = safePath.join(base, 'outside');
70
+ const victim = safePath.join(outside, 'victim');
71
+ mkdirSyncReal(member, { recursive: true });
72
+ mkdirSyncReal(safePath.join(root, 'nested', 'deep'), { recursive: true });
73
+ mkdirSyncReal(dotdotNamed);
74
+ mkdirSyncReal(victim, { recursive: true });
75
+ writeFileSync(safePath.join(victim, 'secret.txt'), 'TOKEN=abc\n');
76
+ writeFileSync(safePath.join(member, 'file.txt'), 'member\n');
77
+ const cap = symlinkCapability();
78
+ const linkOut = tryLink(cap, victim, safePath.join(root, 'link-out'), 'dir');
79
+ const linkIn = tryLink(cap, member, safePath.join(root, 'link-in'), 'dir');
80
+ const dangling = tryLink(cap, safePath.join(root, 'does-not-exist'), safePath.join(root, 'dangling'), 'file');
81
+ const linkLoop = tryLink(cap, root, safePath.join(root, 'loop'), 'dir');
82
+ const rootAlias = tryLink(cap, root, safePath.join(base, 'root-alias'), 'dir');
83
+ let unreadable = null;
84
+ if (!CANNOT_DENY_READS) {
85
+ unreadable = safePath.join(root, 'unreadable');
86
+ mkdirSyncReal(unreadable);
87
+ writeFileSync(safePath.join(unreadable, 'hidden.txt'), 'x');
88
+ chmodSync(unreadable, 0o000);
89
+ }
90
+ const longPath = tryMkdir(safePath.join(root, 'L'.repeat(200)));
91
+ let cleaned = false;
92
+ return {
93
+ root,
94
+ member,
95
+ dotdotNamed,
96
+ outside,
97
+ victim,
98
+ linkOut,
99
+ linkIn,
100
+ dangling,
101
+ linkLoop,
102
+ rootAlias,
103
+ unreadable,
104
+ longPath,
105
+ cleanup: () => {
106
+ if (cleaned)
107
+ return;
108
+ cleaned = true;
109
+ if (unreadable !== null) {
110
+ try {
111
+ chmodSync(unreadable, 0o700);
112
+ }
113
+ catch (error) {
114
+ // Already removed by the subject under test, which a delete sink is
115
+ // entitled to do; a refusal to restore anything else stays loud.
116
+ if (!isFilesystemAccessError(error))
117
+ throw error;
118
+ }
119
+ }
120
+ for (const dir of [root, outside, rootAlias]) {
121
+ if (dir !== null)
122
+ rmSync(dir, { recursive: true, force: true });
123
+ }
124
+ },
125
+ };
126
+ }
127
+ /**
128
+ * Hold a per-test hostile tree, so a suite's wiring is three lines:
129
+ * `const hostile = hostileTreePerTest('x'); beforeEach(hostile.plant); afterEach(hostile.clear);`.
130
+ *
131
+ * Same shape and same reasons as `replantableCorpus`: the tree comes back
132
+ * through a GETTER because it is reminted per test, `plant` clears any tree
133
+ * still standing so nested `describe`s do not leak one, and `tree()` before
134
+ * `plant()` throws by name rather than surfacing as an undefined read.
135
+ *
136
+ * @param prefix - `mkdtemp` prefix for the scratch root, so a leak names its suite
137
+ * @returns Plant/clear/tree, to be driven from the caller's own hooks
138
+ */
139
+ export function hostileTreePerTest(prefix) {
140
+ let scratch;
141
+ let planted;
142
+ const clear = () => {
143
+ planted?.cleanup();
144
+ planted = undefined;
145
+ if (scratch !== undefined)
146
+ removeTempDir(scratch);
147
+ scratch = undefined;
148
+ };
149
+ return {
150
+ plant: () => {
151
+ clear();
152
+ scratch = createTempDir(prefix);
153
+ planted = buildHostileTree(scratch);
154
+ },
155
+ clear,
156
+ tree: () => {
157
+ if (planted === undefined) {
158
+ throw new Error(`hostileTreePerTest('${prefix}'): tree() before plant() — the suite is missing its beforeEach`);
159
+ }
160
+ return planted;
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=hostile-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostile-tree.js","sourceRoot":"","sources":["../../src/testing/hostile-tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The host facts a fixture has to ask before it can build a refusal.
3
+ *
4
+ * ⛔ Framework-free, like everything under `testing/`: no `vitest` import, so
5
+ * the `./testing` subpath keeps the empty third-party set its purity pin
6
+ * asserts. Route a `true` here through the suite's own `skip()` so the skip is
7
+ * visible in the report — a fixture that silently no-ops is a passing test for
8
+ * a property nobody exercised.
9
+ */
10
+ /**
11
+ * Whether a `chmod 000` on this host denies anything.
12
+ *
13
+ * Two hosts read every mode as readable: Windows, where POSIX mode bits do not
14
+ * bind at all, and a POSIX process running as root, which bypasses them. On
15
+ * either, a fixture directory made unreadable is still readable, so a test of
16
+ * "the walker refuses what it cannot list" would pass by listing it.
17
+ *
18
+ * ⚠️ Evaluated ONCE at module load, as a constant rather than a function, on
19
+ * purpose: the answer is a property of the process's identity and platform,
20
+ * neither of which changes mid-run, and a constant is what `it.skipIf(...)`
21
+ * takes. This one line used to be spelled in 28 test files under three names
22
+ * (`CANNOT_DENY_READS`, `PERMISSIONS_ENFORCED`, inline), which is 28 places
23
+ * for the `getuid` guard to drift.
24
+ */
25
+ export declare const CANNOT_DENY_READS: boolean;
26
+ /**
27
+ * The positive spelling, for the suites that phrase the gate as "only run
28
+ * where modes bind". Same fact, no second evaluation.
29
+ */
30
+ export declare const PERMISSIONS_ENFORCED: boolean;
31
+ //# sourceMappingURL=platform-gates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-gates.d.ts","sourceRoot":"","sources":["../../src/testing/platform-gates.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,EAAE,OACkE,CAAC;AAEnG;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,OAA4B,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The host facts a fixture has to ask before it can build a refusal.
3
+ *
4
+ * ⛔ Framework-free, like everything under `testing/`: no `vitest` import, so
5
+ * the `./testing` subpath keeps the empty third-party set its purity pin
6
+ * asserts. Route a `true` here through the suite's own `skip()` so the skip is
7
+ * visible in the report — a fixture that silently no-ops is a passing test for
8
+ * a property nobody exercised.
9
+ */
10
+ /**
11
+ * Whether a `chmod 000` on this host denies anything.
12
+ *
13
+ * Two hosts read every mode as readable: Windows, where POSIX mode bits do not
14
+ * bind at all, and a POSIX process running as root, which bypasses them. On
15
+ * either, a fixture directory made unreadable is still readable, so a test of
16
+ * "the walker refuses what it cannot list" would pass by listing it.
17
+ *
18
+ * ⚠️ Evaluated ONCE at module load, as a constant rather than a function, on
19
+ * purpose: the answer is a property of the process's identity and platform,
20
+ * neither of which changes mid-run, and a constant is what `it.skipIf(...)`
21
+ * takes. This one line used to be spelled in 28 test files under three names
22
+ * (`CANNOT_DENY_READS`, `PERMISSIONS_ENFORCED`, inline), which is 28 places
23
+ * for the `getuid` guard to drift.
24
+ */
25
+ export const CANNOT_DENY_READS = process.platform === 'win32' || (typeof process.getuid === 'function' && process.getuid() === 0);
26
+ /**
27
+ * The positive spelling, for the suites that phrase the gate as "only run
28
+ * where modes bind". Same fact, no second evaluation.
29
+ */
30
+ export const PERMISSIONS_ENFORCED = !CANNOT_DENY_READS;
31
+ //# sourceMappingURL=platform-gates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-gates.js","sourceRoot":"","sources":["../../src/testing/platform-gates.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAEnG;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAY,CAAC,iBAAiB,CAAC"}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * The ONE temp-directory helper family.
3
+ *
4
+ * Every shape a suite needs to mint scratch space lives here: a per-call
5
+ * `createTempDir`, a tracker that removes what it minted, and the per-suite
6
+ * sync/async suite helpers with their bounded teardown. The audit counted
7
+ * eleven definitions of "give me a temp dir that cleans itself up" across
8
+ * eight modules, six of them named differently and none of them visible to
9
+ * the duplication gate; this module is where they were collapsed to.
10
+ *
11
+ * ⛔ Framework-free, like everything under `testing/`: nothing here imports
12
+ * `vitest`, so the `./testing` subpath keeps the empty third-party set its
13
+ * purity pin asserts. Each suite owns its own hooks and calls these from
14
+ * inside them.
15
+ */
16
+ /**
17
+ * Mint a fresh temp directory under the host's (short-name-resolved) tmpdir.
18
+ *
19
+ * The one-liner that used to be spelled in three packages as
20
+ * `createTestTempDir`. Pair with {@link removeTempDir}, or let
21
+ * {@link tempDirTracker} pair them for you.
22
+ *
23
+ * @param prefix - `mkdtemp` prefix, so a leaked directory names its own suite
24
+ * @returns Absolute, forward-slashed path of the new directory
25
+ */
26
+ export declare function createTempDir(prefix: string): string;
27
+ /** The async counterpart of {@link createTempDir}, for suites already on `fs/promises`. */
28
+ export declare function createTempDirAsync(prefix: string): Promise<string>;
29
+ /**
30
+ * Remove a directory {@link createTempDir} minted — and ONLY such a directory.
31
+ *
32
+ * Refuses, by name, to remove anything that is not strictly under the host
33
+ * tmpdir as the filesystem sees it. A teardown is the one place test code
34
+ * runs `rm -rf` on a variable, and a variable that was never assigned (a
35
+ * `beforeAll` that threw), or was assigned a fixture INSIDE the repo, must
36
+ * not become `rm -rf ''` or `rm -rf packages/`. The `dev-tools` copy of this
37
+ * helper carried that guard as a lexical `startsWith`; the guard here asks
38
+ * the filesystem, so a symlinked tmpdir (macOS) still passes.
39
+ *
40
+ * `force: true` tolerates a directory already gone; the retries are Node's
41
+ * own remedy for the transient `EBUSY` / `EPERM` a just-closed handle
42
+ * produces on Windows. Anything left after that is a real teardown failure
43
+ * and stays loud — a teardown that swallows it hides a leaking fixture.
44
+ *
45
+ * @param dir - The directory to remove
46
+ * @throws When `dir` is not strictly under the host tmpdir
47
+ */
48
+ export declare function removeTempDir(dir: string): void;
49
+ /**
50
+ * A factory that remembers every directory it minted, so one `afterEach`
51
+ * removes them all.
52
+ *
53
+ * @param prefix - `mkdtemp` prefix for every directory this tracker mints
54
+ * @returns `create` to mint one more, `cleanupAll` to remove every one so far
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const scratch = tempDirTracker('my-suite-');
59
+ * afterEach(() => scratch.cleanupAll());
60
+ * it('…', () => { const dir = scratch.create(); … });
61
+ * ```
62
+ */
63
+ export declare function tempDirTracker(prefix: string): {
64
+ create: () => string;
65
+ cleanupAll: () => void;
66
+ };
67
+ /** Knobs for {@link removeScratchDir}; all three exist so the behaviour is testable. */
68
+ export interface RemoveScratchDirOptions {
69
+ /** Deadline before the removal is abandoned. Default {@link SCRATCH_REMOVAL_BUDGET_MS}. */
70
+ readonly budgetMs?: number;
71
+ /** Where the give-up notice goes. Default `console.warn`. */
72
+ readonly onWarn?: (message: string) => void;
73
+ /**
74
+ * The removal itself. Defaults to `fs.rm` with recursive/force/retries.
75
+ *
76
+ * Injectable because the *contract* — a removal that fails must warn rather
77
+ * than throw — cannot otherwise be tested on every platform. Driving a real
78
+ * `fs.rm` failure needs a path the OS refuses, and those diverge: a path
79
+ * whose parent component is a regular file yields `ENOTDIR` on POSIX, and
80
+ * resolves silently on Windows. A test written against the POSIX shape
81
+ * passes locally and fails in CI, which is exactly what it did once.
82
+ */
83
+ readonly remove?: (dir: string) => Promise<void>;
84
+ }
85
+ /**
86
+ * Delete a scratch directory as *best effort* — never failing the suite that
87
+ * created it, and never taking longer than its own budget to say so.
88
+ *
89
+ * ## Why this is not just `await rm(dir, { recursive: true, force: true })`
90
+ *
91
+ * A teardown hook that can redden a suite whose every assertion passed is a
92
+ * defect in the harness, not a flake. `packages/lab/test/instrument.test.ts`
93
+ * timed out here on two consecutive Windows runs with all 655 assertions
94
+ * green — only the cleanup lost.
95
+ *
96
+ * The measurement is what rules out the obvious fixes: that scratch dir holds
97
+ * 490 files / 378 KiB across 14 fixture git repos, and deletes in **59 ms**
98
+ * idle. Against vitest's 10,000 ms unit-hook budget that is 170x of headroom,
99
+ * and Windows blew through it anyway. No quantity of real work explains that,
100
+ * so the cause is scheduling — contention from a fully parallel `validate`,
101
+ * plus per-unlink antivirus on Windows — which is unbounded by nature. Hence:
102
+ *
103
+ * - **Raising `hookTimeout` cannot be argued.** You would be picking a number
104
+ * to beat an unbounded quantity, when 10s of 170x headroom already lost. It
105
+ * also punches a hole in the deliberate policy in `vitest.shared.ts` ("no
106
+ * hookTimeout override here on purpose") for every unit hook, to fix one.
107
+ * - **`try`/`catch` around the `rm` cannot work.** A vitest hook timeout is a
108
+ * race decided on the *timer* side; the hook's own catch never sees it. It
109
+ * addresses a failure mode we did not observe and leaves the one we did.
110
+ * - **`maxRetries` alone makes it worse.** Retries target transient
111
+ * `EPERM`/`EBUSY`, which fail *fast*; our failure was *slow*, and retry
112
+ * backoff only adds to it. Kept below as a cheap inner win, not as the fix.
113
+ *
114
+ * So the deadline is taken away from vitest: the removal races a timer of our
115
+ * own, well inside the hook budget, and expiry is a warning rather than a
116
+ * failure. The hook therefore always resolves in time, which makes it
117
+ * *structurally* incapable of reddening a green suite on any machine at any
118
+ * load — rather than merely unlikely to.
119
+ *
120
+ * The cost, stated plainly: under pathological contention the directory
121
+ * survives in the OS temp dir, which the OS reclaims, and the warning names
122
+ * the path. It can never surface as an unhandled rejection, because the only
123
+ * rejection handler is installed before the race.
124
+ *
125
+ * ⚠️ **Abandoning the removal does not stop it, and does not free the worker.**
126
+ * A pending libuv `fs` request is an active handle, so the `rm` runs to
127
+ * completion regardless — measured at 2,407 ms on an 8,000-file tree after the
128
+ * race was decided at 5 ms — and the process cannot exit until it does.
129
+ * `timer.unref()` below unrefs the *timer*, not the removal. So what this buys
130
+ * is bounded: the **hook** always resolves in time, which is what stops a green
131
+ * suite going red. It does **not** shed the work, and under the contention it
132
+ * targets the abandoned removal competes for disk with whatever runs next in
133
+ * the same worker. That is the trade, and it is why the budget wants to be as
134
+ * large as the tier's hook allows rather than as small as possible.
135
+ *
136
+ * @param dir - Directory to remove. An empty string is a no-op, so a suite
137
+ * whose `beforeAll` never ran can call this unconditionally.
138
+ * @param options - Deadline and warning sink
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * afterAll(async () => {
143
+ * await removeScratchDir(scratch);
144
+ * });
145
+ * ```
146
+ */
147
+ export declare function removeScratchDir(dir: string, options?: RemoveScratchDirOptions): Promise<void>;
148
+ /**
149
+ * Get isolated test output directory for current test run
150
+ *
151
+ * Creates a unique directory under `packages/{packageName}/.test-output/{testType}/{runId}`
152
+ * where runId is `{timestamp}-{randomId}` to ensure isolation across parallel test runs.
153
+ *
154
+ * @param packageName - Name of package (e.g., 'rag-lancedb')
155
+ * @param testType - Type of test ('unit', 'integration', 'system')
156
+ * @param subdirs - Optional subdirectories to create within the test output directory
157
+ * @returns Absolute path to the created directory
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * // Create isolated database directory for system tests
162
+ * const dbPath = getTestOutputDir('rag-lancedb', 'system', 'databases', 'test-db');
163
+ * // Result: packages/rag-lancedb/.test-output/system/20260105-143022-abc123/databases/test-db
164
+ *
165
+ * // Create temporary file directory for integration tests
166
+ * const tempDir = getTestOutputDir('agent-skills', 'integration', 'temp-files');
167
+ * // Result: packages/agent-skills/.test-output/integration/20260105-143022-def456/temp-files
168
+ * ```
169
+ */
170
+ export declare function getTestOutputDir(packageName: string, testType: 'unit' | 'integration' | 'system', ...subdirs: string[]): string;
171
+ /**
172
+ * Get the base test output directory for a package
173
+ * Useful for cleanup operations that need to remove all test output
174
+ *
175
+ * @param packageName - Name of package (e.g., 'rag-lancedb')
176
+ * @returns Absolute path to packages/{packageName}/.test-output
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const baseDir = getTestOutputBase('rag-lancedb');
181
+ * // Result: packages/rag-lancedb/.test-output
182
+ * ```
183
+ */
184
+ export declare function getTestOutputBase(packageName: string): string;
185
+ /**
186
+ * Per-suite temp directory pattern (async version)
187
+ * Creates a single temp directory for the entire test suite,
188
+ * with subdirectories for each test. This is 3-5x faster on Windows
189
+ * than creating a new mkdtemp for each test.
190
+ *
191
+ * @param prefix - Prefix for the suite temp directory name
192
+ * @param teardown - Forwarded to {@link removeScratchDir}. Raise `budgetMs` for a
193
+ * suite whose fixture tree is heavy or whose tier allows a longer hook than the
194
+ * unit tier this default is sized against — see {@link SCRATCH_REMOVAL_BUDGET_MS}.
195
+ * @returns Suite helper with beforeAll, afterAll, beforeEach, afterEach, and getTempDir
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const suite = setupAsyncTempDirSuite('my-test');
200
+ *
201
+ * describe('my tests', () => {
202
+ * beforeAll(suite.beforeAll);
203
+ * afterAll(suite.afterAll);
204
+ * beforeEach(suite.beforeEach);
205
+ *
206
+ * it('test 1', async () => {
207
+ * const tempDir = suite.getTempDir();
208
+ * // Use tempDir...
209
+ * });
210
+ * });
211
+ * ```
212
+ */
213
+ export declare function setupAsyncTempDirSuite(prefix: string, teardown?: RemoveScratchDirOptions): {
214
+ beforeAll: () => Promise<void>;
215
+ afterAll: () => Promise<void>;
216
+ beforeEach: () => Promise<void>;
217
+ afterEach: () => Promise<void>;
218
+ getTempDir: () => string;
219
+ };
220
+ /**
221
+ * Per-suite temp directory pattern (sync version)
222
+ * Creates a single temp directory for the entire test suite,
223
+ * with subdirectories for each test. This is 3-5x faster on Windows
224
+ * than creating a new mkdtemp for each test.
225
+ *
226
+ * @param prefix - Prefix for the suite temp directory name
227
+ * @param teardown - Forwarded to {@link removeScratchDir}. Raise `budgetMs` for a
228
+ * suite whose fixture tree is heavy or whose tier allows a longer hook than the
229
+ * unit tier this default is sized against — see {@link SCRATCH_REMOVAL_BUDGET_MS}.
230
+ * @returns Suite helper with beforeAll, afterAll, beforeEach, afterEach, and getTempDir
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const suite = setupSyncTempDirSuite('my-test');
235
+ *
236
+ * describe('my tests', () => {
237
+ * beforeAll(suite.beforeAll);
238
+ * afterAll(suite.afterAll);
239
+ * beforeEach(suite.beforeEach);
240
+ *
241
+ * it('test 1', () => {
242
+ * const tempDir = suite.getTempDir();
243
+ * // Use tempDir...
244
+ * });
245
+ * });
246
+ * ```
247
+ */
248
+ export declare function setupSyncTempDirSuite(prefix: string, teardown?: RemoveScratchDirOptions): {
249
+ beforeAll: () => void;
250
+ afterAll: () => Promise<void>;
251
+ beforeEach: () => void;
252
+ afterEach: () => void;
253
+ getTempDir: () => string;
254
+ };
255
+ //# sourceMappingURL=temp-dir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temp-dir.d.ts","sourceRoot":"","sources":["../../src/testing/temp-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,2FAA2F;AAC3F,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAExE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK/C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,IAAI,CAAA;CAAE,CAa/F;AAuBD,wFAAwF;AACxF,MAAM,WAAW,uBAAuB;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,QAAQ,EAC3C,GAAG,OAAO,EAAE,MAAM,EAAE,GACnB,MAAM,CAuBR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,uBAA4B,GAAG;IAC9F,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,UAAU,EAAE,MAAM,MAAM,CAAC;CAC1B,CAsBA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,uBAA4B,GAAG;IAC7F,SAAS,EAAE,MAAM,IAAI,CAAC;IAItB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,MAAM,CAAC;CAC1B,CAsBA"}