@dotobokuri/fleet-console 1.23.0 → 1.24.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 (69) hide show
  1. package/AGENTS.md +41 -257
  2. package/README.md +8 -0
  3. package/dist/cli.d.ts +11 -1
  4. package/dist/cli.mjs +1738 -453
  5. package/dist/client/assets/{_baseUniq-BjOe5yYF.js → _baseUniq-yooYbTgN.js} +1 -1
  6. package/dist/client/assets/{arc-bFUuO3jf.js → arc-D4mme5Ts.js} +1 -1
  7. package/dist/client/assets/{architectureDiagram-Q4EWVU46-Ba7nimQq.js → architectureDiagram-Q4EWVU46-BAJOviEX.js} +1 -1
  8. package/dist/client/assets/{blockDiagram-DXYQGD6D-CQGf47hl.js → blockDiagram-DXYQGD6D-DuObpxQ3.js} +1 -1
  9. package/dist/client/assets/{c4Diagram-AHTNJAMY-DZF_cAK4.js → c4Diagram-AHTNJAMY-BOT-SD34.js} +1 -1
  10. package/dist/client/assets/channel-TZYyAq1v.js +1 -0
  11. package/dist/client/assets/{chunk-4BX2VUAB-DjUUHSQ5.js → chunk-4BX2VUAB-BF_aMTes.js} +1 -1
  12. package/dist/client/assets/{chunk-4TB4RGXK-Dvqti8bd.js → chunk-4TB4RGXK-M4brBcnD.js} +1 -1
  13. package/dist/client/assets/{chunk-55IACEB6-2BxiBvkp.js → chunk-55IACEB6-CrG-4b2Z.js} +1 -1
  14. package/dist/client/assets/{chunk-EDXVE4YY-CqCjol5O.js → chunk-EDXVE4YY-BxDSygH1.js} +1 -1
  15. package/dist/client/assets/{chunk-FMBD7UC4-eoCfIbly.js → chunk-FMBD7UC4-Bh7MEX7E.js} +1 -1
  16. package/dist/client/assets/{chunk-OYMX7WX6-CoC86V_1.js → chunk-OYMX7WX6-DBnBwxer.js} +1 -1
  17. package/dist/client/assets/{chunk-QZHKN3VN-CJQA5mlQ.js → chunk-QZHKN3VN-bjAogQwW.js} +1 -1
  18. package/dist/client/assets/{chunk-YZCP3GAM-C5C4kbLu.js → chunk-YZCP3GAM-l4FGlk_c.js} +1 -1
  19. package/dist/client/assets/classDiagram-6PBFFD2Q-Dh6hZUl4.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-HSJHXN6E-Dh6hZUl4.js +1 -0
  21. package/dist/client/assets/clone-DgWGnVWH.js +1 -0
  22. package/dist/client/assets/{cose-bilkent-S5V4N54A-CahuunpL.js → cose-bilkent-S5V4N54A-Bm5QA4zo.js} +1 -1
  23. package/dist/client/assets/{dagre-KV5264BT-Cbv5kSi5.js → dagre-KV5264BT-PzXQN-5Q.js} +1 -1
  24. package/dist/client/assets/{diagram-5BDNPKRD-CNm40ZGB.js → diagram-5BDNPKRD-KhaAKPtX.js} +1 -1
  25. package/dist/client/assets/{diagram-G4DWMVQ6-DrlGNT3i.js → diagram-G4DWMVQ6-DBbjfiR8.js} +1 -1
  26. package/dist/client/assets/{diagram-MMDJMWI5-r8Y2oqmw.js → diagram-MMDJMWI5-QWro7PRX.js} +1 -1
  27. package/dist/client/assets/{diagram-TYMM5635-CMzEhXup.js → diagram-TYMM5635-B9LqWrbF.js} +1 -1
  28. package/dist/client/assets/{erDiagram-SMLLAGMA-UFjUUMPz.js → erDiagram-SMLLAGMA-b0VkMMN4.js} +1 -1
  29. package/dist/client/assets/{flowDiagram-DWJPFMVM-Dd1STmYc.js → flowDiagram-DWJPFMVM-edxBAbe6.js} +1 -1
  30. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-CHHU0hVd.js → ganttDiagram-T4ZO3ILL-ks2Gg1_V.js} +1 -1
  31. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-vj2HukAF.js → gitGraphDiagram-UUTBAWPF-DymUyYNl.js} +1 -1
  32. package/dist/client/assets/{graph-DhjfEbEb.js → graph-B-B_mqfh.js} +1 -1
  33. package/dist/client/assets/index-BgLzfg-v.css +1 -0
  34. package/dist/client/assets/index-pDnb-q6f.js +380 -0
  35. package/dist/client/assets/{infoDiagram-42DDH7IO-CC7AEgDm.js → infoDiagram-42DDH7IO-B5iLIYAc.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-DlRw2B9G.js → ishikawaDiagram-UXIWVN3A-DnztLLn7.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-VCZTEJTY-BbRXANKQ.js → journeyDiagram-VCZTEJTY-BKDRfH1Y.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-6JOO6SKY-DtvdCxGN.js → kanban-definition-6JOO6SKY-DPB3Phvr.js} +1 -1
  39. package/dist/client/assets/{layout-Dg5cvw5v.js → layout-BEeRhYTG.js} +1 -1
  40. package/dist/client/assets/{linear-BvVlQLqF.js → linear-QzmfWKSq.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-DzrV8sC-.js → mermaid.core-BVka5dgm.js} +4 -4
  42. package/dist/client/assets/{min-DNGQ1Ciz.js → min-CfXk9kSN.js} +1 -1
  43. package/dist/client/assets/{mindmap-definition-QFDTVHPH-DxJCNZbg.js → mindmap-definition-QFDTVHPH-tGrGhr6Z.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-DEJITSTG-B75XHG-s.js → pieDiagram-DEJITSTG-ZRBqRiRK.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-CL4yoF0m.js → quadrantDiagram-34T5L4WZ-VCINZgct.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-MS252O5E-kGV4vcr1.js → requirementDiagram-MS252O5E-DFLzADh3.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-XADWPNL6-DoA4u7wI.js → sankeyDiagram-XADWPNL6-BbouN8Ji.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-FGHM5R23-ZpNDf1bS.js → sequenceDiagram-FGHM5R23-B58_LF0R.js} +1 -1
  49. package/dist/client/assets/{stateDiagram-FHFEXIEX-BzgKcVAG.js → stateDiagram-FHFEXIEX-pk-OHHuL.js} +1 -1
  50. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-xsN122JA.js +1 -0
  51. package/dist/client/assets/{timeline-definition-GMOUNBTQ-B_DkUDsD.js → timeline-definition-GMOUNBTQ-4jQdCvaj.js} +1 -1
  52. package/dist/client/assets/{vennDiagram-DHZGUBPP-MCUnnsKb.js → vennDiagram-DHZGUBPP-CUr1fH6N.js} +1 -1
  53. package/dist/client/assets/{wardley-RL74JXVD-D_JAMSxj.js → wardley-RL74JXVD-D0cfvvNA.js} +1 -1
  54. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-BHCwZGLq.js → wardleyDiagram-NUSXRM2D-D0l_Rp_Z.js} +1 -1
  55. package/dist/client/assets/{xychartDiagram-5P7HB3ND-FbGwbD3V.js → xychartDiagram-5P7HB3ND-BdR8OEuJ.js} +1 -1
  56. package/dist/client/index.html +3 -3
  57. package/dist/desktop-protocol.d.ts +49 -0
  58. package/dist/desktop-protocol.mjs +99 -0
  59. package/dist/fleet-plugins/diff/routes.mjs +124 -225
  60. package/dist/fleet-plugins/skills/routes.mjs +218 -82
  61. package/dist/fleet-plugins/terminal/routes.mjs +190 -176
  62. package/package.json +7 -2
  63. package/dist/client/assets/channel-D2BahVp1.js +0 -1
  64. package/dist/client/assets/classDiagram-6PBFFD2Q-DsDAVLhS.js +0 -1
  65. package/dist/client/assets/classDiagram-v2-HSJHXN6E-DsDAVLhS.js +0 -1
  66. package/dist/client/assets/clone-CdZy2Xzf.js +0 -1
  67. package/dist/client/assets/index-Dq9EKm2G.css +0 -1
  68. package/dist/client/assets/index-DtzbOdd7.js +0 -380
  69. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-yRK5hzZ4.js +0 -1
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'child_process';
3
3
  import 'fs';
4
- import path3 from 'path';
4
+ import path2 from 'path';
5
5
  import fs from 'fs/promises';
6
6
 
7
7
  // sdk/plugin/node.ts
8
8
  function definePlugin(definition) {
9
9
  return definition;
10
10
  }
11
- function registerRouter(ctx, path4, handler) {
12
- ctx.registerRouter(normalizePath(path4), handler);
11
+ function registerRouter(ctx, path3, handler) {
12
+ ctx.registerRouter(normalizePath(path3), handler);
13
13
  }
14
- function normalizePath(path4) {
15
- if (path4.length === 0 || path4 === "/") return "/";
16
- return path4;
14
+ function normalizePath(path3) {
15
+ if (path3.length === 0 || path3 === "/") return "/";
16
+ return path3;
17
17
  }
18
18
 
19
19
  // ../../packages/core-process/src/hidden.ts
@@ -132,7 +132,7 @@ function parseDiffFileList(nameStatusOutput, numstatOutput) {
132
132
  return files;
133
133
  }
134
134
  async function fetchUntrackedFiles(cwd) {
135
- const result = await runGit(["ls-files", "--others", "--exclude-standard"], { cwd });
135
+ const result = await runGit(["ls-files", "--others", "--exclude-standard", "--", "."], { cwd });
136
136
  return result.stdout.split("\n").filter((p) => p.trim()).map((p) => ({
137
137
  path: p,
138
138
  status: "U",
@@ -140,6 +140,9 @@ async function fetchUntrackedFiles(cwd) {
140
140
  deletions: 0
141
141
  }));
142
142
  }
143
+ async function ensureGitRepository(cwd) {
144
+ await runGit(["rev-parse", "--is-inside-work-tree"], { cwd });
145
+ }
143
146
  function isNoHeadError(error) {
144
147
  if (!(error instanceof GitExecutorError)) return false;
145
148
  if (error.code !== "non_zero_exit") return false;
@@ -147,9 +150,9 @@ function isNoHeadError(error) {
147
150
  }
148
151
  async function resolveGitCwd(theaterPath, rawSubPath) {
149
152
  if (rawSubPath === "") return { gitCwd: theaterPath };
150
- const resolvedSub = path3.resolve(theaterPath, rawSubPath);
151
- if (!resolvedSub.startsWith(theaterPath + path3.sep) && resolvedSub !== theaterPath) return null;
152
- if (path3.normalize(rawSubPath).startsWith("..")) return null;
153
+ const resolvedSub = path2.resolve(theaterPath, rawSubPath);
154
+ if (!resolvedSub.startsWith(theaterPath + path2.sep) && resolvedSub !== theaterPath) return null;
155
+ if (path2.normalize(rawSubPath).startsWith("..")) return null;
153
156
  let realTheater;
154
157
  let realSub;
155
158
  try {
@@ -160,7 +163,7 @@ async function resolveGitCwd(theaterPath, rawSubPath) {
160
163
  } catch {
161
164
  return null;
162
165
  }
163
- const norm = realTheater.endsWith(path3.sep) ? realTheater : realTheater + path3.sep;
166
+ const norm = realTheater.endsWith(path2.sep) ? realTheater : realTheater + path2.sep;
164
167
  if (realSub !== realTheater && !realSub.startsWith(norm)) return null;
165
168
  return { gitCwd: resolvedSub };
166
169
  }
@@ -196,20 +199,21 @@ async function handleDiffChanged(req, res, ctx) {
196
199
  }
197
200
  const { gitCwd } = cwdResult;
198
201
  try {
202
+ await ensureGitRepository(gitCwd);
199
203
  let files;
200
204
  let truncated = false;
201
205
  try {
202
206
  const [nameStatusResult, numstatResult] = await Promise.all([
203
- runGit(["diff", "HEAD", "--name-status", "--diff-filter=MADR"], { cwd: gitCwd }),
204
- runGit(["diff", "HEAD", "--numstat", "--diff-filter=MADR"], { cwd: gitCwd })
207
+ runGit(["diff", "HEAD", "--relative", "--name-status", "--diff-filter=MADR", "--", "."], { cwd: gitCwd }),
208
+ runGit(["diff", "HEAD", "--relative", "--numstat", "--diff-filter=MADR", "--", "."], { cwd: gitCwd })
205
209
  ]);
206
210
  files = parseDiffFileList(nameStatusResult.stdout, numstatResult.stdout);
207
211
  truncated = nameStatusResult.truncated || numstatResult.truncated;
208
212
  } catch (err) {
209
213
  if (!isNoHeadError(err)) throw err;
210
214
  const [nsResult, nsNumstat] = await Promise.all([
211
- runGit(["diff", "--cached", "--name-status", "--diff-filter=MADR"], { cwd: gitCwd }),
212
- runGit(["diff", "--cached", "--numstat", "--diff-filter=MADR"], { cwd: gitCwd })
215
+ runGit(["diff", "--cached", "--relative", "--name-status", "--diff-filter=MADR", "--", "."], { cwd: gitCwd }),
216
+ runGit(["diff", "--cached", "--relative", "--numstat", "--diff-filter=MADR", "--", "."], { cwd: gitCwd })
213
217
  ]);
214
218
  files = parseDiffFileList(nsResult.stdout, nsNumstat.stdout);
215
219
  truncated = nsResult.truncated || nsNumstat.truncated;
@@ -269,12 +273,12 @@ async function handleDiffFile(req, res, ctx) {
269
273
  }
270
274
  const { gitCwd } = cwdResult;
271
275
  const rawFilePath = body.filePath;
272
- const resolvedFilePath = path3.resolve(gitCwd, rawFilePath);
273
- if (!resolvedFilePath.startsWith(gitCwd + path3.sep) && resolvedFilePath !== gitCwd) {
276
+ const resolvedFilePath = path2.resolve(gitCwd, rawFilePath);
277
+ if (!resolvedFilePath.startsWith(gitCwd + path2.sep) && resolvedFilePath !== gitCwd) {
274
278
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
275
279
  return;
276
280
  }
277
- const relativePath = path3.normalize(rawFilePath);
281
+ const relativePath = path2.normalize(rawFilePath);
278
282
  if (relativePath.startsWith("..")) {
279
283
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
280
284
  return;
@@ -292,12 +296,12 @@ async function handleDiffFile(req, res, ctx) {
292
296
  ctx.host.http.writeJson(res, 404, { error: "file_not_found" });
293
297
  return;
294
298
  }
295
- if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path3.sep)) {
299
+ if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path2.sep)) {
296
300
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
297
301
  return;
298
302
  }
299
303
  const result2 = await runGit(
300
- ["diff", "--no-index", "--unified=3", "--", "/dev/null", relativePath],
304
+ ["diff", "--no-index", "--relative", "--unified=3", "--", "/dev/null", relativePath],
301
305
  { cwd: gitCwd, allowExitCodes: [1] }
302
306
  );
303
307
  ctx.host.http.writeJson(res, 200, { content: result2.stdout, truncated: result2.truncated });
@@ -308,7 +312,7 @@ async function handleDiffFile(req, res, ctx) {
308
312
  fs.realpath(gitCwd),
309
313
  fs.realpath(resolvedFilePath)
310
314
  ]);
311
- if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path3.sep)) {
315
+ if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path2.sep)) {
312
316
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
313
317
  return;
314
318
  }
@@ -316,10 +320,10 @@ async function handleDiffFile(req, res, ctx) {
316
320
  }
317
321
  let result;
318
322
  try {
319
- result = await runGit(["diff", "HEAD", "--unified=3", "--", relativePath], { cwd: gitCwd });
323
+ result = await runGit(["diff", "HEAD", "--relative", "--unified=3", "--", relativePath], { cwd: gitCwd });
320
324
  } catch (err) {
321
325
  if (!isNoHeadError(err)) throw err;
322
- result = await runGit(["diff", "--cached", "--unified=3", "--", relativePath], { cwd: gitCwd });
326
+ result = await runGit(["diff", "--cached", "--relative", "--unified=3", "--", relativePath], { cwd: gitCwd });
323
327
  }
324
328
  ctx.host.http.writeJson(res, 200, { content: result.stdout, truncated: result.truncated });
325
329
  } catch (error) {
@@ -377,7 +381,7 @@ async function handleDiffCommit(req, res, ctx) {
377
381
  }
378
382
  const { gitCwd } = cwdResult;
379
383
  try {
380
- const result = await runGit(["show", ref, "--unified=3", "--format="], { cwd: gitCwd });
384
+ const result = await runGit(["show", ref, "--relative", "--unified=3", "--format=", "--", "."], { cwd: gitCwd });
381
385
  ctx.host.http.writeJson(res, 200, { content: result.stdout, ...result.truncated ? { truncated: true } : {} });
382
386
  } catch (error) {
383
387
  if (error instanceof GitExecutorError) {
@@ -391,8 +395,7 @@ async function handleDiffCommit(req, res, ctx) {
391
395
  throw error;
392
396
  }
393
397
  }
394
-
395
- // ../fleet-plugins/diff/server/log.ts
398
+ var WORKTREE_SHA_RE = /^[0-9a-f]{40}$/;
396
399
  function isPlainObject3(value) {
397
400
  return typeof value === "object" && value !== null && !Array.isArray(value);
398
401
  }
@@ -410,30 +413,96 @@ function parseLogOutput(stdout) {
410
413
  const subject = fields[2] ?? "";
411
414
  const authorName = fields[3] ?? "";
412
415
  const relTime = fields[4] ?? "";
413
- const refsRaw = fields[5] ?? "";
414
- const parentsRaw = fields[6] ?? "";
416
+ const authorAt = Number.parseInt(fields[5] ?? "0", 10);
417
+ const refsRaw = fields[6] ?? "";
418
+ const parentsRaw = fields[7] ?? "";
415
419
  if (!fullHash) continue;
416
420
  const refs = refsRaw.split(",").map((r) => r.trim()).filter(Boolean);
417
421
  const parents = parentsRaw.split(" ").map((p) => p.trim()).filter(Boolean);
418
- let additions = 0;
419
- let deletions = 0;
420
- for (const line of lines.slice(1)) {
421
- if (!line.trim()) continue;
422
- const parts = line.split(" ");
423
- if (parts.length < 2) continue;
424
- const a = parseInt(parts[0] ?? "0", 10);
425
- const d = parseInt(parts[1] ?? "0", 10);
426
- if (!isNaN(a)) additions += a;
427
- if (!isNaN(d)) deletions += d;
428
- }
429
- commits.push({ shortHash, fullHash, subject, authorName, relTime, refs, parents, additions, deletions });
422
+ commits.push({
423
+ shortHash,
424
+ fullHash,
425
+ subject,
426
+ authorName,
427
+ relTime,
428
+ authorAt: Number.isFinite(authorAt) ? authorAt : 0,
429
+ refs,
430
+ parents,
431
+ onHead: true
432
+ });
430
433
  }
431
434
  return commits;
432
435
  }
436
+ async function parseWorktreePorcelain(stdout, currentWorktreePath) {
437
+ const worktrees = [];
438
+ let worktreePath = null;
439
+ let sha = "";
440
+ let branch = null;
441
+ let prunable = false;
442
+ const pushCurrent = () => {
443
+ if (!worktreePath || !sha || prunable || /^0+$/.test(sha)) return;
444
+ worktrees.push({
445
+ sha,
446
+ branch,
447
+ worktreePath
448
+ });
449
+ };
450
+ for (const line of stdout.split("\n")) {
451
+ if (line.startsWith("worktree ")) {
452
+ pushCurrent();
453
+ worktreePath = line.slice(9);
454
+ sha = "";
455
+ branch = null;
456
+ prunable = false;
457
+ } else if (line.startsWith("HEAD ")) {
458
+ sha = line.slice(5);
459
+ } else if (line.startsWith("branch ")) {
460
+ const ref = line.slice(7);
461
+ branch = ref.startsWith("refs/heads/") ? ref.slice(11) : ref;
462
+ } else if (line === "prunable" || line.startsWith("prunable ")) {
463
+ prunable = true;
464
+ }
465
+ }
466
+ pushCurrent();
467
+ const normalizedCurrentWorktreePath = await normalizeWorktreePath(currentWorktreePath);
468
+ return Promise.all(worktrees.map(async ({ worktreePath: worktreePath2, ...checkout }) => ({
469
+ ...checkout,
470
+ isCurrent: normalizedCurrentWorktreePath !== "" && await normalizeWorktreePath(worktreePath2) === normalizedCurrentWorktreePath
471
+ })));
472
+ }
473
+ function annotateHeadReachability(commits, revListStdout) {
474
+ const reachable = new Set(revListStdout.split("\n").map((line) => line.trim()).filter(Boolean));
475
+ if (reachable.size === 0) return commits;
476
+ return commits.map((commit) => ({ ...commit, onHead: reachable.has(commit.fullHash) }));
477
+ }
433
478
  function isNoHeadError2(error) {
434
479
  if (!(error instanceof GitExecutorError)) return false;
435
480
  if (error.code !== "non_zero_exit") return false;
436
- return error.stderr.includes("unknown revision") || error.stderr.includes("bad revision");
481
+ return error.stderr.includes("unknown revision") || error.stderr.includes("bad revision") || error.stderr.includes("does not have any commits");
482
+ }
483
+ async function normalizeWorktreePath(worktreePath) {
484
+ if (!worktreePath) return "";
485
+ try {
486
+ return await fs.realpath(worktreePath);
487
+ } catch {
488
+ return worktreePath;
489
+ }
490
+ }
491
+ async function readHeadRevList(gitCwd) {
492
+ try {
493
+ return (await runGit(["rev-list", "-n", "1000", "HEAD"], { cwd: gitCwd })).stdout;
494
+ } catch (error) {
495
+ if (error instanceof GitExecutorError) return "";
496
+ throw error;
497
+ }
498
+ }
499
+ async function readCurrentWorktreePath(gitCwd) {
500
+ try {
501
+ return (await runGit(["rev-parse", "--show-toplevel"], { cwd: gitCwd })).stdout.trim();
502
+ } catch (error) {
503
+ if (error instanceof GitExecutorError) return "";
504
+ throw error;
505
+ }
437
506
  }
438
507
  async function handleDiffLog(req, res, ctx) {
439
508
  if (req.method !== "POST") {
@@ -467,16 +536,25 @@ async function handleDiffLog(req, res, ctx) {
467
536
  }
468
537
  const { gitCwd } = cwdResult;
469
538
  try {
539
+ const [worktrees, currentWorktreePath, headRevList] = await Promise.all([
540
+ runGit(["worktree", "list", "--porcelain"], { cwd: gitCwd }),
541
+ readCurrentWorktreePath(gitCwd),
542
+ readHeadRevList(gitCwd)
543
+ ]);
544
+ const checkouts = await parseWorktreePorcelain(worktrees.stdout, currentWorktreePath);
545
+ const worktreeRevs = [...new Set(checkouts.map((checkout) => checkout.sha))].filter((sha) => WORKTREE_SHA_RE.test(sha) && !/^0+$/.test(sha));
546
+ const headRevs = headRevList ? ["HEAD"] : [];
470
547
  const result = await runGit(
471
- ["log", "-n", "50", "--numstat", "--pretty=format:%x1e%H%x00%h%x00%s%x00%an%x00%ar%x00%D%x00%P"],
548
+ // --all은 refs/stash·refs/notes까지 그래프에 유입시키므로 브랜치/태그/원격 + 현재 HEAD + 워크트리 HEAD로 한정한다
549
+ ["log", "--branches", "--tags", "--remotes", "--date-order", "-n", "200", "--relative", "--decorate=full", "--pretty=format:%x1e%H%x00%h%x00%s%x00%an%x00%ar%x00%at%x00%D%x00%P", ...headRevs, ...worktreeRevs, "--", "."],
472
550
  { cwd: gitCwd }
473
551
  );
474
- const commits = parseLogOutput(result.stdout);
475
- ctx.host.http.writeJson(res, 200, { commits, ...result.truncated ? { truncated: true } : {} });
552
+ const commits = annotateHeadReachability(parseLogOutput(result.stdout), headRevList);
553
+ ctx.host.http.writeJson(res, 200, { commits, checkouts, ...result.truncated ? { truncated: true } : {} });
476
554
  } catch (error) {
477
555
  if (error instanceof GitExecutorError) {
478
556
  if (error.code === "no_git_repo") {
479
- ctx.host.http.writeJson(res, 200, { commits: [] });
557
+ ctx.host.http.writeJson(res, 200, { commits: [], checkouts: [] });
480
558
  return;
481
559
  }
482
560
  if (error.code === "git_unavailable") {
@@ -484,7 +562,7 @@ async function handleDiffLog(req, res, ctx) {
484
562
  return;
485
563
  }
486
564
  if (isNoHeadError2(error)) {
487
- ctx.host.http.writeJson(res, 200, { commits: [] });
565
+ ctx.host.http.writeJson(res, 200, { commits: [], checkouts: [] });
488
566
  return;
489
567
  }
490
568
  ctx.host.http.writeJson(res, 500, { error: "git_failed" });
@@ -493,190 +571,11 @@ async function handleDiffLog(req, res, ctx) {
493
571
  throw error;
494
572
  }
495
573
  }
496
- var REPOS_CAP = 200;
497
- var HARD_CAP_DEPTH = 8;
498
- var DEFAULT_DEPTH = 3;
499
- function isPlainObject4(value) {
500
- return typeof value === "object" && value !== null && !Array.isArray(value);
501
- }
502
- async function resolveRepoBranch(repoDir) {
503
- try {
504
- const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: repoDir });
505
- const branch = result.stdout.trim();
506
- if (branch === "HEAD") {
507
- const sha = await runGit(["rev-parse", "--short", "HEAD"], { cwd: repoDir });
508
- return sha.stdout.trim() || "HEAD";
509
- }
510
- return branch || "HEAD";
511
- } catch {
512
- return "unknown";
513
- }
514
- }
515
- async function detectWorktreeParent(dir, gitFileContent) {
516
- const match = gitFileContent.trim().match(/^gitdir:\s*(.+)$/m);
517
- if (!match) return null;
518
- const rawPointer = match[1].trim();
519
- const resolvedGitdir = path3.normalize(
520
- path3.isAbsolute(rawPointer) ? rawPointer : path3.resolve(dir, rawPointer)
521
- );
522
- if (resolvedGitdir.includes(`${path3.sep}modules${path3.sep}`)) {
523
- return null;
524
- }
525
- const worktreesSegment = `${path3.sep}.git${path3.sep}worktrees${path3.sep}`;
526
- const wtIdx = resolvedGitdir.indexOf(worktreesSegment);
527
- if (wtIdx === -1) {
528
- return null;
529
- }
530
- const parentAbsRaw = resolvedGitdir.slice(0, wtIdx);
531
- let parentAbs;
532
- try {
533
- parentAbs = await fs.realpath(parentAbsRaw);
534
- } catch {
535
- parentAbs = path3.normalize(parentAbsRaw);
536
- }
537
- return { isWorktree: true, parentAbs };
538
- }
539
- async function scanRepos(theaterPath, realTheaterPath, dir, currentDepth, maxDepth, repos, cap = REPOS_CAP) {
540
- if (repos.length >= cap) return true;
541
- const gitPath = path3.join(dir, ".git");
542
- try {
543
- const gitStat = await fs.stat(gitPath);
544
- const relPath = path3.relative(theaterPath, dir);
545
- const name = relPath === "" ? path3.basename(theaterPath) : path3.basename(dir);
546
- const branch = await resolveRepoBranch(dir);
547
- if (gitStat.isFile()) {
548
- const content = await fs.readFile(gitPath, "utf8");
549
- const worktreeResult = await detectWorktreeParent(dir, content);
550
- if (worktreeResult) {
551
- repos.push({ relPath, name, branch, isWorktree: true, _wtParentAbs: worktreeResult.parentAbs });
552
- } else {
553
- repos.push({ relPath, name, branch });
554
- }
555
- } else {
556
- repos.push({ relPath, name, branch });
557
- }
558
- if (repos.length >= cap) return true;
559
- } catch {
560
- }
561
- if (currentDepth >= maxDepth) return false;
562
- let dirHandle = null;
563
- try {
564
- dirHandle = await fs.opendir(dir);
565
- try {
566
- let dirent = await dirHandle.read();
567
- while (dirent !== null) {
568
- if (repos.length >= cap) return true;
569
- if (dirent.isDirectory()) {
570
- const childName = dirent.name;
571
- if (childName === "node_modules" || childName === ".git") {
572
- dirent = await dirHandle.read();
573
- continue;
574
- }
575
- const childPath = path3.join(dir, childName);
576
- let realChildPath;
577
- try {
578
- realChildPath = await fs.realpath(childPath);
579
- } catch {
580
- dirent = await dirHandle.read();
581
- continue;
582
- }
583
- const normalizedReal = realTheaterPath.endsWith(path3.sep) ? realTheaterPath : realTheaterPath + path3.sep;
584
- if (realChildPath !== realTheaterPath && !realChildPath.startsWith(normalizedReal)) {
585
- dirent = await dirHandle.read();
586
- continue;
587
- }
588
- const truncated = await scanRepos(theaterPath, realTheaterPath, childPath, currentDepth + 1, maxDepth, repos, cap);
589
- if (truncated) return true;
590
- }
591
- dirent = await dirHandle.read();
592
- }
593
- return false;
594
- } finally {
595
- await dirHandle.close();
596
- }
597
- } catch {
598
- if (dirHandle) {
599
- try {
600
- await dirHandle.close();
601
- } catch {
602
- }
603
- }
604
- return false;
605
- }
606
- }
607
- function resolveWorktreeParents(raw, realTheaterPath) {
608
- const absToRelPath = /* @__PURE__ */ new Map();
609
- for (const entry of raw) {
610
- const absDir = path3.normalize(
611
- entry.relPath === "" ? realTheaterPath : path3.join(realTheaterPath, entry.relPath)
612
- );
613
- absToRelPath.set(absDir, entry.relPath);
614
- }
615
- return raw.map((entry) => {
616
- const { _wtParentAbs, ...rest } = entry;
617
- if (_wtParentAbs && entry.isWorktree) {
618
- const normalizedParent = path3.normalize(_wtParentAbs);
619
- const parentRelPath = absToRelPath.get(normalizedParent);
620
- if (parentRelPath !== void 0) {
621
- return { ...rest, worktreeOf: parentRelPath };
622
- }
623
- }
624
- return rest;
625
- });
626
- }
627
- async function handleDiffRepos(req, res, ctx) {
628
- if (req.method !== "POST") {
629
- ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
630
- return;
631
- }
632
- if (!ctx.host.security.isTerminalAuthorized(req)) {
633
- ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
634
- return;
635
- }
636
- const body = await ctx.host.http.readJsonBody(req);
637
- if (!isPlainObject4(body)) {
638
- ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
639
- return;
640
- }
641
- const theaterId = body.theaterId;
642
- if (typeof theaterId !== "string") {
643
- ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
644
- return;
645
- }
646
- const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
647
- if (!theaterPath) {
648
- ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
649
- return;
650
- }
651
- const rawDepth = body.maxDepth;
652
- const maxDepth = typeof rawDepth === "number" && Number.isFinite(rawDepth) && rawDepth >= 1 ? Math.min(HARD_CAP_DEPTH, Math.floor(rawDepth)) : DEFAULT_DEPTH;
653
- let realTheaterPath;
654
- try {
655
- realTheaterPath = await fs.realpath(theaterPath);
656
- } catch {
657
- ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
658
- return;
659
- }
660
- const raw = [];
661
- const truncated = await scanRepos(theaterPath, realTheaterPath, theaterPath, 0, maxDepth, raw);
662
- const resolved = resolveWorktreeParents(raw, realTheaterPath);
663
- resolved.sort((a, b) => {
664
- if (a.relPath === "") return -1;
665
- if (b.relPath === "") return 1;
666
- return a.relPath.localeCompare(b.relPath);
667
- });
668
- const result = { repos: resolved, ...truncated ? { truncated: true } : {} };
669
- ctx.host.http.writeJson(res, 200, result);
670
- }
671
574
 
672
575
  // ../fleet-plugins/diff/routes.ts
673
576
  var routes_default = definePlugin({
674
577
  id: "diff",
675
578
  register(ctx) {
676
- registerRouter(ctx, "repos", async ({ req, res }) => {
677
- await handleDiffRepos(req, res, ctx);
678
- return true;
679
- });
680
579
  registerRouter(ctx, "changed", async ({ req, res }) => {
681
580
  await handleDiffChanged(req, res, ctx);
682
581
  return true;