headlesscode 1.0.2

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 (232) hide show
  1. package/ATTRIBUTION.md +53 -0
  2. package/CODE_OF_CONDUCT.md +130 -0
  3. package/CONTRIBUTING.md +107 -0
  4. package/LICENSE +202 -0
  5. package/README.md +486 -0
  6. package/SECURITY.md +211 -0
  7. package/bin/headlesscode.mjs +83 -0
  8. package/package.json +63 -0
  9. package/shared/prompts/review-mode-prompt-short.md +93 -0
  10. package/shared/prompts/review-mode-prompt.md +281 -0
  11. package/shared/rules-code/rules.md +22 -0
  12. package/shared/stacks/cpp/rules.md +30 -0
  13. package/shared/stacks/fastapi/rules.md +30 -0
  14. package/shared/stacks/javascript/rules.md +37 -0
  15. package/shared/stacks/postgresql/rules.md +31 -0
  16. package/shared/stacks/python/rules.md +35 -0
  17. package/shared/stacks/react/rules.md +11 -0
  18. package/shared/stacks/typescript/rules.md +10 -0
  19. package/src/budget/budget.ts +221 -0
  20. package/src/budget/concurrency.ts +126 -0
  21. package/src/budget/cost.ts +309 -0
  22. package/src/budget/index.ts +8 -0
  23. package/src/checkpoints/cli.ts +256 -0
  24. package/src/checkpoints/service.ts +227 -0
  25. package/src/cli.ts +1535 -0
  26. package/src/cloud/docker-provider.ts +334 -0
  27. package/src/cloud/provider.ts +300 -0
  28. package/src/codeintel/call-graph.ts +78 -0
  29. package/src/codeintel/find-references.ts +123 -0
  30. package/src/codeintel/go-to-definition.ts +193 -0
  31. package/src/codeintel/handlers.ts +190 -0
  32. package/src/codeintel/import-graph.ts +173 -0
  33. package/src/codeintel/outline.ts +180 -0
  34. package/src/codeintel/position.ts +77 -0
  35. package/src/codeintel/program.ts +350 -0
  36. package/src/codeintel/rename-symbol.ts +213 -0
  37. package/src/codeintel/tools.ts +280 -0
  38. package/src/codemap/build.ts +135 -0
  39. package/src/codemap/cli.ts +190 -0
  40. package/src/codemap/extract.ts +339 -0
  41. package/src/codemap/files.ts +236 -0
  42. package/src/codemap/fingerprint.ts +65 -0
  43. package/src/codemap/flows.ts +62 -0
  44. package/src/codemap/html.ts +451 -0
  45. package/src/codemap/lock.ts +80 -0
  46. package/src/codemap/types.ts +101 -0
  47. package/src/codesearch/airunner-embedder.ts +185 -0
  48. package/src/codesearch/chunk.ts +339 -0
  49. package/src/codesearch/cli.ts +223 -0
  50. package/src/codesearch/embedder.ts +332 -0
  51. package/src/codesearch/files.ts +280 -0
  52. package/src/codesearch/index.ts +469 -0
  53. package/src/codesearch/ollama-embedder.ts +205 -0
  54. package/src/codesearch/search.ts +141 -0
  55. package/src/codesearch/types.ts +100 -0
  56. package/src/config/mode-models.ts +218 -0
  57. package/src/dashboard/aggregate.ts +364 -0
  58. package/src/dashboard/chat-thread.ts +141 -0
  59. package/src/dashboard/checkpoints.ts +124 -0
  60. package/src/dashboard/cli.ts +193 -0
  61. package/src/dashboard/codemap.ts +44 -0
  62. package/src/dashboard/files.ts +121 -0
  63. package/src/dashboard/page.ts +2803 -0
  64. package/src/dashboard/self-improvement-metrics.ts +282 -0
  65. package/src/dashboard/server.ts +1103 -0
  66. package/src/dashboard/session-launch.ts +310 -0
  67. package/src/dashboard/timeline.ts +273 -0
  68. package/src/dashboard/tool-exec.ts +107 -0
  69. package/src/dashboard/trend-cli.ts +141 -0
  70. package/src/dashboard/trend.ts +413 -0
  71. package/src/decision-proxy/cli.ts +261 -0
  72. package/src/decision-proxy/proxy.ts +569 -0
  73. package/src/deploy/gate-cli.ts +147 -0
  74. package/src/deploy/gate.ts +254 -0
  75. package/src/engine/condense.ts +512 -0
  76. package/src/engine/events.ts +428 -0
  77. package/src/engine/handoff.ts +71 -0
  78. package/src/engine/lazy-tools.ts +160 -0
  79. package/src/engine/local-explore.ts +653 -0
  80. package/src/engine/logger.ts +96 -0
  81. package/src/engine/loop.ts +5517 -0
  82. package/src/engine/parser.ts +347 -0
  83. package/src/engine/prompt.ts +860 -0
  84. package/src/engine/reports.ts +47 -0
  85. package/src/engine/stacks.ts +448 -0
  86. package/src/engine/types.ts +291 -0
  87. package/src/engine/usage.ts +186 -0
  88. package/src/github/app-auth.ts +161 -0
  89. package/src/github/cli.ts +448 -0
  90. package/src/github/installations.ts +133 -0
  91. package/src/github/pr.ts +321 -0
  92. package/src/github/provision.ts +118 -0
  93. package/src/github/push.ts +122 -0
  94. package/src/index-util.ts +50 -0
  95. package/src/index.ts +81 -0
  96. package/src/init/cli.ts +248 -0
  97. package/src/init/gitignore.ts +74 -0
  98. package/src/llm/ollama.ts +308 -0
  99. package/src/llm/openrouter.ts +868 -0
  100. package/src/llm/preflight.ts +367 -0
  101. package/src/llm/transcript-capture.ts +84 -0
  102. package/src/memory/embed.ts +110 -0
  103. package/src/memory/index.ts +22 -0
  104. package/src/memory/local.ts +259 -0
  105. package/src/memory/summarizer.ts +283 -0
  106. package/src/memory/types.ts +153 -0
  107. package/src/memory/uwuchat.ts +157 -0
  108. package/src/migrate/cli.ts +115 -0
  109. package/src/orchestrator/analyze-cli.ts +104 -0
  110. package/src/orchestrator/auto-split.ts +206 -0
  111. package/src/orchestrator/cleanup.ts +1003 -0
  112. package/src/orchestrator/cli.ts +3571 -0
  113. package/src/orchestrator/cost-estimate.ts +564 -0
  114. package/src/orchestrator/cost-history-cli.ts +242 -0
  115. package/src/orchestrator/cost-history.ts +397 -0
  116. package/src/orchestrator/git-sync.ts +250 -0
  117. package/src/orchestrator/index.ts +153 -0
  118. package/src/orchestrator/log-analysis.ts +0 -0
  119. package/src/orchestrator/merge-check.ts +108 -0
  120. package/src/orchestrator/pipeline.ts +411 -0
  121. package/src/orchestrator/resume.ts +1940 -0
  122. package/src/orchestrator/reviewer.ts +503 -0
  123. package/src/orchestrator/split.ts +296 -0
  124. package/src/orchestrator/state.ts +542 -0
  125. package/src/orchestrator/status.ts +697 -0
  126. package/src/orchestrator/verification-gate.ts +134 -0
  127. package/src/orchestrator/watch.ts +898 -0
  128. package/src/permissions/commands.ts +1083 -0
  129. package/src/permissions/config.ts +241 -0
  130. package/src/permissions/index.ts +12 -0
  131. package/src/permissions/protected-files.ts +96 -0
  132. package/src/permissions/store-protection.ts +272 -0
  133. package/src/project-store.ts +648 -0
  134. package/src/projects/cli.ts +382 -0
  135. package/src/qa/qa.ts +487 -0
  136. package/src/tools/browser/handler.ts +346 -0
  137. package/src/tools/browser/service.ts +406 -0
  138. package/src/tools/browser/smoke.ts +78 -0
  139. package/src/tools/browser/tool.ts +99 -0
  140. package/src/tools/executor.ts +2575 -0
  141. package/src/tools/language-detect.ts +183 -0
  142. package/src/tools/output-summarizer.ts +369 -0
  143. package/src/tools/run-tests.ts +302 -0
  144. package/src/tools/set-indentation-tool.ts +49 -0
  145. package/src/tools/test-selection.ts +160 -0
  146. package/src/vendor/tests/smoke.ts +103 -0
  147. package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
  148. package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
  149. package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
  150. package/src/vendor/zoo-code/shim/os-name.ts +18 -0
  151. package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
  152. package/src/vendor/zoo-code/shim/vscode.ts +76 -0
  153. package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
  154. package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
  155. package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
  156. package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
  157. package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
  158. package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
  159. package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
  160. package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
  161. package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
  162. package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
  163. package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
  164. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
  165. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
  166. package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
  167. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
  168. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
  169. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
  170. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
  171. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
  172. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
  173. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
  174. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
  175. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
  176. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
  177. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
  178. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
  179. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
  180. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
  181. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
  182. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
  183. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
  184. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
  185. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
  186. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
  187. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
  188. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
  189. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
  190. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
  191. package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
  192. package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
  193. package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
  194. package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
  195. package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
  196. package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
  197. package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
  198. package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
  199. package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
  200. package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
  201. package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
  202. package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
  203. package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
  204. package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
  205. package/src/vendor/zoo-code/src/shared/language.ts +43 -0
  206. package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
  207. package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
  208. package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
  209. package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
  210. package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
  211. package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
  212. package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
  213. package/src/vendor/zoo-code/src/utils/object.ts +18 -0
  214. package/src/vendor/zoo-code/src/utils/path.ts +94 -0
  215. package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
  216. package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
  217. package/src/vendor/zoo-code/types/global-settings.ts +19 -0
  218. package/src/vendor/zoo-code/types/index.ts +22 -0
  219. package/src/vendor/zoo-code/types/message.ts +375 -0
  220. package/src/vendor/zoo-code/types/mode.ts +241 -0
  221. package/src/vendor/zoo-code/types/todo.ts +19 -0
  222. package/src/vendor/zoo-code/types/tool-params.ts +116 -0
  223. package/src/vendor/zoo-code/types/tool.ts +67 -0
  224. package/src/vendor/zoo-code/types/vscode.ts +84 -0
  225. package/src/vision/describe.ts +242 -0
  226. package/src/vision/tool.ts +91 -0
  227. package/src/watcher/cli.ts +369 -0
  228. package/src/watcher/github.ts +304 -0
  229. package/src/watcher/index.ts +59 -0
  230. package/src/watcher/state.ts +254 -0
  231. package/src/watcher/watch.ts +562 -0
  232. package/tsconfig.json +18 -0
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Git-aware source-file discovery for the codebase index build.
3
+ *
4
+ * Walks a workspace recursively and returns the relative paths of real source
5
+ * files to index, honoring, in order:
6
+ * 1. `.gitignore` + `.git/info/exclude` (via `git ls-files` / `check-ignore`
7
+ * when the workspace is a git repo; when it isn't, falls back to a
8
+ * minimal built-in exclude list);
9
+ * 2. a hard cap on the number of files (a runaway repo must fail loudly,
10
+ * not silently index forever);
11
+ * 3. an explicit extension allow-list (src/codesearch/types.ts) — binary
12
+ * files and non-source assets are never embedded.
13
+ *
14
+ * `git ls-files --cached --others --exclude-standard` returns every tracked
15
+ * file PLUS every untracked file that is NOT gitignored, with proper
16
+ * .gitignore semantics — strictly better than a hand-rolled ignore matcher,
17
+ * and it reuses the repo's own ignore config the way `list_files` and the
18
+ * vendored checkpoint `excludes.ts` conventions expect (see
19
+ * src/vendor/zoo-code/src/services/checkpoints/excludes.ts for the pattern
20
+ * list those conventions grew from).
21
+ */
22
+
23
+ import { execFile } from "node:child_process"
24
+ import * as fs from "node:fs"
25
+ import * as fsp from "node:fs/promises"
26
+ import * as path from "node:path"
27
+ import { promisify } from "node:util"
28
+
29
+ import { CODE_EXTENSIONS } from "./types.js"
30
+
31
+ const execFileP = promisify(execFile)
32
+
33
+ /** Hard cap on files scanned per index build (mirrors upstream's MAX_LIST_FILES_LIMIT). */
34
+ export const MAX_INDEXED_FILES = 50_000
35
+
36
+ /**
37
+ * Generated-artifact directory names skipped EVEN when git-tracked — i.e. even
38
+ * when `.gitignore` doesn't cover them. Real-world repos routinely commit
39
+ * these by accident (a coverage report or build output checked in once and
40
+ * never gitignored); trusting `.gitignore` as the sole source of truth means
41
+ * that mistake silently costs real embedding-API money and index noise
42
+ * forever after. This list is deliberately narrow — only directory names
43
+ * that are near-universally machine-generated and never hand-written source,
44
+ * so it can't plausibly skip something a project actually wants indexed.
45
+ */
46
+ const ALWAYS_EXCLUDE_DIR_NAMES = new Set(["coverage", "dist", "build", "node_modules", ".next", ".nuxt"])
47
+
48
+ /**
49
+ * Generated lockfile basenames skipped even when git-tracked, for the same
50
+ * reason as ALWAYS_EXCLUDE_DIR_NAMES: exact machine-generated dependency
51
+ * manifests with no code-search value, occasionally matched by the
52
+ * extension allow-list (e.g. `package-lock.json`).
53
+ */
54
+ const ALWAYS_EXCLUDE_FILE_BASENAMES = new Set([
55
+ "package-lock.json",
56
+ "yarn.lock",
57
+ "pnpm-lock.yaml",
58
+ "composer.lock",
59
+ "Gemfile.lock",
60
+ "poetry.lock",
61
+ ])
62
+
63
+ /** True when `rel` (a workspace-relative POSIX path) should never be indexed, regardless of .gitignore. */
64
+ function isAlwaysExcluded(rel: string): boolean {
65
+ if (ALWAYS_EXCLUDE_FILE_BASENAMES.has(path.basename(rel))) {
66
+ return true
67
+ }
68
+ return rel.split("/").some((segment) => ALWAYS_EXCLUDE_DIR_NAMES.has(segment))
69
+ }
70
+
71
+ /** Minimal fallback excludes used when the workspace is NOT a git repo. */
72
+ const FALLBACK_EXCLUDES = new Set([
73
+ ".git",
74
+ ".gitignore",
75
+ ".headlesscode",
76
+ ".worktrees",
77
+ ".roo",
78
+ "node_modules",
79
+ "dist",
80
+ "build",
81
+ "out",
82
+ "coverage",
83
+ ".next",
84
+ ".nuxt",
85
+ "vendor",
86
+ "target",
87
+ "__pycache__",
88
+ "venv",
89
+ ".venv",
90
+ "env",
91
+ "Pods",
92
+ ".idea",
93
+ ".vscode",
94
+ ".parcel-cache",
95
+ ".pytest_cache",
96
+ ".terraform",
97
+ ".terragrunt-cache",
98
+ "bin",
99
+ "obj",
100
+ ])
101
+
102
+ /** A file discovered during the walk. */
103
+ export interface IndexableFile {
104
+ /** Workspace-relative POSIX path. */
105
+ rel: string
106
+ /** Absolute path for reading. */
107
+ abs: string
108
+ }
109
+
110
+ async function gitLsFiles(root: string): Promise<string[]> {
111
+ try {
112
+ const { stdout } = await execFileP("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
113
+ cwd: root,
114
+ maxBuffer: 16 * 1024 * 1024,
115
+ })
116
+ return stdout
117
+ .split("\n")
118
+ .map((l) => l.trim())
119
+ .filter((l) => l.length > 0)
120
+ } catch {
121
+ return []
122
+ }
123
+ }
124
+
125
+ /**
126
+ * When the workspace is NOT a git repo (no tracked files), check-ignore can't
127
+ * tell us what's ignored — but a plain `git init`-less directory may still
128
+ * carry a `.gitignore`. We read the root `.gitignore` (plus
129
+ * `.git/info/exclude` if present) and build a tiny matcher from its patterns.
130
+ * This is deliberately a SUBSET of full gitignore semantics (no `**` negation
131
+ * chains, no nested .gitignore files) — good enough for the common "ignore
132
+ * node_modules/.headlesscode/dist" case, and the git-repo path above is the
133
+ * authoritative one.
134
+ */
135
+ function readRootGitignore(root: string): string[] {
136
+ const patterns: string[] = []
137
+ for (const p of [path.join(root, ".gitignore"), path.join(root, ".git", "info", "exclude")]) {
138
+ try {
139
+ const raw = fs.readFileSync(p, "utf-8")
140
+ for (const line of raw.split("\n")) {
141
+ const trimmed = line.trim()
142
+ if (trimmed === "" || trimmed.startsWith("#")) {
143
+ continue
144
+ }
145
+ patterns.push(trimmed)
146
+ }
147
+ } catch {
148
+ // missing file — fine
149
+ }
150
+ }
151
+ return patterns
152
+ }
153
+
154
+ /** Match a relative path against a small set of gitignore-style patterns. */
155
+ function matchesIgnorePatterns(rel: string, patterns: string[]): boolean {
156
+ for (const pattern of patterns) {
157
+ // Negation patterns are not supported in the fallback — a lone "!" is
158
+ // treated as an ignore (fail-closed: skip rather than risk indexing
159
+ // something the user meant to exclude).
160
+ if (pattern.startsWith("!")) {
161
+ continue
162
+ }
163
+ const p = pattern.replace(/^\/+/, "").replace(/\/+$/, "")
164
+ if (p === "") {
165
+ continue
166
+ }
167
+ const isDir = p.endsWith("/")
168
+ const base = isDir ? p.slice(0, -1) : p
169
+ // Directory pattern matches any path under a dir of that name.
170
+ if (isDir) {
171
+ if (rel === base || rel.startsWith(base + "/") || rel.split("/").includes(base)) {
172
+ return true
173
+ }
174
+ continue
175
+ }
176
+ if (p.startsWith("*")) {
177
+ const suffix = p.slice(1)
178
+ if (rel.endsWith(suffix)) {
179
+ return true
180
+ }
181
+ continue
182
+ }
183
+ // Plain path: match exact relative path or any segment.
184
+ if (rel === p || rel.startsWith(p + "/") || rel.split("/").includes(p)) {
185
+ return true
186
+ }
187
+ }
188
+ return false
189
+ }
190
+
191
+ /**
192
+ * Walk the workspace for indexable source files. Returns [] when the
193
+ * workspace doesn't exist or contains nothing indexable.
194
+ */
195
+ export async function walkSourceFiles(workspaceRoot: string): Promise<IndexableFile[]> {
196
+ const root = path.resolve(workspaceRoot)
197
+
198
+ const gitFiles = await gitLsFiles(root)
199
+ if (gitFiles.length > 0) {
200
+ if (gitFiles.length > MAX_INDEXED_FILES) {
201
+ throw new Error(
202
+ `codebase index: workspace has ${gitFiles.length} git-tracked/untracked files — over the ${MAX_INDEXED_FILES} file cap. Refine .gitignore or index a subdirectory.`,
203
+ )
204
+ }
205
+ const out: IndexableFile[] = []
206
+ for (const rel of gitFiles) {
207
+ if (!CODE_EXTENSIONS.has(path.extname(rel).toLowerCase())) {
208
+ continue
209
+ }
210
+ if (isAlwaysExcluded(rel)) {
211
+ continue
212
+ }
213
+ // `git ls-files` reports directories as paths with trailing slashes
214
+ // only in odd edge cases; the extension check above already excludes
215
+ // them. Guard cheaply anyway.
216
+ if (rel.endsWith("/")) {
217
+ continue
218
+ }
219
+ const abs = path.join(root, rel)
220
+ try {
221
+ const st = await fsp.stat(abs)
222
+ if (st.isFile()) {
223
+ out.push({ rel, abs })
224
+ }
225
+ } catch {
226
+ // File disappeared between git listing and stat — skip.
227
+ }
228
+ }
229
+ return out
230
+ }
231
+
232
+ // Not a git repo: hand-rolled recursive walk with the minimal exclude list
233
+ // PLUS the root .gitignore's own patterns (readRootGitignore + the matcher
234
+ // below) — the git path above is authoritative, but a plain directory with
235
+ // a .gitignore (no git init) must still skip the user's ignored files.
236
+ const patterns = readRootGitignore(root)
237
+ const out: IndexableFile[] = []
238
+ const seen = new Set<string>()
239
+ const walk = async (dir: string): Promise<void> => {
240
+ let entries
241
+ try {
242
+ entries = await fsp.readdir(dir, { withFileTypes: true })
243
+ } catch {
244
+ return
245
+ }
246
+ for (const ent of entries) {
247
+ const abs = path.join(dir, ent.name)
248
+ // No reliance on the vendored String.prototype.toPosix() extension
249
+ // (src/vendor/zoo-code/src/utils/path.js) — this module is also
250
+ // used by the index CLI in isolation, and a plain replace is enough.
251
+ const rel = path.relative(root, abs).replace(/\\/g, "/")
252
+ if (ent.isDirectory()) {
253
+ if (
254
+ FALLBACK_EXCLUDES.has(ent.name) ||
255
+ seen.has(rel) ||
256
+ matchesIgnorePatterns(rel, patterns)
257
+ ) {
258
+ continue
259
+ }
260
+ seen.add(rel)
261
+ await walk(abs)
262
+ } else if (ent.isFile()) {
263
+ if (!CODE_EXTENSIONS.has(path.extname(ent.name).toLowerCase())) {
264
+ continue
265
+ }
266
+ if (matchesIgnorePatterns(rel, patterns) || ALWAYS_EXCLUDE_FILE_BASENAMES.has(ent.name)) {
267
+ continue
268
+ }
269
+ out.push({ rel, abs })
270
+ }
271
+ }
272
+ }
273
+ await walk(root)
274
+ if (out.length > MAX_INDEXED_FILES) {
275
+ throw new Error(
276
+ `codebase index: workspace has ${out.length} candidate files — over the ${MAX_INDEXED_FILES} file cap.`,
277
+ )
278
+ }
279
+ return out
280
+ }