github-issue-tower-defence-management 1.173.3 → 1.174.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 (90) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +15 -0
  3. package/bin/adapter/entry-points/cli/index.js +5 -3
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/console/consoleGithubTokenResolver.js +15 -4
  6. package/bin/adapter/entry-points/console/consoleGithubTokenResolver.js.map +1 -1
  7. package/bin/adapter/entry-points/console/ui-dist/assets/{index-BcnLfodS.js → index-g4SqIzCh.js} +2 -2
  8. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  9. package/bin/adapter/entry-points/console/webServer.js +19 -4
  10. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  11. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +3 -1
  12. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  13. package/bin/adapter/repositories/LocalCommandIssueAttachmentRepository.js +7 -12
  14. package/bin/adapter/repositories/LocalCommandIssueAttachmentRepository.js.map +1 -1
  15. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +2 -1
  16. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
  17. package/bin/domain/usecases/HandleScheduledEventUseCase.js +10 -5
  18. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  19. package/bin/domain/usecases/IssueRejectionEvaluator.js +4 -1
  20. package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
  21. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +2 -1
  22. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  23. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +2 -1
  24. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  25. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +143 -0
  26. package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +1 -0
  27. package/bin/domain/usecases/agentReportPrefix.js +5 -0
  28. package/bin/domain/usecases/agentReportPrefix.js.map +1 -0
  29. package/bin/domain/usecases/extractNextStepAgent.js +2 -1
  30. package/bin/domain/usecases/extractNextStepAgent.js.map +1 -1
  31. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +4 -3
  32. package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +1 -1
  33. package/bin/domain/usecases/isRecord.js +6 -0
  34. package/bin/domain/usecases/isRecord.js.map +1 -0
  35. package/bin/domain/usecases/normalizeReportBody.js +6 -0
  36. package/bin/domain/usecases/normalizeReportBody.js.map +1 -0
  37. package/package.json +1 -1
  38. package/src/adapter/entry-points/cli/index.ts +8 -4
  39. package/src/adapter/entry-points/console/consoleGithubTokenResolver.test.ts +45 -2
  40. package/src/adapter/entry-points/console/consoleGithubTokenResolver.ts +18 -3
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +5 -1
  42. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.test.ts +8 -5
  43. package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.ts +2 -2
  44. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +2 -2
  45. package/src/adapter/entry-points/console/ui-dist/assets/{index-BcnLfodS.js → index-g4SqIzCh.js} +2 -2
  46. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  47. package/src/adapter/entry-points/console/webServer.test.ts +75 -5
  48. package/src/adapter/entry-points/console/webServer.ts +23 -4
  49. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +7 -0
  50. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +53 -70
  51. package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.ts +10 -13
  52. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +4 -1
  53. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +27 -0
  54. package/src/domain/usecases/HandleScheduledEventUseCase.ts +7 -3
  55. package/src/domain/usecases/IssueRejectionEvaluator.ts +4 -1
  56. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +4 -1
  57. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +4 -1
  58. package/src/domain/usecases/TriagerApprovalDispatchUseCase.test.ts +879 -0
  59. package/src/domain/usecases/TriagerApprovalDispatchUseCase.ts +250 -0
  60. package/src/domain/usecases/agentReportPrefix.ts +1 -0
  61. package/src/domain/usecases/extractNextStepAgent.test.ts +31 -0
  62. package/src/domain/usecases/extractNextStepAgent.ts +4 -1
  63. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.test.ts +15 -0
  64. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +5 -2
  65. package/src/domain/usecases/isRecord.ts +2 -0
  66. package/src/domain/usecases/normalizeReportBody.ts +2 -0
  67. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  68. package/types/adapter/entry-points/console/consoleGithubTokenResolver.d.ts +1 -0
  69. package/types/adapter/entry-points/console/consoleGithubTokenResolver.d.ts.map +1 -1
  70. package/types/adapter/entry-points/console/webServer.d.ts +3 -1
  71. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  72. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  73. package/types/adapter/repositories/LocalCommandIssueAttachmentRepository.d.ts +3 -2
  74. package/types/adapter/repositories/LocalCommandIssueAttachmentRepository.d.ts.map +1 -1
  75. package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
  76. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +3 -1
  77. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  78. package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
  79. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  80. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  81. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts +15 -0
  82. package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +1 -0
  83. package/types/domain/usecases/agentReportPrefix.d.ts +2 -0
  84. package/types/domain/usecases/agentReportPrefix.d.ts.map +1 -0
  85. package/types/domain/usecases/extractNextStepAgent.d.ts.map +1 -1
  86. package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts.map +1 -1
  87. package/types/domain/usecases/isRecord.d.ts +2 -0
  88. package/types/domain/usecases/isRecord.d.ts.map +1 -0
  89. package/types/domain/usecases/normalizeReportBody.d.ts +2 -0
  90. package/types/domain/usecases/normalizeReportBody.d.ts.map +1 -0
@@ -33,6 +33,10 @@ import {
33
33
  handleTriage,
34
34
  } from './consoleOperationApi';
35
35
  import { ImageFetcher, fetchProxiedImage } from './consoleImageProxy';
36
+ import {
37
+ ConsoleGithubTokenResolver,
38
+ extractRepositoryOwner,
39
+ } from './consoleGithubTokenResolver';
36
40
  import {
37
41
  composeDashboardText,
38
42
  dashboardComposeFilesPresent,
@@ -162,8 +166,10 @@ export const extractProvidedToken = (
162
166
  return null;
163
167
  };
164
168
 
169
+ export const CONSOLE_TOKEN_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
170
+
165
171
  export const buildTokenCookie = (token: string): string =>
166
- `${CONSOLE_TOKEN_COOKIE}=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Strict`;
172
+ `${CONSOLE_TOKEN_COOKIE}=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${CONSOLE_TOKEN_COOKIE_MAX_AGE_SECONDS}`;
167
173
 
168
174
  export const buildKeylessLocation = (requestUrl: URL): string => {
169
175
  const params = new URLSearchParams(requestUrl.searchParams);
@@ -219,7 +225,7 @@ export type WebServerOptions = {
219
225
  dashboardDir: string | null;
220
226
  dashboardDataDir: string | null;
221
227
  dashboardProjectNames: string[];
222
- githubToken?: string | null;
228
+ resolveGithubToken?: ConsoleGithubTokenResolver | null;
223
229
  imageFetcher?: ImageFetcher | null;
224
230
  issueRepository?: IssueRepository | null;
225
231
  resolveIssueRepository?: ConsoleIssueRepositoryResolver | null;
@@ -379,8 +385,21 @@ const handleImageProxy = async (
379
385
  response: http.ServerResponse,
380
386
  searchParams: URLSearchParams,
381
387
  ): Promise<void> => {
382
- const githubToken = options.githubToken ?? null;
383
- if (githubToken === null || githubToken.length === 0) {
388
+ const resolveGithubToken = options.resolveGithubToken ?? null;
389
+ if (resolveGithubToken === null) {
390
+ sendJson(response, 502, { error: 'github token is not configured' });
391
+ return;
392
+ }
393
+ const itemUrl = searchParams.get('itemUrl') ?? '';
394
+ const repositoryOwner = extractRepositoryOwner(itemUrl);
395
+ if (repositoryOwner === null) {
396
+ sendJson(response, 400, {
397
+ error: 'missing or unreadable itemUrl parameter',
398
+ });
399
+ return;
400
+ }
401
+ const githubToken = resolveGithubToken(repositoryOwner);
402
+ if (githubToken.length === 0) {
384
403
  sendJson(response, 502, { error: 'github token is not configured' });
385
404
  return;
386
405
  }
@@ -69,6 +69,7 @@ import { ProxyRateLimitCacheRepository } from '../../repositories/ProxyRateLimit
69
69
  import { UpdateRateLimitCacheUseCase } from '../../../domain/usecases/UpdateRateLimitCacheUseCase';
70
70
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
71
71
  import { RevertNotReadyReviewQueueIssueUseCase } from '../../../domain/usecases/RevertNotReadyReviewQueueIssueUseCase';
72
+ import { TriagerApprovalDispatchUseCase } from '../../../domain/usecases/TriagerApprovalDispatchUseCase';
72
73
  import { AgentDesignationLabelAdoptUseCase } from '../../../domain/usecases/AgentDesignationLabelAdoptUseCase';
73
74
  import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueCommentRepository';
74
75
  import { ProjectRequiredFieldCreateUseCase } from '../../../domain/usecases/ProjectRequiredFieldCreateUseCase';
@@ -496,6 +497,11 @@ export class HandleScheduledEventUseCaseHandler {
496
497
  issueRepository,
497
498
  issueCommentRepository,
498
499
  );
500
+ const triagerApprovalDispatchUseCase = new TriagerApprovalDispatchUseCase(
501
+ projectRepository,
502
+ issueRepository,
503
+ issueCommentRepository,
504
+ );
499
505
  const agentDesignationLabelAdoptUseCase =
500
506
  new AgentDesignationLabelAdoptUseCase(projectRepository, issueRepository);
501
507
 
@@ -533,6 +539,7 @@ export class HandleScheduledEventUseCaseHandler {
533
539
  startPreparationUseCase,
534
540
  revertOrphanedPreparationUseCase,
535
541
  revertNotReadyReviewQueueIssueUseCase,
542
+ triagerApprovalDispatchUseCase,
536
543
  agentDesignationLabelAdoptUseCase,
537
544
  updateRateLimitCacheUseCase,
538
545
  dailySecurityScanUseCase,
@@ -66,6 +66,44 @@ describe('relabelAttachmentMarkdown', () => {
66
66
  });
67
67
 
68
68
  describe('LocalCommandIssueAttachmentRepository', () => {
69
+ const resolveGithubToken = (): string => 'default-token';
70
+
71
+ it('should run the upload command with the github token resolved for the item url', async () => {
72
+ const root = await mkdtemp(join(tmpdir(), 'attachment-test-root-'));
73
+ const received: {
74
+ args: string[];
75
+ options: LocalCommandRunnerOptions | undefined;
76
+ }[] = [];
77
+ const runner: LocalCommandRunner = {
78
+ runCommand: async (_program, args, options) => {
79
+ received.push({ args, options });
80
+ return {
81
+ stdout:
82
+ '![attachment](https://github.com/user-attachments/assets/66666666-7777-8888-9999-000000000000)\n',
83
+ stderr: '',
84
+ exitCode: 0,
85
+ };
86
+ },
87
+ };
88
+ const repository = new LocalCommandIssueAttachmentRepository(
89
+ runner,
90
+ (issueOrPullRequestUrl) =>
91
+ issueOrPullRequestUrl.startsWith('https://github.com/acme/')
92
+ ? 'acme-token'
93
+ : 'other-token',
94
+ root,
95
+ );
96
+
97
+ await repository.uploadAttachment({
98
+ issueOrPullRequestUrl: 'https://github.com/acme/repo/issues/7',
99
+ fileName: 'screenshot.png',
100
+ content: new Uint8Array([1]),
101
+ });
102
+
103
+ expect(received).toHaveLength(1);
104
+ expect(received[0].options?.env).toEqual({ GH_TOKEN: 'acme-token' });
105
+ });
106
+
69
107
  it('should upload the written file and return the markdown the command printed', async () => {
70
108
  const root = await mkdtemp(join(tmpdir(), 'attachment-test-root-'));
71
109
  const received: { program: string; args: string[] }[] = [];
@@ -80,7 +118,11 @@ describe('LocalCommandIssueAttachmentRepository', () => {
80
118
  };
81
119
  },
82
120
  };
83
- const repository = new LocalCommandIssueAttachmentRepository(runner, root);
121
+ const repository = new LocalCommandIssueAttachmentRepository(
122
+ runner,
123
+ resolveGithubToken,
124
+ root,
125
+ );
84
126
 
85
127
  const markdown = await repository.uploadAttachment({
86
128
  issueOrPullRequestUrl: 'https://github.com/owner/repo/issues/1',
@@ -107,7 +149,11 @@ describe('LocalCommandIssueAttachmentRepository', () => {
107
149
  exitCode: 1,
108
150
  }),
109
151
  };
110
- const repository = new LocalCommandIssueAttachmentRepository(runner, root);
152
+ const repository = new LocalCommandIssueAttachmentRepository(
153
+ runner,
154
+ resolveGithubToken,
155
+ root,
156
+ );
111
157
 
112
158
  await expect(
113
159
  repository.uploadAttachment({
@@ -126,7 +172,11 @@ describe('LocalCommandIssueAttachmentRepository', () => {
126
172
  const runner: LocalCommandRunner = {
127
173
  runCommand: async () => ({ stdout: '\n', stderr: '', exitCode: 0 }),
128
174
  };
129
- const repository = new LocalCommandIssueAttachmentRepository(runner, root);
175
+ const repository = new LocalCommandIssueAttachmentRepository(
176
+ runner,
177
+ resolveGithubToken,
178
+ root,
179
+ );
130
180
 
131
181
  await expect(
132
182
  repository.uploadAttachment({
@@ -136,71 +186,4 @@ describe('LocalCommandIssueAttachmentRepository', () => {
136
186
  }),
137
187
  ).rejects.toThrow(`${UPLOAD_COMMAND} returned no markdown`);
138
188
  });
139
-
140
- it('should pass GH_TOKEN env resolved from the owner when resolveGithubToken is provided', async () => {
141
- const root = await mkdtemp(join(tmpdir(), 'attachment-test-root-'));
142
- const received: {
143
- program: string;
144
- args: string[];
145
- options: LocalCommandRunnerOptions | undefined;
146
- }[] = [];
147
- const runner: LocalCommandRunner = {
148
- runCommand: async (program, args, options) => {
149
- received.push({ program, args, options });
150
- return {
151
- stdout:
152
- '![screenshot](https://github.com/user-attachments/assets/abc)\n',
153
- stderr: '',
154
- exitCode: 0,
155
- };
156
- },
157
- };
158
- const resolveGithubToken = (owner: string): string => `token-for-${owner}`;
159
- const repository = new LocalCommandIssueAttachmentRepository(
160
- runner,
161
- root,
162
- resolveGithubToken,
163
- );
164
-
165
- await repository.uploadAttachment({
166
- issueOrPullRequestUrl: 'https://github.com/meta-site/repo/issues/1',
167
- fileName: 'screenshot.png',
168
- content: new Uint8Array([1, 2, 3]),
169
- });
170
-
171
- expect(received).toHaveLength(1);
172
- expect(received[0].options).toEqual({
173
- env: { GH_TOKEN: 'token-for-meta-site' },
174
- });
175
- });
176
-
177
- it('should pass undefined as third argument to runCommand when resolveGithubToken is not provided', async () => {
178
- const root = await mkdtemp(join(tmpdir(), 'attachment-test-root-'));
179
- const received: {
180
- program: string;
181
- args: string[];
182
- options: LocalCommandRunnerOptions | undefined;
183
- }[] = [];
184
- const runner: LocalCommandRunner = {
185
- runCommand: async (program, args, options) => {
186
- received.push({ program, args, options });
187
- return {
188
- stdout:
189
- '![screenshot](https://github.com/user-attachments/assets/abc)\n',
190
- stderr: '',
191
- exitCode: 0,
192
- };
193
- },
194
- };
195
- const repository = new LocalCommandIssueAttachmentRepository(runner, root);
196
-
197
- await repository.uploadAttachment({
198
- issueOrPullRequestUrl: 'https://github.com/owner/repo/issues/1',
199
- fileName: 'screenshot.png',
200
- content: new Uint8Array([1]),
201
- });
202
-
203
- expect(received).toHaveLength(1);
204
- expect(received[0].options).toBeUndefined();
205
- });
206
189
  });
@@ -9,6 +9,10 @@ import { LocalCommandRunner } from '../../domain/usecases/adapter-interfaces/Loc
9
9
 
10
10
  export const UPLOAD_COMMAND = 'upload-file-to-gh-issue';
11
11
 
12
+ export type IssueAttachmentGithubTokenResolver = (
13
+ issueOrPullRequestUrl: string,
14
+ ) => string;
15
+
12
16
  export const ALLOWED_ATTACHMENT_EXTENSIONS = [
13
17
  '.png',
14
18
  '.jpg',
@@ -57,8 +61,8 @@ export const relabelAttachmentMarkdown = (
57
61
  export class LocalCommandIssueAttachmentRepository implements IssueAttachmentRepository {
58
62
  constructor(
59
63
  private readonly localCommandRunner: LocalCommandRunner,
64
+ private readonly resolveGithubToken: IssueAttachmentGithubTokenResolver,
60
65
  private readonly temporaryDirectoryRoot: string = tmpdir(),
61
- private readonly resolveGithubToken?: (repositoryOwner: string) => string,
62
66
  ) {}
63
67
 
64
68
  async uploadAttachment(
@@ -73,21 +77,14 @@ export class LocalCommandIssueAttachmentRepository implements IssueAttachmentRep
73
77
  );
74
78
  try {
75
79
  await writeFile(filePath, request.content);
76
- let commandOptions: { env: { GH_TOKEN: string } } | undefined;
77
- if (this.resolveGithubToken !== undefined) {
78
- const ownerMatch = request.issueOrPullRequestUrl.match(
79
- /https:\/\/github\.com\/([A-Za-z0-9._-]+)\//,
80
- );
81
- if (ownerMatch !== null) {
82
- commandOptions = {
83
- env: { GH_TOKEN: this.resolveGithubToken(ownerMatch[1]) },
84
- };
85
- }
86
- }
87
80
  const result = await this.localCommandRunner.runCommand(
88
81
  UPLOAD_COMMAND,
89
82
  [filePath, request.issueOrPullRequestUrl],
90
- commandOptions,
83
+ {
84
+ env: {
85
+ GH_TOKEN: this.resolveGithubToken(request.issueOrPullRequestUrl),
86
+ },
87
+ },
91
88
  );
92
89
  if (result.exitCode !== 0) {
93
90
  throw new Error(
@@ -6,6 +6,7 @@ import {
6
6
  } from './IssueRejectionEvaluator';
7
7
  import { resolveLabelsNotRequiringPullRequest } from './resolveLabelsNotRequiringPullRequest';
8
8
  import { isPullRequestDeclaredUnnecessary } from './isPullRequestDeclaredUnnecessary';
9
+ import { normalizeReportBody } from './normalizeReportBody';
9
10
 
10
11
  type RejectedReasonType =
11
12
  | 'ISSUE_NOT_FOUND'
@@ -113,7 +114,9 @@ export class CheckIssueReviewReadinessUseCase {
113
114
  allowedIssueAuthors === null || allowedIssueAuthors.includes(author);
114
115
 
115
116
  private reportBodyHasNextStep = (body: string): boolean => {
116
- const reportMatch = body.match(/```json\n([\s\S]*?)\n```/);
117
+ const reportMatch = normalizeReportBody(body).match(
118
+ /```json\n([\s\S]*?)\n```/,
119
+ );
117
120
  if (!reportMatch || reportMatch.length < 2) {
118
121
  return false;
119
122
  }
@@ -24,6 +24,7 @@ import { UpdateIssueStatusByLabelUseCase } from './UpdateIssueStatusByLabelUseCa
24
24
  import { StartPreparationUseCase } from './StartPreparationUseCase';
25
25
  import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
26
26
  import { RevertNotReadyReviewQueueIssueUseCase } from './RevertNotReadyReviewQueueIssueUseCase';
27
+ import { TriagerApprovalDispatchUseCase } from './TriagerApprovalDispatchUseCase';
27
28
  import { AgentDesignationLabelAdoptUseCase } from './AgentDesignationLabelAdoptUseCase';
28
29
  import { ProjectRequiredFieldCreateUseCase } from './ProjectRequiredFieldCreateUseCase';
29
30
  import { SetupTowerDefenceProjectUseCase } from './SetupTowerDefenceProjectUseCase';
@@ -125,6 +126,8 @@ describe('HandleScheduledEventUseCase', () => {
125
126
  mock<RevertOrphanedPreparationUseCase>();
126
127
  const mockRevertNotReadyReviewQueueIssueUseCase =
127
128
  mock<RevertNotReadyReviewQueueIssueUseCase>();
129
+ const mockTriagerApprovalDispatchUseCase =
130
+ mock<TriagerApprovalDispatchUseCase>();
128
131
  const mockAgentDesignationLabelAdoptUseCase =
129
132
  mock<AgentDesignationLabelAdoptUseCase>();
130
133
  const mockUpdateRateLimitCacheUseCase = mock<UpdateRateLimitCacheUseCase>();
@@ -156,6 +159,7 @@ describe('HandleScheduledEventUseCase', () => {
156
159
  mockStartPreparationUseCase,
157
160
  mockRevertOrphanedPreparationUseCase,
158
161
  mockRevertNotReadyReviewQueueIssueUseCase,
162
+ mockTriagerApprovalDispatchUseCase,
159
163
  mockAgentDesignationLabelAdoptUseCase,
160
164
  mockUpdateRateLimitCacheUseCase,
161
165
  mockDailySecurityScanUseCase,
@@ -236,6 +240,29 @@ describe('HandleScheduledEventUseCase', () => {
236
240
  });
237
241
  });
238
242
 
243
+ it('should call TriagerApprovalDispatchUseCase with project URL', async () => {
244
+ const input = {
245
+ projectName: 'test-project',
246
+ org: 'test-org',
247
+ projectUrl: 'https://github.com/test-org/test-project',
248
+ manager: 'test-manager',
249
+ workingReport: {
250
+ repo: 'test-repo',
251
+ members: ['member1'],
252
+ spreadsheetUrl: 'https://docs.google.com/spreadsheets/test',
253
+ },
254
+ urlOfStoryView: 'https://github.com/test-org/test-project/issues',
255
+ disabled: false,
256
+ };
257
+
258
+ await useCase.run(input);
259
+ expect(mockTriagerApprovalDispatchUseCase.run).toHaveBeenCalledWith(
260
+ expect.objectContaining({
261
+ projectUrl: 'https://github.com/test-org/test-project',
262
+ }),
263
+ );
264
+ });
265
+
239
266
  it('should return null and skip all processing when disabled is true', async () => {
240
267
  const input = {
241
268
  projectName: 'test-project',
@@ -28,6 +28,8 @@ import {
28
28
  import { AgentDesignationLabelAdoptUseCase } from './AgentDesignationLabelAdoptUseCase';
29
29
  import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
30
30
  import { RevertNotReadyReviewQueueIssueUseCase } from './RevertNotReadyReviewQueueIssueUseCase';
31
+ import { TriagerApprovalDispatchUseCase } from './TriagerApprovalDispatchUseCase';
32
+ import { isRecord } from './isRecord';
31
33
  import { resolveLabelsAsLlmAgentName } from './resolveLabelsAsLlmAgentName';
32
34
  import { resolveAllowedIssueAuthors } from './resolveAllowedIssueAuthors';
33
35
  import { ProjectRequiredFieldCreateUseCase } from './ProjectRequiredFieldCreateUseCase';
@@ -64,9 +66,6 @@ const isTransientApiError = (error: Error): boolean => {
64
66
  const TRANSIENT_NETWORK_ERROR_CODE_PATTERN =
65
67
  /\b(ECONNRESET|ECONNREFUSED|ECONNABORTED|ETIMEDOUT|EPIPE|ENOTFOUND|EAI_AGAIN|ERR_NETWORK|ERR_SOCKET_CONNECTION_TIMEOUT)\b/;
66
68
 
67
- const isRecord = (value: unknown): value is Record<string, unknown> =>
68
- typeof value === 'object' && value !== null;
69
-
70
69
  const extractHttpStatusFromError = (error: Error): number | null => {
71
70
  if (!isRecord(error)) {
72
71
  return null;
@@ -137,6 +136,7 @@ export class HandleScheduledEventUseCase {
137
136
  readonly startPreparationUseCase: StartPreparationUseCase,
138
137
  readonly revertOrphanedPreparationUseCase: RevertOrphanedPreparationUseCase,
139
138
  readonly revertNotReadyReviewQueueIssueUseCase: RevertNotReadyReviewQueueIssueUseCase,
139
+ readonly triagerApprovalDispatchUseCase: TriagerApprovalDispatchUseCase,
140
140
  readonly agentDesignationLabelAdoptUseCase: AgentDesignationLabelAdoptUseCase,
141
141
  readonly updateRateLimitCacheUseCase: UpdateRateLimitCacheUseCase | null,
142
142
  readonly dailySecurityScanUseCase: DailySecurityScanUseCase | null,
@@ -437,6 +437,10 @@ ${JSON.stringify(e)}
437
437
  changeTargetPathAliases: input.changeTargetPathAliases,
438
438
  allowedIssueAuthors,
439
439
  });
440
+ await this.triagerApprovalDispatchUseCase.run({
441
+ projectUrl: input.projectUrl,
442
+ allowedIssueAuthors,
443
+ });
440
444
  if (this.dailySecurityScanUseCase !== null && input.dailySecurityScan) {
441
445
  await this.dailySecurityScanUseCase.run({
442
446
  targetDates: targetDateTimes,
@@ -2,6 +2,7 @@ import {
2
2
  IssueRepository,
3
3
  RelatedPullRequest,
4
4
  } from './adapter-interfaces/IssueRepository';
5
+ import { normalizeReportBody } from './normalizeReportBody';
5
6
 
6
7
  export type PrRejectedReasonType =
7
8
  | 'PULL_REQUEST_NOT_FOUND'
@@ -300,7 +301,9 @@ export class IssueRejectionEvaluator {
300
301
  body: string | null | undefined,
301
302
  ): boolean => {
302
303
  if (!body) return true;
303
- const match = body.trimEnd().match(/```json\n([\s\S]*?)\n```\s*$/);
304
+ const match = normalizeReportBody(body)
305
+ .trimEnd()
306
+ .match(/```json\n([\s\S]*?)\n```\s*$/);
304
307
  if (!match || !match[1]) return true;
305
308
  let config: unknown;
306
309
  try {
@@ -29,6 +29,7 @@ import { Project } from '../entities/Project';
29
29
  import { ensureAgentOptionAndGetId } from './ensureAgentOptionAndGetId';
30
30
  import { extractNextStepAgent } from './extractNextStepAgent';
31
31
  import { issueReactivationTriggerIsPending } from './issueReactivationTriggerIsPending';
32
+ import { normalizeReportBody } from './normalizeReportBody';
32
33
 
33
34
  export class IssueNotFoundError extends Error {
34
35
  constructor(issueUrl: string) {
@@ -514,7 +515,9 @@ export class NotifyFinishedIssuePreparationUseCase {
514
515
  };
515
516
 
516
517
  private reportBodyHasNextStep = (body: string): boolean => {
517
- const reportMatch = body.match(/```json\n([\s\S]*?)\n```/);
518
+ const reportMatch = normalizeReportBody(body).match(
519
+ /```json\n([\s\S]*?)\n```/,
520
+ );
518
521
  if (!reportMatch || reportMatch.length < 2) {
519
522
  return false;
520
523
  }
@@ -24,6 +24,7 @@ import {
24
24
  } from './returnedToAwaitingWorkspaceMessage';
25
25
  import { extractNextStepAgent } from './extractNextStepAgent';
26
26
  import { ensureAgentOptionAndGetId } from './ensureAgentOptionAndGetId';
27
+ import { normalizeReportBody } from './normalizeReportBody';
27
28
 
28
29
  const ORPHANED_PREPARATION_REJECTION_DETAIL = 'ORPHANED_PREPARATION';
29
30
 
@@ -363,7 +364,9 @@ export class RevertOrphanedPreparationUseCase {
363
364
  };
364
365
 
365
366
  private reportBodyHasNextStep = (body: string): boolean => {
366
- const reportMatch = body.match(/```json\n([\s\S]*?)\n```/);
367
+ const reportMatch = normalizeReportBody(body).match(
368
+ /```json\n([\s\S]*?)\n```/,
369
+ );
367
370
  if (!reportMatch || reportMatch.length < 2) {
368
371
  return false;
369
372
  }