@schoolai/shipyard 3.7.0 → 3.8.0-rc.20260529.0

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 (104) hide show
  1. package/dist/{auth-SS7LV5XK.js → auth-EXHO3AG5.js} +4 -4
  2. package/dist/capability-detector-worker.js +142 -0
  3. package/dist/capability-detector-worker.js.map +1 -0
  4. package/dist/{chunk-DKMDBOFU.js → chunk-2CNIEBKO.js} +21 -11
  5. package/dist/chunk-2CNIEBKO.js.map +1 -0
  6. package/dist/chunk-4T2OQAVL.js +51 -0
  7. package/dist/chunk-4T2OQAVL.js.map +1 -0
  8. package/dist/chunk-5ER6ZHA2.js +46 -0
  9. package/dist/chunk-5ER6ZHA2.js.map +1 -0
  10. package/dist/chunk-7LSEE26O.js +24227 -0
  11. package/dist/chunk-7LSEE26O.js.map +1 -0
  12. package/dist/{chunk-7AHRFPAL.js → chunk-7YOU7MBN.js} +183 -17
  13. package/dist/chunk-7YOU7MBN.js.map +1 -0
  14. package/dist/chunk-CMGJGK6R.js +382 -0
  15. package/dist/chunk-CMGJGK6R.js.map +1 -0
  16. package/dist/{chunk-VPMN47TL.js → chunk-CNR7O5YH.js} +1 -2
  17. package/dist/{chunk-2J3WSIAF.js → chunk-EF2DAODF.js} +18 -3
  18. package/dist/chunk-EF2DAODF.js.map +1 -0
  19. package/dist/chunk-HQ43PHOH.js +1203 -0
  20. package/dist/chunk-HQ43PHOH.js.map +1 -0
  21. package/dist/chunk-KITSAHTX.js +134 -0
  22. package/dist/chunk-KITSAHTX.js.map +1 -0
  23. package/dist/chunk-LESHN5J5.js +6898 -0
  24. package/dist/chunk-LESHN5J5.js.map +1 -0
  25. package/dist/{chunk-LW2MS4T5.js → chunk-LMJFHKRD.js} +15 -12
  26. package/dist/chunk-LMJFHKRD.js.map +1 -0
  27. package/dist/{chunk-SNYEQHUK.js → chunk-NACJENDW.js} +14 -21
  28. package/dist/chunk-NACJENDW.js.map +1 -0
  29. package/dist/{chunk-IISLTKYY.js → chunk-TU63KZFW.js} +2 -2
  30. package/dist/chunk-TX6DK4PK.js +186 -0
  31. package/dist/chunk-TX6DK4PK.js.map +1 -0
  32. package/dist/chunk-UQVXWOPT.js +48 -0
  33. package/dist/chunk-UQVXWOPT.js.map +1 -0
  34. package/dist/{chunk-3MNPDCO5.js → chunk-WBB4XHLH.js} +139 -140
  35. package/dist/chunk-WBB4XHLH.js.map +1 -0
  36. package/dist/chunk-X3MULCV5.js +11 -0
  37. package/dist/chunk-X3MULCV5.js.map +1 -0
  38. package/dist/chunk-YZ3Z3ZYI.js +787 -0
  39. package/dist/chunk-YZ3Z3ZYI.js.map +1 -0
  40. package/dist/{chunk-2UN5AR7V.js → chunk-ZAOPND5G.js} +2 -2
  41. package/dist/chunk-ZFKJAYAN.js +542 -0
  42. package/dist/chunk-ZFKJAYAN.js.map +1 -0
  43. package/dist/cursor-hook-shim.js +316 -0
  44. package/dist/cursor-hook-shim.js.map +1 -0
  45. package/dist/cursor-runner.js +358 -0
  46. package/dist/cursor-runner.js.map +1 -0
  47. package/dist/electron-utility.js +111 -0
  48. package/dist/electron-utility.js.map +1 -0
  49. package/dist/git-pool-V73Q53NX.js +18 -0
  50. package/dist/{git-repo-VRT57DGC.js → git-repo-TN3VZXQV.js} +9 -6
  51. package/dist/index.js +12 -12
  52. package/dist/index.js.map +1 -1
  53. package/dist/{logger-GQCSLSZH.js → logger-QHPTO22N.js} +4 -4
  54. package/dist/login-Q7SZI7JJ.js +20 -0
  55. package/dist/{logout-VUNCW5B2.js → logout-O4AVMO5S.js} +6 -6
  56. package/dist/mcp-servers-F64M5T4I.js +24 -0
  57. package/dist/{roi-Y3MX5UW4.js → roi-EYDLPOCS.js} +5 -5
  58. package/dist/rss-worker.js +159 -0
  59. package/dist/rss-worker.js.map +1 -0
  60. package/dist/{serve-O53FNK64.js → serve-6A7RJWEF.js} +89862 -102999
  61. package/dist/{serve-O53FNK64.js.map → serve-6A7RJWEF.js.map} +1 -1
  62. package/dist/skills-ZHEPSBHW.js +11 -0
  63. package/dist/{start-IDFDHRD6.js → start-YGYYIK53.js} +229 -27
  64. package/dist/start-YGYYIK53.js.map +1 -0
  65. package/dist/vault-crypto-BKDOA65F.js +13 -0
  66. package/dist/vault-crypto-BKDOA65F.js.map +1 -0
  67. package/dist/worker.js +6 -3
  68. package/dist/worker.js.map +1 -1
  69. package/package.json +17 -10
  70. package/dist/chunk-2J3WSIAF.js.map +0 -1
  71. package/dist/chunk-3MNPDCO5.js.map +0 -1
  72. package/dist/chunk-66OBOZ3X.js +0 -79
  73. package/dist/chunk-66OBOZ3X.js.map +0 -1
  74. package/dist/chunk-7AHRFPAL.js.map +0 -1
  75. package/dist/chunk-DKMDBOFU.js.map +0 -1
  76. package/dist/chunk-L2WQMPWS.js +0 -666
  77. package/dist/chunk-L2WQMPWS.js.map +0 -1
  78. package/dist/chunk-LW2MS4T5.js.map +0 -1
  79. package/dist/chunk-PI77CUEP.js +0 -49
  80. package/dist/chunk-PI77CUEP.js.map +0 -1
  81. package/dist/chunk-RXI4637N.js +0 -395
  82. package/dist/chunk-RXI4637N.js.map +0 -1
  83. package/dist/chunk-SNYEQHUK.js.map +0 -1
  84. package/dist/chunk-VBPHGPBR.js +0 -126
  85. package/dist/chunk-VBPHGPBR.js.map +0 -1
  86. package/dist/index.d.ts +0 -2
  87. package/dist/login-L4BBPUYO.js +0 -20
  88. package/dist/mcp-servers-MXS5VAWI.js +0 -18
  89. package/dist/shell-V36EX2IJ.js +0 -27
  90. package/dist/skills-GPGRNV4R.js +0 -9
  91. package/dist/start-IDFDHRD6.js.map +0 -1
  92. package/dist/worker.d.ts +0 -49
  93. /package/dist/{auth-SS7LV5XK.js.map → auth-EXHO3AG5.js.map} +0 -0
  94. /package/dist/{chunk-VPMN47TL.js.map → chunk-CNR7O5YH.js.map} +0 -0
  95. /package/dist/{chunk-IISLTKYY.js.map → chunk-TU63KZFW.js.map} +0 -0
  96. /package/dist/{chunk-2UN5AR7V.js.map → chunk-ZAOPND5G.js.map} +0 -0
  97. /package/dist/{git-repo-VRT57DGC.js.map → git-pool-V73Q53NX.js.map} +0 -0
  98. /package/dist/{logger-GQCSLSZH.js.map → git-repo-TN3VZXQV.js.map} +0 -0
  99. /package/dist/{login-L4BBPUYO.js.map → logger-QHPTO22N.js.map} +0 -0
  100. /package/dist/{mcp-servers-MXS5VAWI.js.map → login-Q7SZI7JJ.js.map} +0 -0
  101. /package/dist/{logout-VUNCW5B2.js.map → logout-O4AVMO5S.js.map} +0 -0
  102. /package/dist/{shell-V36EX2IJ.js.map → mcp-servers-F64M5T4I.js.map} +0 -0
  103. /package/dist/{roi-Y3MX5UW4.js.map → roi-EYDLPOCS.js.map} +0 -0
  104. /package/dist/{skills-GPGRNV4R.js.map → skills-ZHEPSBHW.js.map} +0 -0
@@ -0,0 +1,787 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ gitExecSafe
4
+ } from "./chunk-4T2OQAVL.js";
5
+ import {
6
+ logger
7
+ } from "./chunk-ZAOPND5G.js";
8
+ import {
9
+ getShipyardHome
10
+ } from "./chunk-KITSAHTX.js";
11
+ import {
12
+ external_exports
13
+ } from "./chunk-CNR7O5YH.js";
14
+
15
+ // src/shared/capabilities/git-repo.ts
16
+ import { readdir, readFile as readFile3 } from "fs/promises";
17
+ import { basename, join as join5 } from "path";
18
+
19
+ // src/shared/capabilities/environments-cache.ts
20
+ import { mkdir, readFile, rename as rename2, stat, unlink, writeFile as writeFile2 } from "fs/promises";
21
+ import { dirname as dirname2, isAbsolute, join as join2, resolve } from "path";
22
+
23
+ // src/shared/fs-utils.ts
24
+ import { randomUUID } from "crypto";
25
+ import { existsSync } from "fs";
26
+ import { rename, writeFile } from "fs/promises";
27
+ import { dirname, join, parse } from "path";
28
+ function isEnoent(err) {
29
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
30
+ }
31
+ function isEexist(err) {
32
+ return typeof err === "object" && err !== null && "code" in err && err.code === "EEXIST";
33
+ }
34
+ function isCorruptionError(err) {
35
+ if (err instanceof SyntaxError) return true;
36
+ if (typeof err === "object" && err !== null && "name" in err && err.name === "ZodError")
37
+ return true;
38
+ return false;
39
+ }
40
+ async function atomicWriteFile(filePath, contents, options) {
41
+ const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
42
+ await writeFile(tmpPath, contents, { encoding: "utf-8", mode: options?.mode });
43
+ await rename(tmpPath, filePath);
44
+ }
45
+ function findProjectRoot(startDir) {
46
+ let dir = startDir;
47
+ while (true) {
48
+ if (existsSync(join(dir, "pnpm-workspace.yaml")) || existsSync(join(dir, ".git"))) {
49
+ return { root: dir, foundMarker: true };
50
+ }
51
+ const parent = dirname(dir);
52
+ if (parent === dir || parent === parse(dir).root) {
53
+ return { root: startDir, foundMarker: false };
54
+ }
55
+ dir = parent;
56
+ }
57
+ }
58
+
59
+ // src/shared/capabilities/environments-cache.ts
60
+ var CacheEntrySchema = external_exports.object({
61
+ path: external_exports.string(),
62
+ branch: external_exports.string(),
63
+ remote: external_exports.string().nullable(),
64
+ headMtimeMs: external_exports.number(),
65
+ /**
66
+ * Added after the original cache shape. Optional so old disk caches parse,
67
+ * but `isCacheValid` treats missing as invalid and refreshes once.
68
+ */
69
+ configMtimeMs: external_exports.number().nullable().optional(),
70
+ cachedAt: external_exports.number()
71
+ });
72
+ var CacheFileSchema = external_exports.object({
73
+ entries: external_exports.record(external_exports.string(), CacheEntrySchema)
74
+ });
75
+ function cacheFilePath() {
76
+ return join2(getShipyardHome(), "data", "environments-cache.json");
77
+ }
78
+ var cacheMigrationDone = false;
79
+ async function loadCache() {
80
+ const filePath = cacheFilePath();
81
+ try {
82
+ const raw = await readFile(filePath, "utf8");
83
+ const parsed = JSON.parse(raw);
84
+ const result = CacheFileSchema.safeParse(parsed);
85
+ if (!result.success) {
86
+ logger.debug({ path: filePath }, "environments-cache: malformed, treating as empty");
87
+ cacheMigrationDone = true;
88
+ return /* @__PURE__ */ new Map();
89
+ }
90
+ const cache2 = new Map(Object.entries(result.data.entries));
91
+ if (!cacheMigrationDone) {
92
+ cacheMigrationDone = true;
93
+ await migrateConfigMtimes(cache2);
94
+ }
95
+ return cache2;
96
+ } catch (err) {
97
+ if (isEnoent(err)) {
98
+ cacheMigrationDone = true;
99
+ return /* @__PURE__ */ new Map();
100
+ }
101
+ logger.debug({ err }, "environments-cache: read failed, treating as empty");
102
+ return /* @__PURE__ */ new Map();
103
+ }
104
+ }
105
+ async function migrateConfigMtimes(cache2) {
106
+ const stale = [...cache2.values()].filter((e) => e.configMtimeMs === void 0);
107
+ if (stale.length === 0) return;
108
+ await Promise.all(
109
+ stale.map(async (entry) => {
110
+ const fp = await getGitCacheFingerprint(entry.path);
111
+ cache2.set(entry.path, { ...entry, configMtimeMs: fp.configMtimeMs });
112
+ })
113
+ );
114
+ logger.info({ count: stale.length }, "environments-cache: cache_migration_seeded");
115
+ await saveCache(cache2);
116
+ }
117
+ async function readGitdirFile(dotGitPath) {
118
+ const contents = await readFile(dotGitPath, "utf8");
119
+ const match = contents.match(/^gitdir:\s*(.+)\s*$/m);
120
+ if (!match?.[1]) return null;
121
+ const gitdirRaw = match[1].trim();
122
+ return isAbsolute(gitdirRaw) ? gitdirRaw : resolve(dirname2(dotGitPath), gitdirRaw);
123
+ }
124
+ async function resolveCommonGitDir(gitdir) {
125
+ try {
126
+ const raw = await readFile(join2(gitdir, "commondir"), "utf8");
127
+ const commonDirRaw = raw.trim();
128
+ if (commonDirRaw === "") return gitdir;
129
+ return isAbsolute(commonDirRaw) ? commonDirRaw : resolve(gitdir, commonDirRaw);
130
+ } catch {
131
+ return gitdir;
132
+ }
133
+ }
134
+ async function resolveHeadPath(worktreePath) {
135
+ const paths = await resolveGitMetadataPaths(worktreePath);
136
+ return paths?.headPath ?? null;
137
+ }
138
+ async function resolveGitMetadataPaths(worktreePath) {
139
+ const dotGitPath = join2(worktreePath, ".git");
140
+ try {
141
+ const dotGitStat = await stat(dotGitPath);
142
+ if (dotGitStat.isDirectory()) {
143
+ return { headPath: join2(dotGitPath, "HEAD"), configPath: join2(dotGitPath, "config") };
144
+ }
145
+ if (dotGitStat.isFile()) {
146
+ const gitdir = await readGitdirFile(dotGitPath);
147
+ if (!gitdir) return null;
148
+ const commonGitDir = await resolveCommonGitDir(gitdir);
149
+ return { headPath: join2(gitdir, "HEAD"), configPath: join2(commonGitDir, "config") };
150
+ }
151
+ return null;
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+ async function getPathMtime(path) {
157
+ if (!path) return null;
158
+ try {
159
+ const pathStat = await stat(path);
160
+ return pathStat.mtimeMs;
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+ async function getGitCacheFingerprint(worktreePath) {
166
+ const paths = await resolveGitMetadataPaths(worktreePath);
167
+ if (!paths) return { headMtimeMs: null, configMtimeMs: null };
168
+ const [headMtimeMs, configMtimeMs] = await Promise.all([
169
+ getPathMtime(paths.headPath),
170
+ getPathMtime(paths.configPath)
171
+ ]);
172
+ return { headMtimeMs, configMtimeMs };
173
+ }
174
+ function isCacheValid(entry, current) {
175
+ return entry.headMtimeMs === current.headMtimeMs && (entry.configMtimeMs ?? null) === current.configMtimeMs;
176
+ }
177
+ async function saveCache(entries) {
178
+ const filePath = cacheFilePath();
179
+ const tmpPath = `${filePath}.tmp`;
180
+ try {
181
+ await mkdir(dirname2(filePath), { recursive: true });
182
+ const current = await loadCache();
183
+ for (const [path, entry] of entries) {
184
+ current.set(path, entry);
185
+ }
186
+ const payload = { entries: Object.fromEntries(current) };
187
+ await writeFile2(tmpPath, JSON.stringify(payload), "utf8");
188
+ await rename2(tmpPath, filePath);
189
+ } catch (err) {
190
+ logger.debug({ err, filePath }, "environments-cache: save failed");
191
+ void unlink(tmpPath).catch(() => {
192
+ });
193
+ }
194
+ }
195
+
196
+ // src/shared/capabilities/repo-paths-cache.ts
197
+ import { mkdir as mkdir2, readFile as readFile2, rename as rename3, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
198
+ import { dirname as dirname3, join as join3 } from "path";
199
+ var RepoPathsDiskSchema = external_exports.object({
200
+ repos: external_exports.array(external_exports.string()),
201
+ worktrees: external_exports.array(external_exports.string()),
202
+ populatedAt: external_exports.number()
203
+ });
204
+ var STALE_AFTER_MS_DEFAULT = 5 * 60 * 1e3;
205
+ var cache = null;
206
+ var cacheGeneration = 0;
207
+ var backgroundRefreshInFlight = false;
208
+ function cacheFilePath2() {
209
+ return join3(getShipyardHome(), "data", "repo-paths-cache.json");
210
+ }
211
+ function getCachedRepoPaths() {
212
+ if (!cache) return null;
213
+ return { repos: cache.repos, worktrees: cache.worktrees };
214
+ }
215
+ function setCachedRepoPaths(repos, worktrees, now = Date.now()) {
216
+ cache = { repos, worktrees, populatedAt: now };
217
+ cacheGeneration += 1;
218
+ void persistDisk(cache, cacheGeneration);
219
+ }
220
+ async function hydrateRepoPathsCacheFromDisk(opts) {
221
+ if (cache !== null) return { hit: false, stale: false };
222
+ const filePath = cacheFilePath2();
223
+ let raw;
224
+ try {
225
+ raw = await readFile2(filePath, "utf8");
226
+ } catch (err) {
227
+ if (isEnoent(err)) return { hit: false, stale: false };
228
+ logger.debug({ err, filePath }, "repo-paths-cache: read failed");
229
+ return { hit: false, stale: false };
230
+ }
231
+ let parsed;
232
+ try {
233
+ parsed = JSON.parse(raw);
234
+ } catch {
235
+ return { hit: false, stale: false };
236
+ }
237
+ const result = RepoPathsDiskSchema.safeParse(parsed);
238
+ if (!result.success) return { hit: false, stale: false };
239
+ if (cache !== null) return { hit: false, stale: false };
240
+ const staleAfterMs = opts?.staleAfterMs ?? STALE_AFTER_MS_DEFAULT;
241
+ const ageMs = Math.max(0, Date.now() - result.data.populatedAt);
242
+ const stale = ageMs > staleAfterMs;
243
+ cache = result.data;
244
+ logger.info(
245
+ { repos: result.data.repos.length, worktrees: result.data.worktrees.length, ageMs, stale },
246
+ "repo-paths-cache: hydrated from disk"
247
+ );
248
+ return { hit: true, stale };
249
+ }
250
+ async function persistDisk(entry, generation) {
251
+ const filePath = cacheFilePath2();
252
+ const tmpPath = `${filePath}.tmp`;
253
+ try {
254
+ await mkdir2(dirname3(filePath), { recursive: true });
255
+ await writeFile3(tmpPath, JSON.stringify(entry), "utf8");
256
+ if (generation !== cacheGeneration) {
257
+ await unlink2(tmpPath).catch(() => {
258
+ });
259
+ return;
260
+ }
261
+ await rename3(tmpPath, filePath);
262
+ } catch (err) {
263
+ logger.debug({ err, filePath }, "repo-paths-cache: save failed");
264
+ void unlink2(tmpPath).catch(() => {
265
+ });
266
+ }
267
+ }
268
+ function addWorktreeToCache(worktreePath, sourceRepoPath) {
269
+ if (!cache) return;
270
+ const repos = cache.repos.includes(sourceRepoPath) ? cache.repos : [...cache.repos, sourceRepoPath];
271
+ const worktrees = cache.worktrees.includes(worktreePath) ? cache.worktrees : [...cache.worktrees, worktreePath];
272
+ cache = { repos, worktrees, populatedAt: cache.populatedAt };
273
+ cacheGeneration += 1;
274
+ void persistDisk(cache, cacheGeneration);
275
+ }
276
+ function removeWorktreeFromCache(worktreePath) {
277
+ if (!cache) return;
278
+ const next = cache.worktrees.filter((p) => p !== worktreePath);
279
+ if (next.length === cache.worktrees.length) return;
280
+ cache = { repos: cache.repos, worktrees: next, populatedAt: cache.populatedAt };
281
+ cacheGeneration += 1;
282
+ void persistDisk(cache, cacheGeneration);
283
+ }
284
+ async function refreshRepoPathsInBackground(log) {
285
+ if (backgroundRefreshInFlight) return;
286
+ backgroundRefreshInFlight = true;
287
+ const startGen = cacheGeneration;
288
+ const start = Date.now();
289
+ try {
290
+ const { findGitRepos: findGitRepos2, discoverWorktrees: discoverWorktrees2 } = await import("./git-repo-TN3VZXQV.js");
291
+ const { homedir: homedir2 } = await import("os");
292
+ const repos = await findGitRepos2(homedir2());
293
+ const worktrees = await discoverWorktrees2(repos);
294
+ if (cacheGeneration !== startGen) {
295
+ log.debug(
296
+ { event: "repo_paths_cache_refresh_dropped" },
297
+ "repo-paths-cache: background refresh dropped (cache mutated mid-walk)"
298
+ );
299
+ return;
300
+ }
301
+ if (repos.length === 0 && cache && cache.repos.length > 0) {
302
+ log.warn(
303
+ {
304
+ event: "repo_paths_cache_refresh_empty_preserved",
305
+ prevRepos: cache.repos.length
306
+ },
307
+ "repo-paths-cache: background refresh returned empty; preserving prior cache"
308
+ );
309
+ return;
310
+ }
311
+ setCachedRepoPaths(repos, worktrees);
312
+ log.info(
313
+ {
314
+ event: "repo_paths_cache_refresh_complete",
315
+ repos: repos.length,
316
+ worktrees: worktrees.length,
317
+ durationMs: Date.now() - start
318
+ },
319
+ "repo-paths-cache: background refresh complete"
320
+ );
321
+ } catch (err) {
322
+ log.warn(
323
+ { err, event: "repo_paths_cache_refresh_failed", durationMs: Date.now() - start },
324
+ "repo-paths-cache: background refresh failed"
325
+ );
326
+ } finally {
327
+ backgroundRefreshInFlight = false;
328
+ }
329
+ }
330
+ async function hydrateAndLogRepoPathsCache(log) {
331
+ const result = await hydrateRepoPathsCacheFromDisk();
332
+ log.info(
333
+ {
334
+ event: "repo_paths_cache_hydrate_result",
335
+ hit: result.hit,
336
+ stale: result.stale
337
+ },
338
+ result.hit ? result.stale ? "repo-paths cache hydrated from disk (stale; background refresh scheduled)" : "repo-paths cache hydrated from disk (fresh)" : "repo-paths cache cold (walk will fire inside detect_environments)"
339
+ );
340
+ if (result.hit && result.stale) {
341
+ void refreshRepoPathsInBackground(log);
342
+ }
343
+ return result.hit;
344
+ }
345
+
346
+ // src/shared/capabilities/shell.ts
347
+ import { execFile } from "child_process";
348
+ import { homedir } from "os";
349
+ import { join as join4 } from "path";
350
+ var TIMEOUT_MS = 5e3;
351
+ var AUTH_STATUS_TIMEOUT_MS = 5e3;
352
+ function buildDarwinVersionManagerPaths(home) {
353
+ return [
354
+ join4(home, ".nvm", "current", "bin"),
355
+ join4(home, ".volta", "bin"),
356
+ join4(home, ".local", "bin"),
357
+ join4(home, ".cargo", "bin"),
358
+ "/opt/homebrew/opt/node/bin"
359
+ ];
360
+ }
361
+ var STANDARD_PATHS_BY_PLATFORM = {
362
+ darwin: [
363
+ "/usr/bin",
364
+ "/usr/local/bin",
365
+ "/opt/homebrew/bin",
366
+ ...buildDarwinVersionManagerPaths(homedir())
367
+ ],
368
+ linux: ["/usr/bin", "/usr/local/bin"]
369
+ };
370
+ function augmentPath(currentPath, platform) {
371
+ const standardPaths = STANDARD_PATHS_BY_PLATFORM[platform];
372
+ if (!standardPaths) return currentPath;
373
+ const existing = currentPath.length > 0 ? currentPath.split(":") : [];
374
+ const existingSet = new Set(existing);
375
+ const toPrepend = standardPaths.filter((p) => !existingSet.has(p));
376
+ if (toPrepend.length === 0) return currentPath;
377
+ return existing.length > 0 ? `${toPrepend.join(":")}:${currentPath}` : toPrepend.join(":");
378
+ }
379
+ var augmentedPath = augmentPath(process.env.PATH ?? "", process.platform);
380
+ if (augmentedPath.length > 0) {
381
+ process.env.PATH = augmentedPath;
382
+ }
383
+ function run(command, args, cwd, timeoutMs = TIMEOUT_MS) {
384
+ return new Promise((resolve2, reject) => {
385
+ execFile(command, args, { timeout: timeoutMs, cwd }, (error, stdout) => {
386
+ if (error) {
387
+ reject(error);
388
+ return;
389
+ }
390
+ resolve2(stdout.trim());
391
+ });
392
+ });
393
+ }
394
+ function runWithTimeout(command, args, cwd, timeoutMs) {
395
+ return new Promise((resolve2, reject) => {
396
+ execFile(
397
+ command,
398
+ args,
399
+ { timeout: timeoutMs, cwd, maxBuffer: 10 * 1024 * 1024 },
400
+ (error, stdout) => {
401
+ if (error) {
402
+ reject(error);
403
+ return;
404
+ }
405
+ resolve2(stdout.trim());
406
+ }
407
+ );
408
+ });
409
+ }
410
+ var DIFF_TIMEOUT_MS = 15e3;
411
+ var MAX_DIFF_SIZE = 5e5;
412
+ function isMaxBufferError(err) {
413
+ return err instanceof Error && err.message.includes("maxBuffer");
414
+ }
415
+ var BUFFER_OVERFLOW_MSG = "... diff too large (exceeded buffer limit) ...\n";
416
+ function truncateDiff(result) {
417
+ if (result.length <= MAX_DIFF_SIZE) return result;
418
+ const boundary = result.lastIndexOf("\ndiff --git ", MAX_DIFF_SIZE);
419
+ const cutoff = boundary > 0 ? boundary : MAX_DIFF_SIZE;
420
+ return `${result.slice(0, cutoff)}
421
+
422
+ ... diff truncated (exceeds 500KB) ...
423
+ `;
424
+ }
425
+
426
+ // src/shared/capabilities/git-repo.ts
427
+ var GIT_PROBE_TIMEOUT_MS = 3e3;
428
+ var SLOW_PATH_LOG_THRESHOLD_MS = 2e3;
429
+ async function withProbeTimeout(op, timeoutMs) {
430
+ return new Promise((resolve2) => {
431
+ const timer = setTimeout(() => resolve2(null), timeoutMs);
432
+ timer.unref?.();
433
+ op.then(
434
+ (v) => {
435
+ clearTimeout(timer);
436
+ resolve2(v);
437
+ },
438
+ () => {
439
+ clearTimeout(timer);
440
+ resolve2(null);
441
+ }
442
+ );
443
+ });
444
+ }
445
+ var gitRepoCache = /* @__PURE__ */ new Map();
446
+ async function isGitRepo(cwd) {
447
+ const cached = gitRepoCache.get(cwd);
448
+ if (cached !== void 0) return cached;
449
+ try {
450
+ await runWithTimeout("git", ["rev-parse", "--git-dir"], cwd, TIMEOUT_MS);
451
+ gitRepoCache.set(cwd, true);
452
+ return true;
453
+ } catch {
454
+ gitRepoCache.set(cwd, false);
455
+ return false;
456
+ }
457
+ }
458
+ var ghAvailableCache = false;
459
+ async function isGhAvailable() {
460
+ if (ghAvailableCache) return true;
461
+ try {
462
+ await run("which", ["gh"]);
463
+ ghAvailableCache = true;
464
+ return true;
465
+ } catch {
466
+ return false;
467
+ }
468
+ }
469
+ var topLevelCache = /* @__PURE__ */ new Map();
470
+ async function getGitTopLevel(cwd) {
471
+ const cached = topLevelCache.get(cwd);
472
+ if (cached !== void 0) return cached;
473
+ const topLevel = (await runWithTimeout("git", ["rev-parse", "--show-toplevel"], cwd, TIMEOUT_MS)).trim();
474
+ topLevelCache.set(cwd, topLevel);
475
+ return topLevel;
476
+ }
477
+ function parseOwnerRepo(remoteUrl) {
478
+ const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
479
+ if (!match?.[1] || !match[2]) return null;
480
+ return { owner: match[1], repo: match[2] };
481
+ }
482
+ async function getRepoDefaultBranch(cwd) {
483
+ const head = await gitExecSafe(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
484
+ if (!head) return null;
485
+ return head.replace(/^origin\//, "") || null;
486
+ }
487
+ async function isAncestor(ancestor, descendant, cwd) {
488
+ try {
489
+ await runWithTimeout("git", ["merge-base", "--is-ancestor", ancestor, descendant], cwd, 5e3);
490
+ return true;
491
+ } catch {
492
+ return false;
493
+ }
494
+ }
495
+ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
496
+ "node_modules",
497
+ "Library",
498
+ "Applications",
499
+ "Pictures",
500
+ "Music",
501
+ "Movies",
502
+ "go",
503
+ ".Trash"
504
+ ]);
505
+ var MAX_DEPTH = 4;
506
+ var WORKTREE_DISCOVERY_CONCURRENCY = 8;
507
+ var REPO_METADATA_CONCURRENCY = 16;
508
+ async function mapLimit(items, limit, mapper) {
509
+ const results = new Array(items.length);
510
+ const indexedItems = items.map((item, index) => ({ item, index }));
511
+ let nextIndex = 0;
512
+ const workerCount = Math.min(Math.max(1, limit), items.length);
513
+ await Promise.all(
514
+ Array.from({ length: workerCount }, async () => {
515
+ for (; ; ) {
516
+ const next = indexedItems[nextIndex];
517
+ nextIndex += 1;
518
+ if (!next) return;
519
+ results[next.index] = await mapper(next.item, next.index);
520
+ }
521
+ })
522
+ );
523
+ return results;
524
+ }
525
+ async function findGitRepos(dir, depth = 0) {
526
+ if (depth > MAX_DEPTH) return [];
527
+ try {
528
+ const entries = await readdir(dir, { withFileTypes: true });
529
+ for (const entry of entries) {
530
+ if (entry.name === ".git") {
531
+ return [dir];
532
+ }
533
+ }
534
+ if (depth >= MAX_DEPTH) return [];
535
+ const promises = [];
536
+ for (const entry of entries) {
537
+ if (!entry.isDirectory()) continue;
538
+ if (entry.name.startsWith(".")) continue;
539
+ if (EXCLUDE_DIRS.has(entry.name)) continue;
540
+ promises.push(findGitRepos(join5(dir, entry.name), depth + 1));
541
+ }
542
+ const results = await Promise.all(promises);
543
+ return results.flat();
544
+ } catch {
545
+ return [];
546
+ }
547
+ }
548
+ async function readBranchFromHead(repoPath) {
549
+ const directHeadPath = join5(repoPath, ".git", "HEAD");
550
+ try {
551
+ const direct = await readFile3(directHeadPath, "utf8");
552
+ const trimmed = direct.trim();
553
+ if (trimmed.startsWith("ref: refs/heads/")) {
554
+ return trimmed.slice("ref: refs/heads/".length);
555
+ }
556
+ if (!trimmed.startsWith("gitdir:")) {
557
+ return "HEAD";
558
+ }
559
+ } catch {
560
+ }
561
+ try {
562
+ const headPath = await resolveHeadPath(repoPath);
563
+ if (!headPath) return "HEAD";
564
+ const contents = await readFile3(headPath, "utf8");
565
+ const trimmed2 = contents.trim();
566
+ if (trimmed2.startsWith("ref: refs/heads/")) {
567
+ return trimmed2.slice("ref: refs/heads/".length);
568
+ }
569
+ return "HEAD";
570
+ } catch {
571
+ return "HEAD";
572
+ }
573
+ }
574
+ async function getRepoMetadata(repoPath, cached) {
575
+ try {
576
+ if (cached) {
577
+ return {
578
+ path: repoPath,
579
+ name: basename(repoPath),
580
+ branch: cached.branch,
581
+ ...cached.remote !== null && { remote: cached.remote }
582
+ };
583
+ }
584
+ const [branchResult, remoteResult] = await Promise.all([
585
+ withProbeTimeout(readBranchFromHead(repoPath), GIT_PROBE_TIMEOUT_MS),
586
+ withProbeTimeout(
587
+ gitExecSafe(repoPath, ["remote", "get-url", "origin"]),
588
+ GIT_PROBE_TIMEOUT_MS
589
+ )
590
+ ]);
591
+ const branch = branchResult ?? "HEAD";
592
+ const remote = remoteResult ? remoteResult || void 0 : void 0;
593
+ return {
594
+ path: repoPath,
595
+ name: basename(repoPath),
596
+ branch,
597
+ ...remote && { remote }
598
+ };
599
+ } catch {
600
+ return null;
601
+ }
602
+ }
603
+ async function processRepoPaths(path, idx, fingerprints, prevCache, nextCache) {
604
+ const currentFingerprint = fingerprints[idx] ?? { headMtimeMs: null, configMtimeMs: null };
605
+ const prev = prevCache.get(path);
606
+ const cacheHit = prev !== void 0 && currentFingerprint.headMtimeMs !== null && isCacheValid(prev, currentFingerprint);
607
+ const start = Date.now();
608
+ const info = await withProbeTimeout(
609
+ getRepoMetadata(path, cacheHit ? prev : void 0),
610
+ GIT_PROBE_TIMEOUT_MS
611
+ );
612
+ const durationMs = Date.now() - start;
613
+ if (info === null && durationMs >= GIT_PROBE_TIMEOUT_MS * 0.9) {
614
+ logger.warn(
615
+ { event: "environments_walk_path_timed_out", path, durationMs },
616
+ "environments walk skipped path past per-path timeout"
617
+ );
618
+ } else if (durationMs >= SLOW_PATH_LOG_THRESHOLD_MS) {
619
+ logger.warn(
620
+ { event: "environments_walk_path_slow", path, durationMs },
621
+ "environments walk path resolved slowly"
622
+ );
623
+ }
624
+ if (info !== null && currentFingerprint.headMtimeMs !== null) {
625
+ nextCache.set(path, {
626
+ path,
627
+ branch: info.branch,
628
+ remote: info.remote ?? null,
629
+ headMtimeMs: currentFingerprint.headMtimeMs,
630
+ configMtimeMs: currentFingerprint.configMtimeMs,
631
+ cachedAt: cacheHit && prev ? prev.cachedAt : Date.now()
632
+ });
633
+ }
634
+ return info;
635
+ }
636
+ async function detectEnvironments(lastKnown) {
637
+ const { homedir: homedir2 } = await import("os");
638
+ const cachedPaths = getCachedRepoPaths();
639
+ let repoPaths;
640
+ let worktreePaths;
641
+ if (cachedPaths) {
642
+ repoPaths = cachedPaths.repos;
643
+ worktreePaths = cachedPaths.worktrees;
644
+ } else {
645
+ try {
646
+ repoPaths = await findGitRepos(homedir2());
647
+ } catch (err) {
648
+ if (lastKnown && lastKnown.length > 0) {
649
+ const { logger: logger2 } = await import("./logger-QHPTO22N.js");
650
+ logger2.warn(
651
+ { err, lastKnownCount: lastKnown.length },
652
+ "detectEnvironments findGitRepos threw \u2014 preserving lastKnown"
653
+ );
654
+ return lastKnown;
655
+ }
656
+ return [];
657
+ }
658
+ if (repoPaths.length === 0 && lastKnown && lastKnown.length > 0) {
659
+ const { logger: logger2 } = await import("./logger-QHPTO22N.js");
660
+ logger2.warn(
661
+ { lastKnownCount: lastKnown.length },
662
+ "detectEnvironments walk empty but lastKnown non-empty \u2014 preserving lastKnown"
663
+ );
664
+ return lastKnown;
665
+ }
666
+ worktreePaths = await discoverWorktrees(repoPaths);
667
+ setCachedRepoPaths(repoPaths, worktreePaths);
668
+ }
669
+ const allPaths = [.../* @__PURE__ */ new Set([...repoPaths, ...worktreePaths])];
670
+ const prevCache = await loadCache();
671
+ const fingerprints = await mapLimit(allPaths, REPO_METADATA_CONCURRENCY, getGitCacheFingerprint);
672
+ const nextCache = /* @__PURE__ */ new Map();
673
+ const resolved = await mapLimit(
674
+ allPaths,
675
+ REPO_METADATA_CONCURRENCY,
676
+ (path, idx) => processRepoPaths(path, idx, fingerprints, prevCache, nextCache)
677
+ );
678
+ await saveCache(nextCache);
679
+ return resolved.filter((info) => info !== null);
680
+ }
681
+ async function discoverWorktrees(repoPaths) {
682
+ const results = await mapLimit(
683
+ repoPaths,
684
+ WORKTREE_DISCOVERY_CONCURRENCY,
685
+ async (repoPath) => {
686
+ try {
687
+ return { status: "fulfilled", value: await listLinkedWorktrees(repoPath) };
688
+ } catch (reason) {
689
+ return { status: "rejected", reason };
690
+ }
691
+ }
692
+ );
693
+ return dedupePerRepoWorktreeOutputs(results);
694
+ }
695
+ function dedupePerRepoWorktreeOutputs(results) {
696
+ const seen = /* @__PURE__ */ new Set();
697
+ const out = [];
698
+ for (const r of results) {
699
+ if (r.status !== "fulfilled") continue;
700
+ for (const wt of r.value) {
701
+ if (seen.has(wt)) continue;
702
+ seen.add(wt);
703
+ out.push(wt);
704
+ }
705
+ }
706
+ return out;
707
+ }
708
+ async function listLinkedWorktrees(repoPath) {
709
+ const gitWorktreesDir = join5(repoPath, ".git", "worktrees");
710
+ let entries;
711
+ try {
712
+ entries = await readdir(gitWorktreesDir);
713
+ } catch {
714
+ return [];
715
+ }
716
+ const paths = await Promise.all(
717
+ entries.map(async (name) => {
718
+ try {
719
+ const gitdirFile = join5(gitWorktreesDir, name, "gitdir");
720
+ const content = await readFile3(gitdirFile, "utf8");
721
+ const wtGitPath = content.trim();
722
+ if (wtGitPath.endsWith("/.git")) {
723
+ return wtGitPath.slice(0, -"/.git".length);
724
+ }
725
+ return null;
726
+ } catch {
727
+ return null;
728
+ }
729
+ })
730
+ );
731
+ return paths.filter((p) => p !== null && p !== repoPath);
732
+ }
733
+ function parseWorktreeListOutput(output, repoPath) {
734
+ const paths = [];
735
+ for (const line of output.split("\n")) {
736
+ if (line.startsWith("worktree ")) {
737
+ const wt = line.slice("worktree ".length).trim();
738
+ if (wt && wt !== repoPath) paths.push(wt);
739
+ }
740
+ }
741
+ return paths;
742
+ }
743
+ var _testing = {
744
+ parseWorktreeListOutput,
745
+ dedupePerRepoWorktreeOutputs,
746
+ listLinkedWorktrees,
747
+ readBranchFromHead,
748
+ mapLimit,
749
+ withProbeTimeout,
750
+ GIT_PROBE_TIMEOUT_MS,
751
+ SLOW_PATH_LOG_THRESHOLD_MS,
752
+ resetGhAvailableCache: () => {
753
+ ghAvailableCache = false;
754
+ }
755
+ };
756
+
757
+ export {
758
+ TIMEOUT_MS,
759
+ AUTH_STATUS_TIMEOUT_MS,
760
+ run,
761
+ runWithTimeout,
762
+ DIFF_TIMEOUT_MS,
763
+ isMaxBufferError,
764
+ BUFFER_OVERFLOW_MSG,
765
+ truncateDiff,
766
+ isEnoent,
767
+ isEexist,
768
+ isCorruptionError,
769
+ atomicWriteFile,
770
+ findProjectRoot,
771
+ addWorktreeToCache,
772
+ removeWorktreeFromCache,
773
+ hydrateAndLogRepoPathsCache,
774
+ gitRepoCache,
775
+ isGitRepo,
776
+ isGhAvailable,
777
+ getGitTopLevel,
778
+ parseOwnerRepo,
779
+ getRepoDefaultBranch,
780
+ isAncestor,
781
+ findGitRepos,
782
+ getRepoMetadata,
783
+ detectEnvironments,
784
+ discoverWorktrees,
785
+ _testing
786
+ };
787
+ //# sourceMappingURL=chunk-YZ3Z3ZYI.js.map