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.
- package/.github/workflows/create-pr.yml +12 -5
- package/.github/workflows/umino-project.yml +5 -4
- package/CHANGELOG.md +26 -0
- package/README.md +21 -9
- package/bin/adapter/entry-points/cli/index.js +45 -10
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +32 -8
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/NodeLocalCommandRunner.js +3 -3
- package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +446 -11
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +5 -2
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +111 -16
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +7 -2
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +107 -72
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +26 -13
- package/src/adapter/entry-points/cli/index.ts +74 -13
- package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +12 -12
- package/src/adapter/repositories/NodeLocalCommandRunner.ts +7 -4
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +3 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +689 -12
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +3 -0
- package/src/domain/entities/Issue.ts +1 -0
- package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +1 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +11 -3
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +983 -167
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +177 -26
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +22 -9
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +8 -3
- package/src/domain/usecases/StartPreparationUseCase.test.ts +1696 -290
- package/src/domain/usecases/StartPreparationUseCase.ts +171 -126
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +4 -4
- package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +4 -1
- package/types/adapter/entry-points/cli/index.d.ts +4 -1
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +1 -1
- package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +7 -6
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/entities/Issue.d.ts +1 -0
- package/types/domain/entities/Issue.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +5 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +5 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +10 -18
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +3 -3
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
194
|
-
readmeOverrides.
|
|
195
|
-
cliOverrides.
|
|
196
|
-
configFile.
|
|
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('--
|
|
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
|
-
|
|
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
|
-
|
|
593
|
+
defaultLlmModelName: config.defaultLlmModelName ?? null,
|
|
594
|
+
defaultLlmAgentName: config.defaultLlmAgentName ?? null,
|
|
595
|
+
configFilePath: options.configFilePath,
|
|
537
596
|
maximumPreparingIssuesCount,
|
|
538
|
-
|
|
597
|
+
utilizationPercentageThreshold:
|
|
598
|
+
config.utilizationPercentageThreshold ?? 90,
|
|
599
|
+
allowedIssueAuthors,
|
|
539
600
|
});
|
|
540
601
|
});
|
|
541
602
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const mockExecFileAsync = jest.fn();
|
|
2
2
|
|
|
3
3
|
jest.mock('child_process', () => ({
|
|
4
|
-
|
|
4
|
+
execFile: jest.fn(),
|
|
5
5
|
}));
|
|
6
6
|
|
|
7
7
|
jest.mock('util', () => ({
|
|
8
|
-
promisify: jest.fn(() =>
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
+
import { execFile } from 'child_process';
|
|
3
3
|
import { promisify } from 'util';
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
6
|
|
|
7
7
|
export class NodeLocalCommandRunner implements LocalCommandRunner {
|
|
8
|
-
async runCommand(
|
|
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
|
|
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
|
|