github-issue-tower-defence-management 1.137.0 → 1.137.2
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 +14 -0
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -0
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.js +2 -2
- package/bin/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +20 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.js +6 -1
- package/bin/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +1 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +37 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts +4 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +304 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +30 -1
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +131 -8
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.ts +13 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.d.ts +2 -0
- package/types/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.d.ts +3 -1
- package/types/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.d.ts.map +1 -1
|
@@ -3070,6 +3070,310 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
3070
3070
|
});
|
|
3071
3071
|
});
|
|
3072
3072
|
|
|
3073
|
+
describe('findRelatedOpenPRs', () => {
|
|
3074
|
+
afterEach(() => {
|
|
3075
|
+
jest.restoreAllMocks();
|
|
3076
|
+
});
|
|
3077
|
+
|
|
3078
|
+
const buildTimelineResponse = (nodes: unknown[]) => ({
|
|
3079
|
+
data: {
|
|
3080
|
+
repository: {
|
|
3081
|
+
issue: {
|
|
3082
|
+
timelineItems: {
|
|
3083
|
+
pageInfo: { endCursor: null, hasNextPage: false },
|
|
3084
|
+
nodes,
|
|
3085
|
+
},
|
|
3086
|
+
},
|
|
3087
|
+
},
|
|
3088
|
+
},
|
|
3089
|
+
});
|
|
3090
|
+
|
|
3091
|
+
const buildCrossReferencedEventNode = (overrides: {
|
|
3092
|
+
willCloseTarget: boolean;
|
|
3093
|
+
prUrl: string;
|
|
3094
|
+
prState: string;
|
|
3095
|
+
prBody?: string | null;
|
|
3096
|
+
headRefName?: string;
|
|
3097
|
+
baseRefName?: string;
|
|
3098
|
+
}) => ({
|
|
3099
|
+
__typename: 'CrossReferencedEvent',
|
|
3100
|
+
willCloseTarget: overrides.willCloseTarget,
|
|
3101
|
+
source: {
|
|
3102
|
+
__typename: 'PullRequest',
|
|
3103
|
+
url: overrides.prUrl,
|
|
3104
|
+
number: 427,
|
|
3105
|
+
body: overrides.prBody ?? null,
|
|
3106
|
+
state: overrides.prState,
|
|
3107
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
3108
|
+
isDraft: false,
|
|
3109
|
+
mergeable: 'MERGEABLE',
|
|
3110
|
+
headRefName: overrides.headRefName ?? 'feature-branch',
|
|
3111
|
+
baseRefName: overrides.baseRefName ?? 'main',
|
|
3112
|
+
baseRepository: {
|
|
3113
|
+
branchProtectionRules: { nodes: [] },
|
|
3114
|
+
defaultBranchRef: { name: 'main' },
|
|
3115
|
+
rulesets: { nodes: [] },
|
|
3116
|
+
},
|
|
3117
|
+
commits: {
|
|
3118
|
+
nodes: [
|
|
3119
|
+
{
|
|
3120
|
+
commit: {
|
|
3121
|
+
statusCheckRollup: {
|
|
3122
|
+
contexts: { nodes: [] },
|
|
3123
|
+
},
|
|
3124
|
+
},
|
|
3125
|
+
},
|
|
3126
|
+
],
|
|
3127
|
+
},
|
|
3128
|
+
reviewThreads: { nodes: [] },
|
|
3129
|
+
baseRef: { name: 'main' },
|
|
3130
|
+
},
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
it('returns an empty array when no cross-referenced events exist', async () => {
|
|
3134
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
3135
|
+
new Response(JSON.stringify(buildTimelineResponse([])), {
|
|
3136
|
+
status: 200,
|
|
3137
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3138
|
+
}),
|
|
3139
|
+
);
|
|
3140
|
+
|
|
3141
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3142
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3143
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3144
|
+
);
|
|
3145
|
+
|
|
3146
|
+
expect(result).toEqual([]);
|
|
3147
|
+
});
|
|
3148
|
+
|
|
3149
|
+
it('returns the PR when willCloseTarget is true (same-repo PR, standard behavior)', async () => {
|
|
3150
|
+
mockFetchRoutes({
|
|
3151
|
+
timeline: () =>
|
|
3152
|
+
buildTimelineResponse([
|
|
3153
|
+
buildCrossReferencedEventNode({
|
|
3154
|
+
willCloseTarget: true,
|
|
3155
|
+
prUrl: 'https://github.com/HiromiShikata/secretary/pull/100',
|
|
3156
|
+
prState: 'OPEN',
|
|
3157
|
+
}),
|
|
3158
|
+
]),
|
|
3159
|
+
slimPullRequest: () =>
|
|
3160
|
+
buildSlimPullRequestResponse({
|
|
3161
|
+
url: 'https://github.com/HiromiShikata/secretary/pull/100',
|
|
3162
|
+
}),
|
|
3163
|
+
});
|
|
3164
|
+
|
|
3165
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3166
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3167
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3168
|
+
);
|
|
3169
|
+
|
|
3170
|
+
expect(result).toHaveLength(1);
|
|
3171
|
+
expect(result[0].url).toBe(
|
|
3172
|
+
'https://github.com/HiromiShikata/secretary/pull/100',
|
|
3173
|
+
);
|
|
3174
|
+
});
|
|
3175
|
+
|
|
3176
|
+
it('excludes the PR when willCloseTarget is false and the PR body does not contain a closing keyword referencing the issue URL', async () => {
|
|
3177
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
3178
|
+
new Response(
|
|
3179
|
+
JSON.stringify(
|
|
3180
|
+
buildTimelineResponse([
|
|
3181
|
+
buildCrossReferencedEventNode({
|
|
3182
|
+
willCloseTarget: false,
|
|
3183
|
+
prUrl:
|
|
3184
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3185
|
+
prState: 'OPEN',
|
|
3186
|
+
prBody: 'This PR fixes something unrelated.',
|
|
3187
|
+
}),
|
|
3188
|
+
]),
|
|
3189
|
+
),
|
|
3190
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
3191
|
+
),
|
|
3192
|
+
);
|
|
3193
|
+
|
|
3194
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3195
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3196
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3197
|
+
);
|
|
3198
|
+
|
|
3199
|
+
expect(result).toEqual([]);
|
|
3200
|
+
});
|
|
3201
|
+
|
|
3202
|
+
it('includes the cross-repo PR when willCloseTarget is false but the PR body contains "Closes {issueUrl}"', async () => {
|
|
3203
|
+
mockFetchRoutes({
|
|
3204
|
+
timeline: () =>
|
|
3205
|
+
buildTimelineResponse([
|
|
3206
|
+
buildCrossReferencedEventNode({
|
|
3207
|
+
willCloseTarget: false,
|
|
3208
|
+
prUrl:
|
|
3209
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3210
|
+
prState: 'OPEN',
|
|
3211
|
+
prBody:
|
|
3212
|
+
'Closes https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3213
|
+
}),
|
|
3214
|
+
]),
|
|
3215
|
+
slimPullRequest: () =>
|
|
3216
|
+
buildSlimPullRequestResponse({
|
|
3217
|
+
url: 'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3218
|
+
}),
|
|
3219
|
+
});
|
|
3220
|
+
|
|
3221
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3222
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3223
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3224
|
+
);
|
|
3225
|
+
|
|
3226
|
+
expect(result).toHaveLength(1);
|
|
3227
|
+
expect(result[0].url).toBe(
|
|
3228
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3229
|
+
);
|
|
3230
|
+
});
|
|
3231
|
+
|
|
3232
|
+
it('includes the cross-repo PR when willCloseTarget is false but the PR body contains "Fixes {issueUrl}" (case-insensitive)', async () => {
|
|
3233
|
+
mockFetchRoutes({
|
|
3234
|
+
timeline: () =>
|
|
3235
|
+
buildTimelineResponse([
|
|
3236
|
+
buildCrossReferencedEventNode({
|
|
3237
|
+
willCloseTarget: false,
|
|
3238
|
+
prUrl:
|
|
3239
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3240
|
+
prState: 'OPEN',
|
|
3241
|
+
prBody:
|
|
3242
|
+
'FIXES https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3243
|
+
}),
|
|
3244
|
+
]),
|
|
3245
|
+
slimPullRequest: () =>
|
|
3246
|
+
buildSlimPullRequestResponse({
|
|
3247
|
+
url: 'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3248
|
+
}),
|
|
3249
|
+
});
|
|
3250
|
+
|
|
3251
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3252
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3253
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3254
|
+
);
|
|
3255
|
+
|
|
3256
|
+
expect(result).toHaveLength(1);
|
|
3257
|
+
expect(result[0].url).toBe(
|
|
3258
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3259
|
+
);
|
|
3260
|
+
});
|
|
3261
|
+
|
|
3262
|
+
it('includes the cross-repo PR when willCloseTarget is false but the PR body contains "Resolves {issueUrl}"', async () => {
|
|
3263
|
+
mockFetchRoutes({
|
|
3264
|
+
timeline: () =>
|
|
3265
|
+
buildTimelineResponse([
|
|
3266
|
+
buildCrossReferencedEventNode({
|
|
3267
|
+
willCloseTarget: false,
|
|
3268
|
+
prUrl:
|
|
3269
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3270
|
+
prState: 'OPEN',
|
|
3271
|
+
prBody:
|
|
3272
|
+
'Resolves https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3273
|
+
}),
|
|
3274
|
+
]),
|
|
3275
|
+
slimPullRequest: () =>
|
|
3276
|
+
buildSlimPullRequestResponse({
|
|
3277
|
+
url: 'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3278
|
+
}),
|
|
3279
|
+
});
|
|
3280
|
+
|
|
3281
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3282
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3283
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3284
|
+
);
|
|
3285
|
+
|
|
3286
|
+
expect(result).toHaveLength(1);
|
|
3287
|
+
expect(result[0].url).toBe(
|
|
3288
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3289
|
+
);
|
|
3290
|
+
});
|
|
3291
|
+
|
|
3292
|
+
it('excludes the PR when willCloseTarget is false, the body has a closing keyword, but the URL references a different issue', async () => {
|
|
3293
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
3294
|
+
new Response(
|
|
3295
|
+
JSON.stringify(
|
|
3296
|
+
buildTimelineResponse([
|
|
3297
|
+
buildCrossReferencedEventNode({
|
|
3298
|
+
willCloseTarget: false,
|
|
3299
|
+
prUrl:
|
|
3300
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3301
|
+
prState: 'OPEN',
|
|
3302
|
+
prBody:
|
|
3303
|
+
'Closes https://github.com/HiromiShikata/secretary/issues/9999',
|
|
3304
|
+
}),
|
|
3305
|
+
]),
|
|
3306
|
+
),
|
|
3307
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
3308
|
+
),
|
|
3309
|
+
);
|
|
3310
|
+
|
|
3311
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3312
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3313
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3314
|
+
);
|
|
3315
|
+
|
|
3316
|
+
expect(result).toEqual([]);
|
|
3317
|
+
});
|
|
3318
|
+
|
|
3319
|
+
it('excludes the cross-repo PR when it is not in OPEN state even if the body contains a closing keyword', async () => {
|
|
3320
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
3321
|
+
new Response(
|
|
3322
|
+
JSON.stringify(
|
|
3323
|
+
buildTimelineResponse([
|
|
3324
|
+
buildCrossReferencedEventNode({
|
|
3325
|
+
willCloseTarget: false,
|
|
3326
|
+
prUrl:
|
|
3327
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3328
|
+
prState: 'MERGED',
|
|
3329
|
+
prBody:
|
|
3330
|
+
'Closes https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3331
|
+
}),
|
|
3332
|
+
]),
|
|
3333
|
+
),
|
|
3334
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
3335
|
+
),
|
|
3336
|
+
);
|
|
3337
|
+
|
|
3338
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3339
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3340
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3341
|
+
);
|
|
3342
|
+
|
|
3343
|
+
expect(result).toEqual([]);
|
|
3344
|
+
});
|
|
3345
|
+
|
|
3346
|
+
it('includes the cross-repo PR when willCloseTarget is false but the PR body contains "closes" followed by the issue URL with a trailing slash', async () => {
|
|
3347
|
+
mockFetchRoutes({
|
|
3348
|
+
timeline: () =>
|
|
3349
|
+
buildTimelineResponse([
|
|
3350
|
+
buildCrossReferencedEventNode({
|
|
3351
|
+
willCloseTarget: false,
|
|
3352
|
+
prUrl:
|
|
3353
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3354
|
+
prState: 'OPEN',
|
|
3355
|
+
prBody:
|
|
3356
|
+
'closes https://github.com/HiromiShikata/secretary/issues/2380/',
|
|
3357
|
+
}),
|
|
3358
|
+
]),
|
|
3359
|
+
slimPullRequest: () =>
|
|
3360
|
+
buildSlimPullRequestResponse({
|
|
3361
|
+
url: 'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3362
|
+
}),
|
|
3363
|
+
});
|
|
3364
|
+
|
|
3365
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
3366
|
+
const result = await repository.findRelatedOpenPRs(
|
|
3367
|
+
'https://github.com/HiromiShikata/secretary/issues/2380',
|
|
3368
|
+
);
|
|
3369
|
+
|
|
3370
|
+
expect(result).toHaveLength(1);
|
|
3371
|
+
expect(result[0].url).toBe(
|
|
3372
|
+
'https://github.com/HiromiShikata/repositories-management/pull/427',
|
|
3373
|
+
);
|
|
3374
|
+
});
|
|
3375
|
+
});
|
|
3376
|
+
|
|
3073
3377
|
const createApiV3CheerioRestIssueRepository = () => {
|
|
3074
3378
|
const apiV3IssueRepository = mock<ApiV3IssueRepository>();
|
|
3075
3379
|
const restIssueRepository = mock<RestIssueRepository>();
|
|
@@ -52,6 +52,7 @@ type TimelineItem = {
|
|
|
52
52
|
__typename: string;
|
|
53
53
|
url?: string;
|
|
54
54
|
number?: number;
|
|
55
|
+
body?: string | null;
|
|
55
56
|
state?: string;
|
|
56
57
|
createdAt?: string;
|
|
57
58
|
isDraft?: boolean;
|
|
@@ -1014,6 +1015,26 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1014
1015
|
};
|
|
1015
1016
|
};
|
|
1016
1017
|
|
|
1018
|
+
private prBodyContainsCrossRepoClosingKeyword = (
|
|
1019
|
+
prBody: string | null,
|
|
1020
|
+
issueUrl: string,
|
|
1021
|
+
): boolean => {
|
|
1022
|
+
if (!prBody) return false;
|
|
1023
|
+
const closingKeywords =
|
|
1024
|
+
/(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+/gi;
|
|
1025
|
+
const normalizedIssueUrl = issueUrl.replace(/\/+$/, '');
|
|
1026
|
+
let match: RegExpExecArray | null;
|
|
1027
|
+
while ((match = closingKeywords.exec(prBody)) !== null) {
|
|
1028
|
+
const afterKeyword = prBody.slice(match.index + match[0].length);
|
|
1029
|
+
const urlMatch = afterKeyword.match(/^https?:\/\/[^\s]+/);
|
|
1030
|
+
if (!urlMatch) continue;
|
|
1031
|
+
const candidateUrl = urlMatch[0].replace(/\/+$/, '');
|
|
1032
|
+
if (candidateUrl.toLowerCase() === normalizedIssueUrl.toLowerCase())
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
return false;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1017
1038
|
private readonly requiredCheckNamesCache = new Map<
|
|
1018
1039
|
string,
|
|
1019
1040
|
{ fetchedAtMs: number; names: string[] }
|
|
@@ -1432,6 +1453,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1432
1453
|
... on PullRequest {
|
|
1433
1454
|
url
|
|
1434
1455
|
number
|
|
1456
|
+
body
|
|
1435
1457
|
state
|
|
1436
1458
|
createdAt
|
|
1437
1459
|
isDraft
|
|
@@ -1488,7 +1510,14 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1488
1510
|
if (item.__typename !== 'CrossReferencedEvent') continue;
|
|
1489
1511
|
if (!item.source || item.source.__typename !== 'PullRequest') continue;
|
|
1490
1512
|
if (item.source.state !== 'OPEN') continue;
|
|
1491
|
-
if (
|
|
1513
|
+
if (
|
|
1514
|
+
!item.willCloseTarget &&
|
|
1515
|
+
!this.prBodyContainsCrossRepoClosingKeyword(
|
|
1516
|
+
item.source.body ?? null,
|
|
1517
|
+
issueUrl,
|
|
1518
|
+
)
|
|
1519
|
+
)
|
|
1520
|
+
continue;
|
|
1492
1521
|
|
|
1493
1522
|
const pr = item.source;
|
|
1494
1523
|
const prUrl = pr.url || '';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Issue } from '../../entities/Issue';
|
|
2
2
|
import { IN_TMUX_STATUS_NAME } from '../../entities/WorkflowStatus';
|
|
3
|
+
import { IssueRepository } from '../adapter-interfaces/IssueRepository';
|
|
3
4
|
import { TmuxSessionRepository } from '../adapter-interfaces/TmuxSessionRepository';
|
|
4
5
|
import {
|
|
5
6
|
InTmuxByHumanSessionReconcileUseCase,
|
|
@@ -74,6 +75,25 @@ const createFakeTmuxSessionRepository = (state: {
|
|
|
74
75
|
};
|
|
75
76
|
};
|
|
76
77
|
|
|
78
|
+
const createFakeIssueStateRepository = (
|
|
79
|
+
stateByUrl: Record<string, string> = {},
|
|
80
|
+
): Pick<IssueRepository, 'getIssueOrPullRequestState'> & {
|
|
81
|
+
fetchedUrls: string[];
|
|
82
|
+
} => {
|
|
83
|
+
const fetchedUrls: string[] = [];
|
|
84
|
+
return {
|
|
85
|
+
fetchedUrls,
|
|
86
|
+
getIssueOrPullRequestState: async (url: string) => {
|
|
87
|
+
fetchedUrls.push(url);
|
|
88
|
+
return {
|
|
89
|
+
state: stateByUrl[url] ?? 'open',
|
|
90
|
+
merged: false,
|
|
91
|
+
isPullRequest: false,
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
77
97
|
describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
78
98
|
beforeEach(() => {
|
|
79
99
|
issueCounter = 0;
|
|
@@ -85,7 +105,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
85
105
|
liveSessionNames: [],
|
|
86
106
|
processCommandLines: [],
|
|
87
107
|
});
|
|
88
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
108
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
109
|
+
repository,
|
|
110
|
+
createFakeIssueStateRepository(),
|
|
111
|
+
);
|
|
89
112
|
|
|
90
113
|
const result = await useCase.run({
|
|
91
114
|
issues: [issue],
|
|
@@ -112,7 +135,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
112
135
|
`claude --model opus --agent leader --name ${issue.url}`,
|
|
113
136
|
],
|
|
114
137
|
});
|
|
115
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
138
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
139
|
+
repository,
|
|
140
|
+
createFakeIssueStateRepository(),
|
|
141
|
+
);
|
|
116
142
|
|
|
117
143
|
const result = await useCase.run({
|
|
118
144
|
issues: [issue],
|
|
@@ -131,7 +157,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
131
157
|
liveSessionNames: [toTmuxSessionName(issue.url)],
|
|
132
158
|
processCommandLines: ['claude --model opus --name some-other-task'],
|
|
133
159
|
});
|
|
134
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
160
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
161
|
+
repository,
|
|
162
|
+
createFakeIssueStateRepository(),
|
|
163
|
+
);
|
|
135
164
|
|
|
136
165
|
const result = await useCase.run({
|
|
137
166
|
issues: [issue],
|
|
@@ -152,7 +181,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
152
181
|
liveSessionNames: [toTmuxSessionName(liveIssue.url)],
|
|
153
182
|
processCommandLines: [`claude --model opus --name ${liveIssue.url}`],
|
|
154
183
|
});
|
|
155
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
184
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
185
|
+
repository,
|
|
186
|
+
createFakeIssueStateRepository(),
|
|
187
|
+
);
|
|
156
188
|
|
|
157
189
|
const result = await useCase.run({
|
|
158
190
|
issues: [liveIssue, missingIssueOne, missingIssueTwo],
|
|
@@ -179,7 +211,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
179
211
|
liveSessionNames: [],
|
|
180
212
|
processCommandLines: [],
|
|
181
213
|
});
|
|
182
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
214
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
215
|
+
repository,
|
|
216
|
+
createFakeIssueStateRepository(),
|
|
217
|
+
);
|
|
183
218
|
|
|
184
219
|
const result = await useCase.run({
|
|
185
220
|
issues: [otherStatusIssue, closedIssue, otherAssigneeIssue],
|
|
@@ -200,7 +235,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
200
235
|
liveSessionNames: [],
|
|
201
236
|
processCommandLines: [],
|
|
202
237
|
});
|
|
203
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
238
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
239
|
+
repository,
|
|
240
|
+
createFakeIssueStateRepository(),
|
|
241
|
+
);
|
|
204
242
|
|
|
205
243
|
const result = await useCase.run({
|
|
206
244
|
issues: [futureIssue],
|
|
@@ -219,7 +257,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
219
257
|
liveSessionNames: [],
|
|
220
258
|
processCommandLines: [],
|
|
221
259
|
});
|
|
222
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
260
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
261
|
+
repository,
|
|
262
|
+
createFakeIssueStateRepository(),
|
|
263
|
+
);
|
|
223
264
|
|
|
224
265
|
const result = await useCase.run({
|
|
225
266
|
issues: [hourIssue],
|
|
@@ -240,7 +281,10 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
240
281
|
liveSessionNames: [],
|
|
241
282
|
processCommandLines: [],
|
|
242
283
|
});
|
|
243
|
-
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
284
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
285
|
+
repository,
|
|
286
|
+
createFakeIssueStateRepository(),
|
|
287
|
+
);
|
|
244
288
|
|
|
245
289
|
const result = await useCase.run({
|
|
246
290
|
issues: [dueIssue],
|
|
@@ -252,6 +296,85 @@ describe('InTmuxByHumanSessionReconcileUseCase', () => {
|
|
|
252
296
|
expect(result.launchedIssueUrls).toEqual([dueIssue.url]);
|
|
253
297
|
});
|
|
254
298
|
|
|
299
|
+
it('does not launch a session for an issue whose live GitHub state is closed even when the cached issue still reports it as open', async () => {
|
|
300
|
+
const staleCachedIssue = makeIssue({ isClosed: false });
|
|
301
|
+
const repository = createFakeTmuxSessionRepository({
|
|
302
|
+
liveSessionNames: [],
|
|
303
|
+
processCommandLines: [],
|
|
304
|
+
});
|
|
305
|
+
const issueStateRepository = createFakeIssueStateRepository({
|
|
306
|
+
[staleCachedIssue.url]: 'closed',
|
|
307
|
+
});
|
|
308
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
309
|
+
repository,
|
|
310
|
+
issueStateRepository,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const result = await useCase.run({
|
|
314
|
+
issues: [staleCachedIssue],
|
|
315
|
+
assigneeLogin: ASSIGNEE,
|
|
316
|
+
launcherCommand: LAUNCHER,
|
|
317
|
+
now: NOW,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(issueStateRepository.fetchedUrls).toEqual([staleCachedIssue.url]);
|
|
321
|
+
expect(repository.launches).toEqual([]);
|
|
322
|
+
expect(result.launchedIssueUrls).toEqual([]);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('launches only the issues whose live GitHub state is open when a stale cached issue is mixed in', async () => {
|
|
326
|
+
const staleClosedIssue = makeIssue({ isClosed: false });
|
|
327
|
+
const openIssue = makeIssue();
|
|
328
|
+
const repository = createFakeTmuxSessionRepository({
|
|
329
|
+
liveSessionNames: [],
|
|
330
|
+
processCommandLines: [],
|
|
331
|
+
});
|
|
332
|
+
const issueStateRepository = createFakeIssueStateRepository({
|
|
333
|
+
[staleClosedIssue.url]: 'closed',
|
|
334
|
+
[openIssue.url]: 'open',
|
|
335
|
+
});
|
|
336
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
337
|
+
repository,
|
|
338
|
+
issueStateRepository,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const result = await useCase.run({
|
|
342
|
+
issues: [staleClosedIssue, openIssue],
|
|
343
|
+
assigneeLogin: ASSIGNEE,
|
|
344
|
+
launcherCommand: LAUNCHER,
|
|
345
|
+
now: NOW,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
expect(result.launchedIssueUrls).toEqual([openIssue.url]);
|
|
349
|
+
expect(repository.launches.map((launch) => launch.issueUrl)).toEqual([
|
|
350
|
+
openIssue.url,
|
|
351
|
+
]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('does not fetch the live state for an issue that already has a live session', async () => {
|
|
355
|
+
const issue = makeIssue();
|
|
356
|
+
const repository = createFakeTmuxSessionRepository({
|
|
357
|
+
liveSessionNames: [toTmuxSessionName(issue.url)],
|
|
358
|
+
processCommandLines: [
|
|
359
|
+
`claude --model opus --agent leader --name ${issue.url}`,
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
const issueStateRepository = createFakeIssueStateRepository();
|
|
363
|
+
const useCase = new InTmuxByHumanSessionReconcileUseCase(
|
|
364
|
+
repository,
|
|
365
|
+
issueStateRepository,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
await useCase.run({
|
|
369
|
+
issues: [issue],
|
|
370
|
+
assigneeLogin: ASSIGNEE,
|
|
371
|
+
launcherCommand: LAUNCHER,
|
|
372
|
+
now: NOW,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(issueStateRepository.fetchedUrls).toEqual([]);
|
|
376
|
+
});
|
|
377
|
+
|
|
255
378
|
it('transforms an issue url into the same session name tmux derives from the raw url, replacing only "." and ":" and keeping "/"', () => {
|
|
256
379
|
expect(toTmuxSessionName('https://github.com/demo/repo/issues/42')).toBe(
|
|
257
380
|
'https_//github_com/demo/repo/issues/42',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Issue } from '../../entities/Issue';
|
|
2
2
|
import { IN_TMUX_STATUS_NAME } from '../../entities/WorkflowStatus';
|
|
3
|
+
import { IssueRepository } from '../adapter-interfaces/IssueRepository';
|
|
3
4
|
import { TmuxSessionRepository } from '../adapter-interfaces/TmuxSessionRepository';
|
|
4
5
|
|
|
5
6
|
export type InTmuxByHumanSessionReconcileInput = {
|
|
@@ -25,7 +26,13 @@ export const toTmuxSessionName = (issueUrl: string): string =>
|
|
|
25
26
|
issueUrl.replace(/[.:]/g, '_');
|
|
26
27
|
|
|
27
28
|
export class InTmuxByHumanSessionReconcileUseCase {
|
|
28
|
-
constructor(
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly tmuxSessionRepository: TmuxSessionRepository,
|
|
31
|
+
private readonly issueStateRepository: Pick<
|
|
32
|
+
IssueRepository,
|
|
33
|
+
'getIssueOrPullRequestState'
|
|
34
|
+
>,
|
|
35
|
+
) {}
|
|
29
36
|
|
|
30
37
|
run = async (
|
|
31
38
|
input: InTmuxByHumanSessionReconcileInput,
|
|
@@ -52,6 +59,11 @@ export class InTmuxByHumanSessionReconcileUseCase {
|
|
|
52
59
|
) {
|
|
53
60
|
continue;
|
|
54
61
|
}
|
|
62
|
+
const liveState =
|
|
63
|
+
await this.issueStateRepository.getIssueOrPullRequestState(issue.url);
|
|
64
|
+
if (liveState.state.toLowerCase() !== 'open') {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
55
67
|
await this.tmuxSessionRepository.launchDetachedSession(
|
|
56
68
|
toTmuxSessionName(issue.url),
|
|
57
69
|
launcherCommand,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAiD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,EACjB,6BAA4B,MAAM,EAAE,GAAG,IAAW,KACjD,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,IAAI,EAAE,CAAC;KACzB,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAiD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,EACjB,6BAA4B,MAAM,EAAE,GAAG,IAAW,KACjD,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,IAAI,EAAE,CAAC;KACzB,GAAG,IAAI,CAAC,CAwlBP;CACH"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Issue } from '../../../domain/entities/Issue';
|
|
2
|
+
import { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
|
|
2
3
|
import { LocalCommandRunner } from '../../../domain/usecases/adapter-interfaces/LocalCommandRunner';
|
|
3
4
|
export type ReconcileInTmuxByHumanSessionsParams = {
|
|
4
5
|
inTmuxLauncherCommand: string | null;
|
|
5
6
|
assigneeLogin: string;
|
|
6
7
|
issues: Issue[];
|
|
7
8
|
localCommandRunner: LocalCommandRunner;
|
|
9
|
+
issueStateRepository: Pick<IssueRepository, 'getIssueOrPullRequestState'>;
|
|
8
10
|
now: Date;
|
|
9
11
|
};
|
|
10
12
|
export declare const reconcileInTmuxByHumanSessions: (params: ReconcileInTmuxByHumanSessionsParams) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inTmuxByHumanSessionReconciler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAC;AAIpG,MAAM,MAAM,oCAAoC,GAAG;IACjD,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,QAAQ,oCAAoC,KAC3C,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"inTmuxByHumanSessionReconciler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,6DAA6D,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAC;AAIpG,MAAM,MAAM,oCAAoC,GAAG;IACjD,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,EAAE,IAAI,CAAC,eAAe,EAAE,4BAA4B,CAAC,CAAC;IAC1E,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,QAAQ,oCAAoC,KAC3C,OAAO,CAAC,IAAI,CAsBd,CAAC"}
|
|
@@ -81,6 +81,7 @@ export declare class ApiV3CheerioRestIssueRepository extends BaseGitHubRepositor
|
|
|
81
81
|
update: (issue: Issue, _project: Project) => Promise<void>;
|
|
82
82
|
private parseIssueUrl;
|
|
83
83
|
private computePrStatus;
|
|
84
|
+
private prBodyContainsCrossRepoClosingKeyword;
|
|
84
85
|
private readonly requiredCheckNamesCache;
|
|
85
86
|
private getRequiredCheckNames;
|
|
86
87
|
private getCommitCiContexts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiV3CheerioRestIssueRepository.d.ts","sourceRoot":"","sources":["../../../../src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EAEjB,iBAAiB,EACjB,4BAA4B,EAC5B,+BAA+B,EAChC,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAE7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAG/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,+DAA+D,CAAC;AAClG,OAAO,EAAE,cAAc,EAAE,MAAM,4DAA4D,CAAC;AAC5F,OAAO,EACL,KAAK,EAKN,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,4BAA4B,QAAiB,CAAC;AAC3D,eAAO,MAAM,gCAAgC,QAAgB,CAAC;AAC9D,eAAO,MAAM,4BAA4B,QAAiB,CAAC;AAE3D,MAAM,MAAM,mBAAmB,GAAG;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB,CAAC;
|
|
1
|
+
{"version":3,"file":"ApiV3CheerioRestIssueRepository.d.ts","sourceRoot":"","sources":["../../../../src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts"],"names":[],"mappings":"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"}
|