@sideboard-ai/core 0.1.66 → 0.1.68

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 (35) hide show
  1. package/dist/{agents-EWFD765A.js → agents-JBD7IAGK.js} +10 -6
  2. package/dist/{agents-MD7SCFYX.js → agents-PE3RHQS5.js} +10 -6
  3. package/dist/{chunk-BBEH2CJE.js → chunk-47RBLBRK.js} +22 -7
  4. package/dist/{chunk-IFAU2DEY.js → chunk-6Q7H6SMX.js} +22 -7
  5. package/dist/{chunk-5AG5BUQN.js → chunk-AE5VBOFE.js} +14 -3
  6. package/dist/{chunk-DZYMNW2A.js → chunk-BTL7EMGT.js} +4 -4
  7. package/dist/{chunk-TXQ6K2GR.js → chunk-D4DPQ552.js} +14 -3
  8. package/dist/{chunk-PESJDZTB.js → chunk-DPDGMMGI.js} +2 -2
  9. package/dist/{chunk-R7GUDEPY.js → chunk-EEBDVLDK.js} +22 -20
  10. package/dist/{chunk-LRBRQFBU.js → chunk-N5LMEP65.js} +1 -1
  11. package/dist/{chunk-DNPP7QMK.js → chunk-OICHV4DH.js} +2 -2
  12. package/dist/{chunk-HTOTTJJX.js → chunk-QQC2D4MY.js} +2 -2
  13. package/dist/{chunk-I2KGRAXC.js → chunk-REKGFVUX.js} +22 -20
  14. package/dist/{chunk-YTNLOJPK.js → chunk-ROXEENBT.js} +2 -2
  15. package/dist/{chunk-EVD4KNKR.js → chunk-VROPG6QF.js} +1 -1
  16. package/dist/{chunk-AKPEXIKK.js → chunk-VX6ACNXS.js} +1 -1
  17. package/dist/{connected-teams-CHXO5FFA.js → connected-teams-35EIRJCP.js} +2 -2
  18. package/dist/{connected-teams-GJTESSTX.js → connected-teams-ZRS53OAT.js} +2 -2
  19. package/dist/{coordinator-prompt-ILSULI7C.js → coordinator-prompt-HD6IMCKS.js} +3 -3
  20. package/dist/{coordinator-prompt-E5UGSJES.js → coordinator-prompt-YZYZ5P6P.js} +3 -3
  21. package/dist/{global-workspace-44UG64NA.js → global-workspace-7P5NG3JZ.js} +4 -4
  22. package/dist/{global-workspace-5PM5LA6E.js → global-workspace-Z6GF7D2K.js} +4 -4
  23. package/dist/index.cjs +401 -163
  24. package/dist/index.d.cts +33 -3
  25. package/dist/index.d.ts +33 -3
  26. package/dist/index.js +368 -157
  27. package/dist/mcp/run-stdio.cjs +397 -163
  28. package/dist/mcp/run-stdio.js +363 -156
  29. package/dist/{run-74S2NZWB.js → run-A4RHY7HH.js} +1 -1
  30. package/dist/{run-JLN5JK7Q.js → run-V2WZUMJA.js} +1 -1
  31. package/dist/{workspaces-OVBCSS5Y.js → workspaces-BWJNGEE6.js} +5 -5
  32. package/dist/{workspaces-RXFXQRRF.js → workspaces-O6EZGKQK.js} +5 -5
  33. package/dist/{worktree-S2N63FLV.js → worktree-VDXLUOWR.js} +2 -2
  34. package/dist/{worktree-K2JB53R5.js → worktree-ZPSKEZFE.js} +2 -2
  35. package/package.json +1 -1
@@ -16,14 +16,14 @@ import {
16
16
  resolveQuotaFallbackAgent,
17
17
  sideboardMcpProfile,
18
18
  summarizeTurnStderr
19
- } from "../chunk-BBEH2CJE.js";
20
- import "../chunk-EVD4KNKR.js";
19
+ } from "../chunk-47RBLBRK.js";
20
+ import "../chunk-VROPG6QF.js";
21
21
  import {
22
22
  addWorkspace,
23
23
  ensureWorkspace,
24
24
  removeWorkspace,
25
25
  syncWorkspacesFromThreads
26
- } from "../chunk-HTOTTJJX.js";
26
+ } from "../chunk-QQC2D4MY.js";
27
27
  import {
28
28
  extractPresentedPlan,
29
29
  readPlanFile,
@@ -38,13 +38,13 @@ import {
38
38
  isGlobalThread,
39
39
  isOrchestratorThread,
40
40
  orchestratorSessionPoisonedByBuiltins
41
- } from "../chunk-DNPP7QMK.js";
41
+ } from "../chunk-OICHV4DH.js";
42
42
  import {
43
43
  coordinatorSystemPrompt,
44
44
  coordinatorTurnReminder,
45
45
  enrichWorkspacesWithGithub,
46
46
  ensureGlobalCoordinatorCwd
47
- } from "../chunk-LRBRQFBU.js";
47
+ } from "../chunk-N5LMEP65.js";
48
48
  import {
49
49
  addPrStackLayer,
50
50
  allocateTeamName,
@@ -81,7 +81,7 @@ import {
81
81
  takenSlugsFromThread,
82
82
  threadDisplayLabel,
83
83
  worktreeNameFromPath
84
- } from "../chunk-I2KGRAXC.js";
84
+ } from "../chunk-REKGFVUX.js";
85
85
  import {
86
86
  ATTACHMENTS_DIR,
87
87
  LEGACY_ATTACHMENTS_DIR,
@@ -105,7 +105,7 @@ import {
105
105
  gh,
106
106
  git,
107
107
  run
108
- } from "../chunk-5AG5BUQN.js";
108
+ } from "../chunk-AE5VBOFE.js";
109
109
  import {
110
110
  childEnvWithAppSettings,
111
111
  getLinearApiKey,
@@ -391,9 +391,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
391
391
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
392
392
  );
393
393
  }
394
- const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-5PM5LA6E.js");
394
+ const { isGlobalThread: isGlobalThread2 } = await import("../global-workspace-Z6GF7D2K.js");
395
395
  if (isGlobalThread2(thread)) {
396
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-E5UGSJES.js");
396
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("../coordinator-prompt-YZYZ5P6P.js");
397
397
  ensureGlobalCoordinatorCwd2(
398
398
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
399
399
  );
@@ -1209,7 +1209,7 @@ async function createThread(input, _onSetupLine) {
1209
1209
  return readThread(thread.id) ?? thread;
1210
1210
  }
1211
1211
  async function listLinearIssues(agent, repoPath) {
1212
- const { getAdapter: getAdapter2 } = await import("../agents-MD7SCFYX.js");
1212
+ const { getAdapter: getAdapter2 } = await import("../agents-PE3RHQS5.js");
1213
1213
  await requireAgent(agent, { requireLinear: true });
1214
1214
  const adapter = getAdapter2(agent);
1215
1215
  if (!adapter.listLinearIssues) {
@@ -2058,7 +2058,7 @@ async function adoptThread(input) {
2058
2058
  throw new Error(`Worktree not found: ${input.worktreePath}`);
2059
2059
  }
2060
2060
  const repoPath = await resolveRepoRoot(input.worktreePath);
2061
- const { stdout: branch } = await import("../run-74S2NZWB.js").then(
2061
+ const { stdout: branch } = await import("../run-A4RHY7HH.js").then(
2062
2062
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], input.worktreePath)
2063
2063
  );
2064
2064
  const thread = createEmptyThread({
@@ -2073,7 +2073,7 @@ async function adoptThread(input) {
2073
2073
  messages: input.messages ?? []
2074
2074
  });
2075
2075
  writeThread(thread);
2076
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-RXFXQRRF.js");
2076
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-O6EZGKQK.js");
2077
2077
  await ensureWorkspace2(repoPath);
2078
2078
  return thread;
2079
2079
  }
@@ -2630,24 +2630,25 @@ function capPatch(patch, maxHunk) {
2630
2630
 
2631
2631
  \u2026 truncated (${patch.length - maxHunk} more chars)`;
2632
2632
  }
2633
+ var mergeBaseCache = /* @__PURE__ */ new Map();
2634
+ var MERGE_BASE_TTL_MS = 45e3;
2633
2635
  async function resolveMergeBase(worktreePath, base, repoPath) {
2634
2636
  const baseRef = await resolveDiffBaseRef(worktreePath, base, repoPath);
2637
+ const cacheKey = `${worktreePath}\0${baseRef}`;
2638
+ const hit = mergeBaseCache.get(cacheKey);
2639
+ if (hit && Date.now() - hit.at < MERGE_BASE_TTL_MS) return hit.value;
2635
2640
  const { stdout } = await git(["merge-base", baseRef, "HEAD"], worktreePath, {
2636
2641
  reject: false
2637
2642
  });
2638
- const mb = stdout.trim();
2639
- return mb || baseRef;
2643
+ const mb = stdout.trim() || baseRef;
2644
+ mergeBaseCache.set(cacheKey, { at: Date.now(), value: mb });
2645
+ return mb;
2640
2646
  }
2641
2647
  async function countUnpushedCommits(worktreePath) {
2642
2648
  const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
2643
2649
  reject: false
2644
2650
  });
2645
2651
  const branch = head.stdout.trim();
2646
- if (branch && branch !== "HEAD") {
2647
- await git(["fetch", "origin", branch, "--quiet"], worktreePath, {
2648
- reject: false
2649
- });
2650
- }
2651
2652
  const upstream = await git(
2652
2653
  ["rev-list", "--count", "@{upstream}..HEAD"],
2653
2654
  worktreePath,
@@ -2680,11 +2681,37 @@ function splitCombinedDiff(stdout) {
2680
2681
  }
2681
2682
  return map;
2682
2683
  }
2684
+ function syntheticAddPatch(path, content, maxHunk) {
2685
+ const lines = content.split("\n");
2686
+ const body = lines.map((l) => `+${l}`).join("\n");
2687
+ const header = `diff --git a/${path} b/${path}
2688
+ new file mode 100644
2689
+ --- /dev/null
2690
+ +++ b/${path}
2691
+ @@ -0,0 +1,${lines.length} @@
2692
+ `;
2693
+ return {
2694
+ path,
2695
+ status: "A",
2696
+ patch: capPatch(`${header}${body}`, maxHunk),
2697
+ additions: lines.length,
2698
+ deletions: 0
2699
+ };
2700
+ }
2683
2701
  async function untrackedPatch(worktreePath, path, maxHunk) {
2702
+ const abs = join7(worktreePath, path);
2703
+ try {
2704
+ const st = statSync2(abs);
2705
+ if (st.isFile() && st.size > maxHunk) {
2706
+ const buf = readFileSync5(abs).subarray(0, maxHunk);
2707
+ return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
2708
+ }
2709
+ } catch {
2710
+ }
2684
2711
  const { stdout: patch } = await git(
2685
- ["diff", "--no-index", "--", "/dev/null", path],
2712
+ ["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path],
2686
2713
  worktreePath,
2687
- { reject: false }
2714
+ { reject: false, timeoutMs: 4e3 }
2688
2715
  );
2689
2716
  const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path} b/${path}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path}`);
2690
2717
  const addLines = normalized.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
@@ -2696,22 +2723,97 @@ async function untrackedPatch(worktreePath, path, maxHunk) {
2696
2723
  deletions: 0
2697
2724
  };
2698
2725
  }
2699
- async function listUntrackedPaths(worktreePath) {
2700
- const { stdout } = await git(
2701
- ["ls-files", "-z", "--others", "--exclude-standard"],
2702
- worktreePath,
2703
- { reject: false }
2704
- );
2726
+ async function listUntrackedPaths(worktreePath, path) {
2727
+ const args = ["ls-files", "-z", "--others", "--exclude-standard"];
2728
+ if (path) args.push("--", path);
2729
+ const { stdout } = await git(args, worktreePath, { reject: false });
2705
2730
  return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
2706
2731
  }
2707
- async function listUntrackedFiles(worktreePath, maxHunk) {
2708
- const paths = await listUntrackedPaths(worktreePath);
2709
- if (paths.length === 0) return [];
2710
- return Promise.all(paths.map((path) => untrackedPatch(worktreePath, path, maxHunk)));
2732
+ async function listUntrackedFiles(worktreePath, maxHunk, paths) {
2733
+ const list = paths ?? await listUntrackedPaths(worktreePath);
2734
+ if (list.length === 0) return [];
2735
+ return Promise.all(list.map((path) => untrackedPatch(worktreePath, path, maxHunk)));
2736
+ }
2737
+ function untrackedStubs(paths) {
2738
+ return paths.map((path) => ({
2739
+ path,
2740
+ status: "A",
2741
+ patch: "",
2742
+ additions: 0,
2743
+ deletions: 0
2744
+ }));
2745
+ }
2746
+ function pathspec(path) {
2747
+ return path ? ["--", path] : [];
2748
+ }
2749
+ function gitDiff(args, cwd, opts) {
2750
+ return git(["diff", "--no-ext-diff", "--no-color", ...args], cwd, {
2751
+ reject: false,
2752
+ timeoutMs: opts?.timeoutMs
2753
+ });
2754
+ }
2755
+ function fileFromPatch(path, patch, maxHunk) {
2756
+ let status = "M";
2757
+ if (/^new file mode /m.test(patch) || /^--- \/dev\/null/m.test(patch)) {
2758
+ status = "A";
2759
+ } else if (/^deleted file mode /m.test(patch) || /^\+\+\+ \/dev\/null/m.test(patch)) {
2760
+ status = "D";
2761
+ } else if (/^rename from /m.test(patch)) {
2762
+ status = "R";
2763
+ }
2764
+ const additions = patch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
2765
+ const deletions = patch.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).length;
2766
+ return {
2767
+ path,
2768
+ status,
2769
+ patch: capPatch(patch, maxHunk),
2770
+ additions,
2771
+ deletions
2772
+ };
2773
+ }
2774
+ async function gitDiffParts(worktreePath, diffArgs, includePatches, path) {
2775
+ const extra = pathspec(path);
2776
+ if (includePatches) {
2777
+ const diff = await gitDiff([...diffArgs, ...extra], worktreePath, {
2778
+ timeoutMs: 4e3
2779
+ });
2780
+ return { nameStatus: "", numstat: "", combinedDiff: diff.stdout };
2781
+ }
2782
+ const [ns, num] = await Promise.all([
2783
+ gitDiff(["--name-status", ...diffArgs, ...extra], worktreePath),
2784
+ gitDiff(["--numstat", ...diffArgs, ...extra], worktreePath)
2785
+ ]);
2786
+ return {
2787
+ nameStatus: ns.stdout,
2788
+ numstat: num.stdout,
2789
+ combinedDiff: ""
2790
+ };
2791
+ }
2792
+ function formatStat(files) {
2793
+ if (files.length === 0) return "(no changes)";
2794
+ const additions = files.reduce((n2, f) => n2 + (f.additions ?? 0), 0);
2795
+ const deletions = files.reduce((n2, f) => n2 + (f.deletions ?? 0), 0);
2796
+ const n = files.length;
2797
+ return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
2798
+ }
2799
+ function emptyScopeStats() {
2800
+ const z2 = emptyStat();
2801
+ return {
2802
+ commits: z2,
2803
+ uncommitted: z2,
2804
+ staged: z2,
2805
+ unstaged: z2,
2806
+ last_turn: z2
2807
+ };
2711
2808
  }
2712
2809
  function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
2713
- const counts = parseNumstat(numstat);
2714
2810
  const patches = splitCombinedDiff(combinedDiff);
2811
+ if (!nameStatus.trim() && patches.size > 0) {
2812
+ return [...patches.entries()].map(
2813
+ ([path, patch]) => fileFromPatch(path, patch, maxHunk)
2814
+ );
2815
+ }
2816
+ const counts = parseNumstat(numstat);
2715
2817
  const files = [];
2716
2818
  for (const { status, path } of parseNameStatus(nameStatus)) {
2717
2819
  const n = counts.get(path);
@@ -2733,11 +2835,11 @@ async function computeScopeStats(worktreePath, mergeBase, lastTurnBase, untracke
2733
2835
  { stdout: unstagedNum },
2734
2836
  lastTurnNum
2735
2837
  ] = await Promise.all([
2736
- mergeBase ? git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }) : Promise.resolve({ stdout: "" }),
2737
- git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
2738
- git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
2739
- git(["diff", "--numstat"], worktreePath, { reject: false }),
2740
- lastTurnBase ? git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }) : Promise.resolve({ stdout: "" })
2838
+ mergeBase ? gitDiff(["--numstat", mergeBase], worktreePath) : Promise.resolve({ stdout: "" }),
2839
+ gitDiff(["--numstat", "HEAD"], worktreePath),
2840
+ gitDiff(["--numstat", "--cached"], worktreePath),
2841
+ gitDiff(["--numstat"], worktreePath),
2842
+ lastTurnBase ? gitDiff(["--numstat", lastTurnBase], worktreePath) : Promise.resolve({ stdout: "" })
2741
2843
  ]);
2742
2844
  return {
2743
2845
  commits: mergeBase ? statFromNumstat(commitsNum, untrackedCount) : emptyStat(),
@@ -2756,7 +2858,7 @@ async function listBranchCommits(worktreePath, repoPath, opts) {
2756
2858
  if (head.exitCode !== 0) return [];
2757
2859
  let base = opts?.base;
2758
2860
  try {
2759
- base = base ?? await resolveDefaultBranch(repoPath);
2861
+ base = base ?? await resolveDefaultBranch(repoPath, { network: false });
2760
2862
  } catch {
2761
2863
  base = "HEAD";
2762
2864
  }
@@ -2786,7 +2888,121 @@ async function listBranchCommits(worktreePath, repoPath, opts) {
2786
2888
  }
2787
2889
  return commits;
2788
2890
  }
2891
+ var SHA_RE = /^[0-9a-f]{7,40}$/i;
2892
+ function emptyPathDiff(scope, base, hasLastTurnBase, commitSha = null) {
2893
+ return {
2894
+ scope,
2895
+ commitSha: scope === "commits" ? commitSha : null,
2896
+ base,
2897
+ files: [],
2898
+ stat: formatStat([]),
2899
+ dirty: false,
2900
+ unpushed: 0,
2901
+ hasLastTurnBase,
2902
+ commits: [],
2903
+ scopeStats: emptyScopeStats()
2904
+ };
2905
+ }
2906
+ async function getPathDiff(worktreePath, repoPath, opts) {
2907
+ const { path, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
2908
+ if (scope === "last_turn" && !lastTurnBase) {
2909
+ return emptyPathDiff(scope, "last turn", false);
2910
+ }
2911
+ let diffArgs;
2912
+ let labelBase;
2913
+ if (scope === "staged") {
2914
+ diffArgs = ["--cached"];
2915
+ labelBase = "staged";
2916
+ } else if (scope === "unstaged") {
2917
+ diffArgs = [];
2918
+ labelBase = "unstaged";
2919
+ } else if (scope === "uncommitted") {
2920
+ diffArgs = ["HEAD"];
2921
+ labelBase = "HEAD";
2922
+ } else if (scope === "last_turn") {
2923
+ diffArgs = [lastTurnBase];
2924
+ labelBase = "last turn";
2925
+ } else if (scope === "commits" && commitSha) {
2926
+ diffArgs = [`${commitSha}^!`];
2927
+ labelBase = commitSha.slice(0, 7);
2928
+ } else if (scope === "commits" && opts.base && SHA_RE.test(opts.base)) {
2929
+ diffArgs = [opts.base];
2930
+ labelBase = opts.base.slice(0, 7);
2931
+ } else if (scope === "commits") {
2932
+ let base = opts.base;
2933
+ try {
2934
+ base = base ?? await resolveDefaultBranch(repoPath, { network: false });
2935
+ } catch {
2936
+ base = "HEAD";
2937
+ }
2938
+ const mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
2939
+ diffArgs = [mergeBase];
2940
+ labelBase = mergeBase;
2941
+ } else {
2942
+ diffArgs = ["HEAD"];
2943
+ labelBase = "HEAD";
2944
+ }
2945
+ const diff = await gitDiff(
2946
+ includePatches ? [...diffArgs, "--", path] : ["--name-status", ...diffArgs, "--", path],
2947
+ worktreePath,
2948
+ { timeoutMs: 4e3 }
2949
+ );
2950
+ const filesMap = /* @__PURE__ */ new Map();
2951
+ if (includePatches) {
2952
+ const parsed = filesFromDiff("", "", diff.stdout, maxHunk);
2953
+ for (const file of parsed) filesMap.set(file.path, file);
2954
+ if (diff.stdout.trim() && !filesMap.has(path)) {
2955
+ filesMap.set(path, fileFromPatch(path, diff.stdout, maxHunk));
2956
+ }
2957
+ } else {
2958
+ for (const file of filesFromDiff(diff.stdout, "", "", maxHunk)) {
2959
+ filesMap.set(file.path, file);
2960
+ }
2961
+ }
2962
+ if (filesMap.size === 0) {
2963
+ const maybe = await listUntrackedPaths(worktreePath, path);
2964
+ if (maybe.length > 0) {
2965
+ const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
2966
+ for (const file of extra) filesMap.set(file.path, file);
2967
+ }
2968
+ }
2969
+ const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
2970
+ return {
2971
+ scope,
2972
+ commitSha: scope === "commits" ? commitSha : null,
2973
+ base: labelBase,
2974
+ files,
2975
+ stat: formatStat(files),
2976
+ dirty: files.length > 0,
2977
+ unpushed: 0,
2978
+ hasLastTurnBase,
2979
+ commits: [],
2980
+ scopeStats: emptyScopeStats(),
2981
+ mergeBase: SHA_RE.test(diffArgs[0] ?? "") ? diffArgs[0] : null
2982
+ };
2983
+ }
2789
2984
  async function getDiff(worktreePath, repoPath, opts) {
2985
+ const maxHunk = opts?.maxHunkChars ?? 8e3;
2986
+ const scope = opts?.scope ?? "commits";
2987
+ const lastTurnBase = opts?.lastTurnBase?.trim() || null;
2988
+ const hasLastTurnBase = Boolean(lastTurnBase);
2989
+ const commitSha = opts?.commitSha?.trim() || null;
2990
+ const includePatches = opts?.includePatches ?? true;
2991
+ const pathFilter = opts?.path?.trim() || null;
2992
+ const fileOnly = Boolean(pathFilter);
2993
+ const includeMeta = (opts?.includeMeta ?? true) && !fileOnly;
2994
+ if (fileOnly && pathFilter && !includeMeta) {
2995
+ return getPathDiff(worktreePath, repoPath, {
2996
+ path: pathFilter,
2997
+ scope,
2998
+ commitSha,
2999
+ lastTurnBase,
3000
+ hasLastTurnBase,
3001
+ base: opts?.base,
3002
+ includePatches,
3003
+ maxHunk
3004
+ });
3005
+ }
2790
3006
  const status = await inspectGitWorktree(worktreePath);
2791
3007
  if (status === "missing_worktree") {
2792
3008
  throw new Error("Worktree not found");
@@ -2794,18 +3010,19 @@ async function getDiff(worktreePath, repoPath, opts) {
2794
3010
  if (status === "not_git") {
2795
3011
  throw new Error("Not a Git repository");
2796
3012
  }
2797
- const maxHunk = opts?.maxHunkChars ?? 8e3;
2798
- const scope = opts?.scope ?? "commits";
2799
- const lastTurnBase = opts?.lastTurnBase?.trim() || null;
2800
- const hasLastTurnBase = Boolean(lastTurnBase);
2801
- const commitSha = opts?.commitSha?.trim() || null;
2802
- const head = await git(["rev-parse", "--verify", "HEAD"], worktreePath, {
3013
+ const wantUntracked = opts?.includeUntracked !== void 0 ? opts.includeUntracked : !fileOnly && (scope === "commits" && !commitSha || scope === "uncommitted" || scope === "unstaged" || scope === "last_turn");
3014
+ const needsMergeBase = scope === "commits" && !commitSha || includeMeta;
3015
+ const headP = git(["rev-parse", "--verify", "HEAD"], worktreePath, {
2803
3016
  reject: false
2804
3017
  });
3018
+ const untrackedP = wantUntracked ? listUntrackedPaths(worktreePath, pathFilter) : Promise.resolve([]);
3019
+ const head = await headP;
2805
3020
  const hasHead = head.exitCode === 0;
2806
- const untrackedPaths = await listUntrackedPaths(worktreePath);
2807
3021
  if (!hasHead) {
2808
- const files2 = scope === "staged" ? [] : (await listUntrackedFiles(worktreePath, maxHunk)).sort(
3022
+ const untrackedPaths2 = await untrackedP;
3023
+ const files2 = scope === "staged" ? [] : includePatches ? (await listUntrackedFiles(worktreePath, maxHunk, untrackedPaths2)).sort(
3024
+ (a, b) => a.path.localeCompare(b.path)
3025
+ ) : untrackedStubs(untrackedPaths2).sort(
2809
3026
  (a, b) => a.path.localeCompare(b.path)
2810
3027
  );
2811
3028
  const untrackedStat = {
@@ -2819,7 +3036,7 @@ async function getDiff(worktreePath, repoPath, opts) {
2819
3036
  commitSha: null,
2820
3037
  base: "(no commits)",
2821
3038
  files: files2,
2822
- stat: files2.length ? files2.map((f) => ` ${f.path} | untracked`).join("\n") : "(no changes)",
3039
+ stat: formatStat(files2),
2823
3040
  dirty: files2.length > 0,
2824
3041
  unpushed: 0,
2825
3042
  hasLastTurnBase,
@@ -2833,137 +3050,122 @@ async function getDiff(worktreePath, repoPath, opts) {
2833
3050
  }
2834
3051
  };
2835
3052
  }
2836
- let base = opts?.base;
2837
- try {
2838
- base = base ?? await resolveDefaultBranch(repoPath);
2839
- } catch {
2840
- base = "HEAD";
2841
- }
2842
- base = await resolveDiffBaseRef(worktreePath, base, repoPath);
2843
- if (base.startsWith("origin/")) {
2844
- const short = base.slice("origin/".length);
2845
- await git(["fetch", "origin", short, "--quiet"], worktreePath, {
2846
- reject: false
2847
- });
2848
- base = await resolveDiffBaseRef(worktreePath, short, repoPath);
3053
+ let mergeBase = "HEAD";
3054
+ let baseLabel = "HEAD";
3055
+ if (needsMergeBase) {
3056
+ let base = opts?.base;
3057
+ try {
3058
+ base = base ?? await resolveDefaultBranch(repoPath, { network: false });
3059
+ } catch {
3060
+ base = "HEAD";
3061
+ }
3062
+ mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
3063
+ baseLabel = await resolveDiffBaseRef(worktreePath, base, repoPath);
3064
+ }
3065
+ const includeUntracked = wantUntracked;
3066
+ const metaPromise = includeMeta ? Promise.all([
3067
+ computeScopeStats(
3068
+ worktreePath,
3069
+ mergeBase,
3070
+ lastTurnBase,
3071
+ // Cheap: don't wait on the untracked walk just to pad the badge.
3072
+ 0
3073
+ ),
3074
+ listBranchCommits(worktreePath, repoPath, { base: baseLabel }),
3075
+ isDirty(worktreePath),
3076
+ countUnpushedCommits(worktreePath)
3077
+ ]).then(([scopeStats, commits, dirty, unpushed]) => ({
3078
+ scopeStats,
3079
+ commits,
3080
+ dirty,
3081
+ unpushed
3082
+ })) : Promise.resolve({
3083
+ scopeStats: emptyScopeStats(),
3084
+ commits: [],
3085
+ dirty: false,
3086
+ unpushed: 0
3087
+ });
3088
+ if (scope === "last_turn" && !lastTurnBase) {
3089
+ const meta2 = await metaPromise;
3090
+ return {
3091
+ scope,
3092
+ commitSha: null,
3093
+ base: "last turn",
3094
+ files: [],
3095
+ stat: "(no last agent turn)",
3096
+ dirty: meta2.dirty,
3097
+ unpushed: meta2.unpushed,
3098
+ hasLastTurnBase: false,
3099
+ commits: meta2.commits,
3100
+ scopeStats: meta2.scopeStats
3101
+ };
2849
3102
  }
2850
- const mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
2851
- const scopeStats = await computeScopeStats(
2852
- worktreePath,
2853
- mergeBase,
2854
- lastTurnBase,
2855
- untrackedPaths.length
2856
- );
2857
- const includeUntracked = scope === "commits" && !commitSha || scope === "uncommitted" || scope === "unstaged" || scope === "last_turn";
2858
- let nameStatus = "";
2859
- let numstat = "";
2860
- let combinedDiff = "";
2861
- let labelBase = base;
3103
+ let diffArgs;
3104
+ let labelBase = baseLabel;
2862
3105
  if (scope === "staged") {
2863
- const [ns, num, diff] = await Promise.all([
2864
- git(["diff", "--name-status", "--cached"], worktreePath, { reject: false }),
2865
- git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
2866
- git(["diff", "--cached"], worktreePath, { reject: false })
2867
- ]);
2868
- nameStatus = ns.stdout;
2869
- numstat = num.stdout;
2870
- combinedDiff = diff.stdout;
3106
+ diffArgs = ["--cached"];
2871
3107
  labelBase = "staged";
2872
3108
  } else if (scope === "unstaged") {
2873
- const [ns, num, diff] = await Promise.all([
2874
- git(["diff", "--name-status"], worktreePath, { reject: false }),
2875
- git(["diff", "--numstat"], worktreePath, { reject: false }),
2876
- git(["diff"], worktreePath, { reject: false })
2877
- ]);
2878
- nameStatus = ns.stdout;
2879
- numstat = num.stdout;
2880
- combinedDiff = diff.stdout;
3109
+ diffArgs = [];
2881
3110
  labelBase = "unstaged";
2882
3111
  } else if (scope === "uncommitted") {
2883
- const [ns, num, diff] = await Promise.all([
2884
- git(["diff", "--name-status", "HEAD"], worktreePath, { reject: false }),
2885
- git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
2886
- git(["diff", "HEAD"], worktreePath, { reject: false })
2887
- ]);
2888
- nameStatus = ns.stdout;
2889
- numstat = num.stdout;
2890
- combinedDiff = diff.stdout;
3112
+ diffArgs = ["HEAD"];
2891
3113
  labelBase = "HEAD";
2892
3114
  } else if (scope === "last_turn") {
2893
- if (!lastTurnBase) {
2894
- const commits2 = await listBranchCommits(worktreePath, repoPath, { base });
2895
- return {
2896
- scope,
2897
- commitSha: null,
2898
- base: "last turn",
2899
- files: [],
2900
- stat: "(no last agent turn)",
2901
- dirty: await isDirty(worktreePath),
2902
- unpushed: await countUnpushedCommits(worktreePath),
2903
- hasLastTurnBase: false,
2904
- commits: commits2,
2905
- scopeStats
2906
- };
2907
- }
2908
- const [ns, num, diff] = await Promise.all([
2909
- git(["diff", "--name-status", lastTurnBase], worktreePath, { reject: false }),
2910
- git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }),
2911
- git(["diff", lastTurnBase], worktreePath, { reject: false })
2912
- ]);
2913
- nameStatus = ns.stdout;
2914
- numstat = num.stdout;
2915
- combinedDiff = diff.stdout;
3115
+ diffArgs = [lastTurnBase];
2916
3116
  labelBase = "last turn";
2917
3117
  } else if (scope === "commits" && commitSha) {
2918
- const range = `${commitSha}^!`;
2919
- const [ns, num, diff] = await Promise.all([
2920
- git(["diff", "--name-status", range], worktreePath, { reject: false }),
2921
- git(["diff", "--numstat", range], worktreePath, { reject: false }),
2922
- git(["diff", range], worktreePath, { reject: false })
2923
- ]);
2924
- nameStatus = ns.stdout;
2925
- numstat = num.stdout;
2926
- combinedDiff = diff.stdout;
3118
+ diffArgs = [`${commitSha}^!`];
2927
3119
  labelBase = commitSha.slice(0, 7);
2928
3120
  } else {
2929
- const [ns, num, diff] = await Promise.all([
2930
- git(["diff", "--name-status", mergeBase], worktreePath, { reject: false }),
2931
- git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }),
2932
- git(["diff", mergeBase], worktreePath, { reject: false })
2933
- ]);
2934
- nameStatus = ns.stdout;
2935
- numstat = num.stdout;
2936
- combinedDiff = diff.stdout;
2937
- labelBase = base;
2938
- }
2939
- const commits = await listBranchCommits(worktreePath, repoPath, { base });
3121
+ diffArgs = [mergeBase];
3122
+ labelBase = baseLabel;
3123
+ }
3124
+ const partsPromise = gitDiffParts(
3125
+ worktreePath,
3126
+ diffArgs,
3127
+ includePatches,
3128
+ pathFilter
3129
+ );
3130
+ const [parts, meta, untrackedPaths] = await Promise.all([
3131
+ partsPromise,
3132
+ metaPromise,
3133
+ untrackedP
3134
+ ]);
2940
3135
  const filesMap = /* @__PURE__ */ new Map();
2941
- for (const file of filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk)) {
3136
+ for (const file of filesFromDiff(
3137
+ parts.nameStatus,
3138
+ parts.numstat,
3139
+ parts.combinedDiff,
3140
+ maxHunk
3141
+ )) {
2942
3142
  filesMap.set(file.path, file);
2943
3143
  }
2944
3144
  if (includeUntracked) {
2945
- const untrackedFiles = await listUntrackedFiles(worktreePath, maxHunk);
2946
- for (const file of untrackedFiles) {
3145
+ const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, untrackedPaths) : untrackedStubs(untrackedPaths);
3146
+ for (const file of extra) {
2947
3147
  if (!filesMap.has(file.path)) filesMap.set(file.path, file);
2948
3148
  }
3149
+ } else if (fileOnly && pathFilter && !filesMap.has(pathFilter)) {
3150
+ const maybe = await listUntrackedPaths(worktreePath, pathFilter);
3151
+ if (maybe.length > 0) {
3152
+ const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
3153
+ for (const file of extra) filesMap.set(file.path, file);
3154
+ }
2949
3155
  }
2950
3156
  const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
2951
- const { stdout: statOut } = await git(
2952
- scope === "staged" ? ["diff", "--stat", "--cached"] : scope === "unstaged" ? ["diff", "--stat"] : scope === "uncommitted" ? ["diff", "--stat", "HEAD"] : scope === "last_turn" && lastTurnBase ? ["diff", "--stat", lastTurnBase] : scope === "commits" && commitSha ? ["diff", "--stat", `${commitSha}^!`] : ["diff", "--stat", mergeBase],
2953
- worktreePath,
2954
- { reject: false }
2955
- );
2956
3157
  return {
2957
3158
  scope,
2958
3159
  commitSha: scope === "commits" ? commitSha : null,
2959
3160
  base: labelBase,
2960
3161
  files,
2961
- stat: statOut.trim() || (files.length ? `${files.length} file(s)` : "(no changes)"),
2962
- dirty: await isDirty(worktreePath),
2963
- unpushed: await countUnpushedCommits(worktreePath),
3162
+ stat: formatStat(files),
3163
+ dirty: fileOnly || !includeMeta ? files.length > 0 : meta.dirty,
3164
+ unpushed: meta.unpushed,
2964
3165
  hasLastTurnBase,
2965
- commits,
2966
- scopeStats
3166
+ commits: meta.commits,
3167
+ scopeStats: meta.scopeStats,
3168
+ mergeBase: needsMergeBase ? mergeBase : null
2967
3169
  };
2968
3170
  }
2969
3171
  async function listWorktreeFiles(worktreePath, opts) {
@@ -3179,7 +3381,7 @@ async function previewLand(thread) {
3179
3381
  const target = await resolveDefaultBranch(thread.repoPath);
3180
3382
  if (thread.branchName === target || thread.branchName === "main" || thread.branchName === "master") {
3181
3383
  }
3182
- const { stdout: head } = await import("../run-74S2NZWB.js").then(
3384
+ const { stdout: head } = await import("../run-A4RHY7HH.js").then(
3183
3385
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], thread.worktreePath)
3184
3386
  );
3185
3387
  const current = head.trim();
@@ -3218,7 +3420,7 @@ async function confirmLand(thread, opts) {
3218
3420
  if (preview.dirty) {
3219
3421
  committed = await commitAll(thread.worktreePath, meta.commitMessage);
3220
3422
  }
3221
- const { git: git2 } = await import("../run-74S2NZWB.js");
3423
+ const { git: git2 } = await import("../run-A4RHY7HH.js");
3222
3424
  const { stdout: headOut } = await git2(
3223
3425
  ["rev-parse", "--abbrev-ref", "HEAD"],
3224
3426
  thread.worktreePath,
@@ -4902,6 +5104,11 @@ var Orchestrator = class {
4902
5104
  return getDiff(thread.worktreePath, thread.repoPath, {
4903
5105
  scope: opts?.scope,
4904
5106
  commitSha: opts?.commitSha,
5107
+ base: opts?.base,
5108
+ includePatches: opts?.includePatches,
5109
+ includeMeta: opts?.includeMeta,
5110
+ includeUntracked: opts?.includeUntracked,
5111
+ path: opts?.path,
4905
5112
  lastTurnBase: this.turnBaselines.get(thread.id) ?? null
4906
5113
  });
4907
5114
  }
@@ -5253,7 +5460,7 @@ var Orchestrator = class {
5253
5460
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
5254
5461
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
5255
5462
  try {
5256
- const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-RXFXQRRF.js");
5463
+ const { ensureWorkspace: ensureWorkspace2 } = await import("../workspaces-O6EZGKQK.js");
5257
5464
  await ensureWorkspace2(thread.repoPath);
5258
5465
  } catch {
5259
5466
  }
@@ -5295,7 +5502,7 @@ var Orchestrator = class {
5295
5502
  return restored2;
5296
5503
  }
5297
5504
  if (!existsSync13(thread.worktreePath)) {
5298
- const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-S2N63FLV.js");
5505
+ const { createThreadWorktree: createThreadWorktree2 } = await import("../worktree-VDXLUOWR.js");
5299
5506
  const { execa: execa6 } = await import("execa");
5300
5507
  const slug = thread.worktreePath.split("/").pop();
5301
5508
  const dest = thread.worktreePath;