feat: direct issue status from PR review events

This commit is contained in:
Tianyi Cui
2026-08-10 23:13:32 +08:00
parent 4a9d72818f
commit 00708b950b
16 changed files with 313 additions and 57 deletions
+65 -23
View File
@@ -6,6 +6,7 @@ import {
nextResolvingIssueStatus,
parseReferences,
retainIssueReferences,
resolvingIssueStatusCommand,
requiresPullRequestPolicy,
validateBody,
validateIssue,
@@ -243,32 +244,73 @@ test('requires policy only after a human PR enters review', () => {
)
})
test('advances resolving Issues to the live PR phase', () => {
const draft = { isDraft: true, reviewRequestCount: 1, reviewCount: 4 }
const open = { isDraft: false, reviewRequestCount: 0, reviewCount: 0 }
const requestedReview = { isDraft: false, reviewRequestCount: 1, reviewCount: 0 }
const submittedReview = { isDraft: false, reviewRequestCount: 0, reviewCount: 1 }
for (const status of ['Inbox', 'Backlog', 'Ready']) {
assert.equal(nextResolvingIssueStatus(status, draft), 'In progress')
assert.equal(nextResolvingIssueStatus(status, open), 'In progress')
assert.equal(nextResolvingIssueStatus(status, requestedReview), 'In review')
assert.equal(nextResolvingIssueStatus(status, submittedReview), 'In review')
test('maps only explicit review handoffs to review status commands', () => {
assert.equal(
resolvingIssueStatusCommand('pull_request', {
action: 'review_requested',
}),
'review-requested',
)
assert.equal(
resolvingIssueStatusCommand('pull_request_review', {
action: 'submitted',
review: { state: 'changes_requested' },
}),
'changes-requested',
)
for (const state of ['approved', 'commented']) {
assert.equal(
resolvingIssueStatusCommand('pull_request_review', {
action: 'submitted',
review: { state },
}),
null,
)
}
assert.equal(nextResolvingIssueStatus('In progress', requestedReview), 'In review')
assert.equal(nextResolvingIssueStatus('In progress', submittedReview), 'In review')
assert.equal(
resolvingIssueStatusCommand('pull_request_review', {
action: 'dismissed',
review: { state: 'changes_requested' },
}),
null,
)
})
test('never regresses or reopens a resolving Issue', () => {
const implementation = { isDraft: false, reviewRequestCount: 0, reviewCount: 0 }
const review = { isDraft: false, reviewRequestCount: 0, reviewCount: 1 }
test('keeps ordinary pull request events as forward-only implementation signals', () => {
for (const action of ['opened', 'edited', 'synchronize', 'reopened', 'labeled', 'unlabeled']) {
assert.equal(resolvingIssueStatusCommand('pull_request', { action }), 'implementation')
}
assert.equal(
resolvingIssueStatusCommand('pull_request', { action: 'review_request_removed' }),
null,
)
})
assert.equal(nextResolvingIssueStatus('In progress', implementation), null)
assert.equal(nextResolvingIssueStatus('In review', implementation), null)
assert.equal(nextResolvingIssueStatus('In review', review), null)
assert.equal(nextResolvingIssueStatus('Done', review), null)
assert.equal(nextResolvingIssueStatus('No action', review), null)
assert.equal(nextResolvingIssueStatus(null, review), null)
test('toggles automation-owned work on request changes and repeated review request', () => {
for (const status of ['Inbox', 'Backlog', 'Ready']) {
assert.equal(nextResolvingIssueStatus(status, 'implementation'), 'In progress')
assert.equal(nextResolvingIssueStatus(status, 'review-requested'), 'In review')
assert.equal(nextResolvingIssueStatus(status, 'changes-requested'), 'In progress')
}
let status = nextResolvingIssueStatus(
'In review',
'changes-requested',
'dsh-issue-management',
)
assert.equal(status, 'In progress')
status = nextResolvingIssueStatus(status, 'review-requested')
assert.equal(status, 'In review')
})
test('preserves human review status and terminal Issues', () => {
assert.equal(nextResolvingIssueStatus('In progress', 'implementation'), null)
assert.equal(nextResolvingIssueStatus('In review', 'implementation'), null)
assert.equal(nextResolvingIssueStatus('In review', 'review-requested'), null)
assert.equal(nextResolvingIssueStatus('In review', 'changes-requested', 'tianyicui'), null)
assert.equal(nextResolvingIssueStatus('In review', 'changes-requested'), null)
assert.equal(nextResolvingIssueStatus('Done', 'review-requested'), null)
assert.equal(nextResolvingIssueStatus('No action', 'changes-requested'), null)
assert.equal(nextResolvingIssueStatus(null, 'review-requested'), null)
})
test('keeps lifecycle projection independent of PR metadata enforcement', () => {
@@ -283,7 +325,7 @@ test('keeps lifecycle projection independent of PR metadata enforcement', () =>
}
assert.ok(validatePullRequest(pull).length > 0)
assert.equal(nextResolvingIssueStatus('Inbox', pull), 'In review')
assert.equal(nextResolvingIssueStatus('Inbox', 'review-requested'), 'In review')
})
test('exempts Draft, Bot, and App PRs', () => {