github-issue-tower-defence-management 1.42.2 → 1.42.4

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.
@@ -85,7 +85,7 @@ describe('StartPreparationUseCase', () => {
85
85
  IssueRepository,
86
86
  | 'getAllOpened'
87
87
  | 'getStoryObjectMap'
88
- | 'update'
88
+ | 'updateStatus'
89
89
  | 'findRelatedOpenPRs'
90
90
  | 'getOpenPullRequest'
91
91
  >
@@ -107,7 +107,7 @@ describe('StartPreparationUseCase', () => {
107
107
  mockIssueRepository = {
108
108
  getAllOpened: jest.fn(),
109
109
  getStoryObjectMap: jest.fn().mockResolvedValue(new Map()),
110
- update: jest.fn(),
110
+ updateStatus: jest.fn(),
111
111
  findRelatedOpenPRs: jest.fn().mockResolvedValue([]),
112
112
  getOpenPullRequest: jest.fn().mockResolvedValue(null),
113
113
  };
@@ -194,12 +194,13 @@ describe('StartPreparationUseCase', () => {
194
194
  allowedIssueAuthors: null,
195
195
  codexHomeCandidates: null,
196
196
  });
197
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
198
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
197
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
198
+ expect(mockIssueRepository.updateStatus.mock.calls[0][0]).toBe(mockProject);
199
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
199
200
  url: 'url1',
200
201
  status: 'Preparation',
201
202
  });
202
- expect(mockIssueRepository.update.mock.calls[0][1]).toBe(mockProject);
203
+ expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('2');
203
204
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
204
205
  expect(mockLocalCommandRunner.runCommand.mock.calls[0]).toEqual([
205
206
  'aw',
@@ -363,7 +364,7 @@ describe('StartPreparationUseCase', () => {
363
364
  codexHomeCandidates: null,
364
365
  });
365
366
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
366
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
367
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
367
368
  expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
368
369
  expect(consoleWarnSpy).toHaveBeenCalledWith(
369
370
  'Skipping non-OPEN PR https://github.com/user/repo/pull/999: wrapper requires an open PR.',
@@ -411,7 +412,7 @@ describe('StartPreparationUseCase', () => {
411
412
  codexHomeCandidates: null,
412
413
  });
413
414
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
414
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
415
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
415
416
  expect(consoleWarnSpy).toHaveBeenCalledWith(
416
417
  'Skipping PR https://github.com/user/repo/pull/999: head branch is unavailable.',
417
418
  );
@@ -458,7 +459,7 @@ describe('StartPreparationUseCase', () => {
458
459
  codexHomeCandidates: null,
459
460
  });
460
461
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
461
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
462
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
462
463
  expect(consoleErrorSpy).toHaveBeenCalledWith(
463
464
  expect.stringContaining('branch name contains unexpected characters'),
464
465
  );
@@ -516,7 +517,7 @@ describe('StartPreparationUseCase', () => {
516
517
  codexHomeCandidates: null,
517
518
  });
518
519
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
519
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
520
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
520
521
  expect(consoleWarnSpy).toHaveBeenCalledWith(
521
522
  'Skipping issue url1: 2 related open PRs found (ambiguous).',
522
523
  );
@@ -566,7 +567,7 @@ describe('StartPreparationUseCase', () => {
566
567
  codexHomeCandidates: null,
567
568
  });
568
569
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
569
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
570
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
570
571
  expect(consoleWarnSpy).toHaveBeenCalledWith(
571
572
  'Skipping issue url1: related open PR has unavailable head branch.',
572
573
  );
@@ -611,16 +612,19 @@ describe('StartPreparationUseCase', () => {
611
612
  codexHomeCandidates: null,
612
613
  });
613
614
  // Both awaiting issues should be updated (forward iteration: url1 first, then url2)
614
- expect(mockIssueRepository.update.mock.calls).toHaveLength(2);
615
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
615
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
616
+ expect(mockIssueRepository.updateStatus.mock.calls[0][0]).toBe(mockProject);
617
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
616
618
  url: 'url1',
617
619
  status: 'Preparation',
618
620
  });
619
- expect(mockIssueRepository.update.mock.calls[1][0]).toMatchObject({
621
+ expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('2');
622
+ expect(mockIssueRepository.updateStatus.mock.calls[1][0]).toBe(mockProject);
623
+ expect(mockIssueRepository.updateStatus.mock.calls[1][1]).toMatchObject({
620
624
  url: 'url2',
621
625
  status: 'Preparation',
622
626
  });
623
- expect(mockIssueRepository.update.mock.calls[0][1]).toBe(mockProject);
627
+ expect(mockIssueRepository.updateStatus.mock.calls[1][2]).toBe('2');
624
628
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
625
629
  });
626
630
  it('should stop assigning after maximum preparing issues count is reached', async () => {
@@ -669,7 +673,7 @@ describe('StartPreparationUseCase', () => {
669
673
  codexHomeCandidates: null,
670
674
  });
671
675
  // Loop doesn't run because we're already at max (6 >= 6)
672
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
676
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
673
677
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
674
678
  });
675
679
  it('should pass configFilePath to aw command', async () => {
@@ -1084,7 +1088,7 @@ describe('StartPreparationUseCase', () => {
1084
1088
  codexHomeCandidates: null,
1085
1089
  });
1086
1090
  // No issues are in 'Awaiting Workspace' status, so no updates should happen
1087
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
1091
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
1088
1092
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
1089
1093
  });
1090
1094
  it('should use custom maximumPreparingIssuesCount when provided', async () => {
@@ -1119,7 +1123,7 @@ describe('StartPreparationUseCase', () => {
1119
1123
  allowedIssueAuthors: null,
1120
1124
  codexHomeCandidates: null,
1121
1125
  });
1122
- expect(mockIssueRepository.update.mock.calls).toHaveLength(3);
1126
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(3);
1123
1127
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(3);
1124
1128
  });
1125
1129
  it('should use default maximumPreparingIssuesCount of 6 when null is provided', async () => {
@@ -1154,7 +1158,7 @@ describe('StartPreparationUseCase', () => {
1154
1158
  allowedIssueAuthors: null,
1155
1159
  codexHomeCandidates: null,
1156
1160
  });
1157
- expect(mockIssueRepository.update.mock.calls).toHaveLength(6);
1161
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(6);
1158
1162
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(6);
1159
1163
  });
1160
1164
 
@@ -1223,9 +1227,9 @@ describe('StartPreparationUseCase', () => {
1223
1227
  codexHomeCandidates: null,
1224
1228
  });
1225
1229
 
1226
- expect(mockIssueRepository.update.mock.calls).toHaveLength(2);
1227
- const updatedUrls = mockIssueRepository.update.mock.calls.map(
1228
- (call) => call[0].url,
1230
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
1231
+ const updatedUrls = mockIssueRepository.updateStatus.mock.calls.map(
1232
+ (call) => call[1].url,
1229
1233
  );
1230
1234
  expect(updatedUrls).toContain('https://github.com/user/repo/issues/100');
1231
1235
  expect(updatedUrls).toContain('https://github.com/user/repo/issues/101');
@@ -1264,7 +1268,7 @@ describe('StartPreparationUseCase', () => {
1264
1268
  codexHomeCandidates: null,
1265
1269
  });
1266
1270
 
1267
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
1271
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
1268
1272
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
1269
1273
  expect(mockProjectRepository.getByUrl).not.toHaveBeenCalled();
1270
1274
  });
@@ -1308,7 +1312,7 @@ describe('StartPreparationUseCase', () => {
1308
1312
  codexHomeCandidates: null,
1309
1313
  });
1310
1314
 
1311
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1315
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1312
1316
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
1313
1317
  });
1314
1318
 
@@ -1360,7 +1364,9 @@ describe('StartPreparationUseCase', () => {
1360
1364
  defaultMax * Math.pow(1 - normalizedUtilizationBeyondThreshold, 2),
1361
1365
  );
1362
1366
  expect(mockProjectRepository.getByUrl).toHaveBeenCalled();
1363
- expect(mockIssueRepository.update.mock.calls).toHaveLength(expectedMax);
1367
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(
1368
+ expectedMax,
1369
+ );
1364
1370
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(
1365
1371
  expectedMax,
1366
1372
  );
@@ -1398,7 +1404,7 @@ describe('StartPreparationUseCase', () => {
1398
1404
  codexHomeCandidates: null,
1399
1405
  });
1400
1406
 
1401
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
1407
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
1402
1408
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
1403
1409
  });
1404
1410
 
@@ -1440,7 +1446,7 @@ describe('StartPreparationUseCase', () => {
1440
1446
  codexHomeCandidates: null,
1441
1447
  });
1442
1448
 
1443
- expect(mockIssueRepository.update.mock.calls).toHaveLength(6);
1449
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(6);
1444
1450
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(6);
1445
1451
  });
1446
1452
 
@@ -1478,7 +1484,7 @@ describe('StartPreparationUseCase', () => {
1478
1484
  });
1479
1485
 
1480
1486
  expect(mockProjectRepository.getByUrl).not.toHaveBeenCalled();
1481
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
1487
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
1482
1488
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
1483
1489
  });
1484
1490
 
@@ -1525,7 +1531,9 @@ describe('StartPreparationUseCase', () => {
1525
1531
  const expectedMax = Math.floor(
1526
1532
  6 * Math.pow(1 - normalizedUtilizationBeyondThreshold, 2),
1527
1533
  );
1528
- expect(mockIssueRepository.update.mock.calls).toHaveLength(expectedMax);
1534
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(
1535
+ expectedMax,
1536
+ );
1529
1537
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(
1530
1538
  expectedMax,
1531
1539
  );
@@ -1552,7 +1560,7 @@ describe('StartPreparationUseCase', () => {
1552
1560
  }),
1553
1561
  ).rejects.toThrow('Claude credentials file not found');
1554
1562
 
1555
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
1563
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
1556
1564
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
1557
1565
  });
1558
1566
 
@@ -1589,7 +1597,7 @@ describe('StartPreparationUseCase', () => {
1589
1597
  codexHomeCandidates: null,
1590
1598
  });
1591
1599
 
1592
- expect(mockIssueRepository.update.mock.calls).toHaveLength(0);
1600
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
1593
1601
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
1594
1602
  expect(mockProjectRepository.getByUrl).not.toHaveBeenCalled();
1595
1603
  });
@@ -1632,7 +1640,7 @@ describe('StartPreparationUseCase', () => {
1632
1640
  codexHomeCandidates: null,
1633
1641
  });
1634
1642
 
1635
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1643
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1636
1644
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
1637
1645
  });
1638
1646
 
@@ -1680,8 +1688,8 @@ describe('StartPreparationUseCase', () => {
1680
1688
  codexHomeCandidates: null,
1681
1689
  });
1682
1690
 
1683
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1684
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
1691
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1692
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
1685
1693
  url: 'https://github.com/user/repo/issues/3',
1686
1694
  status: 'Preparation',
1687
1695
  });
@@ -1739,8 +1747,8 @@ describe('StartPreparationUseCase', () => {
1739
1747
  codexHomeCandidates: null,
1740
1748
  });
1741
1749
 
1742
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1743
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
1750
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1751
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
1744
1752
  url: 'https://github.com/user/repo/issues/2',
1745
1753
  status: 'Preparation',
1746
1754
  });
@@ -1801,8 +1809,8 @@ describe('StartPreparationUseCase', () => {
1801
1809
  codexHomeCandidates: null,
1802
1810
  });
1803
1811
 
1804
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1805
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
1812
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1813
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
1806
1814
  url: 'https://github.com/user/repo/issues/2',
1807
1815
  status: 'Preparation',
1808
1816
  });
@@ -1852,8 +1860,8 @@ describe('StartPreparationUseCase', () => {
1852
1860
  codexHomeCandidates: null,
1853
1861
  });
1854
1862
 
1855
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1856
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
1863
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1864
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
1857
1865
  url: 'https://github.com/user/repo/issues/1',
1858
1866
  status: 'Preparation',
1859
1867
  });
@@ -1903,8 +1911,8 @@ describe('StartPreparationUseCase', () => {
1903
1911
  codexHomeCandidates: null,
1904
1912
  });
1905
1913
 
1906
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1907
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
1914
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1915
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
1908
1916
  url: 'https://github.com/user/repo/issues/1',
1909
1917
  status: 'Preparation',
1910
1918
  });
@@ -1954,8 +1962,8 @@ describe('StartPreparationUseCase', () => {
1954
1962
  codexHomeCandidates: null,
1955
1963
  });
1956
1964
 
1957
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
1958
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
1965
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
1966
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
1959
1967
  url: 'https://github.com/user/repo/issues/1',
1960
1968
  status: 'Preparation',
1961
1969
  });
@@ -2012,8 +2020,8 @@ describe('StartPreparationUseCase', () => {
2012
2020
  codexHomeCandidates: null,
2013
2021
  });
2014
2022
 
2015
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
2016
- expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
2023
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
2024
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
2017
2025
  url: 'https://github.com/user/repo/issues/1',
2018
2026
  status: 'Preparation',
2019
2027
  });
@@ -2061,7 +2069,7 @@ describe('StartPreparationUseCase', () => {
2061
2069
  codexHomeCandidates: null,
2062
2070
  });
2063
2071
 
2064
- expect(mockIssueRepository.update.mock.calls).toHaveLength(2);
2072
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
2065
2073
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
2066
2074
  });
2067
2075
 
@@ -2109,7 +2117,7 @@ describe('StartPreparationUseCase', () => {
2109
2117
  codexHomeCandidates: null,
2110
2118
  });
2111
2119
 
2112
- expect(mockIssueRepository.update.mock.calls).toHaveLength(2);
2120
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
2113
2121
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
2114
2122
  });
2115
2123
 
@@ -2159,7 +2167,7 @@ describe('StartPreparationUseCase', () => {
2159
2167
  codexHomeCandidates: null,
2160
2168
  });
2161
2169
 
2162
- expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
2170
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
2163
2171
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
2164
2172
  });
2165
2173
 
@@ -2396,4 +2404,108 @@ describe('StartPreparationUseCase', () => {
2396
2404
  ],
2397
2405
  ).toBe('.codex-dev1');
2398
2406
  });
2407
+
2408
+ it('should persist Preparation status via updateStatus with resolved status option id (regression for issue #519)', async () => {
2409
+ const awaitingIssue = createMockIssue({
2410
+ url: 'https://github.com/user/repo/issues/519',
2411
+ title: 'Regression issue',
2412
+ labels: ['category:impl'],
2413
+ status: 'Awaiting Workspace',
2414
+ itemId: 'item-regression',
2415
+ });
2416
+ mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
2417
+ mockIssueRepository.getStoryObjectMap.mockResolvedValue(
2418
+ createMockStoryObjectMap([awaitingIssue]),
2419
+ );
2420
+ mockIssueRepository.getAllOpened.mockResolvedValueOnce([awaitingIssue]);
2421
+ mockLocalCommandRunner.runCommand.mockResolvedValue({
2422
+ stdout: '',
2423
+ stderr: '',
2424
+ exitCode: 0,
2425
+ });
2426
+
2427
+ await useCase.run({
2428
+ projectUrl: 'https://github.com/user/repo',
2429
+ awaitingWorkspaceStatus: 'Awaiting Workspace',
2430
+ preparationStatus: 'Preparation',
2431
+ defaultAgentName: 'agent1',
2432
+ defaultLlmModelName: 'claude-opus',
2433
+ defaultLlmAgentName: null,
2434
+ configFilePath: '/path/to/config.yml',
2435
+ maximumPreparingIssuesCount: null,
2436
+ utilizationPercentageThreshold: 90,
2437
+ allowedIssueAuthors: null,
2438
+ codexHomeCandidates: null,
2439
+ });
2440
+
2441
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
2442
+ expect(mockIssueRepository.updateStatus.mock.calls[0][0]).toBe(mockProject);
2443
+ expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
2444
+ url: 'https://github.com/user/repo/issues/519',
2445
+ itemId: 'item-regression',
2446
+ });
2447
+ expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('2');
2448
+ const updateStatusCallOrder =
2449
+ mockIssueRepository.updateStatus.mock.invocationCallOrder[0];
2450
+ const runCommandCallOrder =
2451
+ mockLocalCommandRunner.runCommand.mock.invocationCallOrder[0];
2452
+ expect(updateStatusCallOrder).toBeLessThan(runCommandCallOrder);
2453
+ });
2454
+
2455
+ it('should return early and log an error when preparationStatus option is not in the project', async () => {
2456
+ const projectWithoutPreparation: Project = {
2457
+ ...createMockProject(),
2458
+ status: {
2459
+ name: 'Status',
2460
+ fieldId: 'status-field-id',
2461
+ statuses: [
2462
+ {
2463
+ id: '1',
2464
+ name: 'Awaiting Workspace',
2465
+ color: 'GRAY',
2466
+ description: '',
2467
+ },
2468
+ { id: '3', name: 'Done', color: 'GREEN', description: '' },
2469
+ ],
2470
+ },
2471
+ };
2472
+ const awaitingIssue = createMockIssue({
2473
+ url: 'url-missing-option',
2474
+ title: 'Missing Preparation Option',
2475
+ labels: ['category:impl'],
2476
+ status: 'Awaiting Workspace',
2477
+ });
2478
+ mockProjectRepository.getByUrl.mockResolvedValue(projectWithoutPreparation);
2479
+ mockProjectRepository.prepareStatus.mockImplementation(() =>
2480
+ Promise.resolve(projectWithoutPreparation),
2481
+ );
2482
+ mockIssueRepository.getStoryObjectMap.mockResolvedValue(
2483
+ createMockStoryObjectMap([awaitingIssue]),
2484
+ );
2485
+ mockIssueRepository.getAllOpened.mockResolvedValueOnce([awaitingIssue]);
2486
+ const consoleErrorSpy = jest
2487
+ .spyOn(console, 'error')
2488
+ .mockImplementation(() => {});
2489
+
2490
+ await useCase.run({
2491
+ projectUrl: 'https://github.com/user/repo',
2492
+ awaitingWorkspaceStatus: 'Awaiting Workspace',
2493
+ preparationStatus: 'Preparation',
2494
+ defaultAgentName: 'agent1',
2495
+ defaultLlmModelName: 'claude-opus',
2496
+ defaultLlmAgentName: null,
2497
+ configFilePath: '/path/to/config.yml',
2498
+ maximumPreparingIssuesCount: null,
2499
+ utilizationPercentageThreshold: 90,
2500
+ allowedIssueAuthors: null,
2501
+ codexHomeCandidates: null,
2502
+ });
2503
+
2504
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
2505
+ expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
2506
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
2507
+ "Preparation status option 'Preparation' not found in project.",
2508
+ );
2509
+ consoleErrorSpy.mockRestore();
2510
+ });
2399
2511
  });
@@ -13,7 +13,7 @@ export class StartPreparationUseCase {
13
13
  IssueRepository,
14
14
  | 'getAllOpened'
15
15
  | 'getStoryObjectMap'
16
- | 'update'
16
+ | 'updateStatus'
17
17
  | 'findRelatedOpenPRs'
18
18
  | 'getOpenPullRequest'
19
19
  >,
@@ -95,6 +95,16 @@ export class StartPreparationUseCase {
95
95
  await this.issueRepository.getStoryObjectMap(project);
96
96
  const allIssues = await this.issueRepository.getAllOpened(project);
97
97
 
98
+ const preparationStatusOption = project.status.statuses.find(
99
+ (s) => s.name === params.preparationStatus,
100
+ );
101
+ if (!preparationStatusOption) {
102
+ console.error(
103
+ `Preparation status option '${params.preparationStatus}' not found in project.`,
104
+ );
105
+ return;
106
+ }
107
+
98
108
  const awaitingWorkspaceIssues = Array.from(storyObjectMap.values())
99
109
  .map((storyObject) => storyObject.issues)
100
110
  .flat()
@@ -211,8 +221,12 @@ export class StartPreparationUseCase {
211
221
  continue;
212
222
  }
213
223
 
224
+ await this.issueRepository.updateStatus(
225
+ project,
226
+ issue,
227
+ preparationStatusOption.id,
228
+ );
214
229
  issue.status = params.preparationStatus;
215
- await this.issueRepository.update(issue, project);
216
230
 
217
231
  const awArgs: string[] = [
218
232
  issue.url,
@@ -1,13 +1,79 @@
1
1
  import { SpreadsheetRepository } from '../../domain/usecases/adapter-interfaces/SpreadsheetRepository';
2
2
  import { LocalStorageRepository } from './LocalStorageRepository';
3
+ interface SheetsApiClient {
4
+ spreadsheets: {
5
+ get(params: {
6
+ spreadsheetId: string;
7
+ }): Promise<{
8
+ status: number;
9
+ data: {
10
+ sheets?: Array<{
11
+ properties?: {
12
+ title?: string | null;
13
+ } | null;
14
+ }> | null;
15
+ };
16
+ }>;
17
+ values: {
18
+ get(params: {
19
+ spreadsheetId: string;
20
+ range: string;
21
+ }): Promise<{
22
+ status: number;
23
+ data: {
24
+ values?: unknown[][] | null;
25
+ };
26
+ }>;
27
+ update(params: {
28
+ spreadsheetId: string;
29
+ range: string;
30
+ valueInputOption: string;
31
+ requestBody: {
32
+ values: string[][];
33
+ };
34
+ }): Promise<{
35
+ status: number;
36
+ data: unknown;
37
+ }>;
38
+ append(params: {
39
+ spreadsheetId: string;
40
+ range: string;
41
+ valueInputOption: string;
42
+ requestBody: {
43
+ values: string[][];
44
+ };
45
+ }): Promise<{
46
+ status: number;
47
+ data: unknown;
48
+ }>;
49
+ };
50
+ batchUpdate(params: {
51
+ spreadsheetId: string;
52
+ requestBody: {
53
+ requests: Array<{
54
+ addSheet?: {
55
+ properties?: {
56
+ title?: string;
57
+ };
58
+ };
59
+ }>;
60
+ };
61
+ }): Promise<{
62
+ status: number;
63
+ data: unknown;
64
+ }>;
65
+ };
66
+ }
3
67
  export declare class GoogleSpreadsheetRepository implements SpreadsheetRepository {
4
68
  readonly localStorageRepository: LocalStorageRepository;
5
69
  keyFile: string;
6
- constructor(localStorageRepository: LocalStorageRepository, serviceAccountKey?: string);
70
+ private readonly sheetsClient;
71
+ constructor(localStorageRepository: LocalStorageRepository, serviceAccountKey?: string, sheetsClientFactory?: () => SheetsApiClient);
7
72
  getSpreadsheetId: (spreadsheetUrl: string) => string;
8
73
  getSheet: (spreadsheetUrl: string, sheetName: string) => Promise<string[][] | null>;
9
74
  updateCell: (spreadsheetUrl: string, sheetName: string, row: number, column: number, value: string) => Promise<void>;
10
75
  createNewSheetIfNotExists: (spreadsheetUrl: string, sheetName: string) => Promise<void>;
11
76
  appendSheetValues: (spreadsheetUrl: string, sheetName: string, values: string[][]) => Promise<void>;
12
77
  }
78
+ export {};
13
79
  //# sourceMappingURL=GoogleSpreadsheetRepository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GoogleSpreadsheetRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GoogleSpreadsheetRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gEAAgE,CAAC;AAEvG,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAIlE,qBAAa,2BAA4B,YAAW,qBAAqB;IAIrE,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB;IAHzD,OAAO,SAAoC;gBAGhC,sBAAsB,EAAE,sBAAsB,EACvD,iBAAiB,GAAE,MACV;IAKX,gBAAgB,GAAI,gBAAgB,MAAM,KAAG,MAAM,CAGjD;IACF,QAAQ,GACN,gBAAgB,MAAM,EACtB,WAAW,MAAM,KAChB,OAAO,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,CAkC3B;IACF,UAAU,GACR,gBAAgB,MAAM,EACtB,WAAW,MAAM,EACjB,KAAK,MAAM,EACX,QAAQ,MAAM,EACd,OAAO,MAAM,KACZ,OAAO,CAAC,IAAI,CAAC,CAqBd;IACF,yBAAyB,GACvB,gBAAgB,MAAM,EACtB,WAAW,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,CA8Bd;IAEF,iBAAiB,GACf,gBAAgB,MAAM,EACtB,WAAW,MAAM,EACjB,QAAQ,MAAM,EAAE,EAAE,KACjB,OAAO,CAAC,IAAI,CAAC,CAuBd;CACH"}
1
+ {"version":3,"file":"GoogleSpreadsheetRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GoogleSpreadsheetRepository.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ export declare class StartPreparationUseCase {
7
7
  private readonly issueRepository;
8
8
  private readonly claudeRepository;
9
9
  private readonly localCommandRunner;
10
- constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'prepareStatus'>, issueRepository: Pick<IssueRepository, 'getAllOpened' | 'getStoryObjectMap' | 'update' | 'findRelatedOpenPRs' | 'getOpenPullRequest'>, claudeRepository: Pick<ClaudeRepository, 'getUsage'>, localCommandRunner: LocalCommandRunner);
10
+ constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'prepareStatus'>, issueRepository: Pick<IssueRepository, 'getAllOpened' | 'getStoryObjectMap' | 'updateStatus' | 'findRelatedOpenPRs' | 'getOpenPullRequest'>, claudeRepository: Pick<ClaudeRepository, 'getUsage'>, localCommandRunner: LocalCommandRunner);
11
11
  run: (params: {
12
12
  projectUrl: string;
13
13
  awaitingWorkspaceStatus: string;
@@ -1 +1 @@
1
- {"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAQhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAblB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,eAAe,CAC7B,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,mBAAmB,GACnB,QAAQ,GACR,oBAAoB,GACpB,oBAAoB,CACvB,EACgB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EACpD,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,cAAc,EAAE,MAAM,CAAC;QACvB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3C,8BAA8B,EAAE,MAAM,CAAC;QACvC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACrC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACtC,KAAG,OAAO,CAAC,IAAI,CAAC,CA4Mf;CACH"}
1
+ {"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAQhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAblB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,eAAe,CAC7B,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,mBAAmB,GACnB,cAAc,GACd,oBAAoB,GACpB,oBAAoB,CACvB,EACgB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EACpD,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,cAAc,EAAE,MAAM,CAAC;QACvB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3C,8BAA8B,EAAE,MAAM,CAAC;QACvC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACrC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACtC,KAAG,OAAO,CAAC,IAAI,CAAC,CA0Nf;CACH"}