github-issue-tower-defence-management 1.60.1 → 1.63.1

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 (123) hide show
  1. package/.github/workflows/publish.yml +13 -0
  2. package/.github/workflows/test.yml +0 -4
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +53 -10
  5. package/bin/adapter/entry-points/cli/index.js +11 -11
  6. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +3 -22
  8. package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +8 -22
  10. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  11. package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js +56 -0
  12. package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js.map +1 -0
  13. package/bin/adapter/entry-points/handlers/situationFileWriter.js +5 -0
  14. package/bin/adapter/entry-points/handlers/situationFileWriter.js.map +1 -1
  15. package/bin/adapter/proxy/TokenListLoader.js +21 -6
  16. package/bin/adapter/proxy/TokenListLoader.js.map +1 -1
  17. package/bin/adapter/proxy/proxyEntry.js +1 -0
  18. package/bin/adapter/proxy/proxyEntry.js.map +1 -1
  19. package/bin/adapter/repositories/BaseGitHubRepository.js +1 -113
  20. package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
  21. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +5 -3
  22. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
  23. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +8 -7
  24. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  25. package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js +19 -9
  26. package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js.map +1 -1
  27. package/bin/domain/usecases/HandleScheduledEventUseCase.js +15 -3
  28. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  29. package/bin/domain/usecases/IssueRejectionEvaluator.js +8 -1
  30. package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
  31. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +5 -1
  32. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  33. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +1 -1
  34. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  35. package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js +32 -1
  36. package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js.map +1 -1
  37. package/bin/domain/usecases/StartPreparationUseCase.js +91 -12
  38. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  39. package/package.json +1 -4
  40. package/src/adapter/entry-points/cli/index.test.ts +16 -16
  41. package/src/adapter/entry-points/cli/index.ts +8 -11
  42. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +2 -55
  43. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +1 -11
  44. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +6 -56
  45. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +7 -11
  46. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +177 -0
  47. package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +20 -0
  48. package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +36 -0
  49. package/src/adapter/entry-points/handlers/situationFileWriter.ts +8 -0
  50. package/src/adapter/proxy/TokenListLoader.test.ts +50 -1
  51. package/src/adapter/proxy/TokenListLoader.ts +25 -5
  52. package/src/adapter/proxy/proxyEntry.test.ts +270 -1
  53. package/src/adapter/proxy/proxyEntry.ts +2 -1
  54. package/src/adapter/repositories/BaseGitHubRepository.test.ts +1 -186
  55. package/src/adapter/repositories/BaseGitHubRepository.ts +1 -139
  56. package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -1
  57. package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +4 -1
  58. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +60 -19
  59. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -4
  60. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +23 -13
  61. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -1
  62. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -8
  63. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -1
  64. package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
  65. package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +196 -11
  66. package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +32 -15
  67. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -0
  68. package/src/domain/usecases/HandleScheduledEventUseCase.ts +21 -5
  69. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +153 -0
  70. package/src/domain/usecases/IssueRejectionEvaluator.ts +8 -0
  71. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +175 -31
  72. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +7 -1
  73. package/src/domain/usecases/RevertNotReadyAwaitingQualityCheckUseCase.test.ts +32 -0
  74. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +39 -5
  75. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -1
  76. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +139 -20
  77. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +62 -2
  78. package/src/domain/usecases/StartPreparationUseCase.test.ts +404 -21
  79. package/src/domain/usecases/StartPreparationUseCase.ts +152 -16
  80. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +16 -0
  81. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  82. package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
  83. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  84. package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts +3 -0
  85. package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts.map +1 -0
  86. package/types/adapter/entry-points/handlers/situationFileWriter.d.ts +1 -0
  87. package/types/adapter/entry-points/handlers/situationFileWriter.d.ts.map +1 -1
  88. package/types/adapter/proxy/TokenListLoader.d.ts +5 -0
  89. package/types/adapter/proxy/TokenListLoader.d.ts.map +1 -1
  90. package/types/adapter/proxy/proxyEntry.d.ts +2 -1
  91. package/types/adapter/proxy/proxyEntry.d.ts.map +1 -1
  92. package/types/adapter/repositories/BaseGitHubRepository.d.ts +1 -23
  93. package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
  94. package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
  95. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +14 -5
  96. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  97. package/types/domain/entities/ClaudeTokenUsage.d.ts +1 -0
  98. package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -1
  99. package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts +4 -2
  100. package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts.map +1 -1
  101. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +5 -2
  102. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  103. package/types/domain/usecases/IssueRejectionEvaluator.d.ts +1 -1
  104. package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
  105. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  106. package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts +5 -2
  107. package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts.map +1 -1
  108. package/types/domain/usecases/StartPreparationUseCase.d.ts +15 -1
  109. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  110. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +14 -0
  111. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  112. package/bin/adapter/repositories/issue/CheerioIssueRepository.js +0 -136
  113. package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +0 -1
  114. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +0 -1606
  115. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +0 -1
  116. package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +0 -6552
  117. package/src/adapter/repositories/issue/CheerioIssueRepository.ts +0 -142
  118. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.test.ts +0 -118
  119. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +0 -584
  120. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts +0 -40
  121. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +0 -1
  122. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts +0 -220
  123. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +0 -1
@@ -1,33 +1,10 @@
1
- import { promises as fsPromises } from 'fs';
2
- import { serialize } from 'cookie';
3
- import fs from 'fs';
4
1
  import { LocalStorageRepository } from './LocalStorageRepository';
5
- import ky from 'ky';
6
-
7
- interface Cookie {
8
- name: string;
9
- value: string;
10
- domain?: string;
11
- path?: string;
12
- expires?: number;
13
- httpOnly?: boolean;
14
- secure?: boolean;
15
- sameSite?: 'lax' | 'strict' | 'none';
16
- }
17
2
 
18
3
  export class BaseGitHubRepository {
19
- cookie: string | null;
20
4
  constructor(
21
5
  readonly localStorageRepository: LocalStorageRepository,
22
- readonly jsonFilePath: string = './tmp/github.com.cookies.json',
23
6
  readonly ghToken: string = process.env.GH_TOKEN || 'dummy',
24
- readonly ghUserName: string | undefined = process.env.GH_USER_NAME,
25
- readonly ghUserPassword: string | undefined = process.env.GH_USER_PASSWORD,
26
- readonly ghAuthenticatorKey: string | undefined = process.env
27
- .GH_AUTHENTICATOR_KEY,
28
- ) {
29
- this.cookie = null;
30
- }
7
+ ) {}
31
8
  protected extractIssueFromUrl = (
32
9
  issueUrl: string,
33
10
  ): { owner: string; repo: string; issueNumber: number; isIssue: boolean } => {
@@ -46,119 +23,4 @@ export class BaseGitHubRepository {
46
23
  }
47
24
  return { owner, repo, issueNumber, isIssue: pullOrIssue === 'issues' };
48
25
  };
49
-
50
- getCookie = async (): Promise<string> => {
51
- if (!this.cookie) {
52
- this.cookie = await this.createCookieStringFromFile();
53
- }
54
- return this.cookie;
55
- };
56
- createHeader = async (): Promise<Record<string, string>> => {
57
- const cookie = await this.getCookie();
58
- const headers = {
59
- accept:
60
- 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
61
- 'accept-language':
62
- 'en-US,en;q=0.9,es-MX;q=0.8,es;q=0.7,ja-JP;q=0.6,ja;q=0.5',
63
- 'cache-control': 'max-age=0',
64
- 'sec-ch-ua':
65
- '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
66
- 'sec-ch-ua-mobile': '?0',
67
- 'sec-ch-ua-platform': '"Linux"',
68
- 'sec-fetch-dest': 'document',
69
- 'sec-fetch-mode': 'navigate',
70
- 'sec-fetch-site': 'same-origin',
71
- 'sec-fetch-user': '?1',
72
- 'upgrade-insecure-requests': '1',
73
- Referer: 'https://github.com/orgs/community/discussions/30979',
74
- 'Referrer-Policy': 'no-referrer-when-downgrade',
75
- };
76
- return {
77
- ...headers,
78
- cookie: cookie,
79
- };
80
- };
81
- protected createCookieStringFromFile = async (): Promise<string> => {
82
- if (!fs.existsSync(this.jsonFilePath)) {
83
- throw new Error('No cookie file found');
84
- }
85
- const data = await fsPromises.readFile(this.jsonFilePath, {
86
- encoding: 'utf-8',
87
- });
88
- const cookiesData: unknown = JSON.parse(data);
89
- return this.generateCookieHeaderFromJson(cookiesData);
90
- };
91
- protected isCookie = (cookie: object): cookie is Cookie => {
92
- return (
93
- 'name' in cookie &&
94
- typeof cookie.name === 'string' &&
95
- 'value' in cookie &&
96
- typeof cookie.value === 'string' &&
97
- 'domain' in cookie &&
98
- typeof cookie.domain === 'string' &&
99
- 'path' in cookie &&
100
- typeof cookie.path === 'string' &&
101
- 'expires' in cookie &&
102
- typeof cookie.expires === 'number' &&
103
- 'httpOnly' in cookie &&
104
- typeof cookie.httpOnly === 'boolean' &&
105
- 'secure' in cookie &&
106
- typeof cookie.secure === 'boolean' &&
107
- 'sameSite' in cookie &&
108
- typeof cookie.sameSite === 'string' &&
109
- ['lax', 'strict', 'none'].indexOf(cookie.sameSite) !== -1
110
- );
111
- };
112
-
113
- protected generateCookieHeaderFromJson = async (
114
- cookieData: unknown,
115
- ): Promise<string> => {
116
- if (!Array.isArray(cookieData)) {
117
- throw new Error('Invalid cookie array');
118
- }
119
-
120
- const cookies: Cookie[] = cookieData.map((cookieOrig: object) => {
121
- const sameSite =
122
- typeof cookieOrig !== 'object' ||
123
- !('sameSite' in cookieOrig) ||
124
- typeof cookieOrig.sameSite !== 'string'
125
- ? 'none'
126
- : cookieOrig.sameSite.toLowerCase();
127
- const cookie = {
128
- ...cookieOrig,
129
- sameSite,
130
- };
131
-
132
- if (!this.isCookie(cookie)) {
133
- throw new Error(`Invalid cookie properties: ${JSON.stringify(cookie)}`);
134
- }
135
- return cookie;
136
- });
137
- const cookieHeader = cookies
138
- .map((cookie) =>
139
- serialize(cookie.name, cookie.value, {
140
- domain: cookie.domain,
141
- path: cookie.path,
142
- expires: cookie.expires ? new Date(cookie.expires * 1000) : undefined,
143
- httpOnly: cookie.httpOnly,
144
- secure: cookie.secure,
145
- sameSite: cookie.sameSite,
146
- }),
147
- )
148
- .join('; ');
149
- return cookieHeader;
150
- };
151
- refreshCookie = async (): Promise<void> => {
152
- if (!this.ghUserName || !this.ghUserPassword || !this.ghAuthenticatorKey) {
153
- throw new Error(
154
- 'GitHub username, password, and authenticator key must be set',
155
- );
156
- }
157
- const profileUrl = `https://github.com/${this.ghUserName}`;
158
- const headers = await this.createHeader();
159
- const html = await ky.get(profileUrl, { headers }).text();
160
- if (!html.includes(`meta name="user-login" content="${this.ghUserName}"`)) {
161
- throw new Error('Failed to refresh cookie');
162
- }
163
- };
164
26
  }
@@ -28,7 +28,6 @@ describe('GraphqlProjectRepository error handling', () => {
28
28
  mockPost.mockClear();
29
29
  repository = new GraphqlProjectRepository(
30
30
  new LocalStorageRepository(),
31
- '',
32
31
  'dummy-token',
33
32
  );
34
33
  });
@@ -28,7 +28,10 @@ describe('GraphqlProjectRepository.fetchProjectId', () => {
28
28
  beforeEach(() => {
29
29
  jest.useFakeTimers();
30
30
  mockPost.mockReset();
31
- repository = new GraphqlProjectRepository(localStorageRepository, '');
31
+ repository = new GraphqlProjectRepository(
32
+ localStorageRepository,
33
+ 'dummy-token',
34
+ );
32
35
  });
33
36
 
34
37
  afterEach(() => {
@@ -1,6 +1,6 @@
1
1
  const mockEnsureProxyRunning = jest.fn();
2
2
  const mockReadRateLimit = jest.fn();
3
- const mockLoadTokens = jest.fn();
3
+ const mockLoadTokenEntries = jest.fn();
4
4
 
5
5
  jest.mock('../proxy/ensureProxyRunning', () => ({
6
6
  ensureProxyRunning: mockEnsureProxyRunning,
@@ -12,7 +12,7 @@ jest.mock('../proxy/RateLimitCache', () => ({
12
12
  }));
13
13
 
14
14
  jest.mock('../proxy/TokenListLoader', () => ({
15
- loadTokens: mockLoadTokens,
15
+ loadTokenEntries: mockLoadTokenEntries,
16
16
  }));
17
17
 
18
18
  import { ProxyClaudeTokenUsageRepository } from './ProxyClaudeTokenUsageRepository';
@@ -52,24 +52,27 @@ describe('ProxyClaudeTokenUsageRepository', () => {
52
52
  const result = await repository.getAvailableTokenUsages();
53
53
 
54
54
  expect(result).toEqual([]);
55
- expect(mockLoadTokens.mock.calls).toHaveLength(0);
55
+ expect(mockLoadTokenEntries.mock.calls).toHaveLength(0);
56
56
  });
57
57
 
58
58
  it('should return an empty list when the token list cannot be loaded', async () => {
59
- mockLoadTokens.mockReturnValue(null);
59
+ mockLoadTokenEntries.mockReturnValue(null);
60
60
  const repository = new ProxyClaudeTokenUsageRepository('/tokens.json');
61
61
 
62
62
  const result = await repository.getAvailableTokenUsages();
63
63
 
64
64
  expect(result).toEqual([]);
65
- expect(mockLoadTokens.mock.calls).toEqual([['/tokens.json']]);
65
+ expect(mockLoadTokenEntries.mock.calls).toEqual([['/tokens.json']]);
66
66
  });
67
67
 
68
68
  const futureReset = Math.floor(Date.now() / 1000) + 3600;
69
69
  const pastReset = Math.floor(Date.now() / 1000) - 3600;
70
70
 
71
- it('should map each token to its cached utilization', async () => {
72
- mockLoadTokens.mockReturnValue(['token-a', 'token-b']);
71
+ it('should map each token to its cached utilization and include name', async () => {
72
+ mockLoadTokenEntries.mockReturnValue([
73
+ { name: 'alice', token: 'token-a' },
74
+ { name: 'bob', token: 'token-b' },
75
+ ]);
73
76
  mockReadRateLimit.mockImplementation((token: string) => {
74
77
  if (token === 'token-a') {
75
78
  return {
@@ -93,6 +96,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
93
96
 
94
97
  expect(result).toEqual([
95
98
  {
99
+ name: 'alice',
96
100
  token: 'token-a',
97
101
  fiveHourUtilization: 42,
98
102
  blocked: false,
@@ -100,6 +104,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
100
104
  modelWeeklyLimits: {},
101
105
  },
102
106
  {
107
+ name: 'bob',
103
108
  token: 'token-b',
104
109
  fiveHourUtilization: 0,
105
110
  blocked: false,
@@ -110,7 +115,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
110
115
  });
111
116
 
112
117
  it('should propagate the blocked status from the cache', async () => {
113
- mockLoadTokens.mockReturnValue(['token-a']);
118
+ mockLoadTokenEntries.mockReturnValue([
119
+ { name: 'alice', token: 'token-a' },
120
+ ]);
114
121
  mockReadRateLimit.mockReturnValue({
115
122
  fiveHourUtilization: 5,
116
123
  fiveHourReset: futureReset,
@@ -129,6 +136,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
129
136
 
130
137
  expect(result).toEqual([
131
138
  {
139
+ name: 'alice',
132
140
  token: 'token-a',
133
141
  fiveHourUtilization: 5,
134
142
  blocked: true,
@@ -139,7 +147,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
139
147
  });
140
148
 
141
149
  it('should propagate the rejected status from the cache', async () => {
142
- mockLoadTokens.mockReturnValue(['token-a']);
150
+ mockLoadTokenEntries.mockReturnValue([
151
+ { name: 'alice', token: 'token-a' },
152
+ ]);
143
153
  mockReadRateLimit.mockReturnValue({
144
154
  fiveHourUtilization: 100,
145
155
  fiveHourReset: futureReset,
@@ -158,6 +168,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
158
168
 
159
169
  expect(result).toEqual([
160
170
  {
171
+ name: 'alice',
161
172
  token: 'token-a',
162
173
  fiveHourUtilization: 100,
163
174
  blocked: false,
@@ -168,7 +179,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
168
179
  });
169
180
 
170
181
  it('should normalize fiveHourUtilization to 0 when the 5h reset has passed', async () => {
171
- mockLoadTokens.mockReturnValue(['token-a']);
182
+ mockLoadTokenEntries.mockReturnValue([
183
+ { name: 'alice', token: 'token-a' },
184
+ ]);
172
185
  mockReadRateLimit.mockReturnValue({
173
186
  fiveHourUtilization: 100,
174
187
  fiveHourReset: pastReset,
@@ -187,6 +200,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
187
200
 
188
201
  expect(result).toEqual([
189
202
  {
203
+ name: 'alice',
190
204
  token: 'token-a',
191
205
  fiveHourUtilization: 0,
192
206
  blocked: false,
@@ -197,7 +211,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
197
211
  });
198
212
 
199
213
  it('should keep fiveHourUtilization when the 5h reset is in the future', async () => {
200
- mockLoadTokens.mockReturnValue(['token-a']);
214
+ mockLoadTokenEntries.mockReturnValue([
215
+ { name: 'alice', token: 'token-a' },
216
+ ]);
201
217
  mockReadRateLimit.mockReturnValue({
202
218
  fiveHourUtilization: 95,
203
219
  fiveHourReset: futureReset,
@@ -216,6 +232,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
216
232
 
217
233
  expect(result).toEqual([
218
234
  {
235
+ name: 'alice',
219
236
  token: 'token-a',
220
237
  fiveHourUtilization: 95,
221
238
  blocked: false,
@@ -226,7 +243,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
226
243
  });
227
244
 
228
245
  it('should clear a 5h-origin rejection once the 5h reset has passed', async () => {
229
- mockLoadTokens.mockReturnValue(['token-a']);
246
+ mockLoadTokenEntries.mockReturnValue([
247
+ { name: 'alice', token: 'token-a' },
248
+ ]);
230
249
  mockReadRateLimit.mockReturnValue({
231
250
  fiveHourUtilization: 100,
232
251
  fiveHourReset: pastReset,
@@ -245,6 +264,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
245
264
 
246
265
  expect(result).toEqual([
247
266
  {
267
+ name: 'alice',
248
268
  token: 'token-a',
249
269
  fiveHourUtilization: 0,
250
270
  blocked: false,
@@ -255,7 +275,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
255
275
  });
256
276
 
257
277
  it('should clear a 7d-origin rejection once the 7d reset has passed', async () => {
258
- mockLoadTokens.mockReturnValue(['token-a']);
278
+ mockLoadTokenEntries.mockReturnValue([
279
+ { name: 'alice', token: 'token-a' },
280
+ ]);
259
281
  mockReadRateLimit.mockReturnValue({
260
282
  fiveHourUtilization: 10,
261
283
  fiveHourReset: futureReset,
@@ -274,6 +296,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
274
296
 
275
297
  expect(result).toEqual([
276
298
  {
299
+ name: 'alice',
277
300
  token: 'token-a',
278
301
  fiveHourUtilization: 10,
279
302
  blocked: false,
@@ -284,7 +307,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
284
307
  });
285
308
 
286
309
  it('should keep a 5h-origin rejection while the 5h reset is in the future', async () => {
287
- mockLoadTokens.mockReturnValue(['token-a']);
310
+ mockLoadTokenEntries.mockReturnValue([
311
+ { name: 'alice', token: 'token-a' },
312
+ ]);
288
313
  mockReadRateLimit.mockReturnValue({
289
314
  fiveHourUtilization: 100,
290
315
  fiveHourReset: futureReset,
@@ -303,6 +328,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
303
328
 
304
329
  expect(result).toEqual([
305
330
  {
331
+ name: 'alice',
306
332
  token: 'token-a',
307
333
  fiveHourUtilization: 100,
308
334
  blocked: false,
@@ -313,7 +339,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
313
339
  });
314
340
 
315
341
  it('should keep a still-active 7d rejection after the 5h reset has passed', async () => {
316
- mockLoadTokens.mockReturnValue(['token-a']);
342
+ mockLoadTokenEntries.mockReturnValue([
343
+ { name: 'alice', token: 'token-a' },
344
+ ]);
317
345
  mockReadRateLimit.mockReturnValue({
318
346
  fiveHourUtilization: 100,
319
347
  fiveHourReset: pastReset,
@@ -332,6 +360,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
332
360
 
333
361
  expect(result).toEqual([
334
362
  {
363
+ name: 'alice',
335
364
  token: 'token-a',
336
365
  fiveHourUtilization: 0,
337
366
  blocked: false,
@@ -342,7 +371,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
342
371
  });
343
372
 
344
373
  it('should clear a unified rejection once the 5h reset has passed', async () => {
345
- mockLoadTokens.mockReturnValue(['token-a']);
374
+ mockLoadTokenEntries.mockReturnValue([
375
+ { name: 'alice', token: 'token-a' },
376
+ ]);
346
377
  mockReadRateLimit.mockReturnValue({
347
378
  fiveHourUtilization: 100,
348
379
  fiveHourReset: pastReset,
@@ -361,6 +392,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
361
392
 
362
393
  expect(result).toEqual([
363
394
  {
395
+ name: 'alice',
364
396
  token: 'token-a',
365
397
  fiveHourUtilization: 0,
366
398
  blocked: false,
@@ -371,7 +403,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
371
403
  });
372
404
 
373
405
  it('should default rejected to false when no snapshot exists', async () => {
374
- mockLoadTokens.mockReturnValue(['token-a']);
406
+ mockLoadTokenEntries.mockReturnValue([
407
+ { name: 'alice', token: 'token-a' },
408
+ ]);
375
409
  mockReadRateLimit.mockReturnValue(null);
376
410
  const repository = new ProxyClaudeTokenUsageRepository('/tokens.json');
377
411
 
@@ -379,6 +413,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
379
413
 
380
414
  expect(result).toEqual([
381
415
  {
416
+ name: 'alice',
382
417
  token: 'token-a',
383
418
  fiveHourUtilization: 0,
384
419
  blocked: false,
@@ -389,7 +424,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
389
424
  });
390
425
 
391
426
  it('should keep a model weekly rejection while its reset is in the future', async () => {
392
- mockLoadTokens.mockReturnValue(['token-a']);
427
+ mockLoadTokenEntries.mockReturnValue([
428
+ { name: 'alice', token: 'token-a' },
429
+ ]);
393
430
  mockReadRateLimit.mockReturnValue({
394
431
  fiveHourUtilization: 5,
395
432
  fiveHourReset: futureReset,
@@ -410,6 +447,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
410
447
 
411
448
  expect(result).toEqual([
412
449
  {
450
+ name: 'alice',
413
451
  token: 'token-a',
414
452
  fiveHourUtilization: 5,
415
453
  blocked: false,
@@ -422,7 +460,9 @@ describe('ProxyClaudeTokenUsageRepository', () => {
422
460
  });
423
461
 
424
462
  it('should clear a model weekly rejection once its reset has passed', async () => {
425
- mockLoadTokens.mockReturnValue(['token-a']);
463
+ mockLoadTokenEntries.mockReturnValue([
464
+ { name: 'alice', token: 'token-a' },
465
+ ]);
426
466
  mockReadRateLimit.mockReturnValue({
427
467
  fiveHourUtilization: 5,
428
468
  fiveHourReset: futureReset,
@@ -443,6 +483,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
443
483
 
444
484
  expect(result).toEqual([
445
485
  {
486
+ name: 'alice',
446
487
  token: 'token-a',
447
488
  fiveHourUtilization: 5,
448
489
  blocked: false,
@@ -2,7 +2,7 @@ import { ClaudeTokenUsage } from '../../domain/entities/ClaudeTokenUsage';
2
2
  import { ClaudeTokenUsageRepository } from '../../domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository';
3
3
  import { ensureProxyRunning } from '../proxy/ensureProxyRunning';
4
4
  import { PROXY_PORT, readRateLimit } from '../proxy/RateLimitCache';
5
- import { loadTokens } from '../proxy/TokenListLoader';
5
+ import { loadTokenEntries } from '../proxy/TokenListLoader';
6
6
 
7
7
  export class ProxyClaudeTokenUsageRepository implements ClaudeTokenUsageRepository {
8
8
  constructor(
@@ -18,15 +18,16 @@ export class ProxyClaudeTokenUsageRepository implements ClaudeTokenUsageReposito
18
18
  if (this.tokenListJsonPath === null) {
19
19
  return [];
20
20
  }
21
- const tokens = loadTokens(this.tokenListJsonPath);
22
- if (tokens === null) {
21
+ const entries = loadTokenEntries(this.tokenListJsonPath);
22
+ if (entries === null) {
23
23
  return [];
24
24
  }
25
25
  const nowEpochSeconds = Date.now() / 1000;
26
- return tokens.map((token) => {
26
+ return entries.map(({ name, token }) => {
27
27
  const snapshot = readRateLimit(token);
28
28
  if (snapshot === null) {
29
29
  return {
30
+ name,
30
31
  token,
31
32
  fiveHourUtilization: 0,
32
33
  blocked: false,
@@ -63,6 +64,7 @@ export class ProxyClaudeTokenUsageRepository implements ClaudeTokenUsageReposito
63
64
  };
64
65
  }
65
66
  return {
67
+ name,
66
68
  token,
67
69
  fiveHourUtilization,
68
70
  blocked: snapshot.blocked,
@@ -27,6 +27,7 @@ type TimelineItem = {
27
27
  number?: number;
28
28
  state?: string;
29
29
  createdAt?: string;
30
+ isDraft?: boolean;
30
31
  mergeable?: string;
31
32
  headRefName?: string;
32
33
  baseRefName?: string;
@@ -117,6 +118,7 @@ type IssueTimelineResponse = {
117
118
  };
118
119
 
119
120
  type PrStatusComputationData = {
121
+ isDraft?: boolean;
120
122
  mergeable?: string;
121
123
  baseRepository?: {
122
124
  branchProtectionRules?: {
@@ -293,21 +295,9 @@ export class ApiV3CheerioRestIssueRepository
293
295
  'getLatest' | 'set'
294
296
  >,
295
297
  readonly localStorageRepository: LocalStorageRepository,
296
- readonly jsonFilePath: string = './tmp/github.com.cookies.json',
297
298
  readonly ghToken: string = process.env.GH_TOKEN || 'dummy',
298
- readonly ghUserName: string | undefined = process.env.GH_USER_NAME,
299
- readonly ghUserPassword: string | undefined = process.env.GH_USER_PASSWORD,
300
- readonly ghAuthenticatorKey: string | undefined = process.env
301
- .GH_AUTHENTICATOR_KEY,
302
299
  ) {
303
- super(
304
- localStorageRepository,
305
- jsonFilePath,
306
- ghToken,
307
- ghUserName,
308
- ghUserPassword,
309
- ghAuthenticatorKey,
310
- );
300
+ super(localStorageRepository, ghToken);
311
301
  }
312
302
 
313
303
  updateStatus: (
@@ -474,6 +464,23 @@ export class ApiV3CheerioRestIssueRepository
474
464
  labels,
475
465
  );
476
466
  };
467
+ searchIssue = async (query: {
468
+ owner: string;
469
+ repositoryName: string;
470
+ type?: 'issue' | 'pr';
471
+ state?: 'open' | 'closed' | 'all';
472
+ title?: string;
473
+ createdFrom?: string;
474
+ assignee?: string;
475
+ }): Promise<
476
+ {
477
+ url: string;
478
+ title: string;
479
+ number: string;
480
+ }[]
481
+ > => {
482
+ return await this.apiV3IssueRepository.searchIssue(query);
483
+ };
477
484
  updateIssue = async (issue: Issue): Promise<void> => {
478
485
  await this.restIssueRepository.updateIssue(issue);
479
486
  };
@@ -732,6 +739,7 @@ export class ApiV3CheerioRestIssueRepository
732
739
  url: prUrl,
733
740
  branchName: headRefName ?? null,
734
741
  createdAt: new Date(0),
742
+ isDraft: data.isDraft === true,
735
743
  isConflicted,
736
744
  isPassedAllCiJob,
737
745
  isCiStateSuccess,
@@ -771,6 +779,7 @@ export class ApiV3CheerioRestIssueRepository
771
779
  number
772
780
  state
773
781
  createdAt
782
+ isDraft
774
783
  mergeable
775
784
  headRefName
776
785
  baseRefName
@@ -959,6 +968,7 @@ export class ApiV3CheerioRestIssueRepository
959
968
  pullRequest(number: $prNumber) {
960
969
  url
961
970
  state
971
+ isDraft
962
972
  headRefName
963
973
  baseRefName
964
974
  mergeable
@@ -7,7 +7,6 @@ describe('ApiV3IssueRepository', () => {
7
7
  const localStorageRepository = new LocalStorageRepository();
8
8
  const repository = new ApiV3IssueRepository(
9
9
  localStorageRepository,
10
- '',
11
10
  process.env.GH_TOKEN || 'dummy',
12
11
  );
13
12
  test('searchIssue', async () => {
@@ -83,7 +83,6 @@ describe('GraphqlProjectItemRepository', () => {
83
83
  const localStorageRepository = new LocalStorageRepository();
84
84
  const repository = new GraphqlProjectItemRepository(
85
85
  localStorageRepository,
86
- '',
87
86
  'dummy-token',
88
87
  );
89
88
 
@@ -109,7 +108,6 @@ describe('GraphqlProjectItemRepository', () => {
109
108
  const localStorageRepository = new LocalStorageRepository();
110
109
  const repository = new GraphqlProjectItemRepository(
111
110
  localStorageRepository,
112
- '',
113
111
  'dummy-token',
114
112
  );
115
113
 
@@ -157,7 +155,6 @@ describe('GraphqlProjectItemRepository', () => {
157
155
  const localStorageRepository = new LocalStorageRepository();
158
156
  const repository = new GraphqlProjectItemRepository(
159
157
  localStorageRepository,
160
- '',
161
158
  'dummy-token',
162
159
  );
163
160
 
@@ -176,7 +173,6 @@ describe('GraphqlProjectItemRepository', () => {
176
173
  const localStorageRepository = new LocalStorageRepository();
177
174
  const repository = new GraphqlProjectItemRepository(
178
175
  localStorageRepository,
179
- '',
180
176
  'dummy-token',
181
177
  );
182
178
 
@@ -195,7 +191,6 @@ describe('GraphqlProjectItemRepository', () => {
195
191
  const localStorageRepository = new LocalStorageRepository();
196
192
  const repository = new GraphqlProjectItemRepository(
197
193
  localStorageRepository,
198
- '',
199
194
  'dummy-token',
200
195
  );
201
196
 
@@ -244,7 +239,6 @@ describe('GraphqlProjectItemRepository', () => {
244
239
  const localStorageRepository = new LocalStorageRepository();
245
240
  const repository = new GraphqlProjectItemRepository(
246
241
  localStorageRepository,
247
- '',
248
242
  'dummy-token',
249
243
  );
250
244
 
@@ -277,7 +271,6 @@ describe('GraphqlProjectItemRepository', () => {
277
271
  const localStorageRepository = new LocalStorageRepository();
278
272
  const repository = new GraphqlProjectItemRepository(
279
273
  localStorageRepository,
280
- '',
281
274
  'dummy-token',
282
275
  );
283
276
 
@@ -305,7 +298,6 @@ describe('GraphqlProjectItemRepository', () => {
305
298
  const localStorageRepository = new LocalStorageRepository();
306
299
  const repository = new GraphqlProjectItemRepository(
307
300
  localStorageRepository,
308
- '',
309
301
  'dummy-token',
310
302
  );
311
303
 
@@ -6,7 +6,6 @@ describe('RestIssueRepository', () => {
6
6
  const localStorageRepository = new LocalStorageRepository();
7
7
  const restIssueRepository: RestIssueRepository = new RestIssueRepository(
8
8
  localStorageRepository,
9
- '',
10
9
  process.env.GH_TOKEN || 'dummy',
11
10
  );
12
11
 
@@ -4,6 +4,7 @@ export type ClaudeModelWeeklyLimit = {
4
4
  };
5
5
 
6
6
  export type ClaudeTokenUsage = {
7
+ name?: string;
7
8
  token: string;
8
9
  fiveHourUtilization: number;
9
10
  blocked: boolean;