@vibe-agent-toolkit/utils 0.1.42 → 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 (279) hide show
  1. package/README.md +81 -19
  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 +214 -24
  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 +628 -38
  44. package/dist/fs-utils.d.ts.map +1 -1
  45. package/dist/fs-utils.js +770 -71
  46. package/dist/fs-utils.js.map +1 -1
  47. package/dist/fs.d.ts +5 -1
  48. package/dist/fs.d.ts.map +1 -1
  49. package/dist/fs.js +9 -1
  50. package/dist/fs.js.map +1 -1
  51. package/dist/git-root-cache.d.ts +44 -0
  52. package/dist/git-root-cache.d.ts.map +1 -0
  53. package/dist/git-root-cache.js +68 -0
  54. package/dist/git-root-cache.js.map +1 -0
  55. package/dist/git-run.d.ts +167 -0
  56. package/dist/git-run.d.ts.map +1 -0
  57. package/dist/git-run.js +154 -0
  58. package/dist/git-run.js.map +1 -0
  59. package/dist/git-snapshot.d.ts +199 -0
  60. package/dist/git-snapshot.d.ts.map +1 -0
  61. package/dist/git-snapshot.js +250 -0
  62. package/dist/git-snapshot.js.map +1 -0
  63. package/dist/git-tracker.d.ts +166 -3
  64. package/dist/git-tracker.d.ts.map +1 -1
  65. package/dist/git-tracker.js +250 -22
  66. package/dist/git-tracker.js.map +1 -1
  67. package/dist/git-utils.d.ts +95 -1
  68. package/dist/git-utils.d.ts.map +1 -1
  69. package/dist/git-utils.js +227 -81
  70. package/dist/git-utils.js.map +1 -1
  71. package/dist/git.d.ts +13 -4
  72. package/dist/git.d.ts.map +1 -1
  73. package/dist/git.js +12 -4
  74. package/dist/git.js.map +1 -1
  75. package/dist/gitignore-checker.d.ts.map +1 -1
  76. package/dist/gitignore-checker.js +15 -6
  77. package/dist/gitignore-checker.js.map +1 -1
  78. package/dist/glob/glob-pattern.js +5 -5
  79. package/dist/glob/glob-pattern.js.map +1 -1
  80. package/dist/index.d.ts +33 -19
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +113 -33
  83. package/dist/index.js.map +1 -1
  84. package/dist/listing-refusal.d.ts +112 -0
  85. package/dist/listing-refusal.d.ts.map +1 -0
  86. package/dist/listing-refusal.js +105 -0
  87. package/dist/listing-refusal.js.map +1 -0
  88. package/dist/numeric-args.d.ts +24 -0
  89. package/dist/numeric-args.d.ts.map +1 -0
  90. package/dist/numeric-args.js +37 -0
  91. package/dist/numeric-args.js.map +1 -0
  92. package/dist/path-containment.d.ts +64 -0
  93. package/dist/path-containment.d.ts.map +1 -0
  94. package/dist/path-containment.js +111 -0
  95. package/dist/path-containment.js.map +1 -0
  96. package/dist/path-core.d.ts +143 -18
  97. package/dist/path-core.d.ts.map +1 -1
  98. package/dist/path-core.js +171 -23
  99. package/dist/path-core.js.map +1 -1
  100. package/dist/path-utils.d.ts.map +1 -1
  101. package/dist/path-utils.js +42 -47
  102. package/dist/path-utils.js.map +1 -1
  103. package/dist/path.d.ts +1 -1
  104. package/dist/path.d.ts.map +1 -1
  105. package/dist/path.js +1 -1
  106. package/dist/path.js.map +1 -1
  107. package/dist/process.d.ts +7 -0
  108. package/dist/process.d.ts.map +1 -1
  109. package/dist/process.js +7 -0
  110. package/dist/process.js.map +1 -1
  111. package/dist/project-utils.d.ts +7 -1
  112. package/dist/project-utils.d.ts.map +1 -1
  113. package/dist/project-utils.js +24 -10
  114. package/dist/project-utils.js.map +1 -1
  115. package/dist/project.d.ts +7 -5
  116. package/dist/project.d.ts.map +1 -1
  117. package/dist/project.js +7 -5
  118. package/dist/project.js.map +1 -1
  119. package/dist/safe-exec.d.ts +40 -11
  120. package/dist/safe-exec.d.ts.map +1 -1
  121. package/dist/safe-exec.js +111 -25
  122. package/dist/safe-exec.js.map +1 -1
  123. package/dist/skill-targets.d.ts +2 -2
  124. package/dist/skill-targets.js +2 -2
  125. package/dist/skill-test/auth-resolver.d.ts +10 -2
  126. package/dist/skill-test/auth-resolver.d.ts.map +1 -1
  127. package/dist/skill-test/auth-resolver.js +26 -11
  128. package/dist/skill-test/auth-resolver.js.map +1 -1
  129. package/dist/skill-test/index.d.ts +1 -1
  130. package/dist/skill-test/index.d.ts.map +1 -1
  131. package/dist/skill-test/index.js +1 -1
  132. package/dist/skill-test/index.js.map +1 -1
  133. package/dist/skill-test/spawn-claude.d.ts +30 -1
  134. package/dist/skill-test/spawn-claude.d.ts.map +1 -1
  135. package/dist/skill-test/spawn-claude.js +71 -5
  136. package/dist/skill-test/spawn-claude.js.map +1 -1
  137. package/dist/skill-test/transcript.d.ts +41 -2
  138. package/dist/skill-test/transcript.d.ts.map +1 -1
  139. package/dist/skill-test/transcript.js +55 -10
  140. package/dist/skill-test/transcript.js.map +1 -1
  141. package/dist/spawn-hardened.js +0 -1
  142. package/dist/spawn-hardened.js.map +1 -1
  143. package/dist/stdio-blocking.js +13 -14
  144. package/dist/stdio-blocking.js.map +1 -1
  145. package/dist/test-helpers.d.ts +130 -79
  146. package/dist/test-helpers.d.ts.map +1 -1
  147. package/dist/test-helpers.js +229 -124
  148. package/dist/test-helpers.js.map +1 -1
  149. package/dist/testing/executables.d.ts +32 -0
  150. package/dist/testing/executables.d.ts.map +1 -0
  151. package/dist/testing/executables.js +70 -0
  152. package/dist/testing/executables.js.map +1 -0
  153. package/dist/testing/hostile-tree.d.ts +79 -0
  154. package/dist/testing/hostile-tree.d.ts.map +1 -0
  155. package/dist/testing/hostile-tree.js +164 -0
  156. package/dist/testing/hostile-tree.js.map +1 -0
  157. package/dist/testing/platform-gates.d.ts +31 -0
  158. package/dist/testing/platform-gates.d.ts.map +1 -0
  159. package/dist/testing/platform-gates.js +31 -0
  160. package/dist/testing/platform-gates.js.map +1 -0
  161. package/dist/testing/temp-dir.d.ts +255 -0
  162. package/dist/testing/temp-dir.d.ts.map +1 -0
  163. package/dist/testing/temp-dir.js +363 -0
  164. package/dist/testing/temp-dir.js.map +1 -0
  165. package/dist/testing.d.ts +94 -3
  166. package/dist/testing.d.ts.map +1 -1
  167. package/dist/testing.js +119 -3
  168. package/dist/testing.js.map +1 -1
  169. package/dist/text-content.d.ts +197 -0
  170. package/dist/text-content.d.ts.map +1 -0
  171. package/dist/text-content.js +354 -0
  172. package/dist/text-content.js.map +1 -0
  173. package/dist/text-file.d.ts +45 -0
  174. package/dist/text-file.d.ts.map +1 -0
  175. package/dist/text-file.js +51 -0
  176. package/dist/text-file.js.map +1 -0
  177. package/dist/text.d.ts +16 -0
  178. package/dist/text.d.ts.map +1 -0
  179. package/dist/text.js +16 -0
  180. package/dist/text.js.map +1 -0
  181. package/dist/timing-dump.d.ts +136 -0
  182. package/dist/timing-dump.d.ts.map +1 -0
  183. package/dist/timing-dump.js +229 -0
  184. package/dist/timing-dump.js.map +1 -0
  185. package/dist/windows-shell.d.ts +0 -1
  186. package/dist/windows-shell.d.ts.map +1 -1
  187. package/dist/windows-shell.js +0 -1
  188. package/dist/windows-shell.js.map +1 -1
  189. package/dist/zod-introspection.js +2 -2
  190. package/dist/zod-introspection.js.map +1 -1
  191. package/eslint/README.md +224 -50
  192. package/eslint/index.cjs +111 -97
  193. package/eslint/index.d.cts +16 -6
  194. package/eslint/rules/callee-name.cjs +23 -0
  195. package/eslint/rules/commands-import-boundary.cjs +148 -0
  196. package/eslint/rules/dead-import.cjs +62 -12
  197. package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
  198. package/eslint/rules/eslint-rule-factory.cjs +21 -3
  199. package/eslint/rules/exempt-path-matcher.cjs +2 -1
  200. package/eslint/rules/explicit-zod-strictness.cjs +120 -0
  201. package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
  202. package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
  203. package/eslint/rules/no-bare-symlink-in-tests.cjs +213 -0
  204. package/eslint/rules/no-blind-catch.cjs +171 -0
  205. package/eslint/rules/no-child-process-execSync.cjs +8 -0
  206. package/eslint/rules/no-decaying-referent.cjs +217 -0
  207. package/eslint/rules/no-dotdot-containment.cjs +160 -0
  208. package/eslint/rules/no-file-url-string-concat.cjs +4 -1
  209. package/eslint/rules/no-fragile-entrypoint-guard.cjs +228 -0
  210. package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
  211. package/eslint/rules/no-fs-promises-cp.cjs +7 -0
  212. package/eslint/rules/no-fs-realpathSync.cjs +8 -0
  213. package/eslint/rules/no-hardcoded-path-split.cjs +29 -18
  214. package/eslint/rules/no-io-in-unit-tier.cjs +129 -0
  215. package/eslint/rules/no-literal-process-exit.cjs +129 -0
  216. package/eslint/rules/no-manual-path-normalize.cjs +146 -96
  217. package/eslint/rules/no-os-tmpdir.cjs +8 -0
  218. package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
  219. package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
  220. package/eslint/rules/no-path-startswith.cjs +7 -2
  221. package/eslint/rules/no-process-exit-in-phase.cjs +127 -0
  222. package/eslint/rules/no-raw-node-path.cjs +563 -0
  223. package/eslint/rules/no-raw-text-decode.cjs +305 -0
  224. package/eslint/rules/no-registry-count-pin.cjs +255 -0
  225. package/eslint/rules/no-self-package-import.cjs +168 -0
  226. package/eslint/rules/no-test-scoped-functions.cjs +10 -3
  227. package/eslint/rules/no-unix-shell-commands.cjs +6 -2
  228. package/eslint/rules/no-unsafe-root-join.cjs +23 -2
  229. package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
  230. package/eslint/rules/no-version-literal.cjs +151 -0
  231. package/eslint/rules/prefer-startswith-over-regex.cjs +33 -1
  232. package/eslint/rules/require-justified-skip.cjs +8 -2
  233. package/eslint/rules/safe-import.cjs +1 -1
  234. package/package.json +16 -16
  235. package/dist/link-auth/build-headers.d.ts +0 -34
  236. package/dist/link-auth/build-headers.d.ts.map +0 -1
  237. package/dist/link-auth/build-headers.js +0 -58
  238. package/dist/link-auth/build-headers.js.map +0 -1
  239. package/dist/link-auth/expand-macro.d.ts +0 -38
  240. package/dist/link-auth/expand-macro.d.ts.map +0 -1
  241. package/dist/link-auth/expand-macro.js +0 -133
  242. package/dist/link-auth/expand-macro.js.map +0 -1
  243. package/dist/link-auth/macros.yaml +0 -50
  244. package/dist/link-auth/resolve-token.d.ts +0 -83
  245. package/dist/link-auth/resolve-token.d.ts.map +0 -1
  246. package/dist/link-auth/resolve-token.js +0 -101
  247. package/dist/link-auth/resolve-token.js.map +0 -1
  248. package/dist/link-auth/resolve.d.ts +0 -102
  249. package/dist/link-auth/resolve.d.ts.map +0 -1
  250. package/dist/link-auth/resolve.js +0 -66
  251. package/dist/link-auth/resolve.js.map +0 -1
  252. package/dist/link-auth/rewrite.d.ts +0 -52
  253. package/dist/link-auth/rewrite.d.ts.map +0 -1
  254. package/dist/link-auth/rewrite.js +0 -102
  255. package/dist/link-auth/rewrite.js.map +0 -1
  256. package/dist/link-auth/select-provider.d.ts +0 -30
  257. package/dist/link-auth/select-provider.d.ts.map +0 -1
  258. package/dist/link-auth/select-provider.js +0 -55
  259. package/dist/link-auth/select-provider.js.map +0 -1
  260. package/dist/link-auth/template.d.ts +0 -40
  261. package/dist/link-auth/template.d.ts.map +0 -1
  262. package/dist/link-auth/template.js +0 -89
  263. package/dist/link-auth/template.js.map +0 -1
  264. package/dist/link-auth/transforms.d.ts +0 -46
  265. package/dist/link-auth/transforms.d.ts.map +0 -1
  266. package/dist/link-auth/transforms.js +0 -52
  267. package/dist/link-auth/transforms.js.map +0 -1
  268. package/dist/template-entry.d.ts +0 -10
  269. package/dist/template-entry.d.ts.map +0 -1
  270. package/dist/template-entry.js +0 -10
  271. package/dist/template-entry.js.map +0 -1
  272. package/dist/template.d.ts +0 -7
  273. package/dist/template.d.ts.map +0 -1
  274. package/dist/template.js +0 -18
  275. package/dist/template.js.map +0 -1
  276. package/eslint/rules/no-path-join.cjs +0 -14
  277. package/eslint/rules/no-path-relative.cjs +0 -14
  278. package/eslint/rules/no-path-resolve.cjs +0 -14
  279. package/eslint/rules/path-function-rule-factory.cjs +0 -417
@@ -2,9 +2,56 @@
2
2
  * Centralized git command wrapper.
3
3
  * All git commands should go through this module for consistency and testability.
4
4
  */
5
+ import { type UnreadablePolicy } from './listing-refusal.js';
6
+ /**
7
+ * The directories a git listing was REFUSED, read off its stderr.
8
+ *
9
+ * Pure. Returns each refused directory once, worktree-relative and without
10
+ * git's trailing slash, with the errno its reason maps to. A directory git
11
+ * could not open because it no longer exists is not returned: that is the
12
+ * `absent` outcome of {@link listingFailure}, decided there and not here, so
13
+ * the two routes classify a vanished directory the same way.
14
+ *
15
+ * @param stderr - What the listing wrote to stderr
16
+ * @returns The refusals, in the order git printed them, deduplicated
17
+ */
18
+ export declare function unlistableDirectoriesIn(stderr: string): {
19
+ directory: string;
20
+ code: string;
21
+ }[];
22
+ /**
23
+ * The policy every working-tree listing here REQUIRES.
24
+ *
25
+ * A refused directory is otherwise invisible: git's exit status is 0 and its
26
+ * stdout is simply shorter. Each directory git could not open is settled as
27
+ * the SAME {@link DirectoryRefusal} the filesystem walk produces
28
+ * (`file-crawler.ts`) — absolute, forward-slashed, with `transient` derived
29
+ * beside the errno list in `fs-utils.ts` — under the same
30
+ * {@link UnreadablePolicy}, so a caller decides once for both routes.
31
+ *
32
+ * 🪤 This was `onUnreadable?`, and when it was omitted the refusals git printed
33
+ * were DROPPED — not thrown, as the walk did, but read off stderr and ignored.
34
+ * The one lane whose only witness to the gap is stderr was the one lane that
35
+ * defaulted to silence. There is no default now.
36
+ */
37
+ export interface GitListingOptions {
38
+ /** What to do with a directory git could not open while walking the working tree. */
39
+ unreadable: UnreadablePolicy;
40
+ }
5
41
  /**
6
42
  * Find the git repository root by walking up from the given directory.
7
43
  *
44
+ * Memoized module-wide via `git-root-cache.ts`: the walk seeds an entry for
45
+ * every directory it climbs, not just for `startDir`, because the redundancy
46
+ * this removes is overlapping walks from *different* start directories rather
47
+ * than repeated calls with the same one. A `null` answer is memoized too — that
48
+ * is the case that costs a walk to the filesystem root.
49
+ *
50
+ * The memo therefore does NOT see a `.git` directory created or removed after
51
+ * the fact. Anything that mutates repositories in-process (tests; a host that
52
+ * re-enters a command) must call `resetProjectRootCaches()`, which clears this
53
+ * cache along with the project-root one.
54
+ *
8
55
  * @param startDir - Directory to start searching from
9
56
  * @returns Path to git root, or null if not in a git repository
10
57
  */
@@ -16,6 +63,8 @@ export declare function gitFindRoot(startDir: string): string | null;
16
63
  * @param options.cwd - Working directory (git repository root or subdirectory)
17
64
  * @param options.patterns - Optional glob patterns to filter files (e.g., '*.md', 'docs/**\/*.ts')
18
65
  * @param options.includeUntracked - Include untracked files that aren't gitignored (default: false)
66
+ * @param options.unreadable - See {@link GitListingOptions}. Only the untracked
67
+ * listing walks the working tree, so only it can have skipped a directory
19
68
  * @returns Array of file paths relative to the git root, or null if not in a git repo
20
69
  *
21
70
  * @example
@@ -27,11 +76,56 @@ export declare function gitFindRoot(startDir: string): string | null;
27
76
  * const allFiles = gitLsFiles({ cwd: '/project', includeUntracked: true });
28
77
  * ```
29
78
  */
30
- export declare function gitLsFiles(options: {
79
+ export declare function gitLsFiles(options: GitListingOptions & {
31
80
  cwd: string;
32
81
  patterns?: string[];
33
82
  includeUntracked?: boolean;
34
83
  }): string[] | null;
84
+ /**
85
+ * List paths git does NOT track, with each wholly-untracked directory collapsed
86
+ * to a single entry.
87
+ *
88
+ * This is the **prune list**, not a file list, and the distinction is the whole
89
+ * value. `--others --ignored --exclude-standard` alone returns every ignored file
90
+ * individually: measured on an 8,496-path adopter working tree that is 533,557
91
+ * paths in 1.19 s — worse than the crawl it was meant to replace, because
92
+ * `.turbo/cache` alone contributed 418,518 of them. Adding `--directory`
93
+ * collapses each wholly-ignored directory to one entry: **369 entries in 60 ms**.
94
+ * A caller can then decide per directory whether to descend, and skip a
95
+ * half-million paths by name without ever entering them.
96
+ *
97
+ * A collapsed directory entry is returned with a trailing `/`, exactly as git
98
+ * spells it. That is how a caller tells "this whole subtree" from "this one
99
+ * file", so it is deliberately not normalized away here.
100
+ *
101
+ * @param options - Configuration options
102
+ * @param options.cwd - Working directory inside the repository
103
+ * @param options.ignored - Restrict to ignored paths. Off, the listing is
104
+ * untracked-but-not-ignored paths — which is the only way to see an EMPTY
105
+ * untracked directory, since a directory with no files in it is invisible to
106
+ * `ls-files` and to any tree object
107
+ * @param options.directory - Collapse a wholly-untracked directory to one entry
108
+ * @param options.unreadable - See {@link GitListingOptions}. ⚠️ The two
109
+ * listings meet different refusals: without `--ignored`, git collapses a
110
+ * WHOLLY-untracked directory without opening it, but it still opens a
111
+ * non-ignored directory that holds tracked files (to look for untracked
112
+ * siblings) and warns when it cannot; the `ignored` listing opens every
113
+ * non-ignored directory and so meets every refusal the first one does and
114
+ * more (measured on git 2.50)
115
+ * @returns Paths relative to the git root, or null if git did not answer
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * // The prune list: where the ignored territory is, without enumerating it.
120
+ * const prune = gitLsOthers({ cwd: root, ignored: true, directory: true });
121
+ * // → ['dist/', 'node_modules/', '.turbo/', 'notes.local.md']
122
+ * ```
123
+ */
124
+ export declare function gitLsOthers(options: GitListingOptions & {
125
+ cwd: string;
126
+ ignored?: boolean;
127
+ directory?: boolean;
128
+ }): string[] | null;
35
129
  /**
36
130
  * Check if a file path is ignored by git
37
131
  *
@@ -1 +1 @@
1
- {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc3D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,MAAM,EAAE,GAAG,IAAI,CA0ClB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,OAAO,CAkEnF"}
1
+ {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAA0C,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAmDrG;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAW7F;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,iBAAiB;IAChC,qFAAqF;IACrF,UAAU,EAAE,gBAAgB,CAAC;CAC9B;AAkDD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkB3D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,MAAM,EAAE,GAAG,IAAI,CAqBlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GAAG,MAAM,EAAE,GAAG,IAAI,CAiBlB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,OAAO,CAiDnF"}
package/dist/git-utils.js CHANGED
@@ -2,29 +2,153 @@
2
2
  * Centralized git command wrapper.
3
3
  * All git commands should go through this module for consistency and testability.
4
4
  */
5
- import { spawnSync } from 'node:child_process';
6
5
  import { existsSync } from 'node:fs';
7
6
  import { dirname, parse } from 'node:path';
8
- import which from 'which';
7
+ import { directoryRefusalFor, listingFailure } from './fs-utils.js';
8
+ import { lookupGitRoot, rememberGitRoot } from './git-root-cache.js';
9
+ import { runGit } from './git-run.js';
10
+ import { requireUnreadablePolicy, settleRefusal } from './listing-refusal.js';
9
11
  import { safePath } from './path-utils.js';
12
+ /**
13
+ * One line of git's stderr that means "I skipped this directory".
14
+ *
15
+ * `git ls-files --others` (and `--ignored`, and `git add --all`) walks the
16
+ * working tree to find untracked files. On a directory it cannot open it prints
17
+ * this warning, exits 0, and omits the subtree — so stdout alone is a shorter
18
+ * list that nothing can tell from a complete one. The path is spelled
19
+ * **relative to the worktree root** with a trailing slash, even when git was
20
+ * run from a subdirectory (measured: cwd `docs/open`, warning names
21
+ * `'docs/open/sub/'`).
22
+ *
23
+ * Anchored to the line so an unrelated warning naming a directory in passing
24
+ * is not read as a refusal. The reason after the colon is `strerror` text —
25
+ * see {@link STRERROR_TO_ERRNO}.
26
+ */
27
+ const UNLISTABLE_DIRECTORY_LINE = /^warning: could not open directory '(.+?)\/?': (.*)$/gm;
28
+ /**
29
+ * C-locale `strerror` text → errno, for the reasons git can print here.
30
+ *
31
+ * git does not print the errno name, only its message; every listing below
32
+ * runs with `LC_ALL=C` so the message is the C-locale one and this table can
33
+ * be short. An unlisted reason is still a refusal — it becomes `UNKNOWN`, the
34
+ * same answer `listingFailure` gives an error carrying no errno — because
35
+ * dropping the line would reinstate the silent gap for exactly the errnos
36
+ * nobody thought of.
37
+ *
38
+ * The two ABSENCE reasons are listed so that `listingFailure` — the one owner
39
+ * of the absent/unreadable split — can recognise them and the line can be
40
+ * skipped: git prints "No such file or directory" for an untracked directory
41
+ * deleted between its parent's `readdir` and its own `opendir` (a concurrent
42
+ * `rm -rf tmp/`), and the walk route treats that same race as "no longer in
43
+ * the population" rather than as a refusal. Left unmapped it read as an
44
+ * `UNKNOWN` refusal, and the git route aborted a run the walk completed.
45
+ */
46
+ const STRERROR_TO_ERRNO = new Map([
47
+ ['Permission denied', 'EACCES'],
48
+ ['Too many open files', 'EMFILE'],
49
+ ['Too many open files in system', 'ENFILE'],
50
+ ['Too many levels of symbolic links', 'ELOOP'],
51
+ ['Resource temporarily unavailable', 'EAGAIN'],
52
+ ['Input/output error', 'EIO'],
53
+ ['Stale file handle', 'ESTALE'],
54
+ ['Stale NFS file handle', 'ESTALE'],
55
+ ['No such file or directory', 'ENOENT'],
56
+ ['Not a directory', 'ENOTDIR'],
57
+ ]);
58
+ /**
59
+ * The directories a git listing was REFUSED, read off its stderr.
60
+ *
61
+ * Pure. Returns each refused directory once, worktree-relative and without
62
+ * git's trailing slash, with the errno its reason maps to. A directory git
63
+ * could not open because it no longer exists is not returned: that is the
64
+ * `absent` outcome of {@link listingFailure}, decided there and not here, so
65
+ * the two routes classify a vanished directory the same way.
66
+ *
67
+ * @param stderr - What the listing wrote to stderr
68
+ * @returns The refusals, in the order git printed them, deduplicated
69
+ */
70
+ export function unlistableDirectoriesIn(stderr) {
71
+ const seen = new Set();
72
+ const refusals = [];
73
+ for (const match of stderr.matchAll(UNLISTABLE_DIRECTORY_LINE)) {
74
+ const directory = match[1] ?? '';
75
+ if (directory === '' || seen.has(directory))
76
+ continue;
77
+ seen.add(directory);
78
+ const listing = listingFailure({ code: STRERROR_TO_ERRNO.get((match[2] ?? '').trim()) });
79
+ if (listing.outcome === 'unreadable')
80
+ refusals.push({ directory, code: listing.code });
81
+ }
82
+ return refusals;
83
+ }
84
+ /**
85
+ * Run one NUL-delimited `ls-files` listing and report what git skipped.
86
+ *
87
+ * `LC_ALL=C` so the stderr reason is the C-locale `strerror` text the parser
88
+ * knows; `trim: false` because the output is NUL-delimited (see the callers).
89
+ *
90
+ * @param args - The `ls-files` argv
91
+ * @param cwd - Where to run it
92
+ * @param api - The entry point, for the sentence an omitted policy gets
93
+ * @param unreadable - What a skipped directory means to the caller
94
+ * @returns The listed paths, or null when git did not answer
95
+ */
96
+ function runListing(args, cwd, api, unreadable) {
97
+ // Before the spawn: an omitted policy is a programming error, not a fact
98
+ // about the tree, and a tree with nothing unreadable must not hide it.
99
+ requireUnreadablePolicy(unreadable, api);
100
+ const result = runGit(args, { cwd, trim: false, env: { LC_ALL: 'C' } });
101
+ // Every failure means the same thing to this caller — no listing. That covers
102
+ // "not a repository" (128), git missing entirely, and a listing too large for
103
+ // the buffer, which `ok` folds in because a truncated answer here is files
104
+ // silently missing rather than a short list.
105
+ if (!result.ok) {
106
+ return null;
107
+ }
108
+ if (result.stderr !== '') {
109
+ // The warning is worktree-root-relative regardless of `cwd`.
110
+ const worktreeRoot = gitFindRoot(cwd) ?? cwd;
111
+ for (const { directory, code } of unlistableDirectoriesIn(result.stderr)) {
112
+ settleRefusal(unreadable, directoryRefusalFor({ outcome: 'unreadable', code }, safePath.resolve(worktreeRoot, directory)));
113
+ }
114
+ }
115
+ // NUL-separated (from -z), so no path can ever need quote-unescaping — a
116
+ // trailing NUL just produces one empty string at the end, dropped here.
117
+ return result.stdout.split('\0').filter((line) => line.length > 0);
118
+ }
10
119
  /**
11
120
  * Find the git repository root by walking up from the given directory.
12
121
  *
122
+ * Memoized module-wide via `git-root-cache.ts`: the walk seeds an entry for
123
+ * every directory it climbs, not just for `startDir`, because the redundancy
124
+ * this removes is overlapping walks from *different* start directories rather
125
+ * than repeated calls with the same one. A `null` answer is memoized too — that
126
+ * is the case that costs a walk to the filesystem root.
127
+ *
128
+ * The memo therefore does NOT see a `.git` directory created or removed after
129
+ * the fact. Anything that mutates repositories in-process (tests; a host that
130
+ * re-enters a command) must call `resetProjectRootCaches()`, which clears this
131
+ * cache along with the project-root one.
132
+ *
13
133
  * @param startDir - Directory to start searching from
14
134
  * @returns Path to git root, or null if not in a git repository
15
135
  */
16
136
  export function gitFindRoot(startDir) {
17
137
  let currentDir = safePath.resolve(startDir);
18
138
  const root = parse(currentDir).root;
139
+ const climbed = [];
19
140
  while (currentDir !== root) {
141
+ const memoized = lookupGitRoot(currentDir);
142
+ if (memoized !== undefined)
143
+ return rememberGitRoot(climbed, memoized);
144
+ climbed.push(currentDir);
20
145
  const gitDir = safePath.join(currentDir, '.git');
21
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- walking up from validated startDir
22
146
  if (existsSync(gitDir)) {
23
- return currentDir;
147
+ return rememberGitRoot(climbed, currentDir);
24
148
  }
25
149
  currentDir = dirname(currentDir);
26
150
  }
27
- return null;
151
+ return rememberGitRoot(climbed, null);
28
152
  }
29
153
  /**
30
154
  * List files tracked by git, optionally filtered by patterns.
@@ -33,6 +157,8 @@ export function gitFindRoot(startDir) {
33
157
  * @param options.cwd - Working directory (git repository root or subdirectory)
34
158
  * @param options.patterns - Optional glob patterns to filter files (e.g., '*.md', 'docs/**\/*.ts')
35
159
  * @param options.includeUntracked - Include untracked files that aren't gitignored (default: false)
160
+ * @param options.unreadable - See {@link GitListingOptions}. Only the untracked
161
+ * listing walks the working tree, so only it can have skipped a directory
36
162
  * @returns Array of file paths relative to the git root, or null if not in a git repo
37
163
  *
38
164
  * @example
@@ -45,41 +171,79 @@ export function gitFindRoot(startDir) {
45
171
  * ```
46
172
  */
47
173
  export function gitLsFiles(options) {
48
- try {
49
- // Resolve git path using which for security (avoids PATH manipulation)
50
- const gitPath = which.sync('git');
51
- const args = ['ls-files'];
52
- // Include untracked files that aren't gitignored
53
- if (options.includeUntracked) {
54
- args.push('--cached', '--others', '--exclude-standard');
55
- }
56
- // Add patterns if provided
57
- if (options.patterns && options.patterns.length > 0) {
58
- args.push('--', ...options.patterns);
59
- }
60
- const result = spawnSync(gitPath, args, {
61
- cwd: options.cwd,
62
- encoding: 'utf-8',
63
- stdio: 'pipe',
64
- shell: false, // No shell interpreter for security
65
- });
66
- // Exit code 128 typically means not a git repository
67
- if (result.status === 128 || result.error) {
68
- return null;
69
- }
70
- if (result.status !== 0) {
71
- return null;
72
- }
73
- // Parse output into array of file paths
74
- return result.stdout
75
- .split('\n')
76
- .map((line) => line.trim())
77
- .filter((line) => line.length > 0);
174
+ // -z emits NUL-separated, UNQUOTED paths regardless of byte content. Without
175
+ // it, git quotes any path containing non-ASCII bytes (wraps it in double
176
+ // quotes with octal escapes, e.g. `café.md` -> `"caf\303\251.md"`), which is
177
+ // unusable to any exact-string lookup against the real filename.
178
+ const args = ['ls-files', '-z'];
179
+ // Include untracked files that aren't gitignored
180
+ if (options.includeUntracked) {
181
+ args.push('--cached', '--others', '--exclude-standard');
78
182
  }
79
- catch {
80
- // Git not available or other error
81
- return null;
183
+ // Add patterns if provided
184
+ if (options.patterns && options.patterns.length > 0) {
185
+ args.push('--', ...options.patterns);
186
+ }
187
+ // `trim: false` (inside `runListing`) because the output is NUL-delimited:
188
+ // git sorts by byte value, so a path beginning with a space sorts FIRST and a
189
+ // trim would silently rename it to a path that does not exist.
190
+ return runListing(args, options.cwd, 'gitLsFiles', options.unreadable);
191
+ }
192
+ /**
193
+ * List paths git does NOT track, with each wholly-untracked directory collapsed
194
+ * to a single entry.
195
+ *
196
+ * This is the **prune list**, not a file list, and the distinction is the whole
197
+ * value. `--others --ignored --exclude-standard` alone returns every ignored file
198
+ * individually: measured on an 8,496-path adopter working tree that is 533,557
199
+ * paths in 1.19 s — worse than the crawl it was meant to replace, because
200
+ * `.turbo/cache` alone contributed 418,518 of them. Adding `--directory`
201
+ * collapses each wholly-ignored directory to one entry: **369 entries in 60 ms**.
202
+ * A caller can then decide per directory whether to descend, and skip a
203
+ * half-million paths by name without ever entering them.
204
+ *
205
+ * A collapsed directory entry is returned with a trailing `/`, exactly as git
206
+ * spells it. That is how a caller tells "this whole subtree" from "this one
207
+ * file", so it is deliberately not normalized away here.
208
+ *
209
+ * @param options - Configuration options
210
+ * @param options.cwd - Working directory inside the repository
211
+ * @param options.ignored - Restrict to ignored paths. Off, the listing is
212
+ * untracked-but-not-ignored paths — which is the only way to see an EMPTY
213
+ * untracked directory, since a directory with no files in it is invisible to
214
+ * `ls-files` and to any tree object
215
+ * @param options.directory - Collapse a wholly-untracked directory to one entry
216
+ * @param options.unreadable - See {@link GitListingOptions}. ⚠️ The two
217
+ * listings meet different refusals: without `--ignored`, git collapses a
218
+ * WHOLLY-untracked directory without opening it, but it still opens a
219
+ * non-ignored directory that holds tracked files (to look for untracked
220
+ * siblings) and warns when it cannot; the `ignored` listing opens every
221
+ * non-ignored directory and so meets every refusal the first one does and
222
+ * more (measured on git 2.50)
223
+ * @returns Paths relative to the git root, or null if git did not answer
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * // The prune list: where the ignored territory is, without enumerating it.
228
+ * const prune = gitLsOthers({ cwd: root, ignored: true, directory: true });
229
+ * // → ['dist/', 'node_modules/', '.turbo/', 'notes.local.md']
230
+ * ```
231
+ */
232
+ export function gitLsOthers(options) {
233
+ // `--exclude-standard` is unconditional: `--ignored` is rejected outright by
234
+ // git without an exclude source, and without it the not-ignored listing would
235
+ // report every ignored path as merely untracked — the exact inversion of what
236
+ // either caller asked for.
237
+ const args = ['ls-files', '-z', '--others', '--exclude-standard'];
238
+ if (options.ignored) {
239
+ args.push('--ignored');
240
+ }
241
+ if (options.directory) {
242
+ args.push('--directory');
82
243
  }
244
+ // See `gitLsFiles`: NUL-delimited output must not be trimmed, or a path
245
+ // beginning with a space — which git sorts FIRST — comes back renamed.
246
+ return runListing(args, options.cwd, 'gitLsOthers', options.unreadable);
83
247
  }
84
248
  /**
85
249
  * Check if a file path is ignored by git
@@ -117,51 +281,33 @@ export function isGitIgnored(filePath, cwd = process.cwd()) {
117
281
  if (gitFindRoot(cwd) === null) {
118
282
  return false;
119
283
  }
120
- try {
121
- // Resolve git path using which for security (avoids PATH manipulation)
122
- const gitPath = which.sync('git');
123
- const checkIgnoreArgs = ['check-ignore', '-q'];
124
- // git check-ignore returns exit code 0 if file is ignored, 1 if not
125
- const result = spawnSync(gitPath, [...checkIgnoreArgs, filePath], {
126
- cwd,
127
- encoding: 'utf-8',
128
- stdio: 'pipe',
129
- shell: false, // No shell interpreter for security
130
- });
131
- if (result.status === 0) {
132
- return true;
133
- }
134
- // Exit code 128 = fatal error (e.g., path beyond a symbolic link).
135
- // Walk up ancestor directories to check if a parent is gitignored.
136
- // Example: data/ is gitignored, data/symlink/deep/file.md fails with 128,
137
- // but checking data/ directly returns 0.
138
- if (result.status !== 1) {
139
- const resolvedCwd = safePath.resolve(cwd);
140
- const resolvedFile = safePath.resolve(cwd, filePath);
141
- let current = dirname(resolvedFile);
142
- while (current !== resolvedCwd && !current.endsWith(parse(current).root)) {
143
- const ancestorResult = spawnSync(gitPath, [...checkIgnoreArgs, current], {
144
- cwd,
145
- encoding: 'utf-8',
146
- stdio: 'pipe',
147
- shell: false,
148
- });
149
- if (ancestorResult.status === 0) {
150
- return true;
151
- }
152
- // If this ancestor check also fails fatally, keep walking up
153
- // If it returns 1 (not ignored), the parent is tracked — stop walking
154
- if (ancestorResult.status === 1) {
155
- break;
156
- }
157
- current = dirname(current);
284
+ const checkIgnoreArgs = ['check-ignore', '-q'];
285
+ // git check-ignore returns exit code 0 if file is ignored, 1 if not
286
+ const result = runGit([...checkIgnoreArgs, filePath], { cwd });
287
+ if (result.status === 0) {
288
+ return true;
289
+ }
290
+ // Exit code 128 = fatal error (e.g., path beyond a symbolic link).
291
+ // Walk up ancestor directories to check if a parent is gitignored.
292
+ // Example: data/ is gitignored, data/symlink/deep/file.md fails with 128,
293
+ // but checking data/ directly returns 0.
294
+ if (result.status !== 1) {
295
+ const resolvedCwd = safePath.resolve(cwd);
296
+ const resolvedFile = safePath.resolve(cwd, filePath);
297
+ let current = dirname(resolvedFile);
298
+ while (current !== resolvedCwd && !current.endsWith(parse(current).root)) {
299
+ const ancestorResult = runGit([...checkIgnoreArgs, current], { cwd });
300
+ if (ancestorResult.status === 0) {
301
+ return true;
302
+ }
303
+ // If this ancestor check also fails fatally, keep walking up
304
+ // If it returns 1 (not ignored), the parent is tracked — stop walking
305
+ if (ancestorResult.status === 1) {
306
+ break;
158
307
  }
308
+ current = dirname(current);
159
309
  }
160
- return false;
161
- }
162
- catch {
163
- // If git is not available or other error, assume not ignored
164
- return false;
165
310
  }
311
+ return false;
166
312
  }
167
313
  //# sourceMappingURL=git-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-utils.js","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git-utils.js","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"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"}
package/dist/git.d.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * @vibe-agent-toolkit/utils/git
3
3
  *
4
- * Git primitives for build guards and gates: root discovery, tracked-file
5
- * enumeration, gitignore evaluation, and git URL parsing. Node-only —
6
- * shells out to `git` and reads the filesystem.
4
+ * Git primitives for build guards and gates: command execution, root discovery,
5
+ * tracked-file enumeration, tree snapshots, gitignore evaluation, and git URL
6
+ * parsing. Node-only — shells out to `git` and reads the filesystem.
7
+ *
8
+ * This is the ONLY published route to `runGit`. `safeExecSync`/`safeExecResult`
9
+ * on `./process` refuse the `git` binary outright and point here, so a caller
10
+ * that wants git gets the scrubbed environment rather than the ambient one by
11
+ * construction. The scrub itself, and dirty-corrected tree snapshots, come from
12
+ * `@vibe-validate/git`.
7
13
  *
8
14
  * The re-exports below are explicit rather than `export *` on purpose. A
9
15
  * blanket re-export surfaced TWO root finders under two names — `gitFindRoot`
@@ -16,7 +22,10 @@
16
22
  * old code rather than maintain two APIs). `gitFindRoot` is the only root
17
23
  * finder in the package.
18
24
  */
19
- export { gitFindRoot, gitLsFiles, isGitIgnored } from './git-utils.js';
25
+ export { runGit, runGitOrThrow, type GitRunOptions, type GitRunResult, } from './git-run.js';
26
+ export { gitFindRoot, gitLsFiles, gitLsOthers, isGitIgnored } from './git-utils.js';
27
+ export type { RefuseListingContext, UnreadablePolicy } from './listing-refusal.js';
28
+ export { freshGitTreeSnapshot, gitTreeSnapshot, peekGitTreeSnapshot, withGitSnapshotCache, type GitSnapshotEntry, type GitTreeSnapshot, } from './git-snapshot.js';
20
29
  export { loadGitignoreRules } from './gitignore-checker.js';
21
30
  export { GitTracker, type GitTrackerInitOptions } from './git-tracker.js';
22
31
  export { isGitUrl, nonInteractiveGitOverrides, parseGitUrl, type NonInteractiveGitOverrides, type ParsedGitUrl, } from './git-url.js';
package/dist/git.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,EACX,KAAK,0BAA0B,EAC/B,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,MAAM,EACN,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAIpF,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,EACX,KAAK,0BAA0B,EAC/B,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
package/dist/git.js CHANGED
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * @vibe-agent-toolkit/utils/git
3
3
  *
4
- * Git primitives for build guards and gates: root discovery, tracked-file
5
- * enumeration, gitignore evaluation, and git URL parsing. Node-only —
6
- * shells out to `git` and reads the filesystem.
4
+ * Git primitives for build guards and gates: command execution, root discovery,
5
+ * tracked-file enumeration, tree snapshots, gitignore evaluation, and git URL
6
+ * parsing. Node-only — shells out to `git` and reads the filesystem.
7
+ *
8
+ * This is the ONLY published route to `runGit`. `safeExecSync`/`safeExecResult`
9
+ * on `./process` refuse the `git` binary outright and point here, so a caller
10
+ * that wants git gets the scrubbed environment rather than the ambient one by
11
+ * construction. The scrub itself, and dirty-corrected tree snapshots, come from
12
+ * `@vibe-validate/git`.
7
13
  *
8
14
  * The re-exports below are explicit rather than `export *` on purpose. A
9
15
  * blanket re-export surfaced TWO root finders under two names — `gitFindRoot`
@@ -16,7 +22,9 @@
16
22
  * old code rather than maintain two APIs). `gitFindRoot` is the only root
17
23
  * finder in the package.
18
24
  */
19
- export { gitFindRoot, gitLsFiles, isGitIgnored } from './git-utils.js';
25
+ export { runGit, runGitOrThrow, } from './git-run.js';
26
+ export { gitFindRoot, gitLsFiles, gitLsOthers, isGitIgnored } from './git-utils.js';
27
+ export { freshGitTreeSnapshot, gitTreeSnapshot, peekGitTreeSnapshot, withGitSnapshotCache, } from './git-snapshot.js';
20
28
  export { loadGitignoreRules } from './gitignore-checker.js';
21
29
  export { GitTracker } from './git-tracker.js';
22
30
  export { isGitUrl, nonInteractiveGitOverrides, parseGitUrl, } from './git-url.js';
package/dist/git.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,GAGZ,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,MAAM,EACN,aAAa,GAGd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKpF,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,GAGrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,GAGZ,MAAM,cAAc,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"gitignore-checker.d.ts","sourceRoot":"","sources":["../src/gitignore-checker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAe,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAI7C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwCnF"}
1
+ {"version":3,"file":"gitignore-checker.d.ts","sourceRoot":"","sources":["../src/gitignore-checker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAe,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAM7C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8CnF"}
@@ -8,7 +8,9 @@
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import ignore from 'ignore';
11
+ import { isPathAbsentError } from './errors/errno.js';
11
12
  import { safePath, toForwardSlash } from './path-utils.js';
13
+ import { readTextContentSync } from './text-file.js';
12
14
  /**
13
15
  * Load and parse .gitignore files from git root to baseDir.
14
16
  * Returns an ignore instance configured with all applicable .gitignore rules.
@@ -39,15 +41,22 @@ export function loadGitignoreRules(gitRoot, baseDir) {
39
41
  // Load .gitignore files from git root down to baseDir
40
42
  for (const dir of dirsToCheck) {
41
43
  const gitignorePath = safePath.join(dir, '.gitignore');
42
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- constructed from validated gitRoot and baseDir
43
44
  if (fs.existsSync(gitignorePath)) {
44
45
  try {
45
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- validated above
46
- const content = fs.readFileSync(gitignorePath, 'utf-8');
47
- ig.add(content);
46
+ // Through the decoding seam. A `.gitignore` written by PowerShell's `>`
47
+ // is UTF-16LE, and `readFileSync(p, 'utf-8')` would hand `ignore` a
48
+ // string of NUL-interleaved garbage — every pattern silently wrong, with
49
+ // no error anywhere.
50
+ ig.add(readTextContentSync(gitignorePath).text);
48
51
  }
49
- catch {
50
- // Skip gitignore files we can't read
52
+ catch (error) {
53
+ // Gone between `existsSync` and the read: no rules here. A `.gitignore`
54
+ // the OS refuses to read (`EACCES`), or one that is a directory
55
+ // (`EISDIR`), holds rules this checker cannot honour — and a crawl that
56
+ // quietly proceeded without them enumerated the ignored tree with
57
+ // nothing anywhere saying so. Those stay loud.
58
+ if (!isPathAbsentError(error))
59
+ throw error;
51
60
  }
52
61
  }
53
62
  }