feat: enhance review process and role management

- Refactor reviewPass function to identify states with review checks instead of specific review types.
- Introduce review policies (HUMAN, AGENT, AUTO) to control PR review processes based on developer levels.
- Update projectTick to handle review policies and step routing labels for reviewers and testers.
- Add detailed reviewer instructions to templates for clarity on review responsibilities.
- Implement role:level label management, allowing dynamic creation of labels based on project configuration.
- Enhance task_update tool to support state and level updates, ensuring at least one parameter is provided.
- Update work_finish tool to include reviewer actions (approve, reject) in task completion.
- Modify work_start tool to utilize role-level detection for better level assignment.
- Add tests for new functionalities, including review routing and level detection from labels.
This commit is contained in:
Lauren ten Hoor
2026-02-16 18:09:53 +08:00
parent 1464fa82d2
commit d87b9f68a2
25 changed files with 1134 additions and 294 deletions

View File

@@ -19,7 +19,6 @@ export const StateType = {
ACTIVE: "active",
HOLD: "hold",
TERMINAL: "terminal",
REVIEW: "review",
} as const;
export type StateType = (typeof StateType)[keyof typeof StateType];
@@ -30,6 +29,14 @@ export const ExecutionMode = {
} as const;
export type ExecutionMode = (typeof ExecutionMode)[keyof typeof ExecutionMode];
/** Review policy for PR review after developer completion. */
export const ReviewPolicy = {
HUMAN: "human",
AGENT: "agent",
AUTO: "auto",
} as const;
export type ReviewPolicy = (typeof ReviewPolicy)[keyof typeof ReviewPolicy];
/** Role identifier. Built-in: "developer", "tester", "architect". Extensible via config. */
export type Role = string;
/** Action identifier. Built-in actions listed in `Action`; custom actions are also valid strings. */
@@ -63,6 +70,7 @@ export const WorkflowEvent = {
REFINE: "REFINE",
BLOCKED: "BLOCKED",
APPROVE: "APPROVE",
REJECT: "REJECT",
} as const;
export type TransitionTarget = string | {
@@ -84,6 +92,7 @@ export type StateConfig = {
export type WorkflowConfig = {
initial: string;
reviewPolicy?: ReviewPolicy;
states: Record<string, StateConfig>;
};
@@ -99,6 +108,7 @@ export type CompletionRule = {
export const DEFAULT_WORKFLOW: WorkflowConfig = {
initial: "planning",
reviewPolicy: ReviewPolicy.AUTO,
states: {
// ── Main pipeline (happy path) ──────────────────────────────
planning: {
@@ -121,19 +131,31 @@ export const DEFAULT_WORKFLOW: WorkflowConfig = {
label: "Doing",
color: "#f0ad4e",
on: {
[WorkflowEvent.COMPLETE]: { target: "toTest", actions: [Action.GIT_PULL, Action.DETECT_PR] },
[WorkflowEvent.REVIEW]: { target: "reviewing", actions: [Action.DETECT_PR] },
[WorkflowEvent.COMPLETE]: { target: "toReview", actions: [Action.DETECT_PR] },
[WorkflowEvent.BLOCKED]: "refining",
},
},
reviewing: {
type: StateType.REVIEW,
label: "In Review",
color: "#c5def5",
toReview: {
type: StateType.QUEUE,
role: "reviewer",
label: "To Review",
color: "#7057ff",
priority: 2,
check: ReviewCheck.PR_APPROVED,
on: {
[WorkflowEvent.PICKUP]: "reviewing",
[WorkflowEvent.APPROVED]: { target: "toTest", actions: [Action.MERGE_PR, Action.GIT_PULL] },
[WorkflowEvent.MERGE_FAILED]: "toImprove",
},
},
reviewing: {
type: StateType.ACTIVE,
role: "reviewer",
label: "Reviewing",
color: "#c5def5",
on: {
[WorkflowEvent.APPROVE]: { target: "toTest", actions: [Action.MERGE_PR, Action.GIT_PULL] },
[WorkflowEvent.REJECT]: "toImprove",
[WorkflowEvent.BLOCKED]: "refining",
},
},
@@ -240,6 +262,83 @@ export function getLabelColors(workflow: WorkflowConfig): Record<string, string>
return colors;
}
// ---------------------------------------------------------------------------
// Role:level labels — dynamic from config
// ---------------------------------------------------------------------------
/** Step routing label values — per-issue overrides for workflow steps. */
export const StepRouting = {
HUMAN: "human",
AGENT: "agent",
SKIP: "skip",
} as const;
export type StepRoutingValue = (typeof StepRouting)[keyof typeof StepRouting];
/** Known step routing labels (created on the provider during project registration). */
export const STEP_ROUTING_LABELS: readonly string[] = [
"review:human", "review:agent", "review:skip",
"test:skip",
];
/** Step routing label color. */
const STEP_ROUTING_COLOR = "#d93f0b";
/**
* Determine review routing label for an issue based on project policy and developer level.
* Called during developer dispatch to persist the routing decision as a label.
*/
export function resolveReviewRouting(
policy: ReviewPolicy, level: string,
): "review:human" | "review:agent" {
if (policy === ReviewPolicy.HUMAN) return "review:human";
if (policy === ReviewPolicy.AGENT) return "review:agent";
// AUTO: senior → human, else agent
return level === "senior" ? "review:human" : "review:agent";
}
/** Default colors per role for role:level labels. */
const ROLE_LABEL_COLORS: Record<string, string> = {
developer: "#0e8a16",
tester: "#5319e7",
architect: "#0075ca",
reviewer: "#d93f0b",
};
/**
* Generate all role:level label definitions from resolved config roles.
* Returns array of { name, color } for label creation (e.g. "developer:junior").
*/
export function getRoleLabels(
roles: Record<string, { levels: string[]; enabled?: boolean }>,
): Array<{ name: string; color: string }> {
const labels: Array<{ name: string; color: string }> = [];
for (const [roleId, role] of Object.entries(roles)) {
if (role.enabled === false) continue;
for (const level of role.levels) {
labels.push({
name: `${roleId}:${level}`,
color: getRoleLabelColor(roleId),
});
}
}
// Step routing labels (review:human, review:agent, test:skip, etc.)
for (const routingLabel of STEP_ROUTING_LABELS) {
labels.push({ name: routingLabel, color: STEP_ROUTING_COLOR });
}
return labels;
}
/**
* Get the label color for a role. Falls back to gray for unknown roles.
*/
export function getRoleLabelColor(role: string): string {
return ROLE_LABEL_COLORS[role] ?? "#cccccc";
}
// ---------------------------------------------------------------------------
// Queue helpers
// ---------------------------------------------------------------------------
/**
* Get queue labels for a role, ordered by priority (highest first).
*/
@@ -348,7 +447,6 @@ export function findStateKeyByLabel(workflow: WorkflowConfig, label: string): st
*/
function resultToEvent(result: string): string {
if (result === "done") return WorkflowEvent.COMPLETE;
if (result === "review") return WorkflowEvent.REVIEW;
return result.toUpperCase();
}
@@ -405,7 +503,6 @@ export function getNextStateDescription(
if (!targetState) return "";
if (targetState.type === StateType.TERMINAL) return "Done!";
if (targetState.type === StateType.REVIEW) return "awaiting PR review";
if (targetState.type === StateType.HOLD) return "awaiting human decision";
if (targetState.type === StateType.QUEUE && targetState.role) {
return `${targetState.role.toUpperCase()} queue`;
@@ -420,11 +517,12 @@ export function getNextStateDescription(
*/
const RESULT_EMOJI: Record<string, string> = {
done: "✅",
review: "👀",
pass: "🎉",
fail: "❌",
refine: "🤔",
blocked: "🚫",
approve: "✅",
reject: "❌",
};
export function getCompletionEmoji(_role: Role, result: string): string {