@straiffi/archon 1.10.7 → 1.10.9

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 (110) hide show
  1. package/dist/client/assets/{ActivityView-CZdw9ypU.js → ActivityView-CKvbDXeA.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-C4xENL_U.js → GitHubActionsDialog-Dzo0vtak.js} +1 -1
  3. package/dist/client/assets/{VerificationDialog-Bl63STnx.js → VerificationDialog-BJnOitrJ.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B-Dbav9P.js → architectureDiagram-3BPJPVTR-BAIGV9Mj.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-V8QgIP4y.js → blockDiagram-GPEHLZMM-B-5v5jSZ.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CK1CFah9.js → c4Diagram-AAUBKEIU-C6wU415o.js} +1 -1
  7. package/dist/client/assets/channel-5mIy7nm1.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-DebzCgcQ.js → chunk-2J33WTMH-COmvX2tP.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-iAUuccWS.js → chunk-3OPIFGDE-BlANptdY.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-DeuVJv0c.js → chunk-5ZQYHXKU-ETyA2_GU.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-C6mHzmPL.js → chunk-727SXJPM-D5-DudwF.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-D_qASYNv.js → chunk-AQP2D5EJ-k1AJ4sA3.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-rSaGnDPo.js → chunk-CSCIHK7Q-jfksXwY9.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-CFmDkyBp.js → chunk-KSCS5N6A-CHj0FDj9.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-Bxoonz0d.js → chunk-L5ZTLDWV-1ODGxXhG.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-DVwYlvwm.js → chunk-LZXEDZCA-Bgh9X2W0.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-CrYlkN7l.js → chunk-ND2GUHAM-DDkb3sp4.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-B3F5DD0o.js → chunk-NZK2D7GU-B6B8PZ_-.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-6Yr7XpqV.js → chunk-O5CBEL6O-DhiUIFgj.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-CXpOzbd6.js → chunk-WU5MYG2G-Dzd0fbP3.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-BiQ-X2Sg.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CF-wCYW-.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-B5BJffrd.js → dagre-BM42HDAG-DoNDMCXI.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-C4DOfy9O.js → diagram-2AECGRRQ-ETDXSa5q.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CC0n3BGG.js → diagram-5GNKFQAL-CgTUep7e.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-CRzcdj4F.js → diagram-KO2AKTUF-BKTx2iRe.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-DsJtR4ne.js → diagram-LMA3HP47-9iEiScHT.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-DrY7fZ_a.js → diagram-OG6HWLK6-UIZRh7Qa.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-qYLRrYyX.js → erDiagram-TEJ5UH35-BpNJmJva.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-DZwnzJns.js → flowDiagram-I6XJVG4X-C-rK0XZ1.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-f4AoXkmX.js → ganttDiagram-6RSMTGT7-DWyfPvv_.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-_CHXhrED.js → gitGraphDiagram-PVQCEYII-x71bzdc_.js} +1 -1
  33. package/dist/client/assets/index-CIAO5aE_.css +2 -0
  34. package/dist/client/assets/index-nCGyalrn.js +276 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-DPN-bmpB.js → infoDiagram-5YYISTIA-CbRus0Xu.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-D4y7nv2X.js → ishikawaDiagram-YF4QCWOH-DvaMHrgN.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-BqU9b2Ls.js → journeyDiagram-JHISSGLW-BQBu4YGn.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-B3Ff_dpA.js → kanban-definition-UN3LZRKU-8rU-U7L1.js} +1 -1
  39. package/dist/client/assets/{line-Jpug3yeg.js → line-CSZnwjLA.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-BIMhUApg.js → mermaid-parser.core-CTOl2slE.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-Bksciss2.js → mermaid.core-B9gr0Vqf.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-D4jePVLd.js → mindmap-definition-RKZ34NQL-But1_PWQ.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-DVizXBtA.js → pieDiagram-4H26LBE5-CnlVmfSG.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Daakxhvx.js → quadrantDiagram-W4KKPZXB-Dww1ZGuR.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-CLcCdr6S.js → requirementDiagram-4Y6WPE33-CNf_loe8.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DNrYDsXq.js → sankeyDiagram-5OEKKPKP-DBhi37Jj.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B09pYUTV.js → sequenceDiagram-3UESZ5HK-B64hb4E4.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-B8xFgv9d.js → stateDiagram-AJRCARHV-DcS6oB-S.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-PWeeL0tR.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BheFMJuC.js → timeline-definition-PNZ67QCA-Dq-4tXfh.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-wEvykl0Z.js → vennDiagram-CIIHVFJN-CeDklSE_.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-GXXHVTTa.js → wardleyDiagram-YWT4CUSO-D2cJISMm.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BIR4svhm.js → xychartDiagram-2RQKCTM6-CBF-tTpF.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +12 -2
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +94 -23
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agent.js +35 -6
  60. package/dist/server/lib/agent.js.map +1 -1
  61. package/dist/server/lib/agentRuns.js +15 -0
  62. package/dist/server/lib/agentRuns.js.map +1 -1
  63. package/dist/server/lib/automationDeliveryDecision.js +34 -0
  64. package/dist/server/lib/automationDeliveryDecision.js.map +1 -0
  65. package/dist/server/lib/automationProgression.js +5 -2
  66. package/dist/server/lib/automationProgression.js.map +1 -1
  67. package/dist/server/lib/boundedProcess.js +129 -0
  68. package/dist/server/lib/boundedProcess.js.map +1 -0
  69. package/dist/server/lib/bundleReviewOperations.js +7 -0
  70. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  71. package/dist/server/lib/chatOperations.js +140 -1
  72. package/dist/server/lib/chatOperations.js.map +1 -1
  73. package/dist/server/lib/chats.js +34 -2
  74. package/dist/server/lib/chats.js.map +1 -1
  75. package/dist/server/lib/conversationResume.js +152 -0
  76. package/dist/server/lib/conversationResume.js.map +1 -0
  77. package/dist/server/lib/git.js +89 -48
  78. package/dist/server/lib/git.js.map +1 -1
  79. package/dist/server/lib/integrations/github.js +49 -105
  80. package/dist/server/lib/integrations/github.js.map +1 -1
  81. package/dist/server/lib/mobileAccess.js +18 -2
  82. package/dist/server/lib/mobileAccess.js.map +1 -1
  83. package/dist/server/lib/reviewFindings.js +31 -2
  84. package/dist/server/lib/reviewFindings.js.map +1 -1
  85. package/dist/server/lib/reviewRepairCompletion.js +9 -1
  86. package/dist/server/lib/reviewRepairCompletion.js.map +1 -1
  87. package/dist/server/lib/reviewRepairReconciliation.js +8 -13
  88. package/dist/server/lib/reviewRepairReconciliation.js.map +1 -1
  89. package/dist/server/lib/specOperations.js +135 -2
  90. package/dist/server/lib/specOperations.js.map +1 -1
  91. package/dist/server/lib/specs.js +36 -8
  92. package/dist/server/lib/specs.js.map +1 -1
  93. package/dist/server/lib/ticketAutomation.js +27 -4
  94. package/dist/server/lib/ticketAutomation.js.map +1 -1
  95. package/dist/server/lib/tickets.js +72 -20
  96. package/dist/server/lib/tickets.js.map +1 -1
  97. package/dist/server/lib/types.js.map +1 -1
  98. package/dist/server/workers/chat.js +102 -42
  99. package/dist/server/workers/chat.js.map +1 -1
  100. package/dist/server/workers/review.js +14 -26
  101. package/dist/server/workers/review.js.map +1 -1
  102. package/dist/server/workers/specSession.js +199 -92
  103. package/dist/server/workers/specSession.js.map +1 -1
  104. package/package.json +1 -1
  105. package/dist/client/assets/channel-CGJSY1zl.js +0 -1
  106. package/dist/client/assets/classDiagram-4FO5ZUOK-C8R3tolL.js +0 -1
  107. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BQyANHoJ.js +0 -1
  108. package/dist/client/assets/index-BXsDja_y.css +0 -2
  109. package/dist/client/assets/index-Go0iENKj.js +0 -276
  110. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CanC5-kF.js +0 -1
@@ -5,6 +5,7 @@ import { cp as cpAsync, lstat as lstatAsync, mkdir as mkdirAsync, stat as statAs
5
5
  import { tmpdir } from 'os';
6
6
  import { dirname, isAbsolute, join, relative, resolve } from 'path';
7
7
  import db from '../db.js';
8
+ import { runBoundedProcess } from './boundedProcess.js';
8
9
  import { getProjectWorktreeSync } from './projects.js';
9
10
  import { createWorkspaceProcessEnvironment } from './workspaceProcessEnvironment.js';
10
11
  export class UnpushedCommitDiffSelectionError extends Error {
@@ -12,8 +13,10 @@ export class UnpushedCommitDiffSelectionError extends Error {
12
13
  const PROJECT_BRANCH_REMOTE_REFRESH_TTL_MS = 30_000;
13
14
  const MAX_GIT_OUTPUT_BYTES = 512 * 1024 * 1024;
14
15
  const GIT_READ_TIMEOUT_MS = 15_000;
16
+ const GIT_REMOTE_KILL_GRACE_MS = 500;
15
17
  const MAX_CONCURRENT_DIFF_PROCESSES = 8;
16
18
  const lastProjectBranchRemoteRefreshAt = new Map();
19
+ const projectBranchRemoteRefreshes = new Map();
17
20
  const resolveCurrentBranchName = (cwd) => {
18
21
  try {
19
22
  const branch = runGitText(['branch', '--show-current'], cwd).trim();
@@ -113,6 +116,22 @@ const runGitTextAsync = (args, cwd, options = {}) => new Promise((resolvePromise
113
116
  });
114
117
  });
115
118
  });
119
+ const runGitRemoteTextAsync = async (args, cwd) => {
120
+ const result = await runBoundedProcess('git', args, {
121
+ cwd,
122
+ env: createWorkspaceProcessEnvironment(cwd, { GIT_TERMINAL_PROMPT: '0' }),
123
+ timeoutMs: GIT_READ_TIMEOUT_MS,
124
+ killGraceMs: GIT_REMOTE_KILL_GRACE_MS,
125
+ maxOutputBytes: MAX_GIT_OUTPUT_BYTES,
126
+ });
127
+ if (result.code !== 0) {
128
+ const error = new Error(result.stderr.trim() || `git exited with code ${result.code ?? 'unknown'}`);
129
+ error.stderr = result.stderr;
130
+ error.status = result.code;
131
+ throw error;
132
+ }
133
+ return { stdout: result.stdout, stderr: result.stderr };
134
+ };
116
135
  const mapWithConcurrency = async (inputs, concurrency, operation) => {
117
136
  const results = new Array(inputs.length);
118
137
  let nextIndex = 0;
@@ -357,7 +376,7 @@ const refreshSquashUpstream = async (branch, cwd) => {
357
376
  return localUpstreamCommitSha;
358
377
  }
359
378
  try {
360
- await runGitTextAsync(['fetch', '--quiet', fetchSpec.remote, fetchSpec.branch], cwd);
379
+ await runGitRemoteTextAsync(['fetch', '--quiet', fetchSpec.remote, fetchSpec.branch], cwd);
361
380
  }
362
381
  catch {
363
382
  throw new Error('Unable to refresh the upstream branch before squashing commits. Pull or reconcile the branch and try again.');
@@ -477,21 +496,6 @@ const getEmptyProjectBranchResponse = (status = 'unknown') => ({
477
496
  can_pull: false,
478
497
  diff_stats: null,
479
498
  });
480
- const refreshProjectBranchTracking = (repoPath, branch) => {
481
- if (!branch) {
482
- return;
483
- }
484
- try {
485
- const upstreamFetchSpec = resolveBranchUpstreamFetchSpec(branch, repoPath);
486
- const args = upstreamFetchSpec
487
- ? ['fetch', '--quiet', upstreamFetchSpec.remote, upstreamFetchSpec.branch]
488
- : ['fetch', '--quiet', 'origin', branch];
489
- runGitText(args, repoPath);
490
- }
491
- catch {
492
- // Keep the last known tracking state when fetch is unavailable.
493
- }
494
- };
495
499
  const shouldRefreshProjectBranchTracking = (cacheKey, force) => {
496
500
  if (force) {
497
501
  lastProjectBranchRemoteRefreshAt.set(cacheKey, Date.now());
@@ -1390,15 +1394,9 @@ export const deleteWorktree = (branch, project = null) => {
1390
1394
  export const getProjectBranch = (project = null, options = {}) => {
1391
1395
  const repoPath = getRepoPath(project);
1392
1396
  const includeDiffStats = options.includeDiffStats ?? true;
1393
- let output = runGitText(['status', '--porcelain=v2', '--branch', '-z'], repoPath);
1394
- let entries = output.split('\0').filter(Boolean);
1395
- let branchState = getBranchSyncState(entries, repoPath);
1396
- if (branchState.branch && shouldRefreshProjectBranchTracking(getProjectBranchRefreshCacheKey(repoPath, branchState.branch), options.refreshRemote ?? false)) {
1397
- refreshProjectBranchTracking(repoPath, branchState.branch);
1398
- output = runGitText(['status', '--porcelain=v2', '--branch', '-z'], repoPath);
1399
- entries = output.split('\0').filter(Boolean);
1400
- branchState = getBranchSyncState(entries, repoPath);
1401
- }
1397
+ const output = runGitText(['status', '--porcelain=v2', '--branch', '-z'], repoPath);
1398
+ const entries = output.split('\0').filter(Boolean);
1399
+ const branchState = getBranchSyncState(entries, repoPath);
1402
1400
  if (!branchState.branch) {
1403
1401
  const statusLine = output.split('\0').find(entry => entry.startsWith('# branch.head ')) ?? '';
1404
1402
  if (statusLine.endsWith('(detached)')) {
@@ -1416,13 +1414,46 @@ export const getProjectBranch = (project = null, options = {}) => {
1416
1414
  diff_stats: includeDiffStats ? summarizeTicketDiffFiles(getGitDiffFiles(repoPath)) : null,
1417
1415
  };
1418
1416
  };
1417
+ export const getProjectBranchAsync = async (project = null, options = {}) => {
1418
+ const repoPath = getRepoPath(project);
1419
+ const includeDiffStats = options.includeDiffStats ?? true;
1420
+ const readBranchState = async () => {
1421
+ const output = (await runGitTextAsync(['status', '--porcelain=v2', '--branch', '-z'], repoPath, { timeoutMs: GIT_READ_TIMEOUT_MS })).stdout;
1422
+ const entries = output.split('\0').filter(Boolean);
1423
+ const branchState = await getBranchSyncStateAsync(entries, repoPath, null);
1424
+ return { output, branchState };
1425
+ };
1426
+ let { output, branchState } = await readBranchState();
1427
+ if (branchState.branch && shouldRefreshProjectBranchTracking(getProjectBranchRefreshCacheKey(repoPath, branchState.branch), options.refreshRemote ?? false)) {
1428
+ await refreshProjectBranchTrackingAsync(repoPath, branchState.branch);
1429
+ const refreshed = await readBranchState();
1430
+ output = refreshed.output;
1431
+ branchState = refreshed.branchState;
1432
+ }
1433
+ if (!branchState.branch) {
1434
+ const statusLine = output.split('\0').find(entry => entry.startsWith('# branch.head ')) ?? '';
1435
+ return getEmptyProjectBranchResponse(statusLine.endsWith('(detached)') ? 'detached' : 'unknown');
1436
+ }
1437
+ const diffStats = includeDiffStats
1438
+ ? summarizeTicketDiffFiles(await getGitDiffFilesAsync(repoPath))
1439
+ : null;
1440
+ return {
1441
+ branch: branchState.branch,
1442
+ status: 'ready',
1443
+ ahead: branchState.ahead,
1444
+ behind: branchState.behind,
1445
+ has_upstream: branchState.hasUpstream,
1446
+ can_pull: branchState.behind > 0,
1447
+ diff_stats: diffStats,
1448
+ };
1449
+ };
1419
1450
  export const getLocalProjectBranch = (project = null) => {
1420
1451
  const repoPath = getRepoPath(project);
1421
1452
  const output = runGitText(['status', '--porcelain=v2', '--branch', '-z'], repoPath);
1422
1453
  return getBranchSyncState(output.split('\0').filter(Boolean), repoPath).branch;
1423
1454
  };
1424
1455
  export const pullGitBranch = async (cwd, branch = null, project = null) => {
1425
- const gitStatus = getGitStatus(cwd, branch, project, { refreshRemote: true });
1456
+ const gitStatus = await getGitStatusAsync(cwd, branch, project, { refreshRemote: true });
1426
1457
  if (gitStatus.pull_disabled_reason) {
1427
1458
  throw new Error(gitStatus.pull_disabled_reason);
1428
1459
  }
@@ -1433,16 +1464,16 @@ export const pullGitBranch = async (cwd, branch = null, project = null) => {
1433
1464
  if (!upstreamFetchSpec) {
1434
1465
  throw new Error(`Branch "${gitStatus.branch}" has no configured upstream.`);
1435
1466
  }
1436
- await runGitTextAsync(['fetch', '--quiet', upstreamFetchSpec.remote, upstreamFetchSpec.branch], cwd);
1467
+ await runGitRemoteTextAsync(['fetch', '--quiet', upstreamFetchSpec.remote, upstreamFetchSpec.branch], cwd);
1437
1468
  await runGitTextAsync(['merge', '--no-edit', 'FETCH_HEAD'], cwd);
1438
1469
  };
1439
1470
  export const pullProjectBranch = async (project = null) => {
1440
1471
  const repoPath = getRepoPath(project);
1441
- const branchState = getProjectBranch(project, { refreshRemote: true });
1472
+ const branchState = getProjectBranch(project, { includeDiffStats: false });
1442
1473
  await pullGitBranch(repoPath, branchState.branch, project);
1443
1474
  };
1444
1475
  export const rebaseGitBranchOntoBase = async (cwd, branch = null, project = null, baseBranch = null) => {
1445
- const gitStatus = getGitStatus(cwd, branch, project, { refreshRemote: true });
1476
+ const gitStatus = await getGitStatusAsync(cwd, branch, project, { refreshRemote: true });
1446
1477
  const resolvedBaseBranch = baseBranch?.trim() || gitStatus.base_branch;
1447
1478
  const rebaseDisabledReason = getRebaseDisabledReason(gitStatus, resolvedBaseBranch);
1448
1479
  if (rebaseDisabledReason) {
@@ -1454,7 +1485,7 @@ export const rebaseGitBranchOntoBase = async (cwd, branch = null, project = null
1454
1485
  if (!resolvedBaseBranch || resolvedBaseBranch === gitStatus.branch) {
1455
1486
  throw new Error('No base branch is available for this target.');
1456
1487
  }
1457
- await runGitTextAsync(['fetch', '--quiet', 'origin', resolvedBaseBranch], cwd);
1488
+ await runGitRemoteTextAsync(['fetch', '--quiet', 'origin', resolvedBaseBranch], cwd);
1458
1489
  await runGitTextAsync(['rebase', `origin/${resolvedBaseBranch}`], cwd);
1459
1490
  if (project && resolve(cwd) !== getRepoPath(project)) {
1460
1491
  const nextBaseCommit = resolveCommitBaseFromBaseRef(resolveNamedBranchRef(resolvedBaseBranch, cwd), cwd);
@@ -1633,9 +1664,6 @@ const applyGitActionState = (status, options) => {
1633
1664
  export const getGitStatus = (cwd, branch = null, project = null, options = {}) => {
1634
1665
  const recordedBase = branch && project ? resolveRecordedBaseForCwd(cwd, branch, project) : null;
1635
1666
  const branchName = branch ?? resolveCurrentBranchName(cwd);
1636
- if (branchName && (options.refreshRemote ?? false) && shouldRefreshProjectBranchTracking(getProjectBranchRefreshCacheKey(cwd, branchName), options.refreshRemote ?? false)) {
1637
- refreshProjectBranchTracking(cwd, branchName);
1638
- }
1639
1667
  try {
1640
1668
  const output = runGitText(['status', '--porcelain=v2', '--branch', '--untracked-files=all', '-z'], cwd);
1641
1669
  const parsedOutput = parseGitStatusOutput(output);
@@ -1764,18 +1792,31 @@ const resolveFallbackBaseAsync = async (branch, project) => {
1764
1792
  return null;
1765
1793
  }
1766
1794
  };
1767
- const refreshProjectBranchTrackingAsync = async (cwd, branch) => {
1768
- try {
1769
- const upstreamRef = await resolveBranchUpstreamRefAsync(branch, cwd);
1770
- const fetchSpec = parseRemoteBranchRef(upstreamRef);
1771
- const args = fetchSpec
1772
- ? ['fetch', '--quiet', fetchSpec.remote, fetchSpec.branch]
1773
- : ['fetch', '--quiet', 'origin', branch];
1774
- await runGitTextAsync(args, cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
1775
- }
1776
- catch {
1777
- // Keep the last known tracking state when fetch is unavailable.
1795
+ const refreshProjectBranchTrackingAsync = (cwd, branch) => {
1796
+ const key = getProjectBranchRefreshCacheKey(cwd, branch);
1797
+ const existing = projectBranchRemoteRefreshes.get(key);
1798
+ if (existing) {
1799
+ return existing;
1778
1800
  }
1801
+ const refresh = (async () => {
1802
+ try {
1803
+ const upstreamRef = await resolveBranchUpstreamRefAsync(branch, cwd);
1804
+ const fetchSpec = parseRemoteBranchRef(upstreamRef);
1805
+ const args = fetchSpec
1806
+ ? ['fetch', '--quiet', fetchSpec.remote, fetchSpec.branch]
1807
+ : ['fetch', '--quiet', 'origin', branch];
1808
+ await runGitRemoteTextAsync(args, cwd);
1809
+ }
1810
+ catch {
1811
+ // Keep the last known tracking state when fetch is unavailable.
1812
+ }
1813
+ })().finally(() => {
1814
+ if (projectBranchRemoteRefreshes.get(key) === refresh) {
1815
+ projectBranchRemoteRefreshes.delete(key);
1816
+ }
1817
+ });
1818
+ projectBranchRemoteRefreshes.set(key, refresh);
1819
+ return refresh;
1779
1820
  };
1780
1821
  const getUnreadableGitStatus = () => ({
1781
1822
  branch: null,
@@ -1893,7 +1934,7 @@ export const getBoardGitStatus = (cwd, branch = null, project = null) => {
1893
1934
  return getGitStatus(cwd, branch, project, {
1894
1935
  refreshRemote: false,
1895
1936
  includeFiles: false,
1896
- includeWorkspaceSignature: false,
1937
+ includeWorkspaceSignature: true,
1897
1938
  });
1898
1939
  };
1899
1940
  export const getBoardWorktreeGitStatus = (branch, project = null) => {
@@ -2168,7 +2209,7 @@ export const pushAutomationTarget = async (cwd, branch, targetSha, expectedOrigi
2168
2209
  if (expectedOriginIdentity && getAutomationRemoteIdentity(cwd) !== expectedOriginIdentity) {
2169
2210
  throw new Error('Automation origin remote changed after push intent was persisted');
2170
2211
  }
2171
- await runGitTextAsync(['push', '-u', 'origin', `${targetSha}:refs/heads/${branch}`], cwd);
2212
+ await runGitRemoteTextAsync(['push', '-u', 'origin', `${targetSha}:refs/heads/${branch}`], cwd);
2172
2213
  if (expectedOriginIdentity && getAutomationRemoteIdentity(cwd) !== expectedOriginIdentity) {
2173
2214
  throw new Error('Automation origin remote changed while push was running');
2174
2215
  }
@@ -2236,7 +2277,7 @@ export const fetchRemoteBranchState = async (cwd, branch, expectedOriginIdentity
2236
2277
  }
2237
2278
  let output;
2238
2279
  try {
2239
- output = await runGitTextAsync(['ls-remote', '--exit-code', 'origin', `refs/heads/${branch}`], cwd);
2280
+ output = await runGitRemoteTextAsync(['ls-remote', '--exit-code', 'origin', `refs/heads/${branch}`], cwd);
2240
2281
  }
2241
2282
  catch (error) {
2242
2283
  if (error.status === 2) {
@@ -2251,7 +2292,7 @@ export const fetchRemoteBranchState = async (cwd, branch, expectedOriginIdentity
2251
2292
  if (!sha || !ref || extra || ref !== `refs/heads/${branch}` || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(sha)) {
2252
2293
  throw new Error('Remote branch observation was incomplete');
2253
2294
  }
2254
- await runGitTextAsync(['fetch', '--quiet', '--no-tags', 'origin', sha], cwd);
2295
+ await runGitRemoteTextAsync(['fetch', '--quiet', '--no-tags', 'origin', sha], cwd);
2255
2296
  if (expectedOriginIdentity && getAutomationRemoteIdentity(cwd) !== expectedOriginIdentity) {
2256
2297
  throw new Error('Automation origin remote changed while observing push state');
2257
2298
  }