github-issue-tower-defence-management 1.40.0 → 1.42.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 (52) hide show
  1. package/.github/workflows/umino-project.yml +5 -4
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +27 -9
  4. package/bin/adapter/entry-points/cli/index.js +68 -10
  5. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  6. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +44 -8
  7. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  8. package/bin/adapter/repositories/NodeLocalCommandRunner.js +3 -3
  9. package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -1
  10. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +412 -177
  11. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  12. package/bin/domain/usecases/HandleScheduledEventUseCase.js +6 -2
  13. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  14. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +7 -2
  15. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  16. package/bin/domain/usecases/StartPreparationUseCase.js +115 -72
  17. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  18. package/package.json +1 -1
  19. package/src/adapter/entry-points/cli/index.test.ts +184 -13
  20. package/src/adapter/entry-points/cli/index.ts +105 -13
  21. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +12 -12
  22. package/src/adapter/repositories/NodeLocalCommandRunner.ts +7 -4
  23. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +3 -0
  24. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +626 -265
  25. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +3 -0
  26. package/src/domain/entities/Issue.ts +1 -0
  27. package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +1 -0
  28. package/src/domain/usecases/HandleScheduledEventUseCase.ts +13 -3
  29. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +1 -0
  30. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +64 -9
  31. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +8 -3
  32. package/src/domain/usecases/StartPreparationUseCase.test.ts +1978 -295
  33. package/src/domain/usecases/StartPreparationUseCase.ts +185 -126
  34. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +2 -1
  35. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +4 -1
  36. package/types/adapter/entry-points/cli/index.d.ts +5 -1
  37. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  38. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +1 -1
  39. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -1
  40. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +5 -3
  41. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  42. package/types/domain/entities/Issue.d.ts +1 -0
  43. package/types/domain/entities/Issue.d.ts.map +1 -1
  44. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +6 -1
  45. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  46. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  47. package/types/domain/usecases/StartPreparationUseCase.d.ts +11 -18
  48. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  49. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +2 -1
  50. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  51. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +1 -1
  52. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts.map +1 -1
@@ -55,6 +55,7 @@ describe('RestIssueRepository', () => {
55
55
  isInProgress: false,
56
56
  isClosed: false,
57
57
  createdAt: new Date(),
58
+ author: '',
58
59
  };
59
60
 
60
61
  await restIssueRepository.updateLabels(issue, ['default']);
@@ -92,6 +93,7 @@ describe('RestIssueRepository', () => {
92
93
  isInProgress: false,
93
94
  isClosed: false,
94
95
  createdAt: new Date(),
96
+ author: '',
95
97
  };
96
98
 
97
99
  await restIssueRepository.updateLabels(issue, ['test', 'to-remove']);
@@ -130,6 +132,7 @@ describe('RestIssueRepository', () => {
130
132
  isInProgress: false,
131
133
  isClosed: false,
132
134
  createdAt: new Date(),
135
+ author: '',
133
136
  };
134
137
  await restIssueRepository.updateAssigneeList(issue, ['HiromiShikata']);
135
138
  const issueWithAssignee = await restIssueRepository.getIssue(issue.url);
@@ -23,4 +23,5 @@ export type Issue = {
23
23
  isInProgress: boolean;
24
24
  isClosed: boolean;
25
25
  createdAt: Date;
26
+ author: string;
26
27
  };
@@ -66,6 +66,7 @@ describe('GetStoryObjectMapUseCase', () => {
66
66
  isInProgress: false,
67
67
  isClosed: false,
68
68
  createdAt: new Date(),
69
+ author: '',
69
70
  ...overrides,
70
71
  });
71
72
 
@@ -73,9 +73,14 @@ export class HandleScheduledEventUseCase {
73
73
  awaitingWorkspaceStatus: string;
74
74
  preparationStatus: string;
75
75
  defaultAgentName: string;
76
- logFilePath?: string;
76
+ defaultLlmModelName?: string | null;
77
+ defaultLlmAgentName?: string | null;
78
+ configFilePath: string;
77
79
  maximumPreparingIssuesCount: number | null;
80
+ utilizationPercentageThreshold?: number;
81
+ allowedIssueAuthors?: string[] | null;
78
82
  preparationProcessCheckCommand?: string;
83
+ codexHomeCandidates?: string[] | null;
79
84
  } | null;
80
85
  notifyFinishedPreparation?: {
81
86
  preparationStatus: string;
@@ -344,10 +349,15 @@ ${JSON.stringify(e)}
344
349
  awaitingWorkspaceStatus: input.startPreparation.awaitingWorkspaceStatus,
345
350
  preparationStatus: input.startPreparation.preparationStatus,
346
351
  defaultAgentName: input.startPreparation.defaultAgentName,
347
- logFilePath: input.startPreparation.logFilePath,
352
+ defaultLlmModelName: input.startPreparation.defaultLlmModelName ?? null,
353
+ defaultLlmAgentName: input.startPreparation.defaultLlmAgentName ?? null,
354
+ configFilePath: input.startPreparation.configFilePath,
348
355
  maximumPreparingIssuesCount:
349
356
  input.startPreparation.maximumPreparingIssuesCount,
350
- allowIssueCacheMinutes: input.allowIssueCacheMinutes,
357
+ utilizationPercentageThreshold:
358
+ input.startPreparation.utilizationPercentageThreshold ?? 90,
359
+ allowedIssueAuthors: input.startPreparation.allowedIssueAuthors ?? null,
360
+ codexHomeCandidates: input.startPreparation.codexHomeCandidates ?? null,
351
361
  });
352
362
  }
353
363
  if (input.notifyFinishedPreparation) {
@@ -46,6 +46,7 @@ const createMockIssue = (overrides: Partial<Issue> = {}): Issue => ({
46
46
  isInProgress: false,
47
47
  isClosed: false,
48
48
  createdAt: new Date(),
49
+ author: '',
49
50
  ...overrides,
50
51
  });
51
52
 
@@ -30,6 +30,7 @@ const createMockIssue = (overrides: Partial<Issue> = {}): Issue => ({
30
30
  isInProgress: false,
31
31
  isClosed: false,
32
32
  createdAt: new Date(),
33
+ author: '',
33
34
  ...overrides,
34
35
  });
35
36
 
@@ -136,9 +137,13 @@ describe('RevertOrphanedPreparationUseCase', () => {
136
137
  expect(mockIssueRepository.createComment.mock.calls).toHaveLength(1);
137
138
  expect(mockIssueRepository.createComment.mock.calls[0][0]).toBe(stuckIssue);
138
139
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
139
- expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
140
- 'pgrep -fa "claude-agent.*https://github.com/user/repo/issues/10"',
141
- );
140
+ expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe('sh');
141
+ expect(mockLocalCommandRunner.runCommand.mock.calls[0][1]).toEqual([
142
+ '-c',
143
+ 'pgrep -fa "claude-agent.*$1"',
144
+ '--',
145
+ 'https://github.com/user/repo/issues/10',
146
+ ]);
142
147
  });
143
148
 
144
149
  it('should leave in-flight Preparation issue untouched when check command exits zero', async () => {
@@ -196,9 +201,13 @@ describe('RevertOrphanedPreparationUseCase', () => {
196
201
  });
197
202
 
198
203
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
199
- expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
200
- 'check https://github.com/user/repo/issues/10',
201
- );
204
+ expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe('sh');
205
+ expect(mockLocalCommandRunner.runCommand.mock.calls[0][1]).toEqual([
206
+ '-c',
207
+ 'check $1',
208
+ '--',
209
+ 'https://github.com/user/repo/issues/10',
210
+ ]);
202
211
  expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
203
212
  });
204
213
 
@@ -250,6 +259,48 @@ describe('RevertOrphanedPreparationUseCase', () => {
250
259
  ).rejects.toThrow('Project not found');
251
260
  });
252
261
 
262
+ it('should throw when getProject returns null after findProjectIdByUrl succeeds', async () => {
263
+ mockProjectRepository.findProjectIdByUrl.mockResolvedValue('project-1');
264
+ mockProjectRepository.getProject.mockResolvedValue(null);
265
+
266
+ await expect(
267
+ useCase.run({
268
+ projectUrl: 'https://github.com/user/repo',
269
+ preparationStatus: 'Preparation',
270
+ awaitingWorkspaceStatus: 'Awaiting Workspace',
271
+ allowIssueCacheMinutes: 0,
272
+ preparationProcessCheckCommand: 'check {URL}',
273
+ }),
274
+ ).rejects.toThrow('Project not found. projectId: project-1');
275
+ });
276
+
277
+ it('should do nothing when awaitingWorkspaceStatus is not found in project statuses', async () => {
278
+ const preparationIssue = createMockIssue({
279
+ url: 'https://github.com/user/repo/issues/10',
280
+ status: 'Preparation',
281
+ });
282
+ mockIssueRepository.getAllIssues.mockResolvedValue({
283
+ issues: [preparationIssue],
284
+ cacheUsed: false,
285
+ });
286
+ mockLocalCommandRunner.runCommand.mockResolvedValue({
287
+ stdout: '',
288
+ stderr: '',
289
+ exitCode: 1,
290
+ });
291
+
292
+ await useCase.run({
293
+ projectUrl: 'https://github.com/user/repo',
294
+ preparationStatus: 'Preparation',
295
+ awaitingWorkspaceStatus: 'NonExistentStatus',
296
+ allowIssueCacheMinutes: 0,
297
+ preparationProcessCheckCommand: 'check {URL}',
298
+ });
299
+
300
+ expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
301
+ expect(mockIssueRepository.createComment.mock.calls).toHaveLength(0);
302
+ });
303
+
253
304
  it('should do nothing when there are no Preparation issues', async () => {
254
305
  mockIssueRepository.getAllIssues.mockResolvedValue({
255
306
  issues: [
@@ -295,8 +346,12 @@ describe('RevertOrphanedPreparationUseCase', () => {
295
346
  });
296
347
 
297
348
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
298
- expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
299
- 'pgrep -fa "claude-agent.*https://github.com/org/project/issues/99"',
300
- );
349
+ expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe('sh');
350
+ expect(mockLocalCommandRunner.runCommand.mock.calls[0][1]).toEqual([
351
+ '-c',
352
+ 'pgrep -fa "claude-agent.*$1"',
353
+ '--',
354
+ 'https://github.com/org/project/issues/99',
355
+ ]);
301
356
  });
302
357
  });
@@ -51,11 +51,16 @@ export class RevertOrphanedPreparationUseCase {
51
51
  }
52
52
 
53
53
  for (const issue of preparationIssues) {
54
- const command = params.preparationProcessCheckCommand.replace(
54
+ const commandTemplate = params.preparationProcessCheckCommand.replace(
55
55
  '{URL}',
56
- issue.url,
56
+ '$1',
57
57
  );
58
- const { exitCode } = await this.localCommandRunner.runCommand(command);
58
+ const { exitCode } = await this.localCommandRunner.runCommand('sh', [
59
+ '-c',
60
+ commandTemplate,
61
+ '--',
62
+ issue.url,
63
+ ]);
59
64
  if (exitCode !== 0) {
60
65
  await this.issueRepository.updateStatus(
61
66
  project,