auto-claude: subtask-1-1 - Create XState v5 PR review state machine and state utils
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,3 +7,12 @@ export {
|
||||
mapStateToLegacy,
|
||||
} from './task-state-utils';
|
||||
export type { TaskStateName } from './task-state-utils';
|
||||
|
||||
export { prReviewMachine } from './pr-review-machine';
|
||||
export type { PRReviewContext, PRReviewEvent } from './pr-review-machine';
|
||||
export {
|
||||
PR_REVIEW_STATE_NAMES,
|
||||
PR_REVIEW_SETTLED_STATES,
|
||||
mapPRReviewStateToLegacy,
|
||||
} from './pr-review-state-utils';
|
||||
export type { PRReviewStateName } from './pr-review-state-utils';
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import { assign, createMachine } from 'xstate';
|
||||
import type { PRReviewProgress, PRReviewResult } from '../../preload/api/modules/github-api';
|
||||
|
||||
export interface PRReviewContext {
|
||||
prNumber: number | null;
|
||||
projectId: string | null;
|
||||
startedAt: string | null;
|
||||
isFollowup: boolean;
|
||||
progress: PRReviewProgress | null;
|
||||
result: PRReviewResult | null;
|
||||
previousResult: PRReviewResult | null;
|
||||
error: string | null;
|
||||
isExternalReview: boolean;
|
||||
}
|
||||
|
||||
export type PRReviewEvent =
|
||||
| { type: 'START_REVIEW'; prNumber: number; projectId: string }
|
||||
| { type: 'START_FOLLOWUP_REVIEW'; prNumber: number; projectId: string; previousResult: PRReviewResult }
|
||||
| { type: 'SET_PROGRESS'; progress: PRReviewProgress }
|
||||
| { type: 'REVIEW_COMPLETE'; result: PRReviewResult }
|
||||
| { type: 'REVIEW_ERROR'; error: string }
|
||||
| { type: 'CANCEL_REVIEW' }
|
||||
| { type: 'DETECT_EXTERNAL_REVIEW' }
|
||||
| { type: 'CLEAR_REVIEW' };
|
||||
|
||||
const initialContext: PRReviewContext = {
|
||||
prNumber: null,
|
||||
projectId: null,
|
||||
startedAt: null,
|
||||
isFollowup: false,
|
||||
progress: null,
|
||||
result: null,
|
||||
previousResult: null,
|
||||
error: null,
|
||||
isExternalReview: false,
|
||||
};
|
||||
|
||||
export const prReviewMachine = createMachine(
|
||||
{
|
||||
id: 'prReview',
|
||||
initial: 'idle',
|
||||
types: {} as {
|
||||
context: PRReviewContext;
|
||||
events: PRReviewEvent;
|
||||
},
|
||||
context: { ...initialContext },
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
START_REVIEW: {
|
||||
target: 'reviewing',
|
||||
actions: 'setReviewStart',
|
||||
},
|
||||
START_FOLLOWUP_REVIEW: {
|
||||
target: 'reviewing',
|
||||
actions: 'setFollowupReviewStart',
|
||||
},
|
||||
},
|
||||
},
|
||||
reviewing: {
|
||||
on: {
|
||||
START_REVIEW: {
|
||||
target: 'reviewing',
|
||||
guard: 'isNotAlreadyReviewing',
|
||||
},
|
||||
SET_PROGRESS: {
|
||||
actions: 'setProgress',
|
||||
},
|
||||
REVIEW_COMPLETE: {
|
||||
target: 'completed',
|
||||
actions: 'setResult',
|
||||
},
|
||||
REVIEW_ERROR: {
|
||||
target: 'error',
|
||||
actions: 'setError',
|
||||
},
|
||||
CANCEL_REVIEW: {
|
||||
target: 'error',
|
||||
actions: 'setCancelledError',
|
||||
},
|
||||
DETECT_EXTERNAL_REVIEW: {
|
||||
target: 'externalReview',
|
||||
actions: 'setExternalReview',
|
||||
},
|
||||
},
|
||||
},
|
||||
externalReview: {
|
||||
on: {
|
||||
REVIEW_COMPLETE: {
|
||||
target: 'completed',
|
||||
actions: 'setResult',
|
||||
},
|
||||
REVIEW_ERROR: {
|
||||
target: 'error',
|
||||
actions: 'setError',
|
||||
},
|
||||
},
|
||||
},
|
||||
completed: {
|
||||
on: {
|
||||
START_REVIEW: {
|
||||
target: 'reviewing',
|
||||
actions: 'setReviewStart',
|
||||
},
|
||||
START_FOLLOWUP_REVIEW: {
|
||||
target: 'reviewing',
|
||||
actions: 'setFollowupReviewStart',
|
||||
},
|
||||
CLEAR_REVIEW: {
|
||||
target: 'idle',
|
||||
actions: 'clearContext',
|
||||
},
|
||||
},
|
||||
},
|
||||
error: {
|
||||
on: {
|
||||
START_REVIEW: {
|
||||
target: 'reviewing',
|
||||
actions: 'setReviewStart',
|
||||
},
|
||||
CLEAR_REVIEW: {
|
||||
target: 'idle',
|
||||
actions: 'clearContext',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
guards: {
|
||||
isNotAlreadyReviewing: () => false,
|
||||
},
|
||||
actions: {
|
||||
setReviewStart: assign({
|
||||
prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
|
||||
projectId: ({ event }) => (event as { projectId: string }).projectId,
|
||||
startedAt: () => new Date().toISOString(),
|
||||
isFollowup: () => false,
|
||||
progress: () => null,
|
||||
result: () => null,
|
||||
previousResult: () => null,
|
||||
error: () => null,
|
||||
isExternalReview: () => false,
|
||||
}),
|
||||
setFollowupReviewStart: assign({
|
||||
prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
|
||||
projectId: ({ event }) => (event as { projectId: string }).projectId,
|
||||
startedAt: () => new Date().toISOString(),
|
||||
isFollowup: () => true,
|
||||
progress: () => null,
|
||||
result: () => null,
|
||||
previousResult: ({ event }) => (event as { previousResult: PRReviewResult }).previousResult,
|
||||
error: () => null,
|
||||
isExternalReview: () => false,
|
||||
}),
|
||||
setProgress: assign({
|
||||
progress: ({ event }) => (event as { progress: PRReviewProgress }).progress,
|
||||
}),
|
||||
setResult: assign({
|
||||
result: ({ event }) => (event as { result: PRReviewResult }).result,
|
||||
progress: () => null,
|
||||
}),
|
||||
setError: assign({
|
||||
error: ({ event }) => (event as { error: string }).error,
|
||||
progress: () => null,
|
||||
}),
|
||||
setCancelledError: assign({
|
||||
error: () => 'Review cancelled by user',
|
||||
progress: () => null,
|
||||
}),
|
||||
setExternalReview: assign({
|
||||
isExternalReview: () => true,
|
||||
}),
|
||||
clearContext: assign(() => ({ ...initialContext })),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Shared XState PR review state utilities.
|
||||
*
|
||||
* Provides type-safe state names, settled states, and legacy status conversion
|
||||
* derived from the PR review machine definition.
|
||||
*/
|
||||
|
||||
/**
|
||||
* All XState PR review state names.
|
||||
*
|
||||
* IMPORTANT: These must match the state keys in pr-review-machine.ts.
|
||||
* If you add/remove a state in the machine, update this array.
|
||||
*/
|
||||
export const PR_REVIEW_STATE_NAMES = [
|
||||
'idle', 'reviewing', 'externalReview', 'completed', 'error'
|
||||
] as const;
|
||||
|
||||
export type PRReviewStateName = typeof PR_REVIEW_STATE_NAMES[number];
|
||||
|
||||
/**
|
||||
* XState states where the PR review has "settled" — the review lifecycle
|
||||
* has reached a terminal or resting state. Progress events should not
|
||||
* overwrite these states.
|
||||
*/
|
||||
export const PR_REVIEW_SETTLED_STATES: ReadonlySet<string> = new Set<PRReviewStateName>([
|
||||
'completed', 'error'
|
||||
]);
|
||||
|
||||
/**
|
||||
* Legacy review status values used by the existing Zustand store.
|
||||
*/
|
||||
type LegacyPRReviewStatus = 'idle' | 'reviewing' | 'completed' | 'error';
|
||||
|
||||
/**
|
||||
* Convert XState PR review state to legacy status for backward compatibility.
|
||||
*/
|
||||
export function mapPRReviewStateToLegacy(state: string): LegacyPRReviewStatus {
|
||||
switch (state) {
|
||||
case 'idle':
|
||||
return 'idle';
|
||||
case 'reviewing':
|
||||
return 'reviewing';
|
||||
case 'externalReview':
|
||||
return 'reviewing';
|
||||
case 'completed':
|
||||
return 'completed';
|
||||
case 'error':
|
||||
return 'error';
|
||||
default:
|
||||
return 'idle';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user