github-issue-tower-defence-management 1.133.0 → 1.135.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/CHANGELOG.md +15 -0
- package/README.md +5 -4
- package/bin/adapter/entry-points/cli/index.js +11 -1
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +21 -0
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/console/dashboardComposeService.js +18 -0
- package/bin/adapter/entry-points/console/dashboardComposeService.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +81 -54
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/machineStatusWriter.js +6 -0
- package/bin/adapter/entry-points/handlers/machineStatusWriter.js.map +1 -1
- package/bin/adapter/repositories/ProcHostMetricsRepository.js +4 -0
- package/bin/adapter/repositories/ProcHostMetricsRepository.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -0
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +6 -0
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js +26 -4
- package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +42 -1
- package/src/adapter/entry-points/cli/index.ts +20 -1
- package/src/adapter/entry-points/cli/projectConfig.test.ts +75 -0
- package/src/adapter/entry-points/cli/projectConfig.ts +31 -0
- package/src/adapter/entry-points/console/dashboardComposeService.test.ts +32 -0
- package/src/adapter/entry-points/console/dashboardComposeService.ts +20 -0
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +66 -0
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +7 -1
- package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +61 -0
- package/src/adapter/entry-points/handlers/machineStatusWriter.ts +17 -0
- package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +25 -0
- package/src/adapter/repositories/ProcHostMetricsRepository.ts +5 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +26 -3
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +1 -0
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +157 -1
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +7 -0
- package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +55 -0
- package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +34 -5
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts +5 -0
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/console/dashboardComposeService.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/machineStatusWriter.d.ts +7 -0
- package/types/adapter/entry-points/handlers/machineStatusWriter.d.ts.map +1 -1
- package/types/adapter/repositories/ProcHostMetricsRepository.d.ts +1 -0
- package/types/adapter/repositories/ProcHostMetricsRepository.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts +1 -0
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts +6 -1
- package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
|
@@ -86,6 +86,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
86
86
|
handle = async (
|
|
87
87
|
configFilePath: string,
|
|
88
88
|
_verbose: boolean,
|
|
89
|
+
inTmuxProjectOrderOverride: string[] | null = null,
|
|
89
90
|
): Promise<{
|
|
90
91
|
project: Project;
|
|
91
92
|
issues: Issue[];
|
|
@@ -103,6 +104,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
103
104
|
claudeCodeOauthTokenListJsonPath?: string;
|
|
104
105
|
consoleDataOutputDir?: string;
|
|
105
106
|
dashboardDataDir?: string;
|
|
107
|
+
disks?: { title: string; mountpoint: string }[];
|
|
106
108
|
workflowBlockerStoryName?: string;
|
|
107
109
|
inTmuxDataOutputDir?: string;
|
|
108
110
|
newIssueRepo?: string;
|
|
@@ -492,6 +494,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
492
494
|
await writeMachineStatus({
|
|
493
495
|
dashboardDataDir,
|
|
494
496
|
allIssuesCacheDir: `${cachePath}/allIssues-${result.project.id}`,
|
|
497
|
+
disks: mergedInput.disks ?? null,
|
|
495
498
|
});
|
|
496
499
|
} catch (error) {
|
|
497
500
|
console.error(
|
|
@@ -524,7 +527,10 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
524
527
|
inTmuxDataOutputDir: mergedInput.inTmuxDataOutputDir ?? null,
|
|
525
528
|
inTmuxConsoleBaseUrl: mergedInput.inTmuxConsoleBaseUrl ?? null,
|
|
526
529
|
inTmuxConsoleToken: mergedInput.inTmuxConsoleToken ?? null,
|
|
527
|
-
inTmuxProjectOrder:
|
|
530
|
+
inTmuxProjectOrder:
|
|
531
|
+
inTmuxProjectOrderOverride ??
|
|
532
|
+
mergedInput.inTmuxProjectOrder ??
|
|
533
|
+
null,
|
|
528
534
|
pjcode: input.projectName,
|
|
529
535
|
assigneeLogin: input.manager,
|
|
530
536
|
org: input.org,
|
|
@@ -91,6 +91,67 @@ describe('writeMachineStatus', () => {
|
|
|
91
91
|
expect(written).toEqual(expected);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
it('writes a per-mountpoint disks array when a disks list is configured', async () => {
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
path.join(procDirectory, 'meminfo'),
|
|
97
|
+
'MemTotal: 1000 kB\nMemAvailable: 450 kB\n',
|
|
98
|
+
);
|
|
99
|
+
fs.writeFileSync(
|
|
100
|
+
path.join(procDirectory, 'loadavg'),
|
|
101
|
+
'1.50 0.80 0.40 1/100 200\n',
|
|
102
|
+
);
|
|
103
|
+
const statPath = path.join(procDirectory, 'stat');
|
|
104
|
+
fs.writeFileSync(statPath, 'cpu 600 0 0 400 0 0 0 0 0 0\n');
|
|
105
|
+
const sleep = async (): Promise<void> => {
|
|
106
|
+
fs.writeFileSync(statPath, 'cpu 660 0 0 440 0 0 0 0 0 0\n');
|
|
107
|
+
};
|
|
108
|
+
const blocksByMountpoint: Record<
|
|
109
|
+
string,
|
|
110
|
+
{ blocks: number; bfree: number; bavail: number }
|
|
111
|
+
> = {
|
|
112
|
+
'/': { blocks: 1100, bfree: 200, bavail: 100 },
|
|
113
|
+
'/mountpoint-secondary': { blocks: 1000, bfree: 700, bavail: 700 },
|
|
114
|
+
};
|
|
115
|
+
const repository = new ProcHostMetricsRepository(
|
|
116
|
+
procDirectory,
|
|
117
|
+
sleep,
|
|
118
|
+
(mountpoint) => blocksByMountpoint[mountpoint],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await writeMachineStatus({
|
|
122
|
+
dashboardDataDir: dir,
|
|
123
|
+
allIssuesCacheDir: null,
|
|
124
|
+
disks: [
|
|
125
|
+
{ title: 'D', mountpoint: '/' },
|
|
126
|
+
{ title: 'S', mountpoint: '/mountpoint-secondary' },
|
|
127
|
+
],
|
|
128
|
+
hostMetricsRepository: repository,
|
|
129
|
+
now: new Date('2026-06-26T12:00:00.000Z'),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const written = readJson(path.join(dir, 'machine-status.json'));
|
|
133
|
+
expect(written).toMatchObject({
|
|
134
|
+
diskPct: 90,
|
|
135
|
+
disks: [
|
|
136
|
+
{ title: 'D', pct: 90 },
|
|
137
|
+
{ title: 'S', pct: 30 },
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('omits the disks array when no disks list is configured', async () => {
|
|
143
|
+
const { repository } = buildHostMetricsRepository(procDirectory);
|
|
144
|
+
await writeMachineStatus({
|
|
145
|
+
dashboardDataDir: dir,
|
|
146
|
+
allIssuesCacheDir: null,
|
|
147
|
+
hostMetricsRepository: repository,
|
|
148
|
+
now: new Date('2026-06-26T12:00:00.000Z'),
|
|
149
|
+
});
|
|
150
|
+
const written = readJson(path.join(dir, 'machine-status.json'));
|
|
151
|
+
expect(written).toMatchObject({ diskPct: 90 });
|
|
152
|
+
expect(written).not.toHaveProperty('disks');
|
|
153
|
+
});
|
|
154
|
+
|
|
94
155
|
it('writes cycleMinutes null when there is no previous machine-status', async () => {
|
|
95
156
|
const { repository } = buildHostMetricsRepository(procDirectory);
|
|
96
157
|
const cacheDir = path.join(dir, 'allIssues-PVT');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { DiskConfig } from '../cli/projectConfig';
|
|
3
4
|
import {
|
|
4
5
|
ProcHostMetricsRepository,
|
|
5
6
|
cycleMinutesFromFetchTimestamps,
|
|
@@ -8,14 +9,21 @@ import {
|
|
|
8
9
|
export type MachineStatusWriterParams = {
|
|
9
10
|
dashboardDataDir: string | null | undefined;
|
|
10
11
|
allIssuesCacheDir: string | null | undefined;
|
|
12
|
+
disks?: DiskConfig[] | null;
|
|
11
13
|
hostMetricsRepository?: ProcHostMetricsRepository;
|
|
12
14
|
now?: Date;
|
|
13
15
|
};
|
|
14
16
|
|
|
17
|
+
export type MachineStatusDisk = {
|
|
18
|
+
title: string;
|
|
19
|
+
pct: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
15
22
|
export type MachineStatusFile = {
|
|
16
23
|
memPct: number;
|
|
17
24
|
cpuPct: number;
|
|
18
25
|
diskPct: number;
|
|
26
|
+
disks?: MachineStatusDisk[];
|
|
19
27
|
load: [number, number, number];
|
|
20
28
|
cycleMinutes: number | null;
|
|
21
29
|
lastFetchedAt: string | null;
|
|
@@ -68,6 +76,14 @@ export const writeMachineStatus = async (
|
|
|
68
76
|
const diskPct = hostMetricsRepository.readDiskUsedPercent();
|
|
69
77
|
const load = hostMetricsRepository.readLoadAverages();
|
|
70
78
|
|
|
79
|
+
const configuredDisks = params.disks ?? [];
|
|
80
|
+
const disks: MachineStatusDisk[] = configuredDisks.map((disk) => ({
|
|
81
|
+
title: disk.title,
|
|
82
|
+
pct: hostMetricsRepository.readDiskUsedPercentForMountpoint(
|
|
83
|
+
disk.mountpoint,
|
|
84
|
+
),
|
|
85
|
+
}));
|
|
86
|
+
|
|
71
87
|
const machineStatusPath = path.join(dashboardDataDir, 'machine-status.json');
|
|
72
88
|
const previousLastFetchedAt =
|
|
73
89
|
readLastFetchedAtFromJsonFile(machineStatusPath);
|
|
@@ -83,6 +99,7 @@ export const writeMachineStatus = async (
|
|
|
83
99
|
memPct,
|
|
84
100
|
cpuPct,
|
|
85
101
|
diskPct,
|
|
102
|
+
...(disks.length > 0 ? { disks } : {}),
|
|
86
103
|
load: [load.oneMinute, load.fiveMinute, load.fifteenMinute],
|
|
87
104
|
cycleMinutes,
|
|
88
105
|
lastFetchedAt: currentLastFetchedAt,
|
|
@@ -167,6 +167,31 @@ describe('ProcHostMetricsRepository', () => {
|
|
|
167
167
|
expect(repository.readDiskUsedPercent()).toBe(90);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
+
it('reads the df-style disk used percent for a given mountpoint', () => {
|
|
171
|
+
const blocksByMountpoint: Record<string, DiskBlocks> = {
|
|
172
|
+
'/': { blocks: 1100, bfree: 200, bavail: 100 },
|
|
173
|
+
'/mountpoint-a': { blocks: 1000, bfree: 700, bavail: 700 },
|
|
174
|
+
};
|
|
175
|
+
const requestedMountpoints: string[] = [];
|
|
176
|
+
const repository = new ProcHostMetricsRepository(
|
|
177
|
+
procDirectory,
|
|
178
|
+
async () => {},
|
|
179
|
+
(mountpoint) => {
|
|
180
|
+
requestedMountpoints.push(mountpoint);
|
|
181
|
+
const blocks = blocksByMountpoint[mountpoint];
|
|
182
|
+
if (!blocks) {
|
|
183
|
+
throw new Error(`unexpected mountpoint ${mountpoint}`);
|
|
184
|
+
}
|
|
185
|
+
return blocks;
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
expect(repository.readDiskUsedPercentForMountpoint('/mountpoint-a')).toBe(
|
|
189
|
+
30,
|
|
190
|
+
);
|
|
191
|
+
expect(repository.readDiskUsedPercentForMountpoint('/')).toBe(90);
|
|
192
|
+
expect(requestedMountpoints).toEqual(['/mountpoint-a', '/']);
|
|
193
|
+
});
|
|
194
|
+
|
|
170
195
|
it('samples /proc/stat twice and computes the busy percent', async () => {
|
|
171
196
|
const statPath = path.join(procDirectory, 'stat');
|
|
172
197
|
let sampleIndex = 0;
|
|
@@ -173,4 +173,9 @@ export class ProcHostMetricsRepository {
|
|
|
173
173
|
const { blocks, bfree, bavail } = this.readDiskBlocks(this.rootPath);
|
|
174
174
|
return parseDiskUsedPercent(blocks, bfree, bavail);
|
|
175
175
|
};
|
|
176
|
+
|
|
177
|
+
readDiskUsedPercentForMountpoint = (mountpoint: string): number => {
|
|
178
|
+
const { blocks, bfree, bavail } = this.readDiskBlocks(mountpoint);
|
|
179
|
+
return parseDiskUsedPercent(blocks, bfree, bavail);
|
|
180
|
+
};
|
|
176
181
|
}
|
|
@@ -2107,7 +2107,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
2107
2107
|
fetchSpy: FetchSpy,
|
|
2108
2108
|
predicate: (url: string, body: string) => boolean,
|
|
2109
2109
|
): number =>
|
|
2110
|
-
fetchSpy.mock.calls.filter(([input, init]) =>
|
|
2110
|
+
fetchSpy.mock.calls.filter(([input, init]: Parameters<typeof fetch>) =>
|
|
2111
2111
|
predicate(requestUrlOf(input), requestBodyOf(init)),
|
|
2112
2112
|
).length;
|
|
2113
2113
|
|
|
@@ -2287,6 +2287,28 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
2287
2287
|
expect(result?.isCiStateSuccess).toBe(true);
|
|
2288
2288
|
expect(result?.isPassedAllCiJob).toBe(true);
|
|
2289
2289
|
});
|
|
2290
|
+
|
|
2291
|
+
it('returns isCiStateSuccess true when check run conclusions are neutral or skipped', async () => {
|
|
2292
|
+
mockFetchRoutes({
|
|
2293
|
+
slimPullRequest: () => buildSlimPullRequestResponse(),
|
|
2294
|
+
checkRuns: () => ({
|
|
2295
|
+
total_count: 2,
|
|
2296
|
+
check_runs: [
|
|
2297
|
+
{ id: 100, name: 'lint', conclusion: 'neutral' },
|
|
2298
|
+
{ id: 200, name: 'docs', conclusion: 'skipped' },
|
|
2299
|
+
],
|
|
2300
|
+
}),
|
|
2301
|
+
});
|
|
2302
|
+
|
|
2303
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
2304
|
+
const result = await repository.getOpenPullRequest(
|
|
2305
|
+
'https://github.com/HiromiShikata/test-repository/pull/31',
|
|
2306
|
+
);
|
|
2307
|
+
|
|
2308
|
+
expect(result).not.toBeNull();
|
|
2309
|
+
expect(result?.isCiStateSuccess).toBe(true);
|
|
2310
|
+
expect(result?.isPassedAllCiJob).toBe(true);
|
|
2311
|
+
});
|
|
2290
2312
|
});
|
|
2291
2313
|
|
|
2292
2314
|
describe('getOpenPullRequest required check resolution via REST', () => {
|
|
@@ -2625,13 +2647,14 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
2625
2647
|
),
|
|
2626
2648
|
).toBe(2);
|
|
2627
2649
|
const secondSlimCall = fetchSpy.mock.calls
|
|
2628
|
-
.map(([input, init]) =>
|
|
2650
|
+
.map(([input, init]): GraphqlRequestBody | null =>
|
|
2629
2651
|
requestUrlOf(input) === 'https://api.github.com/graphql'
|
|
2630
2652
|
? parseGraphqlRequestBody(init)
|
|
2631
2653
|
: null,
|
|
2632
2654
|
)
|
|
2633
2655
|
.filter(
|
|
2634
|
-
(body)
|
|
2656
|
+
(body): body is GraphqlRequestBody =>
|
|
2657
|
+
body !== null && body.query.includes('headRefOid'),
|
|
2635
2658
|
)[1];
|
|
2636
2659
|
expect(secondSlimCall?.variables.reviewThreadsAfter).toBe('cursor-1');
|
|
2637
2660
|
});
|
|
@@ -398,6 +398,7 @@ ${JSON.stringify(e)}
|
|
|
398
398
|
});
|
|
399
399
|
await this.revertNotReadyReviewQueueIssueUseCase.run({
|
|
400
400
|
projectUrl: input.projectUrl,
|
|
401
|
+
manager: input.manager,
|
|
401
402
|
labelsAsLlmAgentName,
|
|
402
403
|
changeTargetPathAliases: input.changeTargetPathAliases,
|
|
403
404
|
allowedIssueAuthors,
|