github-issue-tower-defence-management 1.47.0 → 1.48.0

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 (93) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +6 -25
  3. package/bin/adapter/entry-points/cli/index.js +2 -55
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +0 -15
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +12 -53
  8. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  9. package/bin/adapter/repositories/GraphqlProjectRepository.js +37 -0
  10. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
  11. package/bin/domain/entities/WorkflowStatus.js +36 -0
  12. package/bin/domain/entities/WorkflowStatus.js.map +1 -0
  13. package/bin/domain/usecases/AnalyzeStoriesUseCase.js +2 -1
  14. package/bin/domain/usecases/AnalyzeStoriesUseCase.js.map +1 -1
  15. package/bin/domain/usecases/ChangeStatusByStoryColorUseCase.js +4 -3
  16. package/bin/domain/usecases/ChangeStatusByStoryColorUseCase.js.map +1 -1
  17. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +2 -1
  18. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +1 -1
  19. package/bin/domain/usecases/CreateEstimationIssueUseCase.js +2 -1
  20. package/bin/domain/usecases/CreateEstimationIssueUseCase.js.map +1 -1
  21. package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js.map +1 -1
  22. package/bin/domain/usecases/HandleScheduledEventUseCase.js +9 -17
  23. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  24. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +13 -15
  25. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  26. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +4 -5
  27. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  28. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +47 -0
  29. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -0
  30. package/bin/domain/usecases/StartPreparationUseCase.js +7 -8
  31. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  32. package/bin/domain/usecases/UpdateIssueStatusByLabelUseCase.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/adapter/entry-points/cli/index.test.ts +8 -212
  35. package/src/adapter/entry-points/cli/index.ts +6 -88
  36. package/src/adapter/entry-points/cli/projectConfig.ts +0 -33
  37. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +5 -17
  38. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +16 -32
  39. package/src/adapter/repositories/GraphqlProjectRepository.ts +55 -1
  40. package/src/domain/entities/WorkflowStatus.ts +41 -0
  41. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +2 -2
  42. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.test.ts +5 -10
  43. package/src/domain/usecases/ChangeStatusByStoryColorUseCase.ts +4 -4
  44. package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.test.ts +0 -11
  45. package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.ts +2 -2
  46. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +2 -2
  47. package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +0 -4
  48. package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +0 -1
  49. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -41
  50. package/src/domain/usecases/HandleScheduledEventUseCase.ts +9 -27
  51. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +0 -202
  52. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +18 -31
  53. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +13 -101
  54. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +10 -10
  55. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +187 -0
  56. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +69 -0
  57. package/src/domain/usecases/StartPreparationUseCase.test.ts +1 -151
  58. package/src/domain/usecases/StartPreparationUseCase.ts +11 -20
  59. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.test.ts +2 -47
  60. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +1 -5
  61. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -1
  62. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  63. package/types/adapter/entry-points/cli/projectConfig.d.ts +0 -3
  64. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  65. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  66. package/types/adapter/repositories/GraphqlProjectRepository.d.ts +4 -1
  67. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
  68. package/types/domain/entities/WorkflowStatus.d.ts +13 -0
  69. package/types/domain/entities/WorkflowStatus.d.ts.map +1 -0
  70. package/types/domain/usecases/AnalyzeStoriesUseCase.d.ts +0 -1
  71. package/types/domain/usecases/AnalyzeStoriesUseCase.d.ts.map +1 -1
  72. package/types/domain/usecases/ChangeStatusByStoryColorUseCase.d.ts +0 -1
  73. package/types/domain/usecases/ChangeStatusByStoryColorUseCase.d.ts.map +1 -1
  74. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +0 -1
  75. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +1 -1
  76. package/types/domain/usecases/CreateEstimationIssueUseCase.d.ts +0 -1
  77. package/types/domain/usecases/CreateEstimationIssueUseCase.d.ts.map +1 -1
  78. package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts +0 -1
  79. package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts.map +1 -1
  80. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +3 -8
  81. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  82. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +1 -4
  83. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  84. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +0 -3
  85. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  86. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts +10 -0
  87. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -0
  88. package/types/domain/usecases/StartPreparationUseCase.d.ts +1 -3
  89. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  90. package/types/domain/usecases/UpdateIssueStatusByLabelUseCase.d.ts +0 -1
  91. package/types/domain/usecases/UpdateIssueStatusByLabelUseCase.d.ts.map +1 -1
  92. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +3 -1
  93. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
@@ -30,12 +30,9 @@ import { OauthAPIProxyClaudeRepository } from '../../repositories/OauthAPIProxyC
30
30
  import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueCommentRepository';
31
31
  import { FetchWebhookRepository } from '../../repositories/FetchWebhookRepository';
32
32
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
33
- import { Project } from '../../../domain/entities/Project';
34
33
 
35
34
  type StartDaemonOptions = {
36
35
  projectUrl?: string;
37
- awaitingWorkspaceStatus?: string;
38
- preparationStatus?: string;
39
36
  defaultAgentName?: string;
40
37
  defaultLlmModelName?: string;
41
38
  defaultLlmAgentName?: string;
@@ -50,9 +47,6 @@ type StartDaemonOptions = {
50
47
  type NotifyFinishedOptions = {
51
48
  issueUrl: string;
52
49
  projectUrl?: string;
53
- preparationStatus?: string;
54
- awaitingWorkspaceStatus?: string;
55
- awaitingQualityCheckStatus?: string;
56
50
  thresholdForAutoReject?: string;
57
51
  workflowBlockerResolvedWebhookUrl?: string;
58
52
  configFilePath: string;
@@ -114,11 +108,6 @@ program
114
108
  'Path to config file for tower defence management',
115
109
  )
116
110
  .option('--projectUrl <url>', 'GitHub project URL')
117
- .option(
118
- '--awaitingWorkspaceStatus <status>',
119
- 'Status for issues awaiting workspace',
120
- )
121
- .option('--preparationStatus <status>', 'Status for issues in preparation')
122
111
  .option('--defaultAgentName <name>', 'Default agent name')
123
112
  .option('--defaultLlmModelName <name>', 'Default LLM model name')
124
113
  .option('--defaultLlmAgentName <name>', 'Default LLM agent name')
@@ -153,8 +142,6 @@ program
153
142
 
154
143
  const cliOverrides: ConfigFile = {
155
144
  projectUrl: options.projectUrl,
156
- awaitingWorkspaceStatus: options.awaitingWorkspaceStatus,
157
- preparationStatus: options.preparationStatus,
158
145
  defaultAgentName: options.defaultAgentName,
159
146
  defaultLlmModelName: options.defaultLlmModelName,
160
147
  defaultLlmAgentName: options.defaultLlmAgentName,
@@ -189,8 +176,6 @@ program
189
176
  );
190
177
 
191
178
  const projectUrl = config.projectUrl;
192
- const awaitingWorkspaceStatus = config.awaitingWorkspaceStatus;
193
- const preparationStatus = config.preparationStatus;
194
179
  const defaultAgentName = config.defaultAgentName;
195
180
 
196
181
  if (!projectUrl) {
@@ -199,18 +184,6 @@ program
199
184
  );
200
185
  process.exit(1);
201
186
  }
202
- if (!awaitingWorkspaceStatus) {
203
- console.error(
204
- 'awaitingWorkspaceStatus is required. Provide via --awaitingWorkspaceStatus, config file, or project README.',
205
- );
206
- process.exit(1);
207
- }
208
- if (!preparationStatus) {
209
- console.error(
210
- 'preparationStatus is required. Provide via --preparationStatus, config file, or project README.',
211
- );
212
- process.exit(1);
213
- }
214
187
  if (!defaultAgentName) {
215
188
  console.error(
216
189
  'defaultAgentName is required. Provide via --defaultAgentName, config file, or project README.',
@@ -253,16 +226,9 @@ program
253
226
  cachePath,
254
227
  token,
255
228
  );
256
- const projectRepository = {
257
- ...new GraphqlProjectRepository(...githubRepositoryParams),
258
-
259
- prepareStatus: async (
260
- _name: string,
261
- project: Project,
262
- ): Promise<Project> => {
263
- return project;
264
- },
265
- };
229
+ const projectRepository = new GraphqlProjectRepository(
230
+ ...githubRepositoryParams,
231
+ );
266
232
  const apiV3IssueRepository = new ApiV3IssueRepository(
267
233
  ...githubRepositoryParams,
268
234
  );
@@ -296,9 +262,6 @@ program
296
262
  );
297
263
  await revertUseCase.run({
298
264
  projectUrl,
299
- preparationStatus,
300
- awaitingWorkspaceStatus,
301
- awaitingQualityCheckStatus: config.awaitingQualityCheckStatus,
302
265
  allowIssueCacheMinutes,
303
266
  preparationProcessCheckCommand,
304
267
  awLogDirectoryPath: config.awLogDirectoryPath,
@@ -328,8 +291,6 @@ program
328
291
 
329
292
  await useCase.run({
330
293
  projectUrl,
331
- awaitingWorkspaceStatus,
332
- preparationStatus,
333
294
  defaultAgentName,
334
295
  defaultLlmModelName: config.defaultLlmModelName ?? null,
335
296
  defaultLlmAgentName: config.defaultLlmAgentName ?? null,
@@ -352,15 +313,6 @@ program
352
313
  )
353
314
  .requiredOption('--issueUrl <url>', 'GitHub issue URL')
354
315
  .option('--projectUrl <url>', 'GitHub project URL')
355
- .option('--preparationStatus <status>', 'Status for issues in preparation')
356
- .option(
357
- '--awaitingWorkspaceStatus <status>',
358
- 'Status for issues awaiting workspace',
359
- )
360
- .option(
361
- '--awaitingQualityCheckStatus <status>',
362
- 'Status for issues awaiting quality check',
363
- )
364
316
  .option(
365
317
  '--thresholdForAutoReject <count>',
366
318
  'Threshold for auto-escalation after consecutive rejections (default: 3)',
@@ -380,9 +332,6 @@ program
380
332
 
381
333
  const cliOverrides: ConfigFile = {
382
334
  projectUrl: options.projectUrl,
383
- preparationStatus: options.preparationStatus,
384
- awaitingWorkspaceStatus: options.awaitingWorkspaceStatus,
385
- awaitingQualityCheckStatus: options.awaitingQualityCheckStatus,
386
335
  thresholdForAutoReject: options.thresholdForAutoReject
387
336
  ? Number(options.thresholdForAutoReject)
388
337
  : undefined,
@@ -408,9 +357,6 @@ program
408
357
  );
409
358
 
410
359
  const projectUrl = config.projectUrl;
411
- const preparationStatus = config.preparationStatus;
412
- const awaitingWorkspaceStatus = config.awaitingWorkspaceStatus;
413
- const awaitingQualityCheckStatus = config.awaitingQualityCheckStatus;
414
360
 
415
361
  if (!projectUrl) {
416
362
  console.error(
@@ -418,24 +364,6 @@ program
418
364
  );
419
365
  process.exit(1);
420
366
  }
421
- if (!preparationStatus) {
422
- console.error(
423
- 'preparationStatus is required. Provide via --preparationStatus, config file, or project README.',
424
- );
425
- process.exit(1);
426
- }
427
- if (!awaitingWorkspaceStatus) {
428
- console.error(
429
- 'awaitingWorkspaceStatus is required. Provide via --awaitingWorkspaceStatus, config file, or project README.',
430
- );
431
- process.exit(1);
432
- }
433
- if (!awaitingQualityCheckStatus) {
434
- console.error(
435
- 'awaitingQualityCheckStatus is required. Provide via --awaitingQualityCheckStatus, config file, or project README.',
436
- );
437
- process.exit(1);
438
- }
439
367
 
440
368
  let thresholdForAutoReject = 3;
441
369
  const rawThreshold = config.thresholdForAutoReject;
@@ -469,16 +397,9 @@ program
469
397
  cachePath,
470
398
  token,
471
399
  );
472
- const projectRepository = {
473
- ...new GraphqlProjectRepository(...githubRepositoryParams),
474
-
475
- prepareStatus: async (
476
- _name: string,
477
- project: Project,
478
- ): Promise<Project> => {
479
- return project;
480
- },
481
- };
400
+ const projectRepository = new GraphqlProjectRepository(
401
+ ...githubRepositoryParams,
402
+ );
482
403
  const apiV3IssueRepository = new ApiV3IssueRepository(
483
404
  ...githubRepositoryParams,
484
405
  );
@@ -508,9 +429,6 @@ program
508
429
  await useCase.run({
509
430
  projectUrl,
510
431
  issueUrl: options.issueUrl,
511
- preparationStatus,
512
- awaitingWorkspaceStatus,
513
- awaitingQualityCheckStatus,
514
432
  thresholdForAutoReject,
515
433
  workflowBlockerResolvedWebhookUrl,
516
434
  });
@@ -3,8 +3,6 @@ import * as fs from 'fs';
3
3
 
4
4
  export type ConfigFile = {
5
5
  projectUrl?: string;
6
- awaitingWorkspaceStatus?: string;
7
- preparationStatus?: string;
8
6
  defaultAgentName?: string;
9
7
  defaultLlmModelName?: string;
10
8
  defaultLlmAgentName?: string;
@@ -12,7 +10,6 @@ export type ConfigFile = {
12
10
  allowIssueCacheMinutes?: number;
13
11
  utilizationPercentageThreshold?: number;
14
12
  allowedIssueAuthors?: string;
15
- awaitingQualityCheckStatus?: string;
16
13
  thresholdForAutoReject?: number;
17
14
  workflowBlockerResolvedWebhookUrl?: string;
18
15
  projectName?: string;
@@ -68,11 +65,6 @@ export const loadConfigFile = (configFilePath: string): ConfigFile => {
68
65
  }
69
66
  return {
70
67
  projectUrl: getStringValue(parsed, 'projectUrl'),
71
- awaitingWorkspaceStatus: getStringValue(
72
- parsed,
73
- 'awaitingWorkspaceStatus',
74
- ),
75
- preparationStatus: getStringValue(parsed, 'preparationStatus'),
76
68
  defaultAgentName: getStringValue(parsed, 'defaultAgentName'),
77
69
  defaultLlmModelName: getStringValue(parsed, 'defaultLlmModelName'),
78
70
  defaultLlmAgentName: getStringValue(parsed, 'defaultLlmAgentName'),
@@ -86,10 +78,6 @@ export const loadConfigFile = (configFilePath: string): ConfigFile => {
86
78
  'utilizationPercentageThreshold',
87
79
  ),
88
80
  allowedIssueAuthors: getStringValue(parsed, 'allowedIssueAuthors'),
89
- awaitingQualityCheckStatus: getStringValue(
90
- parsed,
91
- 'awaitingQualityCheckStatus',
92
- ),
93
81
  thresholdForAutoReject: getNumberValue(parsed, 'thresholdForAutoReject'),
94
82
  workflowBlockerResolvedWebhookUrl: getStringValue(
95
83
  parsed,
@@ -133,11 +121,6 @@ export const parseProjectReadmeConfig = (readme: string): ConfigFile => {
133
121
  return {};
134
122
  }
135
123
  return {
136
- awaitingWorkspaceStatus: getStringValue(
137
- parsed,
138
- 'awaitingWorkspaceStatus',
139
- ),
140
- preparationStatus: getStringValue(parsed, 'preparationStatus'),
141
124
  defaultAgentName: getStringValue(parsed, 'defaultAgentName'),
142
125
  defaultLlmModelName: getStringValue(parsed, 'defaultLlmModelName'),
143
126
  defaultLlmAgentName: getStringValue(parsed, 'defaultLlmAgentName'),
@@ -151,10 +134,6 @@ export const parseProjectReadmeConfig = (readme: string): ConfigFile => {
151
134
  'utilizationPercentageThreshold',
152
135
  ),
153
136
  allowedIssueAuthors: getStringValue(parsed, 'allowedIssueAuthors'),
154
- awaitingQualityCheckStatus: getStringValue(
155
- parsed,
156
- 'awaitingQualityCheckStatus',
157
- ),
158
137
  thresholdForAutoReject: getNumberValue(parsed, 'thresholdForAutoReject'),
159
138
  workflowBlockerResolvedWebhookUrl: getStringValue(
160
139
  parsed,
@@ -183,14 +162,6 @@ export const mergeConfigs = (
183
162
  readmeOverrides: ConfigFile,
184
163
  ): ConfigFile => ({
185
164
  projectUrl: cliOverrides.projectUrl ?? configFile.projectUrl,
186
- awaitingWorkspaceStatus:
187
- readmeOverrides.awaitingWorkspaceStatus ??
188
- cliOverrides.awaitingWorkspaceStatus ??
189
- configFile.awaitingWorkspaceStatus,
190
- preparationStatus:
191
- readmeOverrides.preparationStatus ??
192
- cliOverrides.preparationStatus ??
193
- configFile.preparationStatus,
194
165
  defaultAgentName:
195
166
  readmeOverrides.defaultAgentName ??
196
167
  cliOverrides.defaultAgentName ??
@@ -219,10 +190,6 @@ export const mergeConfigs = (
219
190
  readmeOverrides.allowedIssueAuthors ??
220
191
  cliOverrides.allowedIssueAuthors ??
221
192
  configFile.allowedIssueAuthors,
222
- awaitingQualityCheckStatus:
223
- readmeOverrides.awaitingQualityCheckStatus ??
224
- cliOverrides.awaitingQualityCheckStatus ??
225
- configFile.awaitingQualityCheckStatus,
226
193
  thresholdForAutoReject:
227
194
  readmeOverrides.thresholdForAutoReject ??
228
195
  cliOverrides.thresholdForAutoReject ??
@@ -141,8 +141,6 @@ const validConfig = {
141
141
  projectUrl: 'https://github.com/users/TestOrg/projects/1',
142
142
  manager: 'TestManager',
143
143
  urlOfStoryView: 'https://github.com/users/TestOrg/projects/1/views/1',
144
- disabledStatus: 'Icebox',
145
- defaultStatus: 'Unread',
146
144
  disabled: false,
147
145
  allowIssueCacheMinutes: 1,
148
146
  workingReport: {
@@ -267,17 +265,12 @@ describe('HandleScheduledEventUseCaseHandler', () => {
267
265
  ...validConfig,
268
266
  allowIssueCacheMinutes: 5,
269
267
  startPreparation: {
270
- awaitingWorkspaceStatus: 'Awaiting',
271
- preparationStatus: 'Preparing',
272
268
  defaultAgentName: 'agent1',
273
269
  configFilePath: './config.yml',
274
270
  maximumPreparingIssuesCount: 10,
275
271
  utilizationPercentageThreshold: 97,
276
272
  },
277
273
  notifyFinishedPreparation: {
278
- preparationStatus: 'Preparing',
279
- awaitingWorkspaceStatus: 'Awaiting',
280
- awaitingQualityCheckStatus: 'Awaiting QC',
281
274
  thresholdForAutoReject: 30,
282
275
  workflowBlockerResolvedWebhookUrl: null,
283
276
  },
@@ -298,10 +291,12 @@ describe('HandleScheduledEventUseCaseHandler', () => {
298
291
  expect(firstCallArg.config.allowIssueCacheMinutes).toBe(5);
299
292
  expect(firstCallArg.config.thresholdForAutoReject).toBe(30);
300
293
  expect(firstCallArg.statusNames.awaitingQualityCheckStatus).toBe(
301
- 'Awaiting QC',
294
+ 'Awaiting Quality Check',
295
+ );
296
+ expect(firstCallArg.statusNames.preparationStatus).toBe('Preparation');
297
+ expect(firstCallArg.statusNames.awaitingWorkspaceStatus).toBe(
298
+ 'Awaiting Workspace',
302
299
  );
303
- expect(firstCallArg.statusNames.preparationStatus).toBe('Preparing');
304
- expect(firstCallArg.statusNames.awaitingWorkspaceStatus).toBe('Awaiting');
305
300
  });
306
301
 
307
302
  it('should write situation file with numeric defaults when optional fields are absent', async () => {
@@ -329,8 +324,6 @@ describe('HandleScheduledEventUseCaseHandler', () => {
329
324
  ...validConfig,
330
325
  allowIssueCacheMinutes: 5,
331
326
  startPreparation: {
332
- awaitingWorkspaceStatus: 'Awaiting workspace',
333
- preparationStatus: 'Preparation',
334
327
  defaultAgentName: 'yaml-agent',
335
328
  configFilePath: '/path/to/config.yml',
336
329
  maximumPreparingIssuesCount: 10,
@@ -342,8 +335,6 @@ describe('HandleScheduledEventUseCaseHandler', () => {
342
335
  const readmeContent = `<details>
343
336
  <summary>config</summary>
344
337
  maximumPreparingIssuesCount: 0
345
- awaitingWorkspaceStatus: README Awaiting
346
- preparationStatus: README Preparation
347
338
  defaultAgentName: readme-agent
348
339
  utilizationPercentageThreshold: 80
349
340
  </details>`;
@@ -358,8 +349,6 @@ utilizationPercentageThreshold: 80
358
349
  expect(capturedRunInputs[0][0]).toMatchObject({
359
350
  startPreparation: {
360
351
  maximumPreparingIssuesCount: 0,
361
- awaitingWorkspaceStatus: 'README Awaiting',
362
- preparationStatus: 'README Preparation',
363
352
  defaultAgentName: 'readme-agent',
364
353
  utilizationPercentageThreshold: 80,
365
354
  },
@@ -419,7 +408,6 @@ allowedIssueAuthors: 'user1, user2, user3'
419
408
  allowIssueCacheMinutes: 5,
420
409
  startPreparation: {
421
410
  maximumPreparingIssuesCount: 10,
422
- awaitingWorkspaceStatus: 'Awaiting workspace',
423
411
  defaultAgentName: 'yaml-agent',
424
412
  },
425
413
  });
@@ -30,7 +30,6 @@ import { ConvertCheckboxToIssueInStoryIssueUseCase } from '../../../domain/useca
30
30
  import { ChangeStatusByStoryColorUseCase } from '../../../domain/usecases/ChangeStatusByStoryColorUseCase';
31
31
  import { SetNoStoryIssueToStoryUseCase } from '../../../domain/usecases/SetNoStoryIssueToStoryUseCase';
32
32
  import { CreateNewStoryByLabelUseCase } from '../../../domain/usecases/CreateNewStoryByLabelUseCase';
33
- import { ProjectRepository } from '../../../domain/usecases/adapter-interfaces/ProjectRepository';
34
33
  import { AssignNoAssigneeIssueToManagerUseCase } from '../../../domain/usecases/AssignNoAssigneeIssueToManagerUseCase';
35
34
  import { UpdateIssueStatusByLabelUseCase } from '../../../domain/usecases/UpdateIssueStatusByLabelUseCase';
36
35
  import { StartPreparationUseCase } from '../../../domain/usecases/StartPreparationUseCase';
@@ -40,6 +39,12 @@ import { NotifyFinishedIssuePreparationUseCase } from '../../../domain/usecases/
40
39
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
41
40
  import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueCommentRepository';
42
41
  import { FetchWebhookRepository } from '../../repositories/FetchWebhookRepository';
42
+ import { SetupTowerDefenceProjectUseCase } from '../../../domain/usecases/SetupTowerDefenceProjectUseCase';
43
+ import {
44
+ AWAITING_QUALITY_CHECK_STATUS_NAME,
45
+ AWAITING_WORKSPACE_STATUS_NAME,
46
+ PREPARATION_STATUS_NAME,
47
+ } from '../../../domain/entities/WorkflowStatus';
43
48
 
44
49
  export class HandleScheduledEventUseCaseHandler {
45
50
  handle = async (
@@ -97,12 +102,6 @@ export class HandleScheduledEventUseCaseHandler {
97
102
  startPreparation: input.startPreparation
98
103
  ? {
99
104
  ...input.startPreparation,
100
- awaitingWorkspaceStatus:
101
- readmeConfig.awaitingWorkspaceStatus ??
102
- input.startPreparation.awaitingWorkspaceStatus,
103
- preparationStatus:
104
- readmeConfig.preparationStatus ??
105
- input.startPreparation.preparationStatus,
106
105
  defaultAgentName:
107
106
  readmeConfig.defaultAgentName ??
108
107
  input.startPreparation.defaultAgentName,
@@ -135,15 +134,6 @@ export class HandleScheduledEventUseCaseHandler {
135
134
  notifyFinishedPreparation: input.notifyFinishedPreparation
136
135
  ? {
137
136
  ...input.notifyFinishedPreparation,
138
- awaitingWorkspaceStatus:
139
- readmeConfig.awaitingWorkspaceStatus ??
140
- input.notifyFinishedPreparation.awaitingWorkspaceStatus,
141
- preparationStatus:
142
- readmeConfig.preparationStatus ??
143
- input.notifyFinishedPreparation.preparationStatus,
144
- awaitingQualityCheckStatus:
145
- readmeConfig.awaitingQualityCheckStatus ??
146
- input.notifyFinishedPreparation.awaitingQualityCheckStatus,
147
137
  thresholdForAutoReject:
148
138
  readmeConfig.thresholdForAutoReject ??
149
139
  input.notifyFinishedPreparation.thresholdForAutoReject,
@@ -175,15 +165,9 @@ export class HandleScheduledEventUseCaseHandler {
175
165
  input.credentials.bot.github.password,
176
166
  input.credentials.bot.github.authenticatorKey,
177
167
  ];
178
- const projectRepository: ProjectRepository = {
179
- ...new GraphqlProjectRepository(...githubRepositoryParams),
180
- prepareStatus: async (
181
- _name: string,
182
- project: Project,
183
- ): Promise<Project> => {
184
- return project;
185
- },
186
- };
168
+ const projectRepository = new GraphqlProjectRepository(
169
+ ...githubRepositoryParams,
170
+ );
187
171
  const apiV3IssueRepository = new ApiV3IssueRepository(
188
172
  ...githubRepositoryParams,
189
173
  );
@@ -200,6 +184,9 @@ export class HandleScheduledEventUseCaseHandler {
200
184
  localStorageCacheRepository,
201
185
  ...githubRepositoryParams,
202
186
  );
187
+ const setupTowerDefenceProjectUseCase = new SetupTowerDefenceProjectUseCase(
188
+ projectRepository,
189
+ );
203
190
  const actionAnnouncement = new ActionAnnouncementUseCase(issueRepository);
204
191
  const setWorkflowManagementIssueToStoryUseCase =
205
192
  new SetWorkflowManagementIssueToStoryUseCase(issueRepository);
@@ -268,6 +255,7 @@ export class HandleScheduledEventUseCaseHandler {
268
255
  );
269
256
 
270
257
  const handleScheduledEventUseCase = new HandleScheduledEventUseCase(
258
+ setupTowerDefenceProjectUseCase,
271
259
  actionAnnouncement,
272
260
  setWorkflowManagementIssueToStoryUseCase,
273
261
  clearPastNextActionUseCase,
@@ -297,13 +285,9 @@ export class HandleScheduledEventUseCaseHandler {
297
285
  projectId: result.project.id,
298
286
  issues: result.issues,
299
287
  statusNames: {
300
- awaitingQualityCheckStatus:
301
- mergedInput.notifyFinishedPreparation?.awaitingQualityCheckStatus ??
302
- null,
303
- preparationStatus:
304
- mergedInput.startPreparation?.preparationStatus ?? null,
305
- awaitingWorkspaceStatus:
306
- mergedInput.startPreparation?.awaitingWorkspaceStatus ?? null,
288
+ awaitingQualityCheckStatus: AWAITING_QUALITY_CHECK_STATUS_NAME,
289
+ preparationStatus: PREPARATION_STATUS_NAME,
290
+ awaitingWorkspaceStatus: AWAITING_WORKSPACE_STATUS_NAME,
307
291
  },
308
292
  config: {
309
293
  maximumPreparingIssuesCount:
@@ -9,7 +9,11 @@ export class GraphqlProjectRepository
9
9
  implements
10
10
  Pick<
11
11
  ProjectRepository,
12
- 'getProject' | 'findProjectIdByUrl' | 'getByUrl' | 'updateStoryList'
12
+ | 'getProject'
13
+ | 'findProjectIdByUrl'
14
+ | 'getByUrl'
15
+ | 'updateStoryList'
16
+ | 'updateStatusList'
13
17
  >
14
18
  {
15
19
  extractProjectFromUrl = (
@@ -362,4 +366,54 @@ export class GraphqlProjectRepository
362
366
  }>();
363
367
  return response.data.updateProjectV2Field.projectV2Field.options;
364
368
  };
369
+ updateStatusList = async (
370
+ project: Project,
371
+ newStatusList: (Omit<FieldOption, 'id'> & {
372
+ id: FieldOption['id'] | null;
373
+ })[],
374
+ ): Promise<FieldOption[]> => {
375
+ const mutation = `mutation UpdateStatusOptions($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
376
+ updateProjectV2Field(input: {
377
+ fieldId: $fieldId
378
+ singleSelectOptions: $options
379
+ }) {
380
+ projectV2Field {
381
+ ... on ProjectV2SingleSelectField {
382
+ options {
383
+ id
384
+ name
385
+ color
386
+ description
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }`;
392
+ const variables = {
393
+ fieldId: project.status.fieldId,
394
+ options: newStatusList.map(({ id, name, color, description }) => ({
395
+ ...(id !== null ? { id } : {}),
396
+ name,
397
+ color,
398
+ description,
399
+ })),
400
+ };
401
+ const response = await ky
402
+ .post('https://api.github.com/graphql', {
403
+ json: { query: mutation, variables },
404
+ headers: {
405
+ Authorization: `Bearer ${this.ghToken}`,
406
+ },
407
+ })
408
+ .json<{
409
+ data: {
410
+ updateProjectV2Field: {
411
+ projectV2Field: {
412
+ options: FieldOption[];
413
+ };
414
+ };
415
+ };
416
+ }>();
417
+ return response.data.updateProjectV2Field.projectV2Field.options;
418
+ };
365
419
  }
@@ -0,0 +1,41 @@
1
+ import { FieldOption } from './Project';
2
+
3
+ export const DEFAULT_STATUS_NAME = 'Unread';
4
+ export const AWAITING_WORKSPACE_STATUS_NAME = 'Awaiting Workspace';
5
+ export const PREPARATION_STATUS_NAME = 'Preparation';
6
+ export const AWAITING_QUALITY_CHECK_STATUS_NAME = 'Awaiting Quality Check';
7
+ export const DISABLED_STATUS_NAME = 'Disabled';
8
+
9
+ export type WorkflowStatusDefinition = {
10
+ name: string;
11
+ color: FieldOption['color'];
12
+ description: string;
13
+ };
14
+
15
+ export const REQUIRED_WORKFLOW_STATUSES: WorkflowStatusDefinition[] = [
16
+ {
17
+ name: DEFAULT_STATUS_NAME,
18
+ color: 'GRAY',
19
+ description: 'Default fallback status for issues before triage',
20
+ },
21
+ {
22
+ name: AWAITING_WORKSPACE_STATUS_NAME,
23
+ color: 'YELLOW',
24
+ description: 'Issue is ready and waiting for an agent workspace',
25
+ },
26
+ {
27
+ name: PREPARATION_STATUS_NAME,
28
+ color: 'ORANGE',
29
+ description: 'Agent is preparing the issue',
30
+ },
31
+ {
32
+ name: AWAITING_QUALITY_CHECK_STATUS_NAME,
33
+ color: 'BLUE',
34
+ description: 'Awaiting human quality check',
35
+ },
36
+ {
37
+ name: DISABLED_STATUS_NAME,
38
+ color: 'GRAY',
39
+ description: 'Disabled and excluded from the active workflow',
40
+ },
41
+ ];
@@ -5,6 +5,7 @@ import { Member } from '../entities/Member';
5
5
  import { DateRepository } from './adapter-interfaces/DateRepository';
6
6
  import { StoryObjectMap } from '../entities/StoryObjectMap';
7
7
  import { encodeForURI } from './utils';
8
+ import { DISABLED_STATUS_NAME } from '../entities/WorkflowStatus';
8
9
 
9
10
  export class AnalyzeStoriesUseCase {
10
11
  constructor(
@@ -21,7 +22,6 @@ export class AnalyzeStoriesUseCase {
21
22
  org: string;
22
23
  repo: string;
23
24
  urlOfStoryView: string;
24
- disabledStatus: string;
25
25
  storyObjectMap: StoryObjectMap;
26
26
  members: Member['name'][];
27
27
  }): Promise<void> => {
@@ -66,7 +66,7 @@ export class AnalyzeStoriesUseCase {
66
66
  ...story,
67
67
  };
68
68
 
69
- if (storyIssue.status === input.disabledStatus) {
69
+ if (storyIssue.status === DISABLED_STATUS_NAME) {
70
70
  phases.get('others')?.push(storyIssueObject);
71
71
  } else if (storyIssue.labels.includes('story:phase:finished-qa')) {
72
72
  phases.get('story:phase:finished-qa')?.push(storyIssueObject);
@@ -18,9 +18,9 @@ describe('ChangeStatusByStoryColorUseCase', () => {
18
18
  mockReviewStatus.id = 'status2';
19
19
  mockReviewStatus.name = 'InReview';
20
20
 
21
- const mockIceboxStatus = mock<FieldOption>();
22
- mockIceboxStatus.id = 'status3';
23
- mockIceboxStatus.name = 'Icebox';
21
+ const mockDisabledStatus = mock<FieldOption>();
22
+ mockDisabledStatus.id = 'status3';
23
+ mockDisabledStatus.name = 'Disabled';
24
24
 
25
25
  const basicProject = {
26
26
  ...mock<Project>(),
@@ -38,7 +38,7 @@ describe('ChangeStatusByStoryColorUseCase', () => {
38
38
  status: {
39
39
  name: 'Status Field',
40
40
  fieldId: 'statusFieldId',
41
- statuses: [mockStatus, mockReviewStatus, mockIceboxStatus],
41
+ statuses: [mockStatus, mockReviewStatus, mockDisabledStatus],
42
42
  },
43
43
  };
44
44
 
@@ -118,7 +118,6 @@ describe('ChangeStatusByStoryColorUseCase', () => {
118
118
  cacheUsed: false,
119
119
  org: 'testOrg',
120
120
  repo: 'testRepo',
121
- disabledStatus: 'Icebox',
122
121
  storyObjectMap: basicStoryObjectMap,
123
122
  },
124
123
  expectedCalls: {
@@ -133,7 +132,6 @@ describe('ChangeStatusByStoryColorUseCase', () => {
133
132
  cacheUsed: true,
134
133
  org: 'testOrg',
135
134
  repo: 'testRepo',
136
- disabledStatus: 'Icebox',
137
135
  storyObjectMap: basicStoryObjectMap,
138
136
  },
139
137
  expectedCalls: {
@@ -148,7 +146,6 @@ describe('ChangeStatusByStoryColorUseCase', () => {
148
146
  cacheUsed: false,
149
147
  org: 'testOrg',
150
148
  repo: 'testRepo',
151
- disabledStatus: 'Icebox',
152
149
  storyObjectMap: new Map([
153
150
  [
154
151
  'Story 1',
@@ -180,7 +177,6 @@ describe('ChangeStatusByStoryColorUseCase', () => {
180
177
  cacheUsed: false,
181
178
  org: 'testOrg',
182
179
  repo: 'testRepo',
183
- disabledStatus: 'Icebox',
184
180
  storyObjectMap: new Map([
185
181
  [
186
182
  'Story 1',
@@ -189,7 +185,7 @@ describe('ChangeStatusByStoryColorUseCase', () => {
189
185
  issues: [
190
186
  {
191
187
  ...basicStoryObject1.issues[0],
192
- status: 'Icebox',
188
+ status: 'Disabled',
193
189
  },
194
190
  ],
195
191
  },
@@ -245,7 +241,6 @@ describe('ChangeStatusByStoryColorUseCase', () => {
245
241
  cacheUsed: false,
246
242
  org: 'testOrg',
247
243
  repo: 'testRepo',
248
- disabledStatus: 'Icebox',
249
244
  storyObjectMap: basicStoryObjectMap,
250
245
  }),
251
246
  ).rejects.toThrow('First status is not found');