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.
Files changed (55) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +5 -4
  3. package/bin/adapter/entry-points/cli/index.js +11 -1
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +21 -0
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/console/dashboardComposeService.js +18 -0
  8. package/bin/adapter/entry-points/console/dashboardComposeService.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +81 -54
  10. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  11. package/bin/adapter/entry-points/handlers/machineStatusWriter.js +6 -0
  12. package/bin/adapter/entry-points/handlers/machineStatusWriter.js.map +1 -1
  13. package/bin/adapter/repositories/ProcHostMetricsRepository.js +4 -0
  14. package/bin/adapter/repositories/ProcHostMetricsRepository.js.map +1 -1
  15. package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -0
  16. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  17. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +6 -0
  18. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  19. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js +26 -4
  20. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/adapter/entry-points/cli/index.test.ts +42 -1
  23. package/src/adapter/entry-points/cli/index.ts +20 -1
  24. package/src/adapter/entry-points/cli/projectConfig.test.ts +75 -0
  25. package/src/adapter/entry-points/cli/projectConfig.ts +31 -0
  26. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +32 -0
  27. package/src/adapter/entry-points/console/dashboardComposeService.ts +20 -0
  28. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +66 -0
  29. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +7 -1
  30. package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +61 -0
  31. package/src/adapter/entry-points/handlers/machineStatusWriter.ts +17 -0
  32. package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +25 -0
  33. package/src/adapter/repositories/ProcHostMetricsRepository.ts +5 -0
  34. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +26 -3
  35. package/src/domain/usecases/HandleScheduledEventUseCase.ts +1 -0
  36. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +157 -1
  37. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +7 -0
  38. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +55 -0
  39. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +34 -5
  40. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  41. package/types/adapter/entry-points/cli/projectConfig.d.ts +5 -0
  42. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  43. package/types/adapter/entry-points/console/dashboardComposeService.d.ts.map +1 -1
  44. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +1 -1
  45. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  46. package/types/adapter/entry-points/handlers/machineStatusWriter.d.ts +7 -0
  47. package/types/adapter/entry-points/handlers/machineStatusWriter.d.ts.map +1 -1
  48. package/types/adapter/repositories/ProcHostMetricsRepository.d.ts +1 -0
  49. package/types/adapter/repositories/ProcHostMetricsRepository.d.ts.map +1 -1
  50. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  51. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts +1 -0
  52. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  53. package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts +6 -1
  54. package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts.map +1 -1
  55. 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: mergedInput.inTmuxProjectOrder ?? null,
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) => body !== null && body.query.includes('headRefOid'),
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,