refactor: Centralize role types and configuration (#190)
Creates a single source of truth for all worker roles via lib/roles/.
## New: lib/roles/
- **registry.ts** — All role definitions (dev, qa, architect) with
levels, models, emoji, completion results, session key patterns
- **types.ts** — RoleConfig interface
- **selectors.ts** — Query helpers: getRole(), getLevelsForRole(),
resolveModel(), isValidResult(), roleForLevel(), etc.
- **index.ts** — Barrel exports
## Migrated Files
- **lib/tiers.ts** — Now delegates to registry (backward compat kept)
- **lib/dispatch.ts** — Uses registry for emoji resolution
- **lib/bootstrap-hook.ts** — Uses registry for session key pattern
- **lib/services/tick.ts** — Uses registry for level detection
- **lib/services/heartbeat.ts** — Uses registry for role iteration
- **lib/tools/health.ts** — Uses registry for role iteration
- **lib/tools/work-start.ts** — Uses registry for role enum
- **lib/tools/work-finish.ts** — Uses registry for result validation
- **lib/tools/project-register.ts** — Uses registry for level lists
## Key Benefits
- Adding a new role = add entry to registry.ts (single file)
- No more scattered role unions ("dev" | "qa" | "architect")
- Type-safe role/level/result validation from registry
- Session key pattern auto-generated from registry
- All 64 tests passing (22 new registry tests + 42 existing)
This commit is contained in:
@@ -18,6 +18,7 @@ import { readProjects, getProject } from "../projects.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { checkWorkerHealth, scanOrphanedLabels, fetchGatewaySessions, type HealthFix } from "../services/health.js";
|
||||
import { requireWorkspaceDir, resolveProvider } from "../tool-helpers.js";
|
||||
import { getAllRoleIds } from "../roles/index.js";
|
||||
|
||||
export function createHealthTool() {
|
||||
return (ctx: ToolContext) => ({
|
||||
@@ -51,13 +52,13 @@ export function createHealthTool() {
|
||||
if (!project) continue;
|
||||
const { provider } = await resolveProvider(project);
|
||||
|
||||
for (const role of ["dev", "qa", "architect"] as const) {
|
||||
for (const role of getAllRoleIds()) {
|
||||
// Worker health check (session liveness, label consistency, etc)
|
||||
const healthFixes = await checkWorkerHealth({
|
||||
workspaceDir,
|
||||
groupId: pid,
|
||||
project,
|
||||
role,
|
||||
role: role as any,
|
||||
sessions,
|
||||
autoFix: fix,
|
||||
provider,
|
||||
@@ -69,7 +70,7 @@ export function createHealthTool() {
|
||||
workspaceDir,
|
||||
groupId: pid,
|
||||
project,
|
||||
role,
|
||||
role: role as any,
|
||||
autoFix: fix,
|
||||
provider,
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import { readProjects, writeProjects, emptyWorkerState } from "../projects.js";
|
||||
import { resolveRepoPath } from "../projects.js";
|
||||
import { createProvider } from "../providers/index.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { DEV_LEVELS, QA_LEVELS, ARCHITECT_LEVELS } from "../tiers.js";
|
||||
import { getAllRoleIds, getLevelsForRole } from "../roles/index.js";
|
||||
import { DEFAULT_DEV_INSTRUCTIONS, DEFAULT_QA_INSTRUCTIONS, DEFAULT_ARCHITECT_INSTRUCTIONS } from "../templates.js";
|
||||
|
||||
/**
|
||||
@@ -162,9 +162,9 @@ export function createProjectRegisterTool() {
|
||||
deployBranch,
|
||||
channel,
|
||||
roleExecution,
|
||||
dev: emptyWorkerState([...DEV_LEVELS]),
|
||||
qa: emptyWorkerState([...QA_LEVELS]),
|
||||
architect: emptyWorkerState([...ARCHITECT_LEVELS]),
|
||||
dev: emptyWorkerState([...getLevelsForRole("dev")]),
|
||||
qa: emptyWorkerState([...getLevelsForRole("qa")]),
|
||||
architect: emptyWorkerState([...getLevelsForRole("architect")]),
|
||||
};
|
||||
|
||||
await writeProjects(workspaceDir, data);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getWorker, resolveRepoPath } from "../projects.js";
|
||||
import { executeCompletion, getRule, NEXT_STATE } from "../services/pipeline.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { requireWorkspaceDir, resolveProject, resolveProvider, getPluginConfig } from "../tool-helpers.js";
|
||||
import { getAllRoleIds, isValidResult, getCompletionResults } from "../roles/index.js";
|
||||
|
||||
export function createWorkFinishTool(api: OpenClawPluginApi) {
|
||||
return (ctx: ToolContext) => ({
|
||||
@@ -21,7 +22,7 @@ export function createWorkFinishTool(api: OpenClawPluginApi) {
|
||||
type: "object",
|
||||
required: ["role", "result", "projectGroupId"],
|
||||
properties: {
|
||||
role: { type: "string", enum: ["dev", "qa", "architect"], description: "Worker role" },
|
||||
role: { type: "string", enum: getAllRoleIds(), description: "Worker role" },
|
||||
result: { type: "string", enum: ["done", "pass", "fail", "refine", "blocked"], description: "Completion result" },
|
||||
projectGroupId: { type: "string", description: "Project group ID" },
|
||||
summary: { type: "string", description: "Brief summary" },
|
||||
@@ -37,13 +38,11 @@ export function createWorkFinishTool(api: OpenClawPluginApi) {
|
||||
const prUrl = params.prUrl as string | undefined;
|
||||
const workspaceDir = requireWorkspaceDir(ctx);
|
||||
|
||||
// Validate role:result
|
||||
if (role === "dev" && result !== "done" && result !== "blocked")
|
||||
throw new Error(`DEV can only complete with "done" or "blocked", got "${result}"`);
|
||||
if (role === "architect" && result !== "done" && result !== "blocked")
|
||||
throw new Error(`ARCHITECT can only complete with "done" or "blocked", got "${result}"`);
|
||||
if (role === "qa" && result === "done")
|
||||
throw new Error(`QA cannot use "done". Use "pass", "fail", "refine", or "blocked".`);
|
||||
// Validate role:result using registry
|
||||
if (!isValidResult(role, result)) {
|
||||
const valid = getCompletionResults(role);
|
||||
throw new Error(`${role.toUpperCase()} cannot complete with "${result}". Valid results: ${valid.join(", ")}`);
|
||||
}
|
||||
if (!getRule(role, result))
|
||||
throw new Error(`Invalid completion: ${role}:${result}`);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getWorker } from "../projects.js";
|
||||
import { dispatchTask } from "../dispatch.js";
|
||||
import { findNextIssue, detectRoleFromLabel, detectLevelFromLabels } from "../services/tick.js";
|
||||
import { isDevLevel } from "../tiers.js";
|
||||
import { getAllRoleIds } from "../roles/index.js";
|
||||
import { requireWorkspaceDir, resolveProject, resolveProvider, getPluginConfig } from "../tool-helpers.js";
|
||||
import { DEFAULT_WORKFLOW, getActiveLabel } from "../workflow.js";
|
||||
|
||||
@@ -28,7 +29,7 @@ export function createWorkStartTool(api: OpenClawPluginApi) {
|
||||
properties: {
|
||||
projectGroupId: { type: "string", description: "Project group ID." },
|
||||
issueId: { type: "number", description: "Issue ID. If omitted, picks next by priority." },
|
||||
role: { type: "string", enum: ["dev", "qa", "architect"], description: "Worker role. Auto-detected from label if omitted." },
|
||||
role: { type: "string", enum: getAllRoleIds(), description: "Worker role. Auto-detected from label if omitted." },
|
||||
level: { type: "string", description: "Developer level (junior/medior/senior/reviewer). Auto-detected if omitted." },
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user