@straiffi/archon 1.8.4 → 1.8.6

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 (96) hide show
  1. package/dist/client/assets/{ActivityView-CZwuxu-k.js → ActivityView-DDeEQgbm.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-m0WCUbsY.js → GitHubActionsDialog-DH-B_LR3.js} +1 -1
  3. package/dist/client/assets/{VerificationDialog-D0LL2bcG.js → VerificationDialog-x43F7tbo.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-Da0b3yts.js → architectureDiagram-3BPJPVTR-BH5LLK54.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-DvzsxBUN.js → blockDiagram-GPEHLZMM-xOxShPwZ.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-BEKrFtx2.js → c4Diagram-AAUBKEIU-BXMrXnKR.js} +1 -1
  7. package/dist/client/assets/channel-C0dk5Jiy.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-BAKgBD7I.js → chunk-2J33WTMH-BdDEtNMz.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-BKPTlfa5.js → chunk-3OPIFGDE-PtSkTtIU.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-BDqDj4QJ.js → chunk-5ZQYHXKU-D5PYP24m.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-Cs2r0JtD.js → chunk-727SXJPM-gDX0xZh1.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-DQbaJPN-.js → chunk-AQP2D5EJ-Bat82lQQ.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CxuNtNrM.js → chunk-CSCIHK7Q-D3sau006.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-Cw9Q8JPi.js → chunk-KSCS5N6A-CqEcB7Dr.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-CtuZjEe8.js → chunk-L5ZTLDWV-TRTuwXsF.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-BQQddDdh.js → chunk-LZXEDZCA-DdpNmv37.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-AK6DBTJe.js → chunk-ND2GUHAM-BCUL9zWr.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-CWRA29FQ.js → chunk-NZK2D7GU-BAxuwE_2.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-X-Oio9FM.js → chunk-O5CBEL6O-Cttpc9yf.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-CETO3v_f.js → chunk-WU5MYG2G-620NtSlY.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-C5ptkk7I.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BFuXUVOx.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-Dt0-rccy.js → dagre-BM42HDAG-BTsZ2dQI.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-BWRTEjaU.js → diagram-2AECGRRQ-afPu4kp4.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-C2H5QcoG.js → diagram-5GNKFQAL-SixbJGPN.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-RBK3VSKM.js → diagram-KO2AKTUF-Dk1670ag.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-RGh2ZH4s.js → diagram-LMA3HP47-BDNhjRaO.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-DRZPfcn0.js → diagram-OG6HWLK6-BLowQCLY.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-BMjeuzHP.js → erDiagram-TEJ5UH35-DAg9kUbm.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-DoLSPv4x.js → flowDiagram-I6XJVG4X-BsaPmiIS.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-By8_6nMe.js → ganttDiagram-6RSMTGT7-DfF1TT-s.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-2rHM4CiD.js → gitGraphDiagram-PVQCEYII-CEvTiN6n.js} +1 -1
  33. package/dist/client/assets/index-B5TSPCJr.css +2 -0
  34. package/dist/client/assets/index-DV0vl670.js +274 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-DWOg8hUJ.js → infoDiagram-5YYISTIA-CBjYYrzJ.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-C9YznvSm.js → ishikawaDiagram-YF4QCWOH-BuCkp1uL.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-DSQDDTNI.js → journeyDiagram-JHISSGLW-DkUPCxT5.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-BkmCufHa.js → kanban-definition-UN3LZRKU-CXwNZ0Fa.js} +1 -1
  39. package/dist/client/assets/{line-cufAthH2.js → line-CRTZgJXA.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-BzbhogG7.js → mermaid-parser.core-NTByLn30.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-CsQKe9Kh.js → mermaid.core-DszsB_Gl.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-Ci6JrMb6.js → mindmap-definition-RKZ34NQL-DCrqCcPX.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-CU9I_3Q_.js → pieDiagram-4H26LBE5-CbHeTKto.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DytyOGrA.js → quadrantDiagram-W4KKPZXB-Ptpkh0Dc.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-CnrtyW9-.js → requirementDiagram-4Y6WPE33-73PM-Ae5.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CGAC1tbH.js → sankeyDiagram-5OEKKPKP-B7RCxzNy.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Cqlp3Fns.js → sequenceDiagram-3UESZ5HK-DGXfHCo0.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-DspbWEGS.js → stateDiagram-AJRCARHV-Cl0_SVL3.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CqzC1yAT.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-Bd0SWRDD.js → timeline-definition-PNZ67QCA-Cufq-wYg.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-BZduwhis.js → vennDiagram-CIIHVFJN-ROVZhhP-.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-D6LrBEnX.js → wardleyDiagram-YWT4CUSO-DlHRfLPH.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CoLKir5a.js → xychartDiagram-2RQKCTM6-DW-KpRbf.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/index.js +118 -10
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/lib/agent.js +28 -7
  58. package/dist/server/lib/agent.js.map +1 -1
  59. package/dist/server/lib/automationResumeDecision.js +6 -0
  60. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  61. package/dist/server/lib/bundleReviewOperations.js +81 -2
  62. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  63. package/dist/server/lib/figmaResources.js +52 -0
  64. package/dist/server/lib/figmaResources.js.map +1 -0
  65. package/dist/server/lib/git.js +312 -32
  66. package/dist/server/lib/git.js.map +1 -1
  67. package/dist/server/lib/opencodeTaskSessions.js +19 -8
  68. package/dist/server/lib/opencodeTaskSessions.js.map +1 -1
  69. package/dist/server/lib/reviewFindings.js +31 -5
  70. package/dist/server/lib/reviewFindings.js.map +1 -1
  71. package/dist/server/lib/specArtifact.js +5 -1
  72. package/dist/server/lib/specArtifact.js.map +1 -1
  73. package/dist/server/lib/specResources.js +1 -4
  74. package/dist/server/lib/specResources.js.map +1 -1
  75. package/dist/server/lib/terminal.js +90 -30
  76. package/dist/server/lib/terminal.js.map +1 -1
  77. package/dist/server/lib/types.js.map +1 -1
  78. package/dist/server/workers/build.js +3 -2
  79. package/dist/server/workers/build.js.map +1 -1
  80. package/dist/server/workers/chat.js +5 -1
  81. package/dist/server/workers/chat.js.map +1 -1
  82. package/dist/server/workers/followUp.js +5 -1
  83. package/dist/server/workers/followUp.js.map +1 -1
  84. package/dist/server/workers/plan.js +36 -14
  85. package/dist/server/workers/plan.js.map +1 -1
  86. package/dist/server/workers/review.js +72 -6
  87. package/dist/server/workers/review.js.map +1 -1
  88. package/dist/server/workers/specSession.js +5 -2
  89. package/dist/server/workers/specSession.js.map +1 -1
  90. package/package.json +1 -1
  91. package/dist/client/assets/channel-CHuNNrYD.js +0 -1
  92. package/dist/client/assets/classDiagram-4FO5ZUOK-BZQ7jv-A.js +0 -1
  93. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-7EFkByjs.js +0 -1
  94. package/dist/client/assets/index-3dSPyX_Q.js +0 -274
  95. package/dist/client/assets/index-DZqqZx53.css +0 -2
  96. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bcxu99EH.js +0 -1
@@ -251,6 +251,15 @@ const resolveHeadCommit = (cwd) => {
251
251
  const output = runGitText(['rev-parse', 'HEAD'], cwd).trim();
252
252
  return output || null;
253
253
  };
254
+ const resolveCommitSha = (revision, cwd) => {
255
+ try {
256
+ const output = runGitText(['rev-parse', '--verify', `${revision}^{commit}`], cwd).trim();
257
+ return output || null;
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ };
254
263
  const countCommitRange = (range, cwd) => {
255
264
  const output = runGitText(['rev-list', '--count', range], cwd).trim();
256
265
  const count = Number.parseInt(output || '0', 10);
@@ -318,6 +327,35 @@ const resolveBranchUpstreamFetchSpec = (branch, cwd) => {
318
327
  const upstreamRef = resolveBranchUpstreamRef(branch, cwd);
319
328
  return parseRemoteBranchRef(upstreamRef);
320
329
  };
330
+ const refreshSquashUpstream = async (branch, cwd) => {
331
+ const upstreamRef = resolveBranchUpstreamRef(branch, cwd);
332
+ if (!upstreamRef) {
333
+ return null;
334
+ }
335
+ const fetchSpec = parseRemoteBranchRef(upstreamRef);
336
+ if (!fetchSpec) {
337
+ const localUpstreamCommitSha = resolveCommitSha(upstreamRef, cwd);
338
+ if (!localUpstreamCommitSha) {
339
+ throw new Error('Unable to resolve the local upstream before squashing commits. Refresh and try again.');
340
+ }
341
+ return localUpstreamCommitSha;
342
+ }
343
+ try {
344
+ await runGitTextAsync(['fetch', '--quiet', fetchSpec.remote, fetchSpec.branch], cwd);
345
+ }
346
+ catch {
347
+ throw new Error('Unable to refresh the upstream branch before squashing commits. Pull or reconcile the branch and try again.');
348
+ }
349
+ const refreshedUpstreamRef = resolveBranchUpstreamRef(branch, cwd);
350
+ if (refreshedUpstreamRef !== upstreamRef) {
351
+ throw new Error('The upstream branch changed while preparing the squash. Refresh and try again.');
352
+ }
353
+ const upstreamCommitSha = resolveCommitSha(refreshedUpstreamRef, cwd);
354
+ if (!upstreamCommitSha) {
355
+ throw new Error('Unable to resolve the refreshed upstream commit before squashing commits. Refresh and try again.');
356
+ }
357
+ return upstreamCommitSha;
358
+ };
321
359
  const resolveGitDir = (cwd) => {
322
360
  const gitDir = runGitText(['rev-parse', '--git-dir'], cwd).trim();
323
361
  if (!gitDir) {
@@ -343,6 +381,26 @@ const getGitOperationState = (cwd) => {
343
381
  }
344
382
  return 'idle';
345
383
  };
384
+ const hasActiveGitOperation = (cwd) => {
385
+ if (getGitOperationState(cwd) !== 'idle') {
386
+ return true;
387
+ }
388
+ try {
389
+ const gitDir = resolveGitDir(cwd);
390
+ if (!gitDir) {
391
+ return false;
392
+ }
393
+ return [
394
+ 'CHERRY_PICK_HEAD',
395
+ 'REVERT_HEAD',
396
+ 'BISECT_LOG',
397
+ 'sequencer',
398
+ ].some(marker => existsSync(join(gitDir, marker)));
399
+ }
400
+ catch {
401
+ return true;
402
+ }
403
+ };
346
404
  const getBranchTrackingState = (branch, cwd) => {
347
405
  if (!resolveBranchUpstreamRef(branch, cwd)) {
348
406
  try {
@@ -500,6 +558,13 @@ const resolveCommitBaseFromBaseRef = (baseRef, cwd) => {
500
558
  }
501
559
  return null;
502
560
  };
561
+ const resolveRecordedBaseCommit = (cwd, baseBranch = null, baseCommit = null) => {
562
+ const baseBranchCommit = resolveCommitBaseFromBaseRef(resolveNamedBranchRef(baseBranch, cwd), cwd);
563
+ if (baseBranchCommit) {
564
+ return baseBranchCommit;
565
+ }
566
+ return baseCommit?.trim() || null;
567
+ };
503
568
  const resolveCommitRangeFromBaseRef = (baseRef, cwd) => {
504
569
  const baseCommit = resolveCommitBaseFromBaseRef(baseRef, cwd);
505
570
  return baseCommit ? `${baseCommit}..HEAD` : null;
@@ -508,25 +573,37 @@ const resolveBranchUniqueRange = (cwd) => {
508
573
  return resolveCommitRangeFromBaseRef(resolveDefaultBranchRef(cwd), cwd);
509
574
  };
510
575
  const resolveRecordedBaseRange = (cwd, baseBranch = null, baseCommit = null) => {
511
- const baseBranchRange = resolveCommitRangeFromBaseRef(resolveNamedBranchRef(baseBranch, cwd), cwd);
512
- if (baseBranchRange) {
513
- return baseBranchRange;
514
- }
515
- if (baseCommit) {
516
- return `${baseCommit}..HEAD`;
517
- }
518
- return null;
576
+ const resolvedBaseCommit = resolveRecordedBaseCommit(cwd, baseBranch, baseCommit);
577
+ return resolvedBaseCommit ? `${resolvedBaseCommit}..HEAD` : null;
519
578
  };
520
579
  const resolveUnpushedCommitRange = (cwd, branch, baseBranch = null, baseCommit = null) => {
521
580
  const upstreamRef = resolveBranchUpstreamRef(branch, cwd);
522
581
  if (upstreamRef) {
523
- return `${upstreamRef}..HEAD`;
582
+ const upstreamCommit = resolveCommitSha(upstreamRef, cwd) ?? upstreamRef;
583
+ return {
584
+ range: `${upstreamCommit}..HEAD`,
585
+ baseCommit: upstreamCommit,
586
+ };
587
+ }
588
+ const recordedBaseCommit = resolveRecordedBaseCommit(cwd, baseBranch, baseCommit);
589
+ if (recordedBaseCommit) {
590
+ return {
591
+ range: `${recordedBaseCommit}..HEAD`,
592
+ baseCommit: recordedBaseCommit,
593
+ };
524
594
  }
525
- const recordedBaseRange = resolveRecordedBaseRange(cwd, baseBranch, baseCommit);
526
- if (recordedBaseRange) {
527
- return recordedBaseRange;
595
+ const defaultBaseRef = resolveDefaultBranchRef(cwd);
596
+ const defaultBaseCommit = resolveCommitBaseFromBaseRef(defaultBaseRef, cwd);
597
+ if (defaultBaseCommit) {
598
+ return {
599
+ range: `${defaultBaseCommit}..HEAD`,
600
+ baseCommit: defaultBaseCommit,
601
+ };
528
602
  }
529
- return resolveBranchUniqueRange(cwd);
603
+ return {
604
+ range: null,
605
+ baseCommit: null,
606
+ };
530
607
  };
531
608
  export const resolveBundleReviewBase = ({ cwd, branch, project, pullRequestBaseBranch = null, }) => {
532
609
  const normalizedPullRequestBaseBranch = pullRequestBaseBranch?.trim() || null;
@@ -639,18 +716,31 @@ const getDiffDataForBaseRefAsync = async (cwd, baseRef) => {
639
716
  };
640
717
  }
641
718
  };
719
+ const getUnpushedRangeArgs = (resolvedRange) => {
720
+ return resolvedRange.range ? [resolvedRange.range] : ['HEAD', '--not', '--remotes'];
721
+ };
722
+ const getUnpushedCommitSummary = (cwd, branch, baseBranch = null, baseCommit = null) => {
723
+ const resolvedRange = resolveUnpushedCommitRange(cwd, branch, baseBranch, baseCommit);
724
+ const rangeArgs = getUnpushedRangeArgs(resolvedRange);
725
+ const countOutput = runGitText(['rev-list', '--count', ...rangeArgs], cwd).trim();
726
+ const totalCount = Number.parseInt(countOutput || '0', 10);
727
+ const output = runGitText([
728
+ 'log',
729
+ '--format=%H%x00%h%x00%s%x00%cI%x00',
730
+ '--no-show-signature',
731
+ '-n',
732
+ '20',
733
+ ...rangeArgs,
734
+ ], cwd);
735
+ return {
736
+ commits: parseUnpushedCommitsOutput(output),
737
+ totalCount: Number.isFinite(totalCount) ? totalCount : 0,
738
+ baseCommit: resolvedRange.baseCommit,
739
+ };
740
+ };
642
741
  const listUnpushedCommitRecords = (cwd, branch, baseBranch = null, baseCommit = null, options = {}) => {
643
742
  try {
644
- const branchUniqueRange = resolveUnpushedCommitRange(cwd, branch, baseBranch, baseCommit);
645
- const output = runGitText([
646
- 'log',
647
- '--format=%H%x00%h%x00%s%x00%cI%x00',
648
- '--no-show-signature',
649
- '-n',
650
- '20',
651
- ...(branchUniqueRange ? [branchUniqueRange] : ['HEAD', '--not', '--remotes']),
652
- ], cwd);
653
- return parseUnpushedCommitsOutput(output);
743
+ return getUnpushedCommitSummary(cwd, branch, baseBranch, baseCommit).commits;
654
744
  }
655
745
  catch (error) {
656
746
  if (options.throwOnError) {
@@ -1874,10 +1964,25 @@ export const pushWorktreeBranch = async (branch, project = null, options = {}) =
1874
1964
  const cwd = resolveWorktreePath(branch, project);
1875
1965
  await pushCurrentBranch(cwd, branch, options);
1876
1966
  };
1967
+ export const getUnpushedCommitSummaryForCwd = (cwd, branch, project = null, options = {}) => {
1968
+ try {
1969
+ const recordedBase = project ? resolveRecordedBaseForCwd(cwd, branch, project) : null;
1970
+ const fallbackBase = recordedBase ?? (project ? resolveFallbackBase(branch, project) : null);
1971
+ return getUnpushedCommitSummary(cwd, branch, fallbackBase?.baseBranch ?? null, fallbackBase?.baseCommit ?? null);
1972
+ }
1973
+ catch (error) {
1974
+ if (options.throwOnError) {
1975
+ throw error;
1976
+ }
1977
+ return {
1978
+ commits: [],
1979
+ totalCount: 0,
1980
+ baseCommit: null,
1981
+ };
1982
+ }
1983
+ };
1877
1984
  export const listUnpushedCommitsForCwd = (cwd, branch, project = null, options = {}) => {
1878
- const recordedBase = project ? resolveRecordedBaseForCwd(cwd, branch, project) : null;
1879
- const fallbackBase = recordedBase ?? (project ? resolveFallbackBase(branch, project) : null);
1880
- return listUnpushedCommitRecords(cwd, branch, fallbackBase?.baseBranch ?? null, fallbackBase?.baseCommit ?? null, options);
1985
+ return getUnpushedCommitSummaryForCwd(cwd, branch, project, options).commits;
1881
1986
  };
1882
1987
  const resolveFirstParentOrEmptyTree = (cwd, commitHash) => {
1883
1988
  const commitAndParents = runGitText(['rev-list', '--parents', '-n', '1', commitHash], cwd).trim().split(/\s+/);
@@ -1892,24 +1997,199 @@ export const getUnpushedCommitDiffForCwd = ({ cwd, branch, project = null, commi
1892
1997
  if (commitHash !== null && !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(normalizedCommitHash ?? '')) {
1893
1998
  throw new UnpushedCommitDiffSelectionError('commit must be a full 40- or 64-character Git commit hash.');
1894
1999
  }
1895
- const commits = listUnpushedCommitsForCwd(cwd, branch, project, { throwOnError: true });
1896
- if (commits.length === 0) {
2000
+ const summary = getUnpushedCommitSummaryForCwd(cwd, branch, project, { throwOnError: true });
2001
+ if (summary.commits.length === 0) {
1897
2002
  throw new UnpushedCommitDiffSelectionError('There are no local commits to compare.');
1898
2003
  }
1899
2004
  const selectedCommit = normalizedCommitHash
1900
- ? commits.find(commit => commit.hash === normalizedCommitHash)
1901
- : commits[0];
2005
+ ? summary.commits.find(commit => commit.hash === normalizedCommitHash)
2006
+ : summary.commits[0];
1902
2007
  if (!selectedCommit) {
1903
2008
  throw new UnpushedCommitDiffSelectionError('The selected commit is no longer in the local commits list.');
1904
2009
  }
1905
- const oldestCommit = normalizedCommitHash ? selectedCommit : commits[commits.length - 1];
1906
- const baseCommit = resolveFirstParentOrEmptyTree(cwd, oldestCommit.hash);
2010
+ const oldestCommit = normalizedCommitHash ? selectedCommit : summary.commits[summary.commits.length - 1];
2011
+ const baseCommit = normalizedCommitHash
2012
+ ? resolveFirstParentOrEmptyTree(cwd, oldestCommit.hash)
2013
+ : (summary.baseCommit ?? resolveFirstParentOrEmptyTree(cwd, oldestCommit.hash));
1907
2014
  return getCommittedDiffData(cwd, baseCommit, selectedCommit.hash);
1908
2015
  };
1909
2016
  export const listUnpushedCommits = (branch, project = null) => {
1910
2017
  const cwd = resolveWorktreePath(branch, project);
1911
2018
  return listUnpushedCommitsForCwd(cwd, branch, project);
1912
2019
  };
2020
+ const assertSquashWorkspace = (cwd, branch, project) => {
2021
+ if (hasActiveGitOperation(cwd)) {
2022
+ throw new Error('Finish or abort the current Git operation before squashing commits.');
2023
+ }
2024
+ const status = getGitStatus(cwd, branch, project);
2025
+ if (!status.branch) {
2026
+ throw new Error('Unable to verify the current branch before squashing commits.');
2027
+ }
2028
+ if (status.branch !== branch) {
2029
+ throw new Error(`The workspace branch changed to "${status.branch}". Refresh and try again.`);
2030
+ }
2031
+ if (status.operation_state !== 'idle') {
2032
+ throw new Error('Finish or abort the current Git operation before squashing commits.');
2033
+ }
2034
+ if (status.files.some(file => file.conflicted)) {
2035
+ throw new Error('Resolve conflicted files before squashing commits.');
2036
+ }
2037
+ if (status.is_dirty || status.has_staged_changes || status.has_unstaged_changes) {
2038
+ throw new Error('Commit, stash, or discard workspace changes before squashing commits.');
2039
+ }
2040
+ return status;
2041
+ };
2042
+ const getSquashCommitSummary = (cwd, branch, project) => {
2043
+ try {
2044
+ return getUnpushedCommitSummaryForCwd(cwd, branch, project, { throwOnError: true });
2045
+ }
2046
+ catch {
2047
+ throw new Error('Unable to resolve the complete unpushed commit range. Reconcile the branch and refresh before squashing commits.');
2048
+ }
2049
+ };
2050
+ const assertSquashStateUnchanged = async ({ cwd, branch, project, branchRef, headSha, baseCommitSha, totalCount, }) => {
2051
+ const currentBranchRef = runGitText(['symbolic-ref', '--quiet', 'HEAD'], cwd).trim();
2052
+ const currentHeadSha = resolveHeadCommit(cwd);
2053
+ if (currentBranchRef !== branchRef || currentHeadSha !== headSha) {
2054
+ throw new Error('The branch or HEAD changed while preparing the squash. Refresh and try again.');
2055
+ }
2056
+ assertSquashWorkspace(cwd, branch, project);
2057
+ const refreshedUpstreamCommitSha = await refreshSquashUpstream(branch, cwd);
2058
+ const currentSummary = getSquashCommitSummary(cwd, branch, project);
2059
+ const currentBaseCommitSha = currentSummary.baseCommit
2060
+ ? resolveCommitSha(currentSummary.baseCommit, cwd)
2061
+ : null;
2062
+ if (currentSummary.totalCount !== totalCount
2063
+ || currentBaseCommitSha !== baseCommitSha
2064
+ || (refreshedUpstreamCommitSha !== null && refreshedUpstreamCommitSha !== baseCommitSha)) {
2065
+ throw new Error('The unpushed commit range changed while preparing the squash. Refresh and try again.');
2066
+ }
2067
+ };
2068
+ export const squashUnpushedCommitsForCwd = async (cwd, branch, project, message) => {
2069
+ const normalizedMessage = message.trim();
2070
+ if (!normalizedMessage) {
2071
+ throw new Error('A commit message is required.');
2072
+ }
2073
+ const branchRef = runGitText(['symbolic-ref', '--quiet', 'HEAD'], cwd).trim();
2074
+ if (branchRef !== `refs/heads/${branch}`) {
2075
+ throw new Error(`The workspace is no longer on branch "${branch}". Refresh and try again.`);
2076
+ }
2077
+ const preActionHead = getGitHeadIdentity(cwd);
2078
+ assertSquashWorkspace(cwd, branch, project);
2079
+ const refreshedUpstreamCommitSha = await refreshSquashUpstream(branch, cwd);
2080
+ const summary = getSquashCommitSummary(cwd, branch, project);
2081
+ if (summary.totalCount < 2) {
2082
+ throw new Error('At least two unpushed commits are required to squash.');
2083
+ }
2084
+ const baseCommitSha = summary.baseCommit
2085
+ ? resolveCommitSha(summary.baseCommit, cwd)
2086
+ : null;
2087
+ if (!baseCommitSha) {
2088
+ throw new Error('Unable to resolve the upstream or worktree base. Reconcile the branch before squashing commits.');
2089
+ }
2090
+ if (refreshedUpstreamCommitSha !== null && refreshedUpstreamCommitSha !== baseCommitSha) {
2091
+ throw new Error('The upstream branch changed while preparing the squash. Refresh and try again.');
2092
+ }
2093
+ try {
2094
+ runGitText(['merge-base', '--is-ancestor', baseCommitSha, preActionHead.headSha], cwd);
2095
+ }
2096
+ catch {
2097
+ throw new Error('The resolved base is not an ancestor of HEAD. Pull or rebase the branch before squashing commits.');
2098
+ }
2099
+ const preActionIndexTreeSha = getGitIndexTreeSha(cwd);
2100
+ if (preActionIndexTreeSha !== preActionHead.treeSha) {
2101
+ throw new Error('The index changed while preparing the squash. Refresh and try again.');
2102
+ }
2103
+ await assertSquashStateUnchanged({
2104
+ cwd,
2105
+ branch,
2106
+ project,
2107
+ branchRef,
2108
+ headSha: preActionHead.headSha,
2109
+ baseCommitSha,
2110
+ totalCount: summary.totalCount,
2111
+ });
2112
+ const commitOutput = await runGitTextAsync([
2113
+ 'commit-tree',
2114
+ preActionHead.treeSha,
2115
+ '-p',
2116
+ baseCommitSha,
2117
+ '-m',
2118
+ normalizedMessage,
2119
+ ], cwd);
2120
+ const commitSha = commitOutput.stdout.trim().split(/\s+/)[0] ?? '';
2121
+ if (!commitSha) {
2122
+ throw new Error('Git did not return the new squash commit.');
2123
+ }
2124
+ const commitMetadata = runGitText(['show', '-s', '--format=%H%n%P%n%T', commitSha], cwd).trim().split('\n');
2125
+ const observedCommitSha = commitMetadata[0]?.trim();
2126
+ const observedParents = commitMetadata[1]?.trim().split(/\s+/).filter(Boolean) ?? [];
2127
+ const observedTreeSha = commitMetadata[2]?.trim();
2128
+ if (observedCommitSha !== commitSha
2129
+ || observedParents.length !== 1
2130
+ || observedParents[0] !== baseCommitSha
2131
+ || observedTreeSha !== preActionHead.treeSha) {
2132
+ throw new Error('The new squash commit did not match the expected tree and parent.');
2133
+ }
2134
+ try {
2135
+ await assertSquashStateUnchanged({
2136
+ cwd,
2137
+ branch,
2138
+ project,
2139
+ branchRef,
2140
+ headSha: preActionHead.headSha,
2141
+ baseCommitSha,
2142
+ totalCount: summary.totalCount,
2143
+ });
2144
+ }
2145
+ catch {
2146
+ throw new Error('The branch, workspace, or upstream changed while squashing commits. Refresh and try again.');
2147
+ }
2148
+ try {
2149
+ await runGitTextAsync(['update-ref', branchRef, commitSha, preActionHead.headSha], cwd);
2150
+ }
2151
+ catch {
2152
+ throw new Error('The branch or HEAD changed while squashing commits. No branch ref was overwritten; refresh and try again.');
2153
+ }
2154
+ try {
2155
+ const finalHead = getGitHeadIdentity(cwd);
2156
+ const finalIndexTreeSha = getGitIndexTreeSha(cwd);
2157
+ const finalStatus = assertSquashWorkspace(cwd, branch, project);
2158
+ const finalUpstreamCommitSha = await refreshSquashUpstream(branch, cwd);
2159
+ const finalSummary = getSquashCommitSummary(cwd, branch, project);
2160
+ const finalBaseCommitSha = finalSummary.baseCommit
2161
+ ? resolveCommitSha(finalSummary.baseCommit, cwd)
2162
+ : null;
2163
+ if (finalHead.headSha !== commitSha
2164
+ || finalHead.treeSha !== preActionHead.treeSha
2165
+ || finalIndexTreeSha !== preActionIndexTreeSha
2166
+ || finalStatus.is_dirty
2167
+ || finalSummary.totalCount !== 1
2168
+ || finalBaseCommitSha !== baseCommitSha
2169
+ || (finalUpstreamCommitSha !== null && finalUpstreamCommitSha !== baseCommitSha)) {
2170
+ throw new Error('The workspace or squash base changed while squashing commits.');
2171
+ }
2172
+ }
2173
+ catch (error) {
2174
+ const reason = error instanceof Error ? error.message : String(error);
2175
+ try {
2176
+ await runGitTextAsync(['update-ref', branchRef, preActionHead.headSha, commitSha], cwd);
2177
+ }
2178
+ catch {
2179
+ throw new Error(`${reason} The branch ref could not be rolled back safely; inspect the branch before retrying.`);
2180
+ }
2181
+ throw new Error(`${reason} The branch ref was rolled back safely; refresh the Changes view and try again.`);
2182
+ }
2183
+ return {
2184
+ commitSha,
2185
+ parentSha: baseCommitSha,
2186
+ treeSha: preActionHead.treeSha,
2187
+ };
2188
+ };
2189
+ export const squashUnpushedCommits = async (branch, project, message) => {
2190
+ const cwd = resolveWorktreePath(branch, project);
2191
+ return squashUnpushedCommitsForCwd(cwd, branch, project, message);
2192
+ };
1913
2193
  export const undoLatestUnpushedCommitForCwd = async (cwd, branch, project = null) => {
1914
2194
  const commits = listUnpushedCommitsForCwd(cwd, branch, project);
1915
2195
  if (commits.length === 0) {