github-issue-tower-defence-management 1.39.0 → 1.41.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 (58) hide show
  1. package/.github/workflows/create-pr.yml +12 -5
  2. package/.github/workflows/umino-project.yml +5 -4
  3. package/CHANGELOG.md +26 -0
  4. package/README.md +21 -9
  5. package/bin/adapter/entry-points/cli/index.js +45 -10
  6. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +32 -8
  8. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  9. package/bin/adapter/repositories/NodeLocalCommandRunner.js +3 -3
  10. package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -1
  11. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +446 -11
  12. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  13. package/bin/domain/usecases/HandleScheduledEventUseCase.js +5 -2
  14. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  15. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +111 -16
  16. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  17. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +7 -2
  18. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  19. package/bin/domain/usecases/StartPreparationUseCase.js +107 -72
  20. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/adapter/entry-points/cli/index.test.ts +26 -13
  23. package/src/adapter/entry-points/cli/index.ts +74 -13
  24. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +12 -12
  25. package/src/adapter/repositories/NodeLocalCommandRunner.ts +7 -4
  26. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +3 -0
  27. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +689 -12
  28. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +3 -0
  29. package/src/domain/entities/Issue.ts +1 -0
  30. package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +1 -0
  31. package/src/domain/usecases/HandleScheduledEventUseCase.ts +11 -3
  32. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +983 -167
  33. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +177 -26
  34. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +22 -9
  35. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +8 -3
  36. package/src/domain/usecases/StartPreparationUseCase.test.ts +1696 -290
  37. package/src/domain/usecases/StartPreparationUseCase.ts +171 -126
  38. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +4 -4
  39. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +4 -1
  40. package/types/adapter/entry-points/cli/index.d.ts +4 -1
  41. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  42. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +1 -1
  43. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -1
  44. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +7 -6
  45. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  46. package/types/domain/entities/Issue.d.ts +1 -0
  47. package/types/domain/entities/Issue.d.ts.map +1 -1
  48. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +5 -1
  49. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  50. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +5 -1
  51. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  52. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  53. package/types/domain/usecases/StartPreparationUseCase.d.ts +10 -18
  54. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  55. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +3 -3
  56. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  57. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +1 -1
  58. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts.map +1 -1
@@ -26,9 +26,12 @@ type ConfigFile = {
26
26
  awaitingWorkspaceStatus?: string;
27
27
  preparationStatus?: string;
28
28
  defaultAgentName?: string;
29
- logFilePath?: string;
29
+ defaultLlmModelName?: string;
30
+ defaultLlmAgentName?: string;
30
31
  maximumPreparingIssuesCount?: number;
31
32
  allowIssueCacheMinutes?: number;
33
+ utilizationPercentageThreshold?: number;
34
+ allowedIssueAuthors?: string;
32
35
  awaitingQualityCheckStatus?: string;
33
36
  thresholdForAutoReject?: number;
34
37
  workflowBlockerResolvedWebhookUrl?: string;
@@ -41,9 +44,12 @@ type StartDaemonOptions = {
41
44
  awaitingWorkspaceStatus?: string;
42
45
  preparationStatus?: string;
43
46
  defaultAgentName?: string;
44
- logFilePath?: string;
47
+ defaultLlmModelName?: string;
48
+ defaultLlmAgentName?: string;
45
49
  maximumPreparingIssuesCount?: string;
46
50
  allowIssueCacheMinutes?: string;
51
+ utilizationPercentageThreshold?: string;
52
+ allowedIssueAuthors?: string;
47
53
  preparationProcessCheckCommand?: string;
48
54
  configFilePath: string;
49
55
  };
@@ -93,12 +99,18 @@ export const loadConfigFile = (configFilePath: string): ConfigFile => {
93
99
  ),
94
100
  preparationStatus: getStringValue(parsed, 'preparationStatus'),
95
101
  defaultAgentName: getStringValue(parsed, 'defaultAgentName'),
96
- logFilePath: getStringValue(parsed, 'logFilePath'),
102
+ defaultLlmModelName: getStringValue(parsed, 'defaultLlmModelName'),
103
+ defaultLlmAgentName: getStringValue(parsed, 'defaultLlmAgentName'),
97
104
  maximumPreparingIssuesCount: getNumberValue(
98
105
  parsed,
99
106
  'maximumPreparingIssuesCount',
100
107
  ),
101
108
  allowIssueCacheMinutes: getNumberValue(parsed, 'allowIssueCacheMinutes'),
109
+ utilizationPercentageThreshold: getNumberValue(
110
+ parsed,
111
+ 'utilizationPercentageThreshold',
112
+ ),
113
+ allowedIssueAuthors: getStringValue(parsed, 'allowedIssueAuthors'),
102
114
  awaitingQualityCheckStatus: getStringValue(
103
115
  parsed,
104
116
  'awaitingQualityCheckStatus',
@@ -146,12 +158,18 @@ export const parseProjectReadmeConfig = (readme: string): ConfigFile => {
146
158
  ),
147
159
  preparationStatus: getStringValue(parsed, 'preparationStatus'),
148
160
  defaultAgentName: getStringValue(parsed, 'defaultAgentName'),
149
- logFilePath: getStringValue(parsed, 'logFilePath'),
161
+ defaultLlmModelName: getStringValue(parsed, 'defaultLlmModelName'),
162
+ defaultLlmAgentName: getStringValue(parsed, 'defaultLlmAgentName'),
150
163
  maximumPreparingIssuesCount: getNumberValue(
151
164
  parsed,
152
165
  'maximumPreparingIssuesCount',
153
166
  ),
154
167
  allowIssueCacheMinutes: getNumberValue(parsed, 'allowIssueCacheMinutes'),
168
+ utilizationPercentageThreshold: getNumberValue(
169
+ parsed,
170
+ 'utilizationPercentageThreshold',
171
+ ),
172
+ allowedIssueAuthors: getStringValue(parsed, 'allowedIssueAuthors'),
155
173
  awaitingQualityCheckStatus: getStringValue(
156
174
  parsed,
157
175
  'awaitingQualityCheckStatus',
@@ -190,10 +208,14 @@ export const mergeConfigs = (
190
208
  readmeOverrides.defaultAgentName ??
191
209
  cliOverrides.defaultAgentName ??
192
210
  configFile.defaultAgentName,
193
- logFilePath:
194
- readmeOverrides.logFilePath ??
195
- cliOverrides.logFilePath ??
196
- configFile.logFilePath,
211
+ defaultLlmModelName:
212
+ readmeOverrides.defaultLlmModelName ??
213
+ cliOverrides.defaultLlmModelName ??
214
+ configFile.defaultLlmModelName,
215
+ defaultLlmAgentName:
216
+ readmeOverrides.defaultLlmAgentName ??
217
+ cliOverrides.defaultLlmAgentName ??
218
+ configFile.defaultLlmAgentName,
197
219
  maximumPreparingIssuesCount:
198
220
  readmeOverrides.maximumPreparingIssuesCount ??
199
221
  cliOverrides.maximumPreparingIssuesCount ??
@@ -202,6 +224,14 @@ export const mergeConfigs = (
202
224
  readmeOverrides.allowIssueCacheMinutes ??
203
225
  cliOverrides.allowIssueCacheMinutes ??
204
226
  configFile.allowIssueCacheMinutes,
227
+ utilizationPercentageThreshold:
228
+ readmeOverrides.utilizationPercentageThreshold ??
229
+ cliOverrides.utilizationPercentageThreshold ??
230
+ configFile.utilizationPercentageThreshold,
231
+ allowedIssueAuthors:
232
+ readmeOverrides.allowedIssueAuthors ??
233
+ cliOverrides.allowedIssueAuthors ??
234
+ configFile.allowedIssueAuthors,
205
235
  awaitingQualityCheckStatus:
206
236
  readmeOverrides.awaitingQualityCheckStatus ??
207
237
  cliOverrides.awaitingQualityCheckStatus ??
@@ -361,7 +391,8 @@ program
361
391
  )
362
392
  .option('--preparationStatus <status>', 'Status for issues in preparation')
363
393
  .option('--defaultAgentName <name>', 'Default agent name')
364
- .option('--logFilePath <path>', 'Path to log file')
394
+ .option('--defaultLlmModelName <name>', 'Default LLM model name')
395
+ .option('--defaultLlmAgentName <name>', 'Default LLM agent name')
365
396
  .option(
366
397
  '--maximumPreparingIssuesCount <count>',
367
398
  'Maximum number of issues in preparation status (default: 6)',
@@ -370,6 +401,14 @@ program
370
401
  '--allowIssueCacheMinutes <minutes>',
371
402
  'Allow cache for issues in minutes (default: 0)',
372
403
  )
404
+ .option(
405
+ '--utilizationPercentageThreshold <percent>',
406
+ 'Claude utilization percentage threshold (default: 90)',
407
+ )
408
+ .option(
409
+ '--allowedIssueAuthors <authors>',
410
+ 'Comma-separated list of allowed issue authors',
411
+ )
373
412
  .option(
374
413
  '--preparationProcessCheckCommand <template>',
375
414
  'Shell command template with {URL} placeholder to check if a preparation process is alive',
@@ -388,13 +427,18 @@ program
388
427
  awaitingWorkspaceStatus: options.awaitingWorkspaceStatus,
389
428
  preparationStatus: options.preparationStatus,
390
429
  defaultAgentName: options.defaultAgentName,
391
- logFilePath: options.logFilePath,
430
+ defaultLlmModelName: options.defaultLlmModelName,
431
+ defaultLlmAgentName: options.defaultLlmAgentName,
392
432
  maximumPreparingIssuesCount: options.maximumPreparingIssuesCount
393
433
  ? Number(options.maximumPreparingIssuesCount)
394
434
  : undefined,
395
435
  allowIssueCacheMinutes: options.allowIssueCacheMinutes
396
436
  ? Number(options.allowIssueCacheMinutes)
397
437
  : undefined,
438
+ utilizationPercentageThreshold: options.utilizationPercentageThreshold
439
+ ? Number(options.utilizationPercentageThreshold)
440
+ : undefined,
441
+ allowedIssueAuthors: options.allowedIssueAuthors,
398
442
  preparationProcessCheckCommand: options.preparationProcessCheckCommand,
399
443
  };
400
444
 
@@ -419,7 +463,6 @@ program
419
463
  const awaitingWorkspaceStatus = config.awaitingWorkspaceStatus;
420
464
  const preparationStatus = config.preparationStatus;
421
465
  const defaultAgentName = config.defaultAgentName;
422
- const logFilePath = config.logFilePath;
423
466
 
424
467
  if (!projectUrl) {
425
468
  console.error(
@@ -484,6 +527,12 @@ program
484
527
  const projectRepository = {
485
528
  ...new GraphqlProjectRepository(...githubRepositoryParams),
486
529
  ...new CheerioProjectRepository(...githubRepositoryParams),
530
+ prepareStatus: async (
531
+ _name: string,
532
+ project: Project,
533
+ ): Promise<Project> => {
534
+ return project;
535
+ },
487
536
  };
488
537
  const apiV3IssueRepository = new ApiV3IssueRepository(
489
538
  ...githubRepositoryParams,
@@ -528,14 +577,26 @@ program
528
577
  localCommandRunner,
529
578
  );
530
579
 
580
+ const rawAllowedIssueAuthors = config.allowedIssueAuthors;
581
+ const allowedIssueAuthors = rawAllowedIssueAuthors
582
+ ? rawAllowedIssueAuthors
583
+ .split(',')
584
+ .map((s) => s.trim())
585
+ .filter(Boolean)
586
+ : null;
587
+
531
588
  await useCase.run({
532
589
  projectUrl,
533
590
  awaitingWorkspaceStatus,
534
591
  preparationStatus,
535
592
  defaultAgentName,
536
- logFilePath: logFilePath ?? undefined,
593
+ defaultLlmModelName: config.defaultLlmModelName ?? null,
594
+ defaultLlmAgentName: config.defaultLlmAgentName ?? null,
595
+ configFilePath: options.configFilePath,
537
596
  maximumPreparingIssuesCount,
538
- allowIssueCacheMinutes,
597
+ utilizationPercentageThreshold:
598
+ config.utilizationPercentageThreshold ?? 90,
599
+ allowedIssueAuthors,
539
600
  });
540
601
  });
541
602
 
@@ -1,11 +1,11 @@
1
- const mockExecAsync = jest.fn();
1
+ const mockExecFileAsync = jest.fn();
2
2
 
3
3
  jest.mock('child_process', () => ({
4
- exec: jest.fn(),
4
+ execFile: jest.fn(),
5
5
  }));
6
6
 
7
7
  jest.mock('util', () => ({
8
- promisify: jest.fn(() => mockExecAsync),
8
+ promisify: jest.fn(() => mockExecFileAsync),
9
9
  }));
10
10
 
11
11
  import { NodeLocalCommandRunner } from './NodeLocalCommandRunner';
@@ -20,19 +20,19 @@ describe('NodeLocalCommandRunner', () => {
20
20
 
21
21
  describe('runCommand', () => {
22
22
  it('should execute command successfully', async () => {
23
- mockExecAsync.mockResolvedValue({
23
+ mockExecFileAsync.mockResolvedValue({
24
24
  stdout: 'command output',
25
25
  stderr: '',
26
26
  });
27
27
 
28
- const result = await runner.runCommand('echo "test"');
28
+ const result = await runner.runCommand('echo', ['"test"']);
29
29
 
30
30
  expect(result).toEqual({
31
31
  stdout: 'command output',
32
32
  stderr: '',
33
33
  exitCode: 0,
34
34
  });
35
- expect(mockExecAsync).toHaveBeenCalledWith('echo "test"');
35
+ expect(mockExecFileAsync).toHaveBeenCalledWith('echo', ['"test"']);
36
36
  });
37
37
 
38
38
  it('should handle command errors with exit code', async () => {
@@ -41,9 +41,9 @@ describe('NodeLocalCommandRunner', () => {
41
41
  stdout: 'partial output',
42
42
  stderr: 'error message',
43
43
  });
44
- mockExecAsync.mockRejectedValue(error);
44
+ mockExecFileAsync.mockRejectedValue(error);
45
45
 
46
- const result = await runner.runCommand('invalid-command');
46
+ const result = await runner.runCommand('invalid-command', []);
47
47
 
48
48
  expect(result).toEqual({
49
49
  stdout: 'partial output',
@@ -58,9 +58,9 @@ describe('NodeLocalCommandRunner', () => {
58
58
  stdout: '',
59
59
  stderr: 'command not found',
60
60
  });
61
- mockExecAsync.mockRejectedValue(error);
61
+ mockExecFileAsync.mockRejectedValue(error);
62
62
 
63
- const result = await runner.runCommand('nonexistent');
63
+ const result = await runner.runCommand('nonexistent', []);
64
64
 
65
65
  expect(result).toEqual({
66
66
  stdout: '',
@@ -70,9 +70,9 @@ describe('NodeLocalCommandRunner', () => {
70
70
  });
71
71
 
72
72
  it('should throw error if error format is unexpected', async () => {
73
- mockExecAsync.mockRejectedValue(new Error('Unexpected error'));
73
+ mockExecFileAsync.mockRejectedValue(new Error('Unexpected error'));
74
74
 
75
- await expect(runner.runCommand('command')).rejects.toThrow(
75
+ await expect(runner.runCommand('command', [])).rejects.toThrow(
76
76
  'Unexpected error',
77
77
  );
78
78
  });
@@ -1,17 +1,20 @@
1
1
  import { LocalCommandRunner } from '../../domain/usecases/adapter-interfaces/LocalCommandRunner';
2
- import { exec } from 'child_process';
2
+ import { execFile } from 'child_process';
3
3
  import { promisify } from 'util';
4
4
 
5
- const execAsync = promisify(exec);
5
+ const execFileAsync = promisify(execFile);
6
6
 
7
7
  export class NodeLocalCommandRunner implements LocalCommandRunner {
8
- async runCommand(command: string): Promise<{
8
+ async runCommand(
9
+ program: string,
10
+ args: string[],
11
+ ): Promise<{
9
12
  stdout: string;
10
13
  stderr: string;
11
14
  exitCode: number;
12
15
  }> {
13
16
  try {
14
- const { stdout, stderr } = await execAsync(command);
17
+ const { stdout, stderr } = await execFileAsync(program, args);
15
18
  return {
16
19
  stdout,
17
20
  stderr,
@@ -63,6 +63,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
63
63
  isInProgress: false,
64
64
  isClosed: false,
65
65
  createdAt: new Date('2024-01-01T00:00:00Z'),
66
+ author: '',
66
67
  },
67
68
  },
68
69
  {
@@ -114,6 +115,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
114
115
  isInProgress: false,
115
116
  isClosed: false,
116
117
  createdAt: new Date('2024-01-01T00:00:00Z'),
118
+ author: '',
117
119
  },
118
120
  },
119
121
  ];
@@ -244,6 +246,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
244
246
  isInProgress: false,
245
247
  isClosed: false,
246
248
  createdAt: new Date('2024-01-01'),
249
+ author: '',
247
250
  };
248
251
  const statusId = 'new-status-id';
249
252