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

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 (330) hide show
  1. package/README.md +112 -10
  2. package/dist/archive/archive.d.ts +129 -0
  3. package/dist/archive/archive.d.ts.map +1 -0
  4. package/dist/archive/archive.js +223 -0
  5. package/dist/archive/archive.js.map +1 -0
  6. package/dist/archive/entries.d.ts +132 -0
  7. package/dist/archive/entries.d.ts.map +1 -0
  8. package/dist/archive/entries.js +223 -0
  9. package/dist/archive/entries.js.map +1 -0
  10. package/dist/archive/limits.d.ts +39 -0
  11. package/dist/archive/limits.d.ts.map +1 -0
  12. package/dist/archive/limits.js +59 -0
  13. package/dist/archive/limits.js.map +1 -0
  14. package/dist/archive/records.d.ts +14 -0
  15. package/dist/archive/records.d.ts.map +1 -0
  16. package/dist/archive/records.js +3 -0
  17. package/dist/archive/records.js.map +1 -0
  18. package/dist/archive/tar-reader.d.ts +18 -0
  19. package/dist/archive/tar-reader.d.ts.map +1 -0
  20. package/dist/archive/tar-reader.js +173 -0
  21. package/dist/archive/tar-reader.js.map +1 -0
  22. package/dist/archive/zip-reader.d.ts +76 -0
  23. package/dist/archive/zip-reader.d.ts.map +1 -0
  24. package/dist/archive/zip-reader.js +376 -0
  25. package/dist/archive/zip-reader.js.map +1 -0
  26. package/dist/archive.d.ts +14 -0
  27. package/dist/archive.d.ts.map +1 -0
  28. package/dist/archive.js +12 -0
  29. package/dist/archive.js.map +1 -0
  30. package/dist/asset-reference.d.ts +17 -1
  31. package/dist/asset-reference.d.ts.map +1 -1
  32. package/dist/asset-reference.js +31 -7
  33. package/dist/asset-reference.js.map +1 -1
  34. package/dist/dirent-kind.d.ts +13 -40
  35. package/dist/dirent-kind.d.ts.map +1 -1
  36. package/dist/dirent-kind.js +21 -69
  37. package/dist/dirent-kind.js.map +1 -1
  38. package/dist/entrypoint.d.ts +2 -2
  39. package/dist/entrypoint.d.ts.map +1 -1
  40. package/dist/entrypoint.js +4 -3
  41. package/dist/entrypoint.js.map +1 -1
  42. package/dist/env-flag.d.ts +13 -5
  43. package/dist/env-flag.d.ts.map +1 -1
  44. package/dist/env-flag.js +13 -5
  45. package/dist/env-flag.js.map +1 -1
  46. package/dist/errors/confirmed-absent.d.ts +41 -0
  47. package/dist/errors/confirmed-absent.d.ts.map +1 -0
  48. package/dist/errors/confirmed-absent.js +43 -0
  49. package/dist/errors/confirmed-absent.js.map +1 -0
  50. package/dist/errors/errno-table.d.ts +177 -0
  51. package/dist/errors/errno-table.d.ts.map +1 -0
  52. package/dist/errors/errno-table.js +343 -0
  53. package/dist/errors/errno-table.js.map +1 -0
  54. package/dist/errors/fs-boundary.d.ts +36 -0
  55. package/dist/errors/fs-boundary.d.ts.map +1 -0
  56. package/dist/errors/fs-boundary.js +121 -0
  57. package/dist/errors/fs-boundary.js.map +1 -0
  58. package/dist/errors/fs-fault.d.ts +176 -0
  59. package/dist/errors/fs-fault.d.ts.map +1 -0
  60. package/dist/errors/fs-fault.js +215 -0
  61. package/dist/errors/fs-fault.js.map +1 -0
  62. package/dist/errors/git-errno.d.ts +13 -0
  63. package/dist/errors/git-errno.d.ts.map +1 -0
  64. package/dist/errors/git-errno.js +45 -0
  65. package/dist/errors/git-errno.js.map +1 -0
  66. package/dist/errors/path-present.d.ts +72 -0
  67. package/dist/errors/path-present.d.ts.map +1 -0
  68. package/dist/errors/path-present.js +110 -0
  69. package/dist/errors/path-present.js.map +1 -0
  70. package/dist/errors/rag-index-empty.d.ts +16 -0
  71. package/dist/errors/rag-index-empty.d.ts.map +1 -0
  72. package/dist/errors/rag-index-empty.js +16 -0
  73. package/dist/errors/rag-index-empty.js.map +1 -0
  74. package/dist/errors/suppressed-faults.d.ts +34 -0
  75. package/dist/errors/suppressed-faults.d.ts.map +1 -0
  76. package/dist/errors/suppressed-faults.js +66 -0
  77. package/dist/errors/suppressed-faults.js.map +1 -0
  78. package/dist/file-crawler.d.ts +13 -18
  79. package/dist/file-crawler.d.ts.map +1 -1
  80. package/dist/file-crawler.js +140 -24
  81. package/dist/file-crawler.js.map +1 -1
  82. package/dist/fs-utils.d.ts +75 -50
  83. package/dist/fs-utils.d.ts.map +1 -1
  84. package/dist/fs-utils.js +148 -158
  85. package/dist/fs-utils.js.map +1 -1
  86. package/dist/fs.d.ts +5 -4
  87. package/dist/fs.d.ts.map +1 -1
  88. package/dist/fs.js +5 -4
  89. package/dist/fs.js.map +1 -1
  90. package/dist/git-snapshot.d.ts +25 -17
  91. package/dist/git-snapshot.d.ts.map +1 -1
  92. package/dist/git-snapshot.js +65 -11
  93. package/dist/git-snapshot.js.map +1 -1
  94. package/dist/git-tracker.d.ts +11 -3
  95. package/dist/git-tracker.d.ts.map +1 -1
  96. package/dist/git-tracker.js +29 -6
  97. package/dist/git-tracker.js.map +1 -1
  98. package/dist/git-utils.d.ts.map +1 -1
  99. package/dist/git-utils.js +7 -35
  100. package/dist/git-utils.js.map +1 -1
  101. package/dist/git.d.ts +1 -1
  102. package/dist/git.d.ts.map +1 -1
  103. package/dist/git.js +1 -1
  104. package/dist/git.js.map +1 -1
  105. package/dist/gitignore-checker.d.ts.map +1 -1
  106. package/dist/gitignore-checker.js +5 -4
  107. package/dist/gitignore-checker.js.map +1 -1
  108. package/dist/in-order.d.ts +93 -0
  109. package/dist/in-order.d.ts.map +1 -0
  110. package/dist/in-order.js +165 -0
  111. package/dist/in-order.js.map +1 -0
  112. package/dist/index.d.ts +20 -4
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +30 -4
  115. package/dist/index.js.map +1 -1
  116. package/dist/kill-process-tree.d.ts +43 -0
  117. package/dist/kill-process-tree.d.ts.map +1 -0
  118. package/dist/kill-process-tree.js +48 -0
  119. package/dist/kill-process-tree.js.map +1 -0
  120. package/dist/link-following.d.ts +115 -0
  121. package/dist/link-following.d.ts.map +1 -0
  122. package/dist/link-following.js +283 -0
  123. package/dist/link-following.js.map +1 -0
  124. package/dist/listing-refusal.d.ts +44 -0
  125. package/dist/listing-refusal.d.ts.map +1 -1
  126. package/dist/listing-refusal.js +79 -2
  127. package/dist/listing-refusal.js.map +1 -1
  128. package/dist/name-fold.d.ts +25 -0
  129. package/dist/name-fold.d.ts.map +1 -0
  130. package/dist/name-fold.js +40 -0
  131. package/dist/name-fold.js.map +1 -0
  132. package/dist/path-containment.d.ts +17 -0
  133. package/dist/path-containment.d.ts.map +1 -1
  134. package/dist/path-containment.js +28 -5
  135. package/dist/path-containment.js.map +1 -1
  136. package/dist/path-utils.d.ts +14 -17
  137. package/dist/path-utils.d.ts.map +1 -1
  138. package/dist/path-utils.js +37 -55
  139. package/dist/path-utils.js.map +1 -1
  140. package/dist/process.d.ts +2 -0
  141. package/dist/process.d.ts.map +1 -1
  142. package/dist/process.js +2 -0
  143. package/dist/process.js.map +1 -1
  144. package/dist/project-utils.d.ts.map +1 -1
  145. package/dist/project-utils.js +8 -9
  146. package/dist/project-utils.js.map +1 -1
  147. package/dist/safe-exec.d.ts +6 -2
  148. package/dist/safe-exec.d.ts.map +1 -1
  149. package/dist/safe-exec.js +1 -1
  150. package/dist/safe-exec.js.map +1 -1
  151. package/dist/skill-targets.d.ts.map +1 -1
  152. package/dist/skill-targets.js +3 -1
  153. package/dist/skill-targets.js.map +1 -1
  154. package/dist/skill-test/spawn-claude.d.ts +0 -15
  155. package/dist/skill-test/spawn-claude.d.ts.map +1 -1
  156. package/dist/skill-test/spawn-claude.js +11 -50
  157. package/dist/skill-test/spawn-claude.js.map +1 -1
  158. package/dist/special-file.d.ts +18 -0
  159. package/dist/special-file.d.ts.map +1 -0
  160. package/dist/special-file.js +21 -0
  161. package/dist/special-file.js.map +1 -0
  162. package/dist/terminating-signals.d.ts +129 -0
  163. package/dist/terminating-signals.d.ts.map +1 -0
  164. package/dist/terminating-signals.js +210 -0
  165. package/dist/terminating-signals.js.map +1 -0
  166. package/dist/test-helpers.d.ts +9 -5
  167. package/dist/test-helpers.d.ts.map +1 -1
  168. package/dist/test-helpers.js +18 -21
  169. package/dist/test-helpers.js.map +1 -1
  170. package/dist/test-scratch-guard.d.ts +34 -0
  171. package/dist/test-scratch-guard.d.ts.map +1 -0
  172. package/dist/test-scratch-guard.js +45 -0
  173. package/dist/test-scratch-guard.js.map +1 -0
  174. package/dist/testing/archive-fixtures.d.ts +147 -0
  175. package/dist/testing/archive-fixtures.d.ts.map +1 -0
  176. package/dist/testing/archive-fixtures.js +449 -0
  177. package/dist/testing/archive-fixtures.js.map +1 -0
  178. package/dist/testing/executables.d.ts +6 -0
  179. package/dist/testing/executables.d.ts.map +1 -1
  180. package/dist/testing/executables.js +20 -9
  181. package/dist/testing/executables.js.map +1 -1
  182. package/dist/testing/fault-fs-preload.d.ts +16 -0
  183. package/dist/testing/fault-fs-preload.d.ts.map +1 -0
  184. package/dist/testing/fault-fs-preload.js +22 -0
  185. package/dist/testing/fault-fs-preload.js.map +1 -0
  186. package/dist/testing/fault-fs.d.ts +115 -0
  187. package/dist/testing/fault-fs.d.ts.map +1 -0
  188. package/dist/testing/fault-fs.js +411 -0
  189. package/dist/testing/fault-fs.js.map +1 -0
  190. package/dist/testing/fault-spec.d.ts +83 -0
  191. package/dist/testing/fault-spec.d.ts.map +1 -0
  192. package/dist/testing/fault-spec.js +132 -0
  193. package/dist/testing/fault-spec.js.map +1 -0
  194. package/dist/testing/hostile-tree.d.ts.map +1 -1
  195. package/dist/testing/hostile-tree.js +3 -4
  196. package/dist/testing/hostile-tree.js.map +1 -1
  197. package/dist/testing/platform-gates.d.ts +8 -0
  198. package/dist/testing/platform-gates.d.ts.map +1 -1
  199. package/dist/testing/platform-gates.js +24 -0
  200. package/dist/testing/platform-gates.js.map +1 -1
  201. package/dist/testing/temp-dir.d.ts +31 -0
  202. package/dist/testing/temp-dir.d.ts.map +1 -1
  203. package/dist/testing/temp-dir.js +63 -0
  204. package/dist/testing/temp-dir.js.map +1 -1
  205. package/dist/testing/tier-timeout.d.ts +34 -0
  206. package/dist/testing/tier-timeout.d.ts.map +1 -0
  207. package/dist/testing/tier-timeout.js +45 -0
  208. package/dist/testing/tier-timeout.js.map +1 -0
  209. package/dist/testing/tree-snapshot.d.ts +71 -0
  210. package/dist/testing/tree-snapshot.d.ts.map +1 -0
  211. package/dist/testing/tree-snapshot.js +163 -0
  212. package/dist/testing/tree-snapshot.js.map +1 -0
  213. package/dist/testing.d.ts +6 -0
  214. package/dist/testing.d.ts.map +1 -1
  215. package/dist/testing.js +8 -1
  216. package/dist/testing.js.map +1 -1
  217. package/dist/text-content.d.ts +29 -0
  218. package/dist/text-content.d.ts.map +1 -1
  219. package/dist/text-content.js +64 -0
  220. package/dist/text-content.js.map +1 -1
  221. package/dist/text-file.d.ts +58 -6
  222. package/dist/text-file.d.ts.map +1 -1
  223. package/dist/text-file.js +111 -11
  224. package/dist/text-file.js.map +1 -1
  225. package/dist/text.d.ts +1 -1
  226. package/dist/text.d.ts.map +1 -1
  227. package/dist/text.js +1 -1
  228. package/dist/text.js.map +1 -1
  229. package/dist/timing-dump.d.ts.map +1 -1
  230. package/dist/timing-dump.js +2 -10
  231. package/dist/timing-dump.js.map +1 -1
  232. package/dist/tree-change/apply-decisions.d.ts +101 -0
  233. package/dist/tree-change/apply-decisions.d.ts.map +1 -0
  234. package/dist/tree-change/apply-decisions.js +139 -0
  235. package/dist/tree-change/apply-decisions.js.map +1 -0
  236. package/dist/tree-change/apply.d.ts +98 -0
  237. package/dist/tree-change/apply.d.ts.map +1 -0
  238. package/dist/tree-change/apply.js +364 -0
  239. package/dist/tree-change/apply.js.map +1 -0
  240. package/dist/tree-change/copy-decisions.d.ts +58 -0
  241. package/dist/tree-change/copy-decisions.d.ts.map +1 -0
  242. package/dist/tree-change/copy-decisions.js +66 -0
  243. package/dist/tree-change/copy-decisions.js.map +1 -0
  244. package/dist/tree-change/copy-tree.d.ts +125 -0
  245. package/dist/tree-change/copy-tree.d.ts.map +1 -0
  246. package/dist/tree-change/copy-tree.js +279 -0
  247. package/dist/tree-change/copy-tree.js.map +1 -0
  248. package/dist/tree-change/files.d.ts +167 -0
  249. package/dist/tree-change/files.d.ts.map +1 -0
  250. package/dist/tree-change/files.js +423 -0
  251. package/dist/tree-change/files.js.map +1 -0
  252. package/dist/tree-change/followed-walk.d.ts +54 -0
  253. package/dist/tree-change/followed-walk.d.ts.map +1 -0
  254. package/dist/tree-change/followed-walk.js +81 -0
  255. package/dist/tree-change/followed-walk.js.map +1 -0
  256. package/dist/tree-change/host-chain.d.ts +48 -0
  257. package/dist/tree-change/host-chain.d.ts.map +1 -0
  258. package/dist/tree-change/host-chain.js +85 -0
  259. package/dist/tree-change/host-chain.js.map +1 -0
  260. package/dist/tree-change/identity-compare.d.ts +61 -0
  261. package/dist/tree-change/identity-compare.d.ts.map +1 -0
  262. package/dist/tree-change/identity-compare.js +115 -0
  263. package/dist/tree-change/identity-compare.js.map +1 -0
  264. package/dist/tree-change/identity.d.ts +62 -0
  265. package/dist/tree-change/identity.d.ts.map +1 -0
  266. package/dist/tree-change/identity.js +152 -0
  267. package/dist/tree-change/identity.js.map +1 -0
  268. package/dist/tree-change/index.d.ts +24 -0
  269. package/dist/tree-change/index.d.ts.map +1 -0
  270. package/dist/tree-change/index.js +14 -0
  271. package/dist/tree-change/index.js.map +1 -0
  272. package/dist/tree-change/link-chain.d.ts +180 -0
  273. package/dist/tree-change/link-chain.d.ts.map +1 -0
  274. package/dist/tree-change/link-chain.js +377 -0
  275. package/dist/tree-change/link-chain.js.map +1 -0
  276. package/dist/tree-change/link-containment.d.ts +105 -0
  277. package/dist/tree-change/link-containment.d.ts.map +1 -0
  278. package/dist/tree-change/link-containment.js +196 -0
  279. package/dist/tree-change/link-containment.js.map +1 -0
  280. package/dist/tree-change/link-walk.d.ts +49 -0
  281. package/dist/tree-change/link-walk.d.ts.map +1 -0
  282. package/dist/tree-change/link-walk.js +69 -0
  283. package/dist/tree-change/link-walk.js.map +1 -0
  284. package/dist/tree-change/plan.d.ts +206 -0
  285. package/dist/tree-change/plan.d.ts.map +1 -0
  286. package/dist/tree-change/plan.js +320 -0
  287. package/dist/tree-change/plan.js.map +1 -0
  288. package/dist/tree-change/readable-tree.d.ts +44 -0
  289. package/dist/tree-change/readable-tree.d.ts.map +1 -0
  290. package/dist/tree-change/readable-tree.js +52 -0
  291. package/dist/tree-change/readable-tree.js.map +1 -0
  292. package/dist/tree-change/rollback-error.d.ts +29 -0
  293. package/dist/tree-change/rollback-error.d.ts.map +1 -0
  294. package/dist/tree-change/rollback-error.js +31 -0
  295. package/dist/tree-change/rollback-error.js.map +1 -0
  296. package/dist/tree-change/staging-names.d.ts +27 -0
  297. package/dist/tree-change/staging-names.d.ts.map +1 -0
  298. package/dist/tree-change/staging-names.js +40 -0
  299. package/dist/tree-change/staging-names.js.map +1 -0
  300. package/dist/tree-change/tree-walk.d.ts +81 -0
  301. package/dist/tree-change/tree-walk.d.ts.map +1 -0
  302. package/dist/tree-change/tree-walk.js +212 -0
  303. package/dist/tree-change/tree-walk.js.map +1 -0
  304. package/dist/win32-namespace.d.ts +24 -0
  305. package/dist/win32-namespace.d.ts.map +1 -0
  306. package/dist/win32-namespace.js +31 -0
  307. package/dist/win32-namespace.js.map +1 -0
  308. package/dist/yaml/surgical-yaml.d.ts +12 -3
  309. package/dist/yaml/surgical-yaml.d.ts.map +1 -1
  310. package/dist/yaml/surgical-yaml.js +16 -6
  311. package/dist/yaml/surgical-yaml.js.map +1 -1
  312. package/eslint/README.md +12 -4
  313. package/eslint/rules/fs-binding-scan.cjs +278 -0
  314. package/eslint/rules/no-adhoc-errno.cjs +364 -0
  315. package/eslint/rules/no-bare-symlink-in-tests.cjs +2 -3
  316. package/eslint/rules/no-blind-catch.cjs +1 -1
  317. package/eslint/rules/no-destructive-fs.cjs +359 -0
  318. package/eslint/rules/no-existssync.cjs +70 -0
  319. package/eslint/rules/no-fragile-entrypoint-guard.cjs +3 -2
  320. package/eslint/rules/no-link-following-fs.cjs +68 -0
  321. package/eslint/rules/no-literal-process-exit.cjs +96 -8
  322. package/eslint/rules/no-literal-test-timeout.cjs +214 -0
  323. package/eslint/rules/no-raw-signal-listener.cjs +224 -0
  324. package/eslint/rules/no-stdout-outside-writer.cjs +135 -0
  325. package/package.json +10 -2
  326. package/dist/errors/errno.d.ts +0 -51
  327. package/dist/errors/errno.d.ts.map +0 -1
  328. package/dist/errors/errno.js +0 -112
  329. package/dist/errors/errno.js.map +0 -1
  330. package/eslint/rules/no-fs-promises-cp.cjs +0 -43
@@ -0,0 +1,364 @@
1
+ /**
2
+ * ESLint rule: no-adhoc-errno
3
+ *
4
+ * Bans classifying a filesystem failure by hand. An errno name written as a string
5
+ * in a classifying position is a private errno table, and every private table
6
+ * decides a refusal code a little differently from its neighbours. One classifier
7
+ * (`fsFaultOf` / `classifyFsFault`, plus the single-errno predicates such as
8
+ * `isPathAbsentError`) owns the vocabulary; everything else asks it.
9
+ *
10
+ * Two reports:
11
+ *
12
+ * - `literal` — an errno name as a string that is (a) an operand of `===` `!==`
13
+ * `==` `!=`, (b) a `switch` `case` test, (c) an element of an array literal that
14
+ * is passed to `new Set(...)` or bound to a `const`, or (d) the argument of
15
+ * `.has()` / `.includes()`. (d) also catches the message match
16
+ * `errorMessage.includes('ENOENT')`, which hides an errno in prose.
17
+ * - `adhocRefusal` — inside a `catch (e)`, a `new CommandRefusalError(...)` or
18
+ * `new VatError(...)` whose options carry `cause: e`, where the same catch body
19
+ * also reads `e.code` or calls `fsFaultOf(e)` / `isPathAbsentError(e)`. The catch
20
+ * is deciding the refusal itself; `classifyFsFault` decides it once.
21
+ *
22
+ * An inline array is a lookup table too: `['ENOENT', 'EACCES'].includes(e.code)` (and
23
+ * `.some` / `.indexOf` / `.find` / `.findIndex` / `.every`). An identifier bound once
24
+ * by `const` to an errno string (`const C = 'ENOENT'; e.code === C`) is flagged at its
25
+ * use, and a template literal with no expressions counts as the string it spells.
26
+ *
27
+ * What counts as an errno is a SET, not a pattern: the union of
28
+ * `os.constants.errno` across hosts, plus `EFTYPE` and `UNKNOWN` (libuv names Node
29
+ * does not export) and `EHOSTDOWN` (which Node on macOS omits). A name that merely
30
+ * looks like one (`EXAMPLE`) is not flagged, and `EOF` is not an errno, so it needs
31
+ * no special case.
32
+ *
33
+ * Deliberately out of scope (each is a position this rule does not read):
34
+ * - helper-call arguments, `isErr(e, 'ENOENT')`: the callee is the private table,
35
+ * and judging arbitrary calls would flag every legitimate use of a name;
36
+ * - object literals, `{ code: 'ENOENT' }` / `{ ENOENT: 1 }`: those build or describe
37
+ * an error (fixtures, tables keyed by name), they do not classify one;
38
+ * - dynamic strings, built with a template expression or `+`: not decidable statically.
39
+ *
40
+ * Exempt: test files, and the files named by `exemptFiles` (the ones that must name
41
+ * errnos: the errno table, the git-stderr parser, the fault-injecting fs). No
42
+ * default exemption ships, so a consumer declares its own.
43
+ */
44
+
45
+ const {
46
+ EXEMPT_FILES_SCHEMA,
47
+ UNANCHORED_EXEMPT_FILE,
48
+ UNANCHORED_EXEMPT_MESSAGE,
49
+ createConfigurableExemptPathMatcher,
50
+ isTestFile,
51
+ reportUnanchoredExemptEntries,
52
+ } = require('./exempt-path-matcher.cjs');
53
+
54
+ /**
55
+ * The errno vocabulary, spelled out so the set is identical on every host.
56
+ *
57
+ * `os.constants.errno` differs by platform (Linux exports names macOS omits, and
58
+ * macOS omits `EHOSTDOWN`), so reading it at lint time would make the rule pass on
59
+ * one CI leg and fail on another. A rule module also may not require any external
60
+ * module (`subpath-purity.test.ts`). So the union is written out here, and
61
+ * `no-adhoc-errno.test.ts` fails if this host's `os.constants.errno` has a key the
62
+ * list lacks. `EFTYPE` and `UNKNOWN` are libuv names Node does not export.
63
+ */
64
+ const ERRNO_NAMES = new Set([
65
+ 'E2BIG', 'EACCES', 'EADDRINUSE', 'EADDRNOTAVAIL', 'EAFNOSUPPORT', 'EAGAIN', 'EALREADY',
66
+ 'EBADF', 'EBADMSG', 'EBUSY', 'ECANCELED', 'ECHILD', 'ECONNABORTED', 'ECONNREFUSED',
67
+ 'ECONNRESET', 'EDEADLK', 'EDESTADDRREQ', 'EDOM', 'EDQUOT', 'EEXIST', 'EFAULT', 'EFBIG',
68
+ 'EHOSTUNREACH', 'EIDRM', 'EILSEQ', 'EINPROGRESS', 'EINTR', 'EINVAL', 'EIO', 'EISCONN',
69
+ 'EISDIR', 'ELOOP', 'EMFILE', 'EMLINK', 'EMSGSIZE', 'EMULTIHOP', 'ENAMETOOLONG', 'ENETDOWN',
70
+ 'ENETRESET', 'ENETUNREACH', 'ENFILE', 'ENOBUFS', 'ENODATA', 'ENODEV', 'ENOENT', 'ENOEXEC',
71
+ 'ENOLCK', 'ENOLINK', 'ENOMEM', 'ENOMSG', 'ENOPROTOOPT', 'ENOSPC', 'ENOSR', 'ENOSTR',
72
+ 'ENOSYS', 'ENOTCONN', 'ENOTDIR', 'ENOTEMPTY', 'ENOTSOCK', 'ENOTSUP', 'ENOTTY', 'ENXIO',
73
+ 'EOPNOTSUPP', 'EOVERFLOW', 'EPERM', 'EPIPE', 'EPROTO', 'EPROTONOSUPPORT', 'EPROTOTYPE',
74
+ 'ERANGE', 'EROFS', 'ESPIPE', 'ESRCH', 'ESTALE', 'ETIME', 'ETIMEDOUT', 'ETXTBSY',
75
+ 'EWOULDBLOCK', 'EXDEV',
76
+ // Linux-only names:
77
+ 'EADV', 'EBADE', 'EBADFD', 'EBADR', 'EBADRQC', 'EBADSLT', 'EBFONT', 'ECHRNG', 'ECOMM',
78
+ 'EDEADLOCK', 'EDOTDOT', 'EISNAM', 'EKEYEXPIRED', 'EKEYREJECTED', 'EKEYREVOKED', 'EL2HLT',
79
+ 'EL2NSYNC', 'EL3HLT', 'EL3RST', 'ELIBACC', 'ELIBBAD', 'ELIBEXEC', 'ELIBMAX', 'ELIBSCN',
80
+ 'ELNRNG', 'EMEDIUMTYPE', 'ENAVAIL', 'ENOANO', 'ENOKEY', 'ENOMEDIUM', 'ENONET', 'ENOPKG',
81
+ 'ENOTBLK', 'ENOTNAM', 'ENOTRECOVERABLE', 'ENOTUNIQ', 'EOWNERDEAD', 'EPFNOSUPPORT', 'EREMCHG',
82
+ 'EREMOTE', 'EREMOTEIO', 'ERESTART', 'ERFKILL', 'ESHUTDOWN', 'ESOCKTNOSUPPORT', 'ESRMNT',
83
+ 'ESTRPIPE', 'ETOOMANYREFS', 'EUCLEAN', 'EUNATCH', 'EUSERS', 'EXFULL',
84
+ // Not in os.constants.errno on every host:
85
+ 'EFTYPE', 'UNKNOWN', 'EHOSTDOWN',
86
+ ]);
87
+
88
+ const COMPARISON_OPERATORS = new Set(['===', '!==', '==', '!=']);
89
+ const MEMBERSHIP_METHODS = new Set(['has', 'includes']);
90
+ /** Methods that make an INLINE array literal a lookup table: `['ENOENT'].includes(e.code)`. */
91
+ const ARRAY_LOOKUP_METHODS = new Set(['includes', 'some', 'indexOf', 'find', 'findIndex', 'every']);
92
+ const FUNCTION_TYPES = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']);
93
+ const REFUSAL_CLASSES = new Set(['CommandRefusalError', 'VatError']);
94
+ const ERRNO_PREDICATES = new Set(['fsFaultOf', 'isPathAbsentError']);
95
+
96
+ /**
97
+ * The string a literal spells: a string `Literal`, or a template literal with no
98
+ * expressions (a backticked ENOENT is the same literal).
99
+ *
100
+ * @returns {string | undefined}
101
+ */
102
+ function literalText(node) {
103
+ if (node?.type === 'Literal') {
104
+ return typeof node.value === 'string' ? node.value : undefined;
105
+ }
106
+ if (node?.type === 'TemplateLiteral' && node.expressions.length === 0 && node.quasis.length === 1) {
107
+ return node.quasis[0].value.cooked ?? undefined;
108
+ }
109
+ return undefined;
110
+ }
111
+
112
+ /** @returns {boolean} True when `node` is a string literal naming an errno. */
113
+ function isErrnoLiteral(node) {
114
+ const text = literalText(node);
115
+ return text !== undefined && ERRNO_NAMES.has(text);
116
+ }
117
+
118
+ /** The declaring variable of an identifier, found by walking out through its scopes. */
119
+ function resolveVariable(sourceCode, node) {
120
+ for (let scope = sourceCode.getScope(node); scope; scope = scope.upper) {
121
+ const variable = scope.set.get(node.name);
122
+ if (variable) {
123
+ return variable;
124
+ }
125
+ }
126
+ return undefined;
127
+ }
128
+
129
+ /** True when `node` is an identifier bound once, by `const`, to an errno literal. */
130
+ function isConstErrnoAlias(sourceCode, node) {
131
+ if (node.type !== 'Identifier') {
132
+ return false;
133
+ }
134
+ const variable = resolveVariable(sourceCode, node);
135
+ const definition = variable?.defs.length === 1 ? variable.defs[0] : undefined;
136
+ return (
137
+ definition?.type === 'Variable' &&
138
+ definition.parent?.kind === 'const' &&
139
+ definition.node.id.type === 'Identifier' &&
140
+ isErrnoLiteral(unwrapExpression(definition.node.init))
141
+ );
142
+ }
143
+
144
+ /** Step over `as const`, `satisfies` and `!` wrappers to the expression inside. */
145
+ function unwrapExpression(node) {
146
+ let current = node;
147
+ while (
148
+ current &&
149
+ (current.type === 'TSAsExpression' ||
150
+ current.type === 'TSSatisfiesExpression' ||
151
+ current.type === 'TSNonNullExpression')
152
+ ) {
153
+ current = current.expression;
154
+ }
155
+ return current;
156
+ }
157
+
158
+ /** The array literal's enclosing context: the thing that makes it a lookup table. */
159
+ function isLookupArray(arrayNode) {
160
+ // Climb through `as const` wrappers so `['ENOENT'] as const` is seen as bound.
161
+ let child = arrayNode;
162
+ let parent = arrayNode.parent;
163
+ while (parent && unwrapExpression(parent) !== parent && parent.expression === child) {
164
+ child = parent;
165
+ parent = parent.parent;
166
+ }
167
+ if (parent?.type === 'MemberExpression') {
168
+ return (
169
+ parent.object === child &&
170
+ !parent.computed &&
171
+ parent.property.type === 'Identifier' &&
172
+ ARRAY_LOOKUP_METHODS.has(parent.property.name)
173
+ );
174
+ }
175
+ if (parent?.type === 'NewExpression') {
176
+ return parent.callee.type === 'Identifier' && parent.callee.name === 'Set' && parent.arguments[0] === child;
177
+ }
178
+ return (
179
+ parent?.type === 'VariableDeclarator' &&
180
+ parent.init === child &&
181
+ parent.parent?.type === 'VariableDeclaration' &&
182
+ parent.parent.kind === 'const'
183
+ );
184
+ }
185
+
186
+ /** @returns {boolean} True when the literal sits in one of the four classifying positions. */
187
+ function isClassifyingPosition(node) {
188
+ const parent = node.parent;
189
+ switch (parent?.type) {
190
+ case 'BinaryExpression':
191
+ return COMPARISON_OPERATORS.has(parent.operator);
192
+ case 'SwitchCase':
193
+ return parent.test === node;
194
+ case 'ArrayExpression':
195
+ return isLookupArray(parent);
196
+ case 'CallExpression':
197
+ return (
198
+ parent.arguments.includes(node) &&
199
+ parent.callee.type === 'MemberExpression' &&
200
+ !parent.callee.computed &&
201
+ parent.callee.property.type === 'Identifier' &&
202
+ MEMBERSHIP_METHODS.has(parent.callee.property.name)
203
+ );
204
+ default:
205
+ return false;
206
+ }
207
+ }
208
+
209
+ /** Every AST child of `node`, skipping the back-pointer. */
210
+ function childNodes(node) {
211
+ const children = [];
212
+ for (const [key, value] of Object.entries(node)) {
213
+ if (key === 'parent') {
214
+ continue;
215
+ }
216
+ for (const item of Array.isArray(value) ? value : [value]) {
217
+ if (item && typeof item.type === 'string') {
218
+ children.push(item);
219
+ }
220
+ }
221
+ }
222
+ return children;
223
+ }
224
+
225
+ /**
226
+ * Collect every node under `root` (inclusive) that `predicate` accepts, without
227
+ * descending into a nested catch or function: those judge their own bodies, so a
228
+ * re-wrap is reported once, by the catch that owns it.
229
+ */
230
+ function collectNodes(root, predicate) {
231
+ const found = [];
232
+ const pending = [root];
233
+ while (pending.length > 0) {
234
+ const node = pending.pop();
235
+ if (predicate(node)) {
236
+ found.push(node);
237
+ }
238
+ if (node.type !== 'CatchClause' && !FUNCTION_TYPES.has(node.type)) {
239
+ pending.push(...childNodes(node));
240
+ }
241
+ }
242
+ return found;
243
+ }
244
+
245
+ const isIdentifier = (node, name) => node?.type === 'Identifier' && node.name === name;
246
+
247
+ /** `e.code`, `fsFaultOf(e)` or `isPathAbsentError(e)` for the catch parameter `name`. */
248
+ function inspectsErrno(node, name) {
249
+ if (node.type === 'MemberExpression') {
250
+ return (
251
+ isIdentifier(node.object, name) &&
252
+ ((!node.computed && isIdentifier(node.property, 'code')) ||
253
+ (node.computed && node.property.type === 'Literal' && node.property.value === 'code'))
254
+ );
255
+ }
256
+ return (
257
+ node.type === 'CallExpression' &&
258
+ node.callee.type === 'Identifier' &&
259
+ ERRNO_PREDICATES.has(node.callee.name) &&
260
+ isIdentifier(node.arguments[0], name)
261
+ );
262
+ }
263
+
264
+ /** `new CommandRefusalError(...)` / `new VatError(...)` carrying `cause: <name>`. */
265
+ function wrapsCause(node, name) {
266
+ return (
267
+ node.type === 'NewExpression' &&
268
+ node.callee.type === 'Identifier' &&
269
+ REFUSAL_CLASSES.has(node.callee.name) &&
270
+ node.arguments.some(
271
+ (argument) =>
272
+ argument.type === 'ObjectExpression' &&
273
+ argument.properties.some(
274
+ (property) =>
275
+ property.type === 'Property' &&
276
+ !property.computed &&
277
+ (isIdentifier(property.key, 'cause') || property.key.value === 'cause') &&
278
+ isIdentifier(property.value, name),
279
+ ),
280
+ )
281
+ );
282
+ }
283
+
284
+ module.exports = {
285
+ meta: {
286
+ type: 'problem',
287
+ docs: {
288
+ description:
289
+ 'Ban classifying a filesystem failure by hand: an errno name as a string in a comparison, case, Set/const array or .has()/.includes(), and a catch that inspects the errno then wraps it as a refusal cause',
290
+ category: 'Filesystem and process',
291
+ bans: 'an errno name (`ENOENT`, `EACCES`, …) as a string in a classifying position, and a catch that reads `e.code` then builds a refusal with `cause: e`',
292
+ useInstead: '`fsFaultOf(e)` / `classifyFsFault(e, …)`, or a single-errno predicate such as `isPathAbsentError(e)`',
293
+ // Not in `recommended`: the remedy names this repo's own classifier
294
+ // (`fsFaultOf`, `classifyFsFault`) and the refusal classes `CommandRefusalError` /
295
+ // `VatError`, none of which an adopter has. VAT enables it explicitly, naming
296
+ // the files that must spell errnos (the table, the git-stderr parser, the
297
+ // fault-injecting fs).
298
+ recommended: false,
299
+ recommendedSeverity: 'error',
300
+ },
301
+ fixable: null,
302
+ schema: [EXEMPT_FILES_SCHEMA],
303
+ messages: {
304
+ literal:
305
+ "Errno name '{{name}}' written as a string classifies a filesystem failure by hand. " +
306
+ 'Ask the one classifier instead: fsFaultOf(e)?.faultClass, classifyFsFault(e, …), or a ' +
307
+ 'single-errno predicate such as isPathAbsentError(e). A private errno table drifts from its neighbours.',
308
+ adhocRefusal:
309
+ 'This catch inspects the errno ({{param}}.code / fsFaultOf / isPathAbsentError) and then builds a refusal ' +
310
+ 'with cause: {{param}} by hand. Let classifyFsFault({{param}}, …) pick the refusal once.',
311
+ [UNANCHORED_EXEMPT_FILE]: UNANCHORED_EXEMPT_MESSAGE,
312
+ },
313
+ },
314
+
315
+ create(context) {
316
+ const filename = context.getFilename();
317
+
318
+ if (createConfigurableExemptPathMatcher([])(context)(filename)) {
319
+ // Still surface a malformed exemption list: this file may be exempt only
320
+ // BECAUSE an entry is unanchored.
321
+ return {
322
+ Program(node) {
323
+ reportUnanchoredExemptEntries(context, node);
324
+ },
325
+ };
326
+ }
327
+
328
+ if (isTestFile(filename)) {
329
+ return {};
330
+ }
331
+
332
+ return {
333
+ Program(node) {
334
+ reportUnanchoredExemptEntries(context, node);
335
+ },
336
+
337
+ 'Literal, TemplateLiteral'(node) {
338
+ if (isErrnoLiteral(node) && isClassifyingPosition(node)) {
339
+ context.report({ node, messageId: 'literal', data: { name: literalText(node) } });
340
+ }
341
+ },
342
+
343
+ // `const C = 'ENOENT'; e.code === C`: flagged at the use, where the check is.
344
+ Identifier(node) {
345
+ if (isClassifyingPosition(node) && isConstErrnoAlias(context.sourceCode, node)) {
346
+ context.report({ node, messageId: 'literal', data: { name: node.name } });
347
+ }
348
+ },
349
+
350
+ CatchClause(node) {
351
+ if (node.param?.type !== 'Identifier') {
352
+ return;
353
+ }
354
+ const param = node.param.name;
355
+ if (collectNodes(node.body, (child) => inspectsErrno(child, param)).length === 0) {
356
+ return;
357
+ }
358
+ for (const wrapped of collectNodes(node.body, (child) => wrapsCause(child, param))) {
359
+ context.report({ node: wrapped, messageId: 'adhocRefusal', data: { param } });
360
+ }
361
+ },
362
+ };
363
+ },
364
+ };
@@ -11,9 +11,8 @@
11
11
  * production code at it would be worse advice than the bare call. Prefer a
12
12
  * junction for a directory link on win32, or catch the failure and name the
13
13
  * missing privilege. An `eslint-disable` justification is the sanctioned way
14
- * to say "this platform is deliberately out of scope" — see
15
- * `cli/src/commands/agent/install.ts`, where `--dev` is knowingly unavailable
16
- * on an unprivileged Windows and fails saying exactly that.
14
+ * to say "this is the guarded form" — see `dev-tools/src/link-workspace-packages.ts`,
15
+ * which makes a junction on win32 and names the package a failure was for.
17
16
  *
18
17
  * ⚠️ **The name is now narrower than the rule.** It covers shipped code too;
19
18
  * renaming it is a public-API change to `@vibe-agent-toolkit/utils/eslint` and
@@ -23,7 +23,7 @@
23
23
  * The rule is a FLOOR, deliberately syntactic: the clause is fine if either
24
24
  *
25
25
  * 1. the error binding is REFERENCED anywhere in the body — narrowing on it
26
- * (`isFilesystemAccessError(e)`, `e instanceof X`, `e.code === 'ENOENT'`),
26
+ * (`fsFaultOf(e)`, `e instanceof X`, `e.code === 'ENOENT'`),
27
27
  * carrying it into a report (`errors.push(String(e))`, `{ ok: false, error }`),
28
28
  * or logging it — or
29
29
  * 2. the body THROWS (a rethrow, or a translation into a louder error), at the