@sideboard-ai/core 0.1.67 → 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-TS3EI2YI.js → agents-JBD7IAGK.js} +6 -6
  2. package/dist/{agents-OTBZVTZO.js → agents-PE3RHQS5.js} +6 -6
  3. package/dist/{chunk-K7LM4CLD.js → chunk-47RBLBRK.js} +4 -4
  4. package/dist/{chunk-B4PRRKMA.js → chunk-6Q7H6SMX.js} +4 -4
  5. package/dist/{chunk-FGDJOJ2L.js → chunk-AE5VBOFE.js} +1 -1
  6. package/dist/{chunk-DB3I75FI.js → chunk-BTL7EMGT.js} +4 -4
  7. package/dist/{chunk-4UIYN56C.js → chunk-D4DPQ552.js} +1 -1
  8. package/dist/{chunk-WAFIF22N.js → chunk-DPDGMMGI.js} +2 -2
  9. package/dist/{chunk-4BUCGNZS.js → chunk-EEBDVLDK.js} +22 -20
  10. package/dist/{chunk-57WWNMFP.js → chunk-N5LMEP65.js} +1 -1
  11. package/dist/{chunk-JWZNK3CQ.js → chunk-OICHV4DH.js} +2 -2
  12. package/dist/{chunk-5HTTCYWA.js → chunk-QQC2D4MY.js} +2 -2
  13. package/dist/{chunk-LW5CMQGZ.js → chunk-REKGFVUX.js} +22 -20
  14. package/dist/{chunk-7ZOQY4A7.js → chunk-ROXEENBT.js} +2 -2
  15. package/dist/{chunk-M5V4QIWF.js → chunk-VROPG6QF.js} +1 -1
  16. package/dist/{chunk-ZRFAZVGL.js → chunk-VX6ACNXS.js} +1 -1
  17. package/dist/{connected-teams-RUNMOCG5.js → connected-teams-35EIRJCP.js} +2 -2
  18. package/dist/{connected-teams-OMDHTL6A.js → connected-teams-ZRS53OAT.js} +2 -2
  19. package/dist/{coordinator-prompt-3BSMKZL4.js → coordinator-prompt-HD6IMCKS.js} +3 -3
  20. package/dist/{coordinator-prompt-6HMTOX4G.js → coordinator-prompt-YZYZ5P6P.js} +3 -3
  21. package/dist/{global-workspace-QS7A23CB.js → global-workspace-7P5NG3JZ.js} +4 -4
  22. package/dist/{global-workspace-YZ2NJVLL.js → global-workspace-Z6GF7D2K.js} +4 -4
  23. package/dist/index.cjs +367 -158
  24. package/dist/index.d.cts +27 -1
  25. package/dist/index.d.ts +27 -1
  26. package/dist/index.js +364 -157
  27. package/dist/mcp/run-stdio.cjs +367 -158
  28. package/dist/mcp/run-stdio.js +363 -156
  29. package/dist/{run-Y2MI3EVV.js → run-A4RHY7HH.js} +1 -1
  30. package/dist/{run-JLD2Y52J.js → run-V2WZUMJA.js} +1 -1
  31. package/dist/{workspaces-EBFW7I7S.js → workspaces-BWJNGEE6.js} +5 -5
  32. package/dist/{workspaces-AIUYUXB7.js → workspaces-O6EZGKQK.js} +5 -5
  33. package/dist/{worktree-WMDJW2BL.js → worktree-VDXLUOWR.js} +2 -2
  34. package/dist/{worktree-7NBPIVFZ.js → worktree-ZPSKEZFE.js} +2 -2
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-WAFIF22N.js";
7
+ } from "./chunk-DPDGMMGI.js";
8
8
  import {
9
9
  BRIGHTSY_MCP_ALLOWED_TOOLS,
10
10
  CLAUDE_MODEL_CATALOG,
@@ -58,7 +58,7 @@ import {
58
58
  sideboardMcpProfile,
59
59
  threadRequestsBrightsyMcp,
60
60
  writeInjectedMcpConfig
61
- } from "./chunk-B4PRRKMA.js";
61
+ } from "./chunk-6Q7H6SMX.js";
62
62
  import {
63
63
  CLOUD_COORDINATOR_BUSY_REPLY,
64
64
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -82,7 +82,7 @@ import {
82
82
  orchestratorSessionPoisonedByBuiltins,
83
83
  parseForceStopMessage,
84
84
  takenTeamSlugsForOrchestration
85
- } from "./chunk-7ZOQY4A7.js";
85
+ } from "./chunk-ROXEENBT.js";
86
86
  import {
87
87
  brightsyConfigPath,
88
88
  brightsyMcpServerName,
@@ -94,7 +94,7 @@ import {
94
94
  loadBrightsyConfig,
95
95
  saveBrightsyConfig,
96
96
  switchBrightsyAccount
97
- } from "./chunk-DB3I75FI.js";
97
+ } from "./chunk-BTL7EMGT.js";
98
98
  import {
99
99
  LEGACY_PLAN_FILE_REL,
100
100
  PLAN_FILE_NAME,
@@ -124,7 +124,7 @@ import {
124
124
  enrichWorkspacesWithGithub,
125
125
  ensureGlobalCoordinatorCwd,
126
126
  formatWorkspaceInventory
127
- } from "./chunk-ZRFAZVGL.js";
127
+ } from "./chunk-VX6ACNXS.js";
128
128
  import {
129
129
  HARNESS_ENV_KEYS,
130
130
  appSettingsPath,
@@ -232,7 +232,7 @@ import {
232
232
  worktreeDisplayLabel,
233
233
  worktreeDisplayLabelForGroup,
234
234
  worktreeNameFromPath
235
- } from "./chunk-4BUCGNZS.js";
235
+ } from "./chunk-EEBDVLDK.js";
236
236
  import {
237
237
  ATTACHMENTS_DIR,
238
238
  LEGACY_ATTACHMENTS_DIR,
@@ -295,7 +295,7 @@ import {
295
295
  resolveGhAuthToken,
296
296
  run,
297
297
  withExportedPath
298
- } from "./chunk-4UIYN56C.js";
298
+ } from "./chunk-D4DPQ552.js";
299
299
 
300
300
  // src/integrations/github.ts
301
301
  async function getGitHubStatus() {
@@ -724,9 +724,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
724
724
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
725
725
  );
726
726
  }
727
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-QS7A23CB.js");
727
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-7P5NG3JZ.js");
728
728
  if (isGlobalThread2(thread)) {
729
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-3BSMKZL4.js");
729
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-HD6IMCKS.js");
730
730
  ensureGlobalCoordinatorCwd2(
731
731
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
732
732
  );
@@ -1512,24 +1512,25 @@ function capPatch(patch, maxHunk) {
1512
1512
 
1513
1513
  \u2026 truncated (${patch.length - maxHunk} more chars)`;
1514
1514
  }
1515
+ var mergeBaseCache = /* @__PURE__ */ new Map();
1516
+ var MERGE_BASE_TTL_MS = 45e3;
1515
1517
  async function resolveMergeBase(worktreePath, base, repoPath) {
1516
1518
  const baseRef = await resolveDiffBaseRef(worktreePath, base, repoPath);
1519
+ const cacheKey = `${worktreePath}\0${baseRef}`;
1520
+ const hit = mergeBaseCache.get(cacheKey);
1521
+ if (hit && Date.now() - hit.at < MERGE_BASE_TTL_MS) return hit.value;
1517
1522
  const { stdout } = await git(["merge-base", baseRef, "HEAD"], worktreePath, {
1518
1523
  reject: false
1519
1524
  });
1520
- const mb = stdout.trim();
1521
- return mb || baseRef;
1525
+ const mb = stdout.trim() || baseRef;
1526
+ mergeBaseCache.set(cacheKey, { at: Date.now(), value: mb });
1527
+ return mb;
1522
1528
  }
1523
1529
  async function countUnpushedCommits(worktreePath) {
1524
1530
  const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
1525
1531
  reject: false
1526
1532
  });
1527
1533
  const branch = head.stdout.trim();
1528
- if (branch && branch !== "HEAD") {
1529
- await git(["fetch", "origin", branch, "--quiet"], worktreePath, {
1530
- reject: false
1531
- });
1532
- }
1533
1534
  const upstream = await git(
1534
1535
  ["rev-list", "--count", "@{upstream}..HEAD"],
1535
1536
  worktreePath,
@@ -1562,11 +1563,37 @@ function splitCombinedDiff(stdout) {
1562
1563
  }
1563
1564
  return map;
1564
1565
  }
1566
+ function syntheticAddPatch(path, content, maxHunk) {
1567
+ const lines = content.split("\n");
1568
+ const body = lines.map((l) => `+${l}`).join("\n");
1569
+ const header = `diff --git a/${path} b/${path}
1570
+ new file mode 100644
1571
+ --- /dev/null
1572
+ +++ b/${path}
1573
+ @@ -0,0 +1,${lines.length} @@
1574
+ `;
1575
+ return {
1576
+ path,
1577
+ status: "A",
1578
+ patch: capPatch(`${header}${body}`, maxHunk),
1579
+ additions: lines.length,
1580
+ deletions: 0
1581
+ };
1582
+ }
1565
1583
  async function untrackedPatch(worktreePath, path, maxHunk) {
1584
+ const abs = join4(worktreePath, path);
1585
+ try {
1586
+ const st = statSync2(abs);
1587
+ if (st.isFile() && st.size > maxHunk) {
1588
+ const buf = readFileSync4(abs).subarray(0, maxHunk);
1589
+ return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
1590
+ }
1591
+ } catch {
1592
+ }
1566
1593
  const { stdout: patch } = await git(
1567
- ["diff", "--no-index", "--", "/dev/null", path],
1594
+ ["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path],
1568
1595
  worktreePath,
1569
- { reject: false }
1596
+ { reject: false, timeoutMs: 4e3 }
1570
1597
  );
1571
1598
  const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path} b/${path}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path}`);
1572
1599
  const addLines = normalized.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
@@ -1578,22 +1605,97 @@ async function untrackedPatch(worktreePath, path, maxHunk) {
1578
1605
  deletions: 0
1579
1606
  };
1580
1607
  }
1581
- async function listUntrackedPaths(worktreePath) {
1582
- const { stdout } = await git(
1583
- ["ls-files", "-z", "--others", "--exclude-standard"],
1584
- worktreePath,
1585
- { reject: false }
1586
- );
1608
+ async function listUntrackedPaths(worktreePath, path) {
1609
+ const args = ["ls-files", "-z", "--others", "--exclude-standard"];
1610
+ if (path) args.push("--", path);
1611
+ const { stdout } = await git(args, worktreePath, { reject: false });
1587
1612
  return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
1588
1613
  }
1589
- async function listUntrackedFiles(worktreePath, maxHunk) {
1590
- const paths = await listUntrackedPaths(worktreePath);
1591
- if (paths.length === 0) return [];
1592
- return Promise.all(paths.map((path) => untrackedPatch(worktreePath, path, maxHunk)));
1614
+ async function listUntrackedFiles(worktreePath, maxHunk, paths) {
1615
+ const list = paths ?? await listUntrackedPaths(worktreePath);
1616
+ if (list.length === 0) return [];
1617
+ return Promise.all(list.map((path) => untrackedPatch(worktreePath, path, maxHunk)));
1618
+ }
1619
+ function untrackedStubs(paths) {
1620
+ return paths.map((path) => ({
1621
+ path,
1622
+ status: "A",
1623
+ patch: "",
1624
+ additions: 0,
1625
+ deletions: 0
1626
+ }));
1627
+ }
1628
+ function pathspec(path) {
1629
+ return path ? ["--", path] : [];
1630
+ }
1631
+ function gitDiff(args, cwd, opts) {
1632
+ return git(["diff", "--no-ext-diff", "--no-color", ...args], cwd, {
1633
+ reject: false,
1634
+ timeoutMs: opts?.timeoutMs
1635
+ });
1636
+ }
1637
+ function fileFromPatch(path, patch, maxHunk) {
1638
+ let status = "M";
1639
+ if (/^new file mode /m.test(patch) || /^--- \/dev\/null/m.test(patch)) {
1640
+ status = "A";
1641
+ } else if (/^deleted file mode /m.test(patch) || /^\+\+\+ \/dev\/null/m.test(patch)) {
1642
+ status = "D";
1643
+ } else if (/^rename from /m.test(patch)) {
1644
+ status = "R";
1645
+ }
1646
+ const additions = patch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
1647
+ const deletions = patch.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).length;
1648
+ return {
1649
+ path,
1650
+ status,
1651
+ patch: capPatch(patch, maxHunk),
1652
+ additions,
1653
+ deletions
1654
+ };
1655
+ }
1656
+ async function gitDiffParts(worktreePath, diffArgs, includePatches, path) {
1657
+ const extra = pathspec(path);
1658
+ if (includePatches) {
1659
+ const diff = await gitDiff([...diffArgs, ...extra], worktreePath, {
1660
+ timeoutMs: 4e3
1661
+ });
1662
+ return { nameStatus: "", numstat: "", combinedDiff: diff.stdout };
1663
+ }
1664
+ const [ns, num] = await Promise.all([
1665
+ gitDiff(["--name-status", ...diffArgs, ...extra], worktreePath),
1666
+ gitDiff(["--numstat", ...diffArgs, ...extra], worktreePath)
1667
+ ]);
1668
+ return {
1669
+ nameStatus: ns.stdout,
1670
+ numstat: num.stdout,
1671
+ combinedDiff: ""
1672
+ };
1673
+ }
1674
+ function formatStat(files) {
1675
+ if (files.length === 0) return "(no changes)";
1676
+ const additions = files.reduce((n2, f) => n2 + (f.additions ?? 0), 0);
1677
+ const deletions = files.reduce((n2, f) => n2 + (f.deletions ?? 0), 0);
1678
+ const n = files.length;
1679
+ return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
1680
+ }
1681
+ function emptyScopeStats() {
1682
+ const z2 = emptyStat();
1683
+ return {
1684
+ commits: z2,
1685
+ uncommitted: z2,
1686
+ staged: z2,
1687
+ unstaged: z2,
1688
+ last_turn: z2
1689
+ };
1593
1690
  }
1594
1691
  function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
1595
- const counts = parseNumstat(numstat);
1596
1692
  const patches = splitCombinedDiff(combinedDiff);
1693
+ if (!nameStatus.trim() && patches.size > 0) {
1694
+ return [...patches.entries()].map(
1695
+ ([path, patch]) => fileFromPatch(path, patch, maxHunk)
1696
+ );
1697
+ }
1698
+ const counts = parseNumstat(numstat);
1597
1699
  const files = [];
1598
1700
  for (const { status, path } of parseNameStatus(nameStatus)) {
1599
1701
  const n = counts.get(path);
@@ -1615,11 +1717,11 @@ async function computeScopeStats(worktreePath, mergeBase, lastTurnBase, untracke
1615
1717
  { stdout: unstagedNum },
1616
1718
  lastTurnNum
1617
1719
  ] = await Promise.all([
1618
- mergeBase ? git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }) : Promise.resolve({ stdout: "" }),
1619
- git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
1620
- git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
1621
- git(["diff", "--numstat"], worktreePath, { reject: false }),
1622
- lastTurnBase ? git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }) : Promise.resolve({ stdout: "" })
1720
+ mergeBase ? gitDiff(["--numstat", mergeBase], worktreePath) : Promise.resolve({ stdout: "" }),
1721
+ gitDiff(["--numstat", "HEAD"], worktreePath),
1722
+ gitDiff(["--numstat", "--cached"], worktreePath),
1723
+ gitDiff(["--numstat"], worktreePath),
1724
+ lastTurnBase ? gitDiff(["--numstat", lastTurnBase], worktreePath) : Promise.resolve({ stdout: "" })
1623
1725
  ]);
1624
1726
  return {
1625
1727
  commits: mergeBase ? statFromNumstat(commitsNum, untrackedCount) : emptyStat(),
@@ -1638,7 +1740,7 @@ async function listBranchCommits(worktreePath, repoPath, opts) {
1638
1740
  if (head.exitCode !== 0) return [];
1639
1741
  let base = opts?.base;
1640
1742
  try {
1641
- base = base ?? await resolveDefaultBranch(repoPath);
1743
+ base = base ?? await resolveDefaultBranch(repoPath, { network: false });
1642
1744
  } catch {
1643
1745
  base = "HEAD";
1644
1746
  }
@@ -1668,7 +1770,121 @@ async function listBranchCommits(worktreePath, repoPath, opts) {
1668
1770
  }
1669
1771
  return commits;
1670
1772
  }
1773
+ var SHA_RE = /^[0-9a-f]{7,40}$/i;
1774
+ function emptyPathDiff(scope, base, hasLastTurnBase, commitSha = null) {
1775
+ return {
1776
+ scope,
1777
+ commitSha: scope === "commits" ? commitSha : null,
1778
+ base,
1779
+ files: [],
1780
+ stat: formatStat([]),
1781
+ dirty: false,
1782
+ unpushed: 0,
1783
+ hasLastTurnBase,
1784
+ commits: [],
1785
+ scopeStats: emptyScopeStats()
1786
+ };
1787
+ }
1788
+ async function getPathDiff(worktreePath, repoPath, opts) {
1789
+ const { path, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
1790
+ if (scope === "last_turn" && !lastTurnBase) {
1791
+ return emptyPathDiff(scope, "last turn", false);
1792
+ }
1793
+ let diffArgs;
1794
+ let labelBase;
1795
+ if (scope === "staged") {
1796
+ diffArgs = ["--cached"];
1797
+ labelBase = "staged";
1798
+ } else if (scope === "unstaged") {
1799
+ diffArgs = [];
1800
+ labelBase = "unstaged";
1801
+ } else if (scope === "uncommitted") {
1802
+ diffArgs = ["HEAD"];
1803
+ labelBase = "HEAD";
1804
+ } else if (scope === "last_turn") {
1805
+ diffArgs = [lastTurnBase];
1806
+ labelBase = "last turn";
1807
+ } else if (scope === "commits" && commitSha) {
1808
+ diffArgs = [`${commitSha}^!`];
1809
+ labelBase = commitSha.slice(0, 7);
1810
+ } else if (scope === "commits" && opts.base && SHA_RE.test(opts.base)) {
1811
+ diffArgs = [opts.base];
1812
+ labelBase = opts.base.slice(0, 7);
1813
+ } else if (scope === "commits") {
1814
+ let base = opts.base;
1815
+ try {
1816
+ base = base ?? await resolveDefaultBranch(repoPath, { network: false });
1817
+ } catch {
1818
+ base = "HEAD";
1819
+ }
1820
+ const mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
1821
+ diffArgs = [mergeBase];
1822
+ labelBase = mergeBase;
1823
+ } else {
1824
+ diffArgs = ["HEAD"];
1825
+ labelBase = "HEAD";
1826
+ }
1827
+ const diff = await gitDiff(
1828
+ includePatches ? [...diffArgs, "--", path] : ["--name-status", ...diffArgs, "--", path],
1829
+ worktreePath,
1830
+ { timeoutMs: 4e3 }
1831
+ );
1832
+ const filesMap = /* @__PURE__ */ new Map();
1833
+ if (includePatches) {
1834
+ const parsed = filesFromDiff("", "", diff.stdout, maxHunk);
1835
+ for (const file of parsed) filesMap.set(file.path, file);
1836
+ if (diff.stdout.trim() && !filesMap.has(path)) {
1837
+ filesMap.set(path, fileFromPatch(path, diff.stdout, maxHunk));
1838
+ }
1839
+ } else {
1840
+ for (const file of filesFromDiff(diff.stdout, "", "", maxHunk)) {
1841
+ filesMap.set(file.path, file);
1842
+ }
1843
+ }
1844
+ if (filesMap.size === 0) {
1845
+ const maybe = await listUntrackedPaths(worktreePath, path);
1846
+ if (maybe.length > 0) {
1847
+ const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
1848
+ for (const file of extra) filesMap.set(file.path, file);
1849
+ }
1850
+ }
1851
+ const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
1852
+ return {
1853
+ scope,
1854
+ commitSha: scope === "commits" ? commitSha : null,
1855
+ base: labelBase,
1856
+ files,
1857
+ stat: formatStat(files),
1858
+ dirty: files.length > 0,
1859
+ unpushed: 0,
1860
+ hasLastTurnBase,
1861
+ commits: [],
1862
+ scopeStats: emptyScopeStats(),
1863
+ mergeBase: SHA_RE.test(diffArgs[0] ?? "") ? diffArgs[0] : null
1864
+ };
1865
+ }
1671
1866
  async function getDiff(worktreePath, repoPath, opts) {
1867
+ const maxHunk = opts?.maxHunkChars ?? 8e3;
1868
+ const scope = opts?.scope ?? "commits";
1869
+ const lastTurnBase = opts?.lastTurnBase?.trim() || null;
1870
+ const hasLastTurnBase = Boolean(lastTurnBase);
1871
+ const commitSha = opts?.commitSha?.trim() || null;
1872
+ const includePatches = opts?.includePatches ?? true;
1873
+ const pathFilter = opts?.path?.trim() || null;
1874
+ const fileOnly = Boolean(pathFilter);
1875
+ const includeMeta = (opts?.includeMeta ?? true) && !fileOnly;
1876
+ if (fileOnly && pathFilter && !includeMeta) {
1877
+ return getPathDiff(worktreePath, repoPath, {
1878
+ path: pathFilter,
1879
+ scope,
1880
+ commitSha,
1881
+ lastTurnBase,
1882
+ hasLastTurnBase,
1883
+ base: opts?.base,
1884
+ includePatches,
1885
+ maxHunk
1886
+ });
1887
+ }
1672
1888
  const status = await inspectGitWorktree(worktreePath);
1673
1889
  if (status === "missing_worktree") {
1674
1890
  throw new Error("Worktree not found");
@@ -1676,18 +1892,19 @@ async function getDiff(worktreePath, repoPath, opts) {
1676
1892
  if (status === "not_git") {
1677
1893
  throw new Error("Not a Git repository");
1678
1894
  }
1679
- const maxHunk = opts?.maxHunkChars ?? 8e3;
1680
- const scope = opts?.scope ?? "commits";
1681
- const lastTurnBase = opts?.lastTurnBase?.trim() || null;
1682
- const hasLastTurnBase = Boolean(lastTurnBase);
1683
- const commitSha = opts?.commitSha?.trim() || null;
1684
- const head = await git(["rev-parse", "--verify", "HEAD"], worktreePath, {
1895
+ const wantUntracked = opts?.includeUntracked !== void 0 ? opts.includeUntracked : !fileOnly && (scope === "commits" && !commitSha || scope === "uncommitted" || scope === "unstaged" || scope === "last_turn");
1896
+ const needsMergeBase = scope === "commits" && !commitSha || includeMeta;
1897
+ const headP = git(["rev-parse", "--verify", "HEAD"], worktreePath, {
1685
1898
  reject: false
1686
1899
  });
1900
+ const untrackedP = wantUntracked ? listUntrackedPaths(worktreePath, pathFilter) : Promise.resolve([]);
1901
+ const head = await headP;
1687
1902
  const hasHead = head.exitCode === 0;
1688
- const untrackedPaths = await listUntrackedPaths(worktreePath);
1689
1903
  if (!hasHead) {
1690
- const files2 = scope === "staged" ? [] : (await listUntrackedFiles(worktreePath, maxHunk)).sort(
1904
+ const untrackedPaths2 = await untrackedP;
1905
+ const files2 = scope === "staged" ? [] : includePatches ? (await listUntrackedFiles(worktreePath, maxHunk, untrackedPaths2)).sort(
1906
+ (a, b) => a.path.localeCompare(b.path)
1907
+ ) : untrackedStubs(untrackedPaths2).sort(
1691
1908
  (a, b) => a.path.localeCompare(b.path)
1692
1909
  );
1693
1910
  const untrackedStat = {
@@ -1701,7 +1918,7 @@ async function getDiff(worktreePath, repoPath, opts) {
1701
1918
  commitSha: null,
1702
1919
  base: "(no commits)",
1703
1920
  files: files2,
1704
- stat: files2.length ? files2.map((f) => ` ${f.path} | untracked`).join("\n") : "(no changes)",
1921
+ stat: formatStat(files2),
1705
1922
  dirty: files2.length > 0,
1706
1923
  unpushed: 0,
1707
1924
  hasLastTurnBase,
@@ -1715,137 +1932,122 @@ async function getDiff(worktreePath, repoPath, opts) {
1715
1932
  }
1716
1933
  };
1717
1934
  }
1718
- let base = opts?.base;
1719
- try {
1720
- base = base ?? await resolveDefaultBranch(repoPath);
1721
- } catch {
1722
- base = "HEAD";
1723
- }
1724
- base = await resolveDiffBaseRef(worktreePath, base, repoPath);
1725
- if (base.startsWith("origin/")) {
1726
- const short = base.slice("origin/".length);
1727
- await git(["fetch", "origin", short, "--quiet"], worktreePath, {
1728
- reject: false
1729
- });
1730
- base = await resolveDiffBaseRef(worktreePath, short, repoPath);
1935
+ let mergeBase = "HEAD";
1936
+ let baseLabel = "HEAD";
1937
+ if (needsMergeBase) {
1938
+ let base = opts?.base;
1939
+ try {
1940
+ base = base ?? await resolveDefaultBranch(repoPath, { network: false });
1941
+ } catch {
1942
+ base = "HEAD";
1943
+ }
1944
+ mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
1945
+ baseLabel = await resolveDiffBaseRef(worktreePath, base, repoPath);
1946
+ }
1947
+ const includeUntracked = wantUntracked;
1948
+ const metaPromise = includeMeta ? Promise.all([
1949
+ computeScopeStats(
1950
+ worktreePath,
1951
+ mergeBase,
1952
+ lastTurnBase,
1953
+ // Cheap: don't wait on the untracked walk just to pad the badge.
1954
+ 0
1955
+ ),
1956
+ listBranchCommits(worktreePath, repoPath, { base: baseLabel }),
1957
+ isDirty(worktreePath),
1958
+ countUnpushedCommits(worktreePath)
1959
+ ]).then(([scopeStats, commits, dirty, unpushed]) => ({
1960
+ scopeStats,
1961
+ commits,
1962
+ dirty,
1963
+ unpushed
1964
+ })) : Promise.resolve({
1965
+ scopeStats: emptyScopeStats(),
1966
+ commits: [],
1967
+ dirty: false,
1968
+ unpushed: 0
1969
+ });
1970
+ if (scope === "last_turn" && !lastTurnBase) {
1971
+ const meta2 = await metaPromise;
1972
+ return {
1973
+ scope,
1974
+ commitSha: null,
1975
+ base: "last turn",
1976
+ files: [],
1977
+ stat: "(no last agent turn)",
1978
+ dirty: meta2.dirty,
1979
+ unpushed: meta2.unpushed,
1980
+ hasLastTurnBase: false,
1981
+ commits: meta2.commits,
1982
+ scopeStats: meta2.scopeStats
1983
+ };
1731
1984
  }
1732
- const mergeBase = await resolveMergeBase(worktreePath, base, repoPath);
1733
- const scopeStats = await computeScopeStats(
1734
- worktreePath,
1735
- mergeBase,
1736
- lastTurnBase,
1737
- untrackedPaths.length
1738
- );
1739
- const includeUntracked = scope === "commits" && !commitSha || scope === "uncommitted" || scope === "unstaged" || scope === "last_turn";
1740
- let nameStatus = "";
1741
- let numstat = "";
1742
- let combinedDiff = "";
1743
- let labelBase = base;
1985
+ let diffArgs;
1986
+ let labelBase = baseLabel;
1744
1987
  if (scope === "staged") {
1745
- const [ns, num, diff] = await Promise.all([
1746
- git(["diff", "--name-status", "--cached"], worktreePath, { reject: false }),
1747
- git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
1748
- git(["diff", "--cached"], worktreePath, { reject: false })
1749
- ]);
1750
- nameStatus = ns.stdout;
1751
- numstat = num.stdout;
1752
- combinedDiff = diff.stdout;
1988
+ diffArgs = ["--cached"];
1753
1989
  labelBase = "staged";
1754
1990
  } else if (scope === "unstaged") {
1755
- const [ns, num, diff] = await Promise.all([
1756
- git(["diff", "--name-status"], worktreePath, { reject: false }),
1757
- git(["diff", "--numstat"], worktreePath, { reject: false }),
1758
- git(["diff"], worktreePath, { reject: false })
1759
- ]);
1760
- nameStatus = ns.stdout;
1761
- numstat = num.stdout;
1762
- combinedDiff = diff.stdout;
1991
+ diffArgs = [];
1763
1992
  labelBase = "unstaged";
1764
1993
  } else if (scope === "uncommitted") {
1765
- const [ns, num, diff] = await Promise.all([
1766
- git(["diff", "--name-status", "HEAD"], worktreePath, { reject: false }),
1767
- git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
1768
- git(["diff", "HEAD"], worktreePath, { reject: false })
1769
- ]);
1770
- nameStatus = ns.stdout;
1771
- numstat = num.stdout;
1772
- combinedDiff = diff.stdout;
1994
+ diffArgs = ["HEAD"];
1773
1995
  labelBase = "HEAD";
1774
1996
  } else if (scope === "last_turn") {
1775
- if (!lastTurnBase) {
1776
- const commits2 = await listBranchCommits(worktreePath, repoPath, { base });
1777
- return {
1778
- scope,
1779
- commitSha: null,
1780
- base: "last turn",
1781
- files: [],
1782
- stat: "(no last agent turn)",
1783
- dirty: await isDirty(worktreePath),
1784
- unpushed: await countUnpushedCommits(worktreePath),
1785
- hasLastTurnBase: false,
1786
- commits: commits2,
1787
- scopeStats
1788
- };
1789
- }
1790
- const [ns, num, diff] = await Promise.all([
1791
- git(["diff", "--name-status", lastTurnBase], worktreePath, { reject: false }),
1792
- git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }),
1793
- git(["diff", lastTurnBase], worktreePath, { reject: false })
1794
- ]);
1795
- nameStatus = ns.stdout;
1796
- numstat = num.stdout;
1797
- combinedDiff = diff.stdout;
1997
+ diffArgs = [lastTurnBase];
1798
1998
  labelBase = "last turn";
1799
1999
  } else if (scope === "commits" && commitSha) {
1800
- const range = `${commitSha}^!`;
1801
- const [ns, num, diff] = await Promise.all([
1802
- git(["diff", "--name-status", range], worktreePath, { reject: false }),
1803
- git(["diff", "--numstat", range], worktreePath, { reject: false }),
1804
- git(["diff", range], worktreePath, { reject: false })
1805
- ]);
1806
- nameStatus = ns.stdout;
1807
- numstat = num.stdout;
1808
- combinedDiff = diff.stdout;
2000
+ diffArgs = [`${commitSha}^!`];
1809
2001
  labelBase = commitSha.slice(0, 7);
1810
2002
  } else {
1811
- const [ns, num, diff] = await Promise.all([
1812
- git(["diff", "--name-status", mergeBase], worktreePath, { reject: false }),
1813
- git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }),
1814
- git(["diff", mergeBase], worktreePath, { reject: false })
1815
- ]);
1816
- nameStatus = ns.stdout;
1817
- numstat = num.stdout;
1818
- combinedDiff = diff.stdout;
1819
- labelBase = base;
1820
- }
1821
- const commits = await listBranchCommits(worktreePath, repoPath, { base });
2003
+ diffArgs = [mergeBase];
2004
+ labelBase = baseLabel;
2005
+ }
2006
+ const partsPromise = gitDiffParts(
2007
+ worktreePath,
2008
+ diffArgs,
2009
+ includePatches,
2010
+ pathFilter
2011
+ );
2012
+ const [parts, meta, untrackedPaths] = await Promise.all([
2013
+ partsPromise,
2014
+ metaPromise,
2015
+ untrackedP
2016
+ ]);
1822
2017
  const filesMap = /* @__PURE__ */ new Map();
1823
- for (const file of filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk)) {
2018
+ for (const file of filesFromDiff(
2019
+ parts.nameStatus,
2020
+ parts.numstat,
2021
+ parts.combinedDiff,
2022
+ maxHunk
2023
+ )) {
1824
2024
  filesMap.set(file.path, file);
1825
2025
  }
1826
2026
  if (includeUntracked) {
1827
- const untrackedFiles = await listUntrackedFiles(worktreePath, maxHunk);
1828
- for (const file of untrackedFiles) {
2027
+ const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, untrackedPaths) : untrackedStubs(untrackedPaths);
2028
+ for (const file of extra) {
1829
2029
  if (!filesMap.has(file.path)) filesMap.set(file.path, file);
1830
2030
  }
2031
+ } else if (fileOnly && pathFilter && !filesMap.has(pathFilter)) {
2032
+ const maybe = await listUntrackedPaths(worktreePath, pathFilter);
2033
+ if (maybe.length > 0) {
2034
+ const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
2035
+ for (const file of extra) filesMap.set(file.path, file);
2036
+ }
1831
2037
  }
1832
2038
  const files = [...filesMap.values()].sort((a, b) => a.path.localeCompare(b.path));
1833
- const { stdout: statOut } = await git(
1834
- 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],
1835
- worktreePath,
1836
- { reject: false }
1837
- );
1838
2039
  return {
1839
2040
  scope,
1840
2041
  commitSha: scope === "commits" ? commitSha : null,
1841
2042
  base: labelBase,
1842
2043
  files,
1843
- stat: statOut.trim() || (files.length ? `${files.length} file(s)` : "(no changes)"),
1844
- dirty: await isDirty(worktreePath),
1845
- unpushed: await countUnpushedCommits(worktreePath),
2044
+ stat: formatStat(files),
2045
+ dirty: fileOnly || !includeMeta ? files.length > 0 : meta.dirty,
2046
+ unpushed: meta.unpushed,
1846
2047
  hasLastTurnBase,
1847
- commits,
1848
- scopeStats
2048
+ commits: meta.commits,
2049
+ scopeStats: meta.scopeStats,
2050
+ mergeBase: needsMergeBase ? mergeBase : null
1849
2051
  };
1850
2052
  }
1851
2053
  async function listWorktreeFiles(worktreePath, opts) {
@@ -2864,7 +3066,7 @@ async function previewLand(thread) {
2864
3066
  const target = await resolveDefaultBranch(thread.repoPath);
2865
3067
  if (thread.branchName === target || thread.branchName === "main" || thread.branchName === "master") {
2866
3068
  }
2867
- const { stdout: head } = await import("./run-JLD2Y52J.js").then(
3069
+ const { stdout: head } = await import("./run-V2WZUMJA.js").then(
2868
3070
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], thread.worktreePath)
2869
3071
  );
2870
3072
  const current = head.trim();
@@ -2903,7 +3105,7 @@ async function confirmLand(thread, opts) {
2903
3105
  if (preview.dirty) {
2904
3106
  committed = await commitAll(thread.worktreePath, meta.commitMessage);
2905
3107
  }
2906
- const { git: git2 } = await import("./run-JLD2Y52J.js");
3108
+ const { git: git2 } = await import("./run-V2WZUMJA.js");
2907
3109
  const { stdout: headOut } = await git2(
2908
3110
  ["rev-parse", "--abbrev-ref", "HEAD"],
2909
3111
  thread.worktreePath,
@@ -3001,7 +3203,7 @@ async function createThread(input, _onSetupLine) {
3001
3203
  return readThread(thread.id) ?? thread;
3002
3204
  }
3003
3205
  async function listLinearIssues(agent, repoPath) {
3004
- const { getAdapter: getAdapter2 } = await import("./agents-TS3EI2YI.js");
3206
+ const { getAdapter: getAdapter2 } = await import("./agents-JBD7IAGK.js");
3005
3207
  await requireAgent(agent, { requireLinear: true });
3006
3208
  const adapter = getAdapter2(agent);
3007
3209
  if (!adapter.listLinearIssues) {
@@ -3529,7 +3731,7 @@ async function adoptThread(input) {
3529
3731
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3530
3732
  }
3531
3733
  const repoPath = await resolveRepoRoot(input.worktreePath);
3532
- const { stdout: branch } = await import("./run-JLD2Y52J.js").then(
3734
+ const { stdout: branch } = await import("./run-V2WZUMJA.js").then(
3533
3735
  ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], input.worktreePath)
3534
3736
  );
3535
3737
  const thread = createEmptyThread({
@@ -3544,7 +3746,7 @@ async function adoptThread(input) {
3544
3746
  messages: input.messages ?? []
3545
3747
  });
3546
3748
  writeThread(thread);
3547
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-EBFW7I7S.js");
3749
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-BWJNGEE6.js");
3548
3750
  await ensureWorkspace2(repoPath);
3549
3751
  return thread;
3550
3752
  }
@@ -5487,6 +5689,11 @@ var Orchestrator = class {
5487
5689
  return getDiff(thread.worktreePath, thread.repoPath, {
5488
5690
  scope: opts?.scope,
5489
5691
  commitSha: opts?.commitSha,
5692
+ base: opts?.base,
5693
+ includePatches: opts?.includePatches,
5694
+ includeMeta: opts?.includeMeta,
5695
+ includeUntracked: opts?.includeUntracked,
5696
+ path: opts?.path,
5490
5697
  lastTurnBase: this.turnBaselines.get(thread.id) ?? null
5491
5698
  });
5492
5699
  }
@@ -5838,7 +6045,7 @@ var Orchestrator = class {
5838
6045
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
5839
6046
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
5840
6047
  try {
5841
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-EBFW7I7S.js");
6048
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-BWJNGEE6.js");
5842
6049
  await ensureWorkspace2(thread.repoPath);
5843
6050
  } catch {
5844
6051
  }
@@ -5880,7 +6087,7 @@ var Orchestrator = class {
5880
6087
  return restored2;
5881
6088
  }
5882
6089
  if (!existsSync13(thread.worktreePath)) {
5883
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-7NBPIVFZ.js");
6090
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-ZPSKEZFE.js");
5884
6091
  const { execa: execa6 } = await import("execa");
5885
6092
  const slug = thread.worktreePath.split("/").pop();
5886
6093
  const dest = thread.worktreePath;
@@ -5989,7 +6196,7 @@ async function startOrchestration(opts) {
5989
6196
  sourceRef: "default",
5990
6197
  ...createOpts
5991
6198
  }).catch(async () => {
5992
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-7NBPIVFZ.js");
6199
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-ZPSKEZFE.js");
5993
6200
  const repo = await resolveRepoRoot2(repoPath);
5994
6201
  const def = await resolveDefaultBranch2(repo);
5995
6202
  return createThread({