@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
@@ -56,6 +56,33 @@ export declare class GitTracker {
56
56
  private readonly activeSet;
57
57
  /** Absolute paths of every directory that contains at least one active-set file. */
58
58
  private readonly activeAncestors;
59
+ /**
60
+ * Lowercased absolute path → the root-relative path spelled the way git
61
+ * spelled it. Built from the very same `git ls-files` output `activeSet`
62
+ * comes from, so it costs one extra `Map` and no extra git invocation.
63
+ */
64
+ private readonly indexPaths;
65
+ /**
66
+ * Absolute, forward-slashed directories the active-set listing could not
67
+ * OPEN, so nothing beneath them is in the set — read off `git ls-files
68
+ * --others`' stderr through the listing's `unreadable` policy (`degrade`).
69
+ *
70
+ * 🚨 Beneath one of these the active set has no opinion. Its rule is "absent
71
+ * from the set and present on disk ⇒ ignored", and that rule is sound only
72
+ * where the listing LOOKED: an untracked, non-ignored file under a `--x`
73
+ * directory is absent because git could not read the directory, not because
74
+ * a pattern excludes it, and `git check-ignore` — which answers from the
75
+ * patterns and needs no listing — says so. Before this was recorded the
76
+ * tracker called such a file gitignored, silently, and every consumer of
77
+ * that answer (the leak judge, the link-graph walker, audit's distributed
78
+ * lane) was one refused `opendir` from a wrong verdict.
79
+ *
80
+ * The directory ITSELF is non-ignored by construction — git opened it
81
+ * precisely because no pattern excluded it — so it and its ancestors are
82
+ * entered into {@link activeAncestors}: a walker must not prune what the
83
+ * listing could not see into.
84
+ */
85
+ private readonly unlistableDirectories;
59
86
  private initialized;
60
87
  private activeSetPopulated;
61
88
  /** Whether `git ls-files` actually answered during {@link initialize}. */
@@ -72,8 +99,60 @@ export declare class GitTracker {
72
99
  * With `includeUntracked: false`, only tracked files are pre-populated.
73
100
  * Untracked non-ignored files will miss the cache and fall through to
74
101
  * `git check-ignore` via {@link isIgnored}.
102
+ *
103
+ * ## This is the one bracket in the crawl-timing seam's `shared` stratum
104
+ *
105
+ * The `git ls-files` spawn below is preparation BOTH crawlers consume and
106
+ * NEITHER owns — the incumbent link walk and the projection's contributors are
107
+ * each handed a tracker by their caller — so it is charged to
108
+ * {@link CRAWL_SHARED_GIT_TRACKER_ID}, in a stratum belonging to no arm.
109
+ *
110
+ * The bracket is here, inside the class, and not at the six sites that build a
111
+ * tracker, for the reason `crawl-timing.ts` gives about `ResourceRegistry`: six
112
+ * copies are six chances to disagree, and a seventh site added later would
113
+ * silently go uncharged. Here, every caller is covered by construction — which
114
+ * includes `@vibe-agent-toolkit/discovery`, a package that could not have filed
115
+ * a row from its own call site at all, since it depends on `utils` alone.
116
+ *
117
+ * The early return above is deliberately OUTSIDE it: a re-entrant call does no
118
+ * work, and charging it would inflate `calls` with questions rather than
119
+ * spawns.
75
120
  */
76
121
  initialize(options?: GitTrackerInitOptions): Promise<void>;
122
+ /**
123
+ * The active set read off a snapshot this process has ALREADY taken — or
124
+ * `null` to say "ask git yourself".
125
+ *
126
+ * ## Why this is the same question, not a similar one
127
+ *
128
+ * `git ls-files --cached --others --exclude-standard` returns
129
+ * `tracked ∪ (untracked ∧ ¬ignored)`. A snapshot is `git add --all` **without**
130
+ * `--force` staged into a throwaway index, whose membership is that same set
131
+ * by construction — the exclusion of ignored paths is `--force`'s absence in
132
+ * both cases. So where a snapshot is already in hand, the spawn this method
133
+ * replaces would re-derive a set the process is already holding.
134
+ *
135
+ * It is deliberately a PEEK ({@link peekGitTreeSnapshot}) and never a take: a
136
+ * snapshot costs a `git add --all`, so causing one to avoid an `ls-files`
137
+ * would be a large loss dressed as a saving. On the incumbent walk no snapshot
138
+ * is ever taken, the peek misses, and this tracker spawns exactly as it always
139
+ * has.
140
+ *
141
+ * ## The one divergence, and why it is an improvement
142
+ *
143
+ * `--cached` reads the REAL index, so a tracked file deleted from the working
144
+ * tree is still listed. `git add --all` stages that deletion, so the snapshot
145
+ * omits it. The set is therefore not identical — it is the same set minus
146
+ * paths that do not exist, which is what {@link isIgnoredByActiveSet} already
147
+ * documents the active set to be ("it can only ever contain paths that
148
+ * EXIST"). The snapshot-sourced set honours that sentence more exactly than
149
+ * the spawn does.
150
+ *
151
+ * @param includeUntracked - The caller's requested membership
152
+ * @returns Root-relative paths in `git ls-files` shape, or `null` when no
153
+ * snapshot is available or the request is one a snapshot cannot answer
154
+ */
155
+ private activePathsFromOpenSnapshot;
77
156
  /**
78
157
  * Did git actually answer, or is this tracker an empty shell?
79
158
  *
@@ -98,6 +177,22 @@ export declare class GitTracker {
98
177
  * `hasActiveDescendant` / `isIgnoredByActiveSet` ancestor lookup misses on Windows.
99
178
  */
100
179
  private populateAncestorSet;
180
+ /**
181
+ * Record `start` and every directory above it up to (not including) the
182
+ * project root as active ancestors, stopping at the first one already known
183
+ * — everything above it necessarily is too.
184
+ *
185
+ * @param start - Forward-slashed absolute directory to begin at
186
+ */
187
+ private recordAncestorsFrom;
188
+ /**
189
+ * Whether the active set can say anything about a path: it cannot beneath a
190
+ * directory the listing was refused — see {@link unlistableDirectories}.
191
+ *
192
+ * @param normalizedAbsolutePath - Forward-slashed absolute path
193
+ * @returns True when the path lies strictly beneath a refused directory
194
+ */
195
+ private beneathUnlistable;
101
196
  /**
102
197
  * Returns true if the given absolute path IS an active-set file OR is an
103
198
  * ancestor directory of at least one active-set file.
@@ -117,7 +212,9 @@ export declare class GitTracker {
117
212
  * For paths INSIDE the project root **that exist on disk**, membership in the
118
213
  * active set is authoritative: such a path is ignored iff it is not in the
119
214
  * active set AND not an ancestor of any active-set path. No `git
120
- * check-ignore` spawn.
215
+ * check-ignore` spawn. The one exception is a path beneath a directory the
216
+ * listing could not OPEN — see {@link unlistableDirectories} — where absence
217
+ * says nothing and the question goes to `git check-ignore`.
121
218
  *
122
219
  * The existence qualifier is load-bearing, not a caveat. The active set is
123
220
  * built from `git ls-files`, so it can only ever contain paths that EXIST — a
@@ -131,7 +228,38 @@ export declare class GitTracker {
131
228
  * so a repeated miss on the same path stays O(1).
132
229
  *
133
230
  * For paths OUTSIDE the project root, falls back to {@link isIgnored} so
134
- * legacy behavior is preserved.
231
+ * legacy behavior is preserved. That fallback is the expensive one, and its
232
+ * cost does not resemble the in-root cost at all.
233
+ *
234
+ * ## ⚠️ The out-of-root fallback costs an order of magnitude more per path
235
+ *
236
+ * Measured in the D9 parity fixture: an out-of-root path costs
237
+ * **185–427 ms**, against **12–28 ms** for every in-repo path — ≈7–36× on
238
+ * those ranges.
239
+ *
240
+ * The mechanism is the delegation above plus what git does with it.
241
+ * {@link isIgnored} runs `git check-ignore` with the PROJECT ROOT as cwd; git
242
+ * exits 128 for a path outside that repository; and {@link isGitIgnored}'s
243
+ * exit-128 recovery walk then spawns `check-ignore` ONCE PER ANCESTOR up to
244
+ * `/`. No ancestor of an out-of-root path is ever the project root that would
245
+ * stop the walk, and every one of those ancestors is outside the repository
246
+ * too — so each also exits 128 and the walk never breaks early either. The
247
+ * verdict is cached, so the price is paid once per DISTINCT out-of-root path
248
+ * rather than once per call.
249
+ *
250
+ * The consequence for a caller choosing between the two oracles: switching
251
+ * from {@link isIgnored} to this method removes the spawn population **only
252
+ * for in-root paths**. A lane feeding out-of-root paths in bulk keeps the
253
+ * per-path spawn in full. So a comment asserting that the out-of-project case
254
+ * is rare, safe or cheap is asserting something this measurement contradicts:
255
+ * it is rare in some lanes, and it is never cheap.
256
+ *
257
+ * The exit-128 walk itself is documented in {@link isGitIgnored} — but only
258
+ * for the two cases already answered there: the SYMLINK case it exists to
259
+ * serve, and the NO-REPOSITORY case, which an early return now short-circuits
260
+ * before any spawn. The IN-REPO OUT-OF-ROOT path is the third case, it is
261
+ * documented nowhere there, and it is the one that still pays the walk in
262
+ * full. That is why the number lives here.
135
263
  *
136
264
  * Requires {@link initialize} with `includeUntracked: true` (the default).
137
265
  * When initialized without untracked files, this method delegates to
@@ -139,8 +267,21 @@ export declare class GitTracker {
139
267
  * possible per-path spawn.
140
268
  *
141
269
  * @param absolutePath - Absolute path to check
270
+ * @param knownToExist - The caller's own answer to the existence question, when
271
+ * it has already asked. Supplying it skips this method's `existsSync`, which
272
+ * is otherwise paid once per path that is absent from the active set — i.e.
273
+ * once per ignored path, and the projection's `filesystem` extent enumerates
274
+ * all of them (11,108 calls on an 8,496-path adopter tree).
275
+ *
276
+ * **It must mean what `existsSync` means: `stat` succeeds, following
277
+ * symlinks.** A caller holding only an `lstat` result has a DIFFERENT fact —
278
+ * `lstat` succeeds on a dangling symlink where `existsSync` returns false —
279
+ * and must narrow it to `exists && symlinkResolves !== false` rather than
280
+ * pass the `lstat` boolean through, or dangling symlinks silently stop
281
+ * falling back to `git check-ignore` and start reporting as ignored.
282
+ * Omit it and nothing changes.
142
283
  */
143
- isIgnoredByActiveSet(absolutePath: string): boolean;
284
+ isIgnoredByActiveSet(absolutePath: string, knownToExist?: boolean): boolean;
144
285
  private isWithinProjectRoot;
145
286
  /**
146
287
  * Check if a file is ignored by git.
@@ -151,6 +292,28 @@ export declare class GitTracker {
151
292
  * @returns true if file is gitignored, false otherwise
152
293
  */
153
294
  isIgnored(filePath: string): boolean;
295
+ /**
296
+ * The spelling git records for a path, or `null` if git has no record of it.
297
+ *
298
+ * This is the casing oracle, not another ignore check. On a case-insensitive
299
+ * filesystem `docs/Readme.md` and `docs/README.md` are one inode with two
300
+ * spellings, and Node's two `realpath` implementations disagree about which
301
+ * one they hand back — so anything that derives an identity from a path needs
302
+ * a single authoritative spelling, and git's is it wherever git has one.
303
+ *
304
+ * The lookup key is lowercased, which is the point: the caller asks with
305
+ * whatever casing it observed and gets back the casing git holds.
306
+ *
307
+ * Answers only from the pre-populated set — never spawns. A path git does not
308
+ * know (untracked-and-ignored, non-existent, outside the project root, or any
309
+ * path at all when `git ls-files` did not answer) returns `null`, and the
310
+ * caller falls back to the on-disk casing.
311
+ *
312
+ * @param absolutePath - Absolute path to look up
313
+ * @returns Root-relative, forward-slashed path as git spells it — relative to
314
+ * THIS tracker's project root — or `null` when git has no record of it
315
+ */
316
+ indexPathFor(absolutePath: string): string | null;
154
317
  /**
155
318
  * Get cache statistics.
156
319
  */
@@ -1 +1 @@
1
- {"version":3,"file":"git-tracker.d.ts","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0B;IAC1D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAS;IACnC,0EAA0E;IAC1E,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM;IAK/B;;;;;;;;;;;OAWG;IACG,UAAU,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BhE;;;;;;;;;;;;;OAaG;IACH,QAAQ,IAAI,OAAO;IAInB;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAQlD;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IA0BnD,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAqBpC;;OAEG;IACH,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;IAQrF;;OAEG;IACH,KAAK,IAAI,IAAI;CAQd"}
1
+ {"version":3,"file":"git-tracker.d.ts","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0B;IAC1D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAgB;IACtD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAS;IACnC,0EAA0E;IAC1E,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM;IAK/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,UAAU,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0ChE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,OAAO,CAAC,2BAA2B;IA6BnC;;;;;;;;;;;;;OAaG;IACH,QAAQ,IAAI,OAAO;IAInB;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAe3B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAUlD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0EG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO;IAiC3E,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAqBpC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIjD;;OAEG;IACH,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;IAQrF;;OAEG;IACH,KAAK,IAAI,IAAI;CAUd"}
@@ -6,6 +6,8 @@
6
6
  */
7
7
  import { existsSync } from 'node:fs';
8
8
  import { dirname } from 'node:path';
9
+ import { CRAWL_SHARED_GIT_TRACKER_ID, crawlTimingStart, recordSharedPass, } from './crawl-timing.js';
10
+ import { peekGitTreeSnapshot } from './git-snapshot.js';
9
11
  import { gitLsFiles, isGitIgnored } from './git-utils.js';
10
12
  import { safePath, toForwardSlash } from './path-utils.js';
11
13
  /**
@@ -42,6 +44,33 @@ export class GitTracker {
42
44
  activeSet = new Set();
43
45
  /** Absolute paths of every directory that contains at least one active-set file. */
44
46
  activeAncestors = new Set();
47
+ /**
48
+ * Lowercased absolute path → the root-relative path spelled the way git
49
+ * spelled it. Built from the very same `git ls-files` output `activeSet`
50
+ * comes from, so it costs one extra `Map` and no extra git invocation.
51
+ */
52
+ indexPaths = new Map();
53
+ /**
54
+ * Absolute, forward-slashed directories the active-set listing could not
55
+ * OPEN, so nothing beneath them is in the set — read off `git ls-files
56
+ * --others`' stderr through the listing's `unreadable` policy (`degrade`).
57
+ *
58
+ * 🚨 Beneath one of these the active set has no opinion. Its rule is "absent
59
+ * from the set and present on disk ⇒ ignored", and that rule is sound only
60
+ * where the listing LOOKED: an untracked, non-ignored file under a `--x`
61
+ * directory is absent because git could not read the directory, not because
62
+ * a pattern excludes it, and `git check-ignore` — which answers from the
63
+ * patterns and needs no listing — says so. Before this was recorded the
64
+ * tracker called such a file gitignored, silently, and every consumer of
65
+ * that answer (the leak judge, the link-graph walker, audit's distributed
66
+ * lane) was one refused `opendir` from a wrong verdict.
67
+ *
68
+ * The directory ITSELF is non-ignored by construction — git opened it
69
+ * precisely because no pattern excluded it — so it and its ancestors are
70
+ * entered into {@link activeAncestors}: a walker must not prune what the
71
+ * listing could not see into.
72
+ */
73
+ unlistableDirectories = [];
45
74
  initialized = false;
46
75
  activeSetPopulated = false;
47
76
  /** Whether `git ls-files` actually answered during {@link initialize}. */
@@ -61,27 +90,121 @@ export class GitTracker {
61
90
  * With `includeUntracked: false`, only tracked files are pre-populated.
62
91
  * Untracked non-ignored files will miss the cache and fall through to
63
92
  * `git check-ignore` via {@link isIgnored}.
93
+ *
94
+ * ## This is the one bracket in the crawl-timing seam's `shared` stratum
95
+ *
96
+ * The `git ls-files` spawn below is preparation BOTH crawlers consume and
97
+ * NEITHER owns — the incumbent link walk and the projection's contributors are
98
+ * each handed a tracker by their caller — so it is charged to
99
+ * {@link CRAWL_SHARED_GIT_TRACKER_ID}, in a stratum belonging to no arm.
100
+ *
101
+ * The bracket is here, inside the class, and not at the six sites that build a
102
+ * tracker, for the reason `crawl-timing.ts` gives about `ResourceRegistry`: six
103
+ * copies are six chances to disagree, and a seventh site added later would
104
+ * silently go uncharged. Here, every caller is covered by construction — which
105
+ * includes `@vibe-agent-toolkit/discovery`, a package that could not have filed
106
+ * a row from its own call site at all, since it depends on `utils` alone.
107
+ *
108
+ * The early return above is deliberately OUTSIDE it: a re-entrant call does no
109
+ * work, and charging it would inflate `calls` with questions rather than
110
+ * spawns.
64
111
  */
65
112
  async initialize(options) {
66
113
  if (this.initialized) {
67
114
  return;
68
115
  }
116
+ const startedAt = crawlTimingStart();
69
117
  const includeUntracked = options?.includeUntracked ?? true;
70
- const files = gitLsFiles({
118
+ const files = this.activePathsFromOpenSnapshot(includeUntracked) ?? gitLsFiles({
71
119
  cwd: this.projectRoot,
72
120
  ...(includeUntracked ? { includeUntracked: true } : {}),
121
+ // Degrade, and RECORD: a directory git could not open is a shorter list
122
+ // nothing can tell from a complete one — see {@link unlistableDirectories}
123
+ // for how the tracker keeps that gap honest. Only the untracked listing
124
+ // walks the tree, so the tracked-only one never calls this.
125
+ unreadable: {
126
+ degrade: (refusal) => {
127
+ this.unlistableDirectories.push(refusal.directory);
128
+ },
129
+ },
73
130
  });
74
131
  if (files !== null) {
75
132
  for (const relativePath of files) {
76
133
  const absolutePath = safePath.resolve(this.projectRoot, relativePath);
77
134
  this.cache.set(absolutePath, false); // false = not ignored
78
135
  this.activeSet.add(absolutePath);
136
+ this.indexPaths.set(absolutePath.toLowerCase(), toForwardSlash(relativePath));
79
137
  }
80
138
  this.populateAncestorSet();
81
139
  }
82
140
  this.gitAnswered = files !== null;
83
141
  this.activeSetPopulated = includeUntracked && files !== null;
84
142
  this.initialized = true;
143
+ // After the state above is settled, so a throw from the seam could never
144
+ // leave a half-initialized tracker; and charged even when git did not answer,
145
+ // because a failed `git ls-files` still spawned a process and still cost the
146
+ // command the time it took to fail.
147
+ recordSharedPass(CRAWL_SHARED_GIT_TRACKER_ID, startedAt);
148
+ }
149
+ /**
150
+ * The active set read off a snapshot this process has ALREADY taken — or
151
+ * `null` to say "ask git yourself".
152
+ *
153
+ * ## Why this is the same question, not a similar one
154
+ *
155
+ * `git ls-files --cached --others --exclude-standard` returns
156
+ * `tracked ∪ (untracked ∧ ¬ignored)`. A snapshot is `git add --all` **without**
157
+ * `--force` staged into a throwaway index, whose membership is that same set
158
+ * by construction — the exclusion of ignored paths is `--force`'s absence in
159
+ * both cases. So where a snapshot is already in hand, the spawn this method
160
+ * replaces would re-derive a set the process is already holding.
161
+ *
162
+ * It is deliberately a PEEK ({@link peekGitTreeSnapshot}) and never a take: a
163
+ * snapshot costs a `git add --all`, so causing one to avoid an `ls-files`
164
+ * would be a large loss dressed as a saving. On the incumbent walk no snapshot
165
+ * is ever taken, the peek misses, and this tracker spawns exactly as it always
166
+ * has.
167
+ *
168
+ * ## The one divergence, and why it is an improvement
169
+ *
170
+ * `--cached` reads the REAL index, so a tracked file deleted from the working
171
+ * tree is still listed. `git add --all` stages that deletion, so the snapshot
172
+ * omits it. The set is therefore not identical — it is the same set minus
173
+ * paths that do not exist, which is what {@link isIgnoredByActiveSet} already
174
+ * documents the active set to be ("it can only ever contain paths that
175
+ * EXIST"). The snapshot-sourced set honours that sentence more exactly than
176
+ * the spawn does.
177
+ *
178
+ * @param includeUntracked - The caller's requested membership
179
+ * @returns Root-relative paths in `git ls-files` shape, or `null` when no
180
+ * snapshot is available or the request is one a snapshot cannot answer
181
+ */
182
+ activePathsFromOpenSnapshot(includeUntracked) {
183
+ // A snapshot cannot express the tracked-ONLY set: `add --all` stages
184
+ // untracked-not-ignored files too, and nothing in the result marks which
185
+ // entries were already tracked. Declining is the only correct answer.
186
+ if (!includeUntracked)
187
+ return null;
188
+ const snapshot = peekGitTreeSnapshot(this.projectRoot);
189
+ if (snapshot === undefined)
190
+ return null;
191
+ const relativePaths = [];
192
+ for (const entry of snapshot.entries) {
193
+ // A snapshot covers the whole REPOSITORY, which may be an ancestor of this
194
+ // tracker's root — `git ls-files` run at that root scopes its listing to
195
+ // it, so anything above is not this tracker's business and must not enter
196
+ // the active set. Everything below is kept verbatim, symlinks and
197
+ // submodule gitlinks included, because `--cached` lists those too.
198
+ const relativePath = toForwardSlash(safePath.relative(this.normalizedProjectRoot, entry.absolutePath));
199
+ // A leading `..` SEGMENT, not a `../` prefix: compared as a path segment,
200
+ // a file legitimately named `..hidden` is kept rather than silently
201
+ // dropped as if it were an escape.
202
+ if (relativePath.length === 0 || relativePath.split('/')[0] === '..') {
203
+ continue;
204
+ }
205
+ relativePaths.push(relativePath);
206
+ }
207
+ return relativePaths;
85
208
  }
86
209
  /**
87
210
  * Did git actually answer, or is this tracker an empty shell?
@@ -109,24 +232,49 @@ export class GitTracker {
109
232
  * `hasActiveDescendant` / `isIgnoredByActiveSet` ancestor lookup misses on Windows.
110
233
  */
111
234
  populateAncestorSet() {
112
- const root = this.normalizedProjectRoot;
113
235
  for (const absolutePath of this.activeSet) {
114
- let current = toForwardSlash(dirname(absolutePath));
115
- while (current !== root && current.length > root.length) {
116
- if (this.activeAncestors.has(current)) {
117
- // Ancestor (and all of its ancestors) already recorded — avoid redundant work.
118
- break;
119
- }
120
- this.activeAncestors.add(current);
121
- const parent = toForwardSlash(dirname(current));
122
- if (parent === current) {
123
- break;
124
- }
125
- current = parent;
126
- }
236
+ this.recordAncestorsFrom(toForwardSlash(dirname(absolutePath)));
237
+ }
238
+ // A directory the listing could not open is itself non-ignored (git opened
239
+ // it because no pattern excluded it) and may hold active files nobody could
240
+ // list — so it counts as an ancestor from ITSELF down, not from its parent.
241
+ for (const directory of this.unlistableDirectories) {
242
+ this.recordAncestorsFrom(directory);
127
243
  }
128
244
  // projectRoot itself is always an implicit ancestor of everything under it.
129
- this.activeAncestors.add(root);
245
+ this.activeAncestors.add(this.normalizedProjectRoot);
246
+ }
247
+ /**
248
+ * Record `start` and every directory above it up to (not including) the
249
+ * project root as active ancestors, stopping at the first one already known
250
+ * — everything above it necessarily is too.
251
+ *
252
+ * @param start - Forward-slashed absolute directory to begin at
253
+ */
254
+ recordAncestorsFrom(start) {
255
+ const root = this.normalizedProjectRoot;
256
+ let current = start;
257
+ while (current !== root && current.length > root.length) {
258
+ if (this.activeAncestors.has(current)) {
259
+ break;
260
+ }
261
+ this.activeAncestors.add(current);
262
+ const parent = toForwardSlash(dirname(current));
263
+ if (parent === current) {
264
+ break;
265
+ }
266
+ current = parent;
267
+ }
268
+ }
269
+ /**
270
+ * Whether the active set can say anything about a path: it cannot beneath a
271
+ * directory the listing was refused — see {@link unlistableDirectories}.
272
+ *
273
+ * @param normalizedAbsolutePath - Forward-slashed absolute path
274
+ * @returns True when the path lies strictly beneath a refused directory
275
+ */
276
+ beneathUnlistable(normalizedAbsolutePath) {
277
+ return this.unlistableDirectories.some((directory) => normalizedAbsolutePath.startsWith(`${directory}/`));
130
278
  }
131
279
  /**
132
280
  * Returns true if the given absolute path IS an active-set file OR is an
@@ -145,7 +293,9 @@ export class GitTracker {
145
293
  return true;
146
294
  }
147
295
  const normalized = safePath.resolve(absolutePath);
148
- return this.activeSet.has(normalized) || this.activeAncestors.has(normalized);
296
+ // Beneath a refused directory nothing was listed, so "no active descendant"
297
+ // would be a claim the listing never earned.
298
+ return this.activeSet.has(normalized) || this.activeAncestors.has(normalized) || this.beneathUnlistable(normalized);
149
299
  }
150
300
  /**
151
301
  * Fast O(1) ignore check against the pre-populated active set.
@@ -153,7 +303,9 @@ export class GitTracker {
153
303
  * For paths INSIDE the project root **that exist on disk**, membership in the
154
304
  * active set is authoritative: such a path is ignored iff it is not in the
155
305
  * active set AND not an ancestor of any active-set path. No `git
156
- * check-ignore` spawn.
306
+ * check-ignore` spawn. The one exception is a path beneath a directory the
307
+ * listing could not OPEN — see {@link unlistableDirectories} — where absence
308
+ * says nothing and the question goes to `git check-ignore`.
157
309
  *
158
310
  * The existence qualifier is load-bearing, not a caveat. The active set is
159
311
  * built from `git ls-files`, so it can only ever contain paths that EXIST — a
@@ -167,7 +319,38 @@ export class GitTracker {
167
319
  * so a repeated miss on the same path stays O(1).
168
320
  *
169
321
  * For paths OUTSIDE the project root, falls back to {@link isIgnored} so
170
- * legacy behavior is preserved.
322
+ * legacy behavior is preserved. That fallback is the expensive one, and its
323
+ * cost does not resemble the in-root cost at all.
324
+ *
325
+ * ## ⚠️ The out-of-root fallback costs an order of magnitude more per path
326
+ *
327
+ * Measured in the D9 parity fixture: an out-of-root path costs
328
+ * **185–427 ms**, against **12–28 ms** for every in-repo path — ≈7–36× on
329
+ * those ranges.
330
+ *
331
+ * The mechanism is the delegation above plus what git does with it.
332
+ * {@link isIgnored} runs `git check-ignore` with the PROJECT ROOT as cwd; git
333
+ * exits 128 for a path outside that repository; and {@link isGitIgnored}'s
334
+ * exit-128 recovery walk then spawns `check-ignore` ONCE PER ANCESTOR up to
335
+ * `/`. No ancestor of an out-of-root path is ever the project root that would
336
+ * stop the walk, and every one of those ancestors is outside the repository
337
+ * too — so each also exits 128 and the walk never breaks early either. The
338
+ * verdict is cached, so the price is paid once per DISTINCT out-of-root path
339
+ * rather than once per call.
340
+ *
341
+ * The consequence for a caller choosing between the two oracles: switching
342
+ * from {@link isIgnored} to this method removes the spawn population **only
343
+ * for in-root paths**. A lane feeding out-of-root paths in bulk keeps the
344
+ * per-path spawn in full. So a comment asserting that the out-of-project case
345
+ * is rare, safe or cheap is asserting something this measurement contradicts:
346
+ * it is rare in some lanes, and it is never cheap.
347
+ *
348
+ * The exit-128 walk itself is documented in {@link isGitIgnored} — but only
349
+ * for the two cases already answered there: the SYMLINK case it exists to
350
+ * serve, and the NO-REPOSITORY case, which an early return now short-circuits
351
+ * before any spawn. The IN-REPO OUT-OF-ROOT path is the third case, it is
352
+ * documented nowhere there, and it is the one that still pays the walk in
353
+ * full. That is why the number lives here.
171
354
  *
172
355
  * Requires {@link initialize} with `includeUntracked: true` (the default).
173
356
  * When initialized without untracked files, this method delegates to
@@ -175,8 +358,21 @@ export class GitTracker {
175
358
  * possible per-path spawn.
176
359
  *
177
360
  * @param absolutePath - Absolute path to check
361
+ * @param knownToExist - The caller's own answer to the existence question, when
362
+ * it has already asked. Supplying it skips this method's `existsSync`, which
363
+ * is otherwise paid once per path that is absent from the active set — i.e.
364
+ * once per ignored path, and the projection's `filesystem` extent enumerates
365
+ * all of them (11,108 calls on an 8,496-path adopter tree).
366
+ *
367
+ * **It must mean what `existsSync` means: `stat` succeeds, following
368
+ * symlinks.** A caller holding only an `lstat` result has a DIFFERENT fact —
369
+ * `lstat` succeeds on a dangling symlink where `existsSync` returns false —
370
+ * and must narrow it to `exists && symlinkResolves !== false` rather than
371
+ * pass the `lstat` boolean through, or dangling symlinks silently stop
372
+ * falling back to `git check-ignore` and start reporting as ignored.
373
+ * Omit it and nothing changes.
178
374
  */
179
- isIgnoredByActiveSet(absolutePath) {
375
+ isIgnoredByActiveSet(absolutePath, knownToExist) {
180
376
  if (!this.activeSetPopulated) {
181
377
  return this.isIgnored(absolutePath);
182
378
  }
@@ -188,10 +384,16 @@ export class GitTracker {
188
384
  if (this.activeSet.has(normalized) || this.activeAncestors.has(normalized)) {
189
385
  return false;
190
386
  }
387
+ // Absent from the set beneath a directory the listing could not open: the
388
+ // set never looked there, so absence is not evidence — see
389
+ // {@link unlistableDirectories}. Only `git check-ignore` can answer.
390
+ if (this.beneathUnlistable(normalized)) {
391
+ return this.isIgnored(absolutePath);
392
+ }
191
393
  // Absent from the active set. That means "ignored" only for a path that is
192
394
  // actually there; otherwise the set has no opinion and git must be asked.
193
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- caller-supplied path, read-only existence probe
194
- if (!existsSync(normalized)) {
395
+ const present = knownToExist ?? existsSync(normalized);
396
+ if (!present) {
195
397
  return this.isIgnored(absolutePath);
196
398
  }
197
399
  return true;
@@ -228,6 +430,30 @@ export class GitTracker {
228
430
  this.cache.set(cacheKey, ignored);
229
431
  return ignored;
230
432
  }
433
+ /**
434
+ * The spelling git records for a path, or `null` if git has no record of it.
435
+ *
436
+ * This is the casing oracle, not another ignore check. On a case-insensitive
437
+ * filesystem `docs/Readme.md` and `docs/README.md` are one inode with two
438
+ * spellings, and Node's two `realpath` implementations disagree about which
439
+ * one they hand back — so anything that derives an identity from a path needs
440
+ * a single authoritative spelling, and git's is it wherever git has one.
441
+ *
442
+ * The lookup key is lowercased, which is the point: the caller asks with
443
+ * whatever casing it observed and gets back the casing git holds.
444
+ *
445
+ * Answers only from the pre-populated set — never spawns. A path git does not
446
+ * know (untracked-and-ignored, non-existent, outside the project root, or any
447
+ * path at all when `git ls-files` did not answer) returns `null`, and the
448
+ * caller falls back to the on-disk casing.
449
+ *
450
+ * @param absolutePath - Absolute path to look up
451
+ * @returns Root-relative, forward-slashed path as git spells it — relative to
452
+ * THIS tracker's project root — or `null` when git has no record of it
453
+ */
454
+ indexPathFor(absolutePath) {
455
+ return this.indexPaths.get(safePath.resolve(absolutePath).toLowerCase()) ?? null;
456
+ }
231
457
  /**
232
458
  * Get cache statistics.
233
459
  */
@@ -245,6 +471,8 @@ export class GitTracker {
245
471
  this.cache.clear();
246
472
  this.activeSet.clear();
247
473
  this.activeAncestors.clear();
474
+ this.indexPaths.clear();
475
+ this.unlistableDirectories.length = 0;
248
476
  this.initialized = false;
249
477
  this.activeSetPopulated = false;
250
478
  this.gitAnswered = false;
@@ -1 +1 @@
1
- {"version":3,"file":"git-tracker.js","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git-tracker.js","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"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"}