feat: enhance workflow and testing infrastructure
- Introduced ExecutionMode type for project execution modes (parallel, sequential). - Updated SetupOpts to use ExecutionMode instead of string literals. - Enhanced workflow states to include a new "In Review" state with appropriate transitions. - Implemented TestHarness for end-to-end testing, including command interception and workspace setup. - Created TestProvider for in-memory issue tracking during tests. - Refactored project registration and setup tools to utilize ExecutionMode. - Updated various tools to ensure compatibility with new workflow and execution modes. - Added new dependencies: cockatiel for resilience and zod for schema validation.
This commit is contained in:
98
lib/services/review.ts
Normal file
98
lib/services/review.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* review.ts — Poll review-type states for PR status changes.
|
||||
*
|
||||
* Scans review states in the workflow and transitions issues
|
||||
* whose PR check condition (merged/approved) is met.
|
||||
* Called by the heartbeat service during its periodic sweep.
|
||||
*/
|
||||
import type { IssueProvider } from "../providers/provider.js";
|
||||
import { PrState } from "../providers/provider.js";
|
||||
import {
|
||||
Action,
|
||||
ReviewCheck,
|
||||
WorkflowEvent,
|
||||
StateType,
|
||||
type WorkflowConfig,
|
||||
type StateConfig,
|
||||
} from "../workflow.js";
|
||||
import { runCommand } from "../run-command.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
|
||||
/**
|
||||
* Scan review-type states and transition issues whose PR check condition is met.
|
||||
* Returns the number of transitions made.
|
||||
*/
|
||||
export async function reviewPass(opts: {
|
||||
workspaceDir: string;
|
||||
groupId: string;
|
||||
workflow: WorkflowConfig;
|
||||
provider: IssueProvider;
|
||||
repoPath: string;
|
||||
gitPullTimeoutMs?: number;
|
||||
}): Promise<number> {
|
||||
const { workspaceDir, groupId, workflow, provider, repoPath, gitPullTimeoutMs = 30_000 } = opts;
|
||||
let transitions = 0;
|
||||
|
||||
// Find all review-type states
|
||||
const reviewStates = Object.entries(workflow.states)
|
||||
.filter(([, s]) => s.type === StateType.REVIEW) as [string, StateConfig][];
|
||||
|
||||
for (const [stateKey, state] of reviewStates) {
|
||||
if (!state.on || !state.check) continue;
|
||||
|
||||
const issues = await provider.listIssuesByLabel(state.label);
|
||||
for (const issue of issues) {
|
||||
const status = await provider.getPrStatus(issue.iid);
|
||||
|
||||
const conditionMet =
|
||||
(state.check === ReviewCheck.PR_MERGED && status.state === PrState.MERGED) ||
|
||||
(state.check === ReviewCheck.PR_APPROVED && (status.state === PrState.APPROVED || status.state === PrState.MERGED));
|
||||
|
||||
if (!conditionMet) continue;
|
||||
|
||||
// Find the success transition (first non-BLOCKED event)
|
||||
const successEvent = Object.keys(state.on).find((e) => e !== WorkflowEvent.BLOCKED);
|
||||
if (!successEvent) continue;
|
||||
|
||||
const transition = state.on[successEvent];
|
||||
const targetKey = typeof transition === "string" ? transition : transition.target;
|
||||
const actions = typeof transition === "object" ? transition.actions : undefined;
|
||||
const targetState = workflow.states[targetKey];
|
||||
if (!targetState) continue;
|
||||
|
||||
// Execute transition actions
|
||||
if (actions) {
|
||||
for (const action of actions) {
|
||||
switch (action) {
|
||||
case Action.GIT_PULL:
|
||||
try { await runCommand(["git", "pull"], { timeoutMs: gitPullTimeoutMs, cwd: repoPath }); } catch { /* best-effort */ }
|
||||
break;
|
||||
case Action.CLOSE_ISSUE:
|
||||
await provider.closeIssue(issue.iid);
|
||||
break;
|
||||
case Action.REOPEN_ISSUE:
|
||||
await provider.reopenIssue(issue.iid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transition label
|
||||
await provider.transitionLabel(issue.iid, state.label, targetState.label);
|
||||
|
||||
await auditLog(workspaceDir, "review_transition", {
|
||||
groupId,
|
||||
issueId: issue.iid,
|
||||
from: state.label,
|
||||
to: targetState.label,
|
||||
check: state.check,
|
||||
prState: status.state,
|
||||
prUrl: status.url,
|
||||
});
|
||||
|
||||
transitions++;
|
||||
}
|
||||
}
|
||||
|
||||
return transitions;
|
||||
}
|
||||
Reference in New Issue
Block a user