github-issue-tower-defence-management 1.164.0 → 1.165.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 +12 -0
- package/bin/adapter/entry-points/cli/index.js +1 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js +1 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js +1 -0
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +37 -0
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +56 -5
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/projectFieldDefinition.js +8 -0
- package/bin/adapter/repositories/projectFieldDefinition.js.map +1 -1
- package/bin/domain/entities/RequiredProjectField.js +7 -1
- package/bin/domain/entities/RequiredProjectField.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/NotifyFinishedIssuePreparationUseCase.js +65 -44
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +28 -2
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +5 -4
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +2 -0
- package/src/adapter/entry-points/cli/index.ts +1 -0
- package/src/adapter/entry-points/cli/projectConfig.ts +3 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +1 -0
- package/src/adapter/entry-points/console/consoleTabNames.test.ts +1 -0
- package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +1 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +3 -0
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +2 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
- package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +2 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +1 -0
- package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +1 -0
- package/src/adapter/repositories/GraphqlProjectRepository.diskCache.test.ts +1 -0
- package/src/adapter/repositories/GraphqlProjectRepository.test.ts +2 -0
- package/src/adapter/repositories/GraphqlProjectRepository.ts +50 -0
- package/src/adapter/repositories/GraphqlProjectRepositoryProjectLocation.test.ts +1 -0
- package/src/adapter/repositories/ProjectIssuesCacheRepository.test.ts +1 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +156 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +91 -6
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +1 -0
- package/src/adapter/repositories/projectFieldDefinition.test.ts +1 -0
- package/src/adapter/repositories/projectFieldDefinition.ts +12 -0
- package/src/domain/entities/Issue.ts +1 -0
- package/src/domain/entities/Project.ts +5 -0
- package/src/domain/entities/RequiredProjectField.ts +6 -0
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +1 -0
- package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +2 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +1 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +2 -0
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +1 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +146 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +110 -64
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +1 -0
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +75 -1
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +50 -7
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +2 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +2 -0
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +2 -0
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +2 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +2 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +1 -0
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +5 -0
- package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +2 -0
- package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +1 -0
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +2 -0
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts +3 -0
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +2 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/projectFieldDefinition.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/entities/Project.d.ts +5 -0
- package/types/domain/entities/Project.d.ts.map +1 -1
- package/types/domain/entities/RequiredProjectField.d.ts +1 -0
- package/types/domain/entities/RequiredProjectField.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -0
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +4 -2
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts +5 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +3 -0
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
|
@@ -72,6 +72,7 @@ describeWhenCredentials('GraphqlProjectRepository', () => {
|
|
|
72
72
|
name: 'Workflow Management',
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
|
+
agent: null,
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
it('should add a new option while preserving all existing options', async () => {
|
|
@@ -151,6 +152,7 @@ describeWhenCredentials('GraphqlProjectRepository', () => {
|
|
|
151
152
|
},
|
|
152
153
|
completionDate50PercentConfidence: null,
|
|
153
154
|
dependedIssueUrlSeparatedByComma: null,
|
|
155
|
+
agent: null,
|
|
154
156
|
|
|
155
157
|
status: {
|
|
156
158
|
fieldId: 'PVTSSF_lAHOAGJHa84AFhgFzgDLt0c',
|
|
@@ -601,6 +601,56 @@ export class GraphqlProjectRepository
|
|
|
601
601
|
);
|
|
602
602
|
return options;
|
|
603
603
|
};
|
|
604
|
+
updateAgentList = async (
|
|
605
|
+
project: Project,
|
|
606
|
+
newAgentList: (Omit<FieldOption, 'id'> & {
|
|
607
|
+
id: FieldOption['id'] | null;
|
|
608
|
+
})[],
|
|
609
|
+
): Promise<FieldOption[]> => {
|
|
610
|
+
if (!project.agent) {
|
|
611
|
+
throw new Error('Project has no agent field');
|
|
612
|
+
}
|
|
613
|
+
const mutation = `mutation UpdateAgentOptions($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
|
|
614
|
+
updateProjectV2Field(input: {
|
|
615
|
+
fieldId: $fieldId
|
|
616
|
+
singleSelectOptions: $options
|
|
617
|
+
}) {
|
|
618
|
+
projectV2Field {
|
|
619
|
+
... on ProjectV2SingleSelectField {
|
|
620
|
+
options {
|
|
621
|
+
id
|
|
622
|
+
name
|
|
623
|
+
color
|
|
624
|
+
description
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}`;
|
|
630
|
+
const variables = {
|
|
631
|
+
fieldId: project.agent.fieldId,
|
|
632
|
+
options: newAgentList.map(({ id, name, color, description }) => ({
|
|
633
|
+
...(id !== null ? { id } : {}),
|
|
634
|
+
name,
|
|
635
|
+
color,
|
|
636
|
+
description,
|
|
637
|
+
})),
|
|
638
|
+
};
|
|
639
|
+
const response = await postGithubGraphqlJson<{
|
|
640
|
+
data: {
|
|
641
|
+
updateProjectV2Field: {
|
|
642
|
+
projectV2Field: {
|
|
643
|
+
options: FieldOption[];
|
|
644
|
+
};
|
|
645
|
+
};
|
|
646
|
+
};
|
|
647
|
+
}>({
|
|
648
|
+
ghToken: this.ghToken,
|
|
649
|
+
query: mutation,
|
|
650
|
+
variables,
|
|
651
|
+
});
|
|
652
|
+
return response.data.updateProjectV2Field.projectV2Field.options;
|
|
653
|
+
};
|
|
604
654
|
updateStatusList = async (
|
|
605
655
|
project: Project,
|
|
606
656
|
newStatusList: (Omit<FieldOption, 'id'> & {
|
|
@@ -186,6 +186,7 @@ describe('GraphqlProjectRepository project location', () => {
|
|
|
186
186
|
remainingEstimationMinutes: null,
|
|
187
187
|
dependedIssueUrlSeparatedByComma: null,
|
|
188
188
|
completionDate50PercentConfidence: null,
|
|
189
|
+
agent: null,
|
|
189
190
|
});
|
|
190
191
|
|
|
191
192
|
expect(mockPostGithubGraphqlJson).not.toHaveBeenCalled();
|
|
@@ -28,6 +28,7 @@ const buildTestProject = (id: string): Project => ({
|
|
|
28
28
|
remainingEstimationMinutes: null,
|
|
29
29
|
dependedIssueUrlSeparatedByComma: null,
|
|
30
30
|
completionDate50PercentConfidence: null,
|
|
31
|
+
agent: null,
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
const buildCachedIssueRecord = (url: string, title: string) => ({
|
|
@@ -151,6 +152,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
151
152
|
closingIssueReferenceUrls: [
|
|
152
153
|
'https://github.com/HiromiShikata/test-repository/issues/7',
|
|
153
154
|
],
|
|
155
|
+
agent: null,
|
|
154
156
|
},
|
|
155
157
|
},
|
|
156
158
|
{
|
|
@@ -207,6 +209,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
207
209
|
createdAt: new Date('2024-01-01T00:00:00Z'),
|
|
208
210
|
author: '',
|
|
209
211
|
closingIssueReferenceUrls: [],
|
|
212
|
+
agent: null,
|
|
210
213
|
},
|
|
211
214
|
},
|
|
212
215
|
];
|
|
@@ -716,6 +719,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
716
719
|
remainingEstimationMinutes: null,
|
|
717
720
|
dependedIssueUrlSeparatedByComma: null,
|
|
718
721
|
completionDate50PercentConfidence: null,
|
|
722
|
+
agent: null,
|
|
719
723
|
};
|
|
720
724
|
const issue = {
|
|
721
725
|
nameWithOwner: 'HiromiShikata/test-repository',
|
|
@@ -742,6 +746,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
742
746
|
createdAt: new Date('2024-01-01'),
|
|
743
747
|
author: '',
|
|
744
748
|
closingIssueReferenceUrls: [],
|
|
749
|
+
agent: null,
|
|
745
750
|
};
|
|
746
751
|
const statusId = 'new-status-id';
|
|
747
752
|
|
|
@@ -1171,6 +1176,157 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
1171
1176
|
});
|
|
1172
1177
|
});
|
|
1173
1178
|
|
|
1179
|
+
describe('mergePullRequest', () => {
|
|
1180
|
+
afterEach(() => {
|
|
1181
|
+
jest.restoreAllMocks();
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it('should PUT to the GitHub merge API with no merge_method when the repository allows merge commits', async () => {
|
|
1185
|
+
const fetchSpy = jest
|
|
1186
|
+
.spyOn(global, 'fetch')
|
|
1187
|
+
.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
1188
|
+
|
|
1189
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1190
|
+
await repository.mergePullRequest(
|
|
1191
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1192
|
+
);
|
|
1193
|
+
|
|
1194
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
1195
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
1196
|
+
'https://api.github.com/repos/HiromiShikata/test-repository/pulls/42/merge',
|
|
1197
|
+
expect.objectContaining({
|
|
1198
|
+
method: 'PUT',
|
|
1199
|
+
body: JSON.stringify({}),
|
|
1200
|
+
}),
|
|
1201
|
+
);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
it('should fall back to squash merge when the repository disallows merge commits (HTTP 405)', async () => {
|
|
1205
|
+
const fetchSpy = jest
|
|
1206
|
+
.spyOn(global, 'fetch')
|
|
1207
|
+
.mockResolvedValueOnce(
|
|
1208
|
+
new Response(
|
|
1209
|
+
JSON.stringify({
|
|
1210
|
+
message: 'Merge commits are not allowed on this repository.',
|
|
1211
|
+
}),
|
|
1212
|
+
{ status: 405, headers: { 'Content-Type': 'application/json' } },
|
|
1213
|
+
),
|
|
1214
|
+
)
|
|
1215
|
+
.mockResolvedValueOnce(
|
|
1216
|
+
new Response(
|
|
1217
|
+
JSON.stringify({
|
|
1218
|
+
allow_squash_merge: true,
|
|
1219
|
+
allow_rebase_merge: true,
|
|
1220
|
+
}),
|
|
1221
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
1222
|
+
),
|
|
1223
|
+
)
|
|
1224
|
+
.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
1225
|
+
|
|
1226
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1227
|
+
await repository.mergePullRequest(
|
|
1228
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
expect(fetchSpy).toHaveBeenCalledTimes(3);
|
|
1232
|
+
expect(fetchSpy).toHaveBeenNthCalledWith(
|
|
1233
|
+
3,
|
|
1234
|
+
'https://api.github.com/repos/HiromiShikata/test-repository/pulls/42/merge',
|
|
1235
|
+
expect.objectContaining({
|
|
1236
|
+
method: 'PUT',
|
|
1237
|
+
body: JSON.stringify({ merge_method: 'squash' }),
|
|
1238
|
+
}),
|
|
1239
|
+
);
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
it('should fall back to rebase merge when squash merge is also disallowed', async () => {
|
|
1243
|
+
const fetchSpy = jest
|
|
1244
|
+
.spyOn(global, 'fetch')
|
|
1245
|
+
.mockResolvedValueOnce(
|
|
1246
|
+
new Response(
|
|
1247
|
+
JSON.stringify({
|
|
1248
|
+
message: 'Merge commits are not allowed on this repository.',
|
|
1249
|
+
}),
|
|
1250
|
+
{ status: 405, headers: { 'Content-Type': 'application/json' } },
|
|
1251
|
+
),
|
|
1252
|
+
)
|
|
1253
|
+
.mockResolvedValueOnce(
|
|
1254
|
+
new Response(
|
|
1255
|
+
JSON.stringify({
|
|
1256
|
+
allow_squash_merge: false,
|
|
1257
|
+
allow_rebase_merge: true,
|
|
1258
|
+
}),
|
|
1259
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
1260
|
+
),
|
|
1261
|
+
)
|
|
1262
|
+
.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
1263
|
+
|
|
1264
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1265
|
+
await repository.mergePullRequest(
|
|
1266
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
expect(fetchSpy).toHaveBeenCalledTimes(3);
|
|
1270
|
+
expect(fetchSpy).toHaveBeenNthCalledWith(
|
|
1271
|
+
3,
|
|
1272
|
+
'https://api.github.com/repos/HiromiShikata/test-repository/pulls/42/merge',
|
|
1273
|
+
expect.objectContaining({
|
|
1274
|
+
method: 'PUT',
|
|
1275
|
+
body: JSON.stringify({ merge_method: 'rebase' }),
|
|
1276
|
+
}),
|
|
1277
|
+
);
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
it('should throw with the original error when no alternative merge method is available after HTTP 405', async () => {
|
|
1281
|
+
jest
|
|
1282
|
+
.spyOn(global, 'fetch')
|
|
1283
|
+
.mockResolvedValueOnce(
|
|
1284
|
+
new Response(
|
|
1285
|
+
JSON.stringify({
|
|
1286
|
+
message: 'Merge commits are not allowed on this repository.',
|
|
1287
|
+
}),
|
|
1288
|
+
{ status: 405, headers: { 'Content-Type': 'application/json' } },
|
|
1289
|
+
),
|
|
1290
|
+
)
|
|
1291
|
+
.mockResolvedValueOnce(
|
|
1292
|
+
new Response(
|
|
1293
|
+
JSON.stringify({
|
|
1294
|
+
allow_squash_merge: false,
|
|
1295
|
+
allow_rebase_merge: false,
|
|
1296
|
+
}),
|
|
1297
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
1298
|
+
),
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1302
|
+
await expect(
|
|
1303
|
+
repository.mergePullRequest(
|
|
1304
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1305
|
+
),
|
|
1306
|
+
).rejects.toThrow(
|
|
1307
|
+
'Failed to merge PR https://github.com/HiromiShikata/test-repository/pull/42: HTTP 405 Merge commits are not allowed on this repository.',
|
|
1308
|
+
);
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
it('should throw when the merge API responds with a non-405 error', async () => {
|
|
1312
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
1313
|
+
new Response(JSON.stringify({ message: 'Not Found' }), {
|
|
1314
|
+
status: 404,
|
|
1315
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1316
|
+
}),
|
|
1317
|
+
);
|
|
1318
|
+
|
|
1319
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1320
|
+
await expect(
|
|
1321
|
+
repository.mergePullRequest(
|
|
1322
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1323
|
+
),
|
|
1324
|
+
).rejects.toThrow(
|
|
1325
|
+
'Failed to merge PR https://github.com/HiromiShikata/test-repository/pull/42: HTTP 404 Not Found',
|
|
1326
|
+
);
|
|
1327
|
+
});
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1174
1330
|
describe('rate-limit-aware retry on console operations', () => {
|
|
1175
1331
|
afterEach(() => {
|
|
1176
1332
|
jest.restoreAllMocks();
|
|
@@ -362,6 +362,12 @@ function isRefContainer(value: unknown): value is { ref: string } {
|
|
|
362
362
|
return isRecord(value) && typeof value.ref === 'string';
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
function isRepoMergeSettings(
|
|
366
|
+
value: unknown,
|
|
367
|
+
): value is { allow_squash_merge?: boolean; allow_rebase_merge?: boolean } {
|
|
368
|
+
return isRecord(value);
|
|
369
|
+
}
|
|
370
|
+
|
|
365
371
|
type IssueOrPullRequestBodyResponse = {
|
|
366
372
|
body: string | null;
|
|
367
373
|
};
|
|
@@ -615,6 +621,10 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
615
621
|
const status = item.customFields.find(
|
|
616
622
|
(field) => normalizeFieldName(field.name) === 'status',
|
|
617
623
|
)?.value;
|
|
624
|
+
const agent =
|
|
625
|
+
item.customFields.find(
|
|
626
|
+
(field) => normalizeFieldName(field.name) === 'agent',
|
|
627
|
+
)?.value ?? null;
|
|
618
628
|
const { owner, repo } = this.extractIssueFromUrl(item.url);
|
|
619
629
|
|
|
620
630
|
return {
|
|
@@ -644,6 +654,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
644
654
|
createdAt: new Date(item.createdAt || '2000-01-01'),
|
|
645
655
|
author: item.author,
|
|
646
656
|
closingIssueReferenceUrls: item.closingIssueReferenceUrls,
|
|
657
|
+
agent,
|
|
647
658
|
};
|
|
648
659
|
};
|
|
649
660
|
private restoreIssuesFromCache = (rawIssues: unknown): Issue[] | null => {
|
|
@@ -915,6 +926,33 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
915
926
|
);
|
|
916
927
|
};
|
|
917
928
|
|
|
929
|
+
setIssueAgentField = async (
|
|
930
|
+
issueUrl: string,
|
|
931
|
+
project: Project,
|
|
932
|
+
agentOptionId: string,
|
|
933
|
+
): Promise<void> => {
|
|
934
|
+
if (!project.agent) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const existingProjectItem =
|
|
938
|
+
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
|
|
939
|
+
issueUrl,
|
|
940
|
+
project.id,
|
|
941
|
+
);
|
|
942
|
+
const projectItemId =
|
|
943
|
+
existingProjectItem?.id ??
|
|
944
|
+
(await this.graphqlProjectItemRepository.addIssueToProject(
|
|
945
|
+
project.id,
|
|
946
|
+
issueUrl,
|
|
947
|
+
));
|
|
948
|
+
await this.graphqlProjectItemRepository.updateProjectField(
|
|
949
|
+
project.id,
|
|
950
|
+
project.agent.fieldId,
|
|
951
|
+
projectItemId,
|
|
952
|
+
{ singleSelectOptionId: agentOptionId },
|
|
953
|
+
);
|
|
954
|
+
};
|
|
955
|
+
|
|
918
956
|
updateNextActionDate = async (
|
|
919
957
|
issueUrl: string,
|
|
920
958
|
project: Project,
|
|
@@ -2247,24 +2285,71 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2247
2285
|
|
|
2248
2286
|
mergePullRequest = async (prUrl: string): Promise<void> => {
|
|
2249
2287
|
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2288
|
+
const mergeUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/merge`;
|
|
2289
|
+
const headers = {
|
|
2290
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
2291
|
+
'Content-Type': 'application/json',
|
|
2292
|
+
Accept: 'application/vnd.github+json',
|
|
2293
|
+
};
|
|
2294
|
+
const mergeWith = (mergeMethod?: 'squash' | 'rebase') =>
|
|
2295
|
+
this.fetchWithRateLimitRetry(() =>
|
|
2296
|
+
fetch(mergeUrl, {
|
|
2297
|
+
method: 'PUT',
|
|
2298
|
+
headers,
|
|
2299
|
+
body: JSON.stringify(
|
|
2300
|
+
mergeMethod ? { merge_method: mergeMethod } : {},
|
|
2301
|
+
),
|
|
2302
|
+
}),
|
|
2303
|
+
);
|
|
2304
|
+
const response = await mergeWith();
|
|
2305
|
+
if (response.ok) {
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
if (response.status === 405) {
|
|
2309
|
+
const fallbackMethod = await this.resolveAllowedMergeMethod(owner, repo);
|
|
2310
|
+
if (fallbackMethod !== null) {
|
|
2311
|
+
const retryResponse = await mergeWith(fallbackMethod);
|
|
2312
|
+
if (retryResponse.ok) {
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
const retryReason =
|
|
2316
|
+
await this.formatGitHubErrorWithStatus(retryResponse);
|
|
2317
|
+
throw new Error(`Failed to merge PR ${prUrl}: ${retryReason}`);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2321
|
+
throw new Error(`Failed to merge PR ${prUrl}: ${reason}`);
|
|
2322
|
+
};
|
|
2323
|
+
|
|
2324
|
+
private resolveAllowedMergeMethod = async (
|
|
2325
|
+
owner: string,
|
|
2326
|
+
repo: string,
|
|
2327
|
+
): Promise<'squash' | 'rebase' | null> => {
|
|
2250
2328
|
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2251
2329
|
fetch(
|
|
2252
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}
|
|
2330
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
2253
2331
|
{
|
|
2254
|
-
method: 'PUT',
|
|
2255
2332
|
headers: {
|
|
2256
2333
|
Authorization: `Bearer ${this.ghToken}`,
|
|
2257
|
-
'Content-Type': 'application/json',
|
|
2258
2334
|
Accept: 'application/vnd.github+json',
|
|
2259
2335
|
},
|
|
2260
|
-
body: JSON.stringify({}),
|
|
2261
2336
|
},
|
|
2262
2337
|
),
|
|
2263
2338
|
);
|
|
2264
2339
|
if (!response.ok) {
|
|
2265
|
-
|
|
2266
|
-
throw new Error(`Failed to merge PR ${prUrl}: ${reason}`);
|
|
2340
|
+
return null;
|
|
2267
2341
|
}
|
|
2342
|
+
const data: unknown = await response.json();
|
|
2343
|
+
if (!isRepoMergeSettings(data)) {
|
|
2344
|
+
return null;
|
|
2345
|
+
}
|
|
2346
|
+
if (data.allow_squash_merge === true) {
|
|
2347
|
+
return 'squash';
|
|
2348
|
+
}
|
|
2349
|
+
if (data.allow_rebase_merge === true) {
|
|
2350
|
+
return 'rebase';
|
|
2351
|
+
}
|
|
2352
|
+
return null;
|
|
2268
2353
|
};
|
|
2269
2354
|
|
|
2270
2355
|
requestChangesWithInlineComment = async (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FieldOption, Project } from '../../domain/entities/Project';
|
|
2
2
|
import {
|
|
3
|
+
AGENT_FIELD_NAME,
|
|
3
4
|
DEPENDED_ISSUE_URL_FIELD_NAME,
|
|
4
5
|
NEXT_ACTION_DATE_FIELD_NAME,
|
|
5
6
|
NEXT_ACTION_HOUR_FIELD_NAME,
|
|
@@ -77,6 +78,10 @@ export const projectFromDefinition = (
|
|
|
77
78
|
const completionDate50PercentConfidence = definition.fields.find((field) =>
|
|
78
79
|
normalizeFieldName(field.name).startsWith('completiondate'),
|
|
79
80
|
);
|
|
81
|
+
const agentField = definition.fields.find(
|
|
82
|
+
(field) =>
|
|
83
|
+
normalizeFieldName(field.name) === normalizeFieldName(AGENT_FIELD_NAME),
|
|
84
|
+
);
|
|
80
85
|
return {
|
|
81
86
|
id: definition.id,
|
|
82
87
|
url: definition.url,
|
|
@@ -127,5 +132,12 @@ export const projectFromDefinition = (
|
|
|
127
132
|
fieldId: completionDate50PercentConfidence.fieldId,
|
|
128
133
|
}
|
|
129
134
|
: null,
|
|
135
|
+
agent: agentField
|
|
136
|
+
? {
|
|
137
|
+
name: agentField.name,
|
|
138
|
+
fieldId: agentField.fieldId,
|
|
139
|
+
options: agentField.options,
|
|
140
|
+
}
|
|
141
|
+
: null,
|
|
130
142
|
};
|
|
131
143
|
};
|
|
@@ -40,6 +40,7 @@ export const NEXT_ACTION_DATE_FIELD_NAME = 'Next Action Date';
|
|
|
40
40
|
export const NEXT_ACTION_HOUR_FIELD_NAME = 'Next Action Hour';
|
|
41
41
|
export const DEPENDED_ISSUE_URL_FIELD_NAME =
|
|
42
42
|
'Depended Issue URL separated by comma';
|
|
43
|
+
export const AGENT_FIELD_NAME = 'Agent';
|
|
43
44
|
|
|
44
45
|
export const REQUIRED_PROJECT_FIELDS: RequiredProjectFieldDefinition[] = [
|
|
45
46
|
{
|
|
@@ -62,4 +63,9 @@ export const REQUIRED_PROJECT_FIELDS: RequiredProjectFieldDefinition[] = [
|
|
|
62
63
|
dataType: 'TEXT',
|
|
63
64
|
options: [],
|
|
64
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: AGENT_FIELD_NAME,
|
|
68
|
+
dataType: 'SINGLE_SELECT',
|
|
69
|
+
options: [],
|
|
70
|
+
},
|
|
65
71
|
];
|
|
@@ -41,6 +41,7 @@ describe('GetStoryObjectMapUseCase', () => {
|
|
|
41
41
|
remainingEstimationMinutes: null,
|
|
42
42
|
dependedIssueUrlSeparatedByComma: null,
|
|
43
43
|
completionDate50PercentConfidence: null,
|
|
44
|
+
agent: null,
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
const createBasicIssue = (overrides: Partial<Issue> = {}): Issue => ({
|
|
@@ -68,6 +69,7 @@ describe('GetStoryObjectMapUseCase', () => {
|
|
|
68
69
|
createdAt: new Date(),
|
|
69
70
|
author: '',
|
|
70
71
|
closingIssueReferenceUrls: [],
|
|
72
|
+
agent: null,
|
|
71
73
|
...overrides,
|
|
72
74
|
});
|
|
73
75
|
|
|
@@ -160,6 +160,7 @@ export class HandleScheduledEventUseCase {
|
|
|
160
160
|
changeTargetPathAliases?: Record<string, string> | null;
|
|
161
161
|
allowedIssueAuthors?: string[] | null;
|
|
162
162
|
autoAssignManagerAuthors?: string[] | null;
|
|
163
|
+
agents?: string[] | null;
|
|
163
164
|
startPreparation?: {
|
|
164
165
|
defaultAgentName: string;
|
|
165
166
|
defaultLlmModelName?: string | null;
|
|
@@ -195,6 +196,7 @@ export class HandleScheduledEventUseCase {
|
|
|
195
196
|
}
|
|
196
197
|
await this.projectRequiredFieldCreateUseCase.run({
|
|
197
198
|
projectUrl: input.projectUrl,
|
|
199
|
+
agents: input.agents ?? null,
|
|
198
200
|
});
|
|
199
201
|
await this.setupTowerDefenceProjectUseCase.run({
|
|
200
202
|
projectUrl: input.projectUrl,
|