refactor: standardize level names across all roles (#195 phase 1)
Rename levels to use consistent industry-standard terminology: - dev: medior → mid - qa: reviewer → mid, tester → junior, add senior level - architect: opus → senior, sonnet → junior Add backward-compatible migration for projects.json and openclaw.json config via level aliases in selectors and projects parser. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { HEARTBEAT_DEFAULTS } from "../services/heartbeat.js";
|
||||
|
||||
type ModelConfig = { dev: Record<string, string>; qa: Record<string, string> };
|
||||
type ModelConfig = { dev: Record<string, string>; qa: Record<string, string>; architect: Record<string, string> };
|
||||
|
||||
/**
|
||||
* Write DevClaw model level config to openclaw.json plugins section.
|
||||
|
||||
@@ -11,7 +11,7 @@ import { createAgent, resolveWorkspacePath } from "./agent.js";
|
||||
import { writePluginConfig } from "./config.js";
|
||||
import { scaffoldWorkspace } from "./workspace.js";
|
||||
|
||||
export type ModelConfig = { dev: Record<string, string>; qa: Record<string, string> };
|
||||
export type ModelConfig = { dev: Record<string, string>; qa: Record<string, string>; architect: Record<string, string> };
|
||||
|
||||
export type SetupOpts = {
|
||||
/** OpenClaw plugin API for config access. */
|
||||
@@ -27,7 +27,7 @@ export type SetupOpts = {
|
||||
/** Override workspace path (auto-detected from agent if not given). */
|
||||
workspacePath?: string;
|
||||
/** Model overrides per role.level. Missing levels use defaults. */
|
||||
models?: { dev?: Partial<Record<string, string>>; qa?: Partial<Record<string, string>> };
|
||||
models?: { dev?: Partial<Record<string, string>>; qa?: Partial<Record<string, string>>; architect?: Partial<Record<string, string>> };
|
||||
/** Plugin-level project execution mode: parallel or sequential. Default: parallel. */
|
||||
projectExecution?: "parallel" | "sequential";
|
||||
};
|
||||
@@ -115,6 +115,7 @@ async function tryMigrateBinding(
|
||||
function buildModelConfig(overrides?: SetupOpts["models"]): ModelConfig {
|
||||
const dev: Record<string, string> = { ...DEFAULT_MODELS.dev };
|
||||
const qa: Record<string, string> = { ...DEFAULT_MODELS.qa };
|
||||
const architect: Record<string, string> = { ...DEFAULT_MODELS.architect };
|
||||
|
||||
if (overrides?.dev) {
|
||||
for (const [level, model] of Object.entries(overrides.dev)) {
|
||||
@@ -126,6 +127,11 @@ function buildModelConfig(overrides?: SetupOpts["models"]): ModelConfig {
|
||||
if (model) qa[level] = model;
|
||||
}
|
||||
}
|
||||
if (overrides?.architect) {
|
||||
for (const [level, model] of Object.entries(overrides.architect)) {
|
||||
if (model) architect[level] = model;
|
||||
}
|
||||
}
|
||||
|
||||
return { dev, qa };
|
||||
return { dev, qa, architect };
|
||||
}
|
||||
|
||||
@@ -8,16 +8,17 @@ import { runCommand } from "../run-command.js";
|
||||
export type ModelAssignment = {
|
||||
dev: {
|
||||
junior: string;
|
||||
medior: string;
|
||||
mid: string;
|
||||
senior: string;
|
||||
};
|
||||
qa: {
|
||||
reviewer: string;
|
||||
tester: string;
|
||||
junior: string;
|
||||
mid: string;
|
||||
senior: string;
|
||||
};
|
||||
architect: {
|
||||
opus: string;
|
||||
sonnet: string;
|
||||
junior: string;
|
||||
senior: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -36,9 +37,9 @@ export async function selectModelsWithLLM(
|
||||
if (availableModels.length === 1) {
|
||||
const model = availableModels[0].model;
|
||||
return {
|
||||
dev: { junior: model, medior: model, senior: model },
|
||||
qa: { reviewer: model, tester: model },
|
||||
architect: { opus: model, sonnet: model },
|
||||
dev: { junior: model, mid: model, senior: model },
|
||||
qa: { junior: model, mid: model, senior: model },
|
||||
architect: { junior: model, senior: model },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,18 +51,16 @@ export async function selectModelsWithLLM(
|
||||
Available models:
|
||||
${modelList}
|
||||
|
||||
Assign models to these roles based on capability:
|
||||
All roles use the same level scheme based on task complexity:
|
||||
- **senior** (most capable): Complex architecture, refactoring, critical decisions
|
||||
- **medior** (balanced): Features, bug fixes, code review
|
||||
- **junior** (fast/efficient): Simple fixes, testing, routine tasks
|
||||
- **reviewer** (same as medior): Code review
|
||||
- **tester** (same as junior): Testing
|
||||
- **mid** (balanced): Features, bug fixes, code review, standard tasks
|
||||
- **junior** (fast/efficient): Simple fixes, routine tasks
|
||||
|
||||
Rules:
|
||||
1. Prefer same provider for consistency
|
||||
2. Assign most capable model to senior
|
||||
3. Assign mid-tier model to medior/reviewer
|
||||
4. Assign fastest/cheapest model to junior/tester
|
||||
3. Assign mid-tier model to mid
|
||||
4. Assign fastest/cheapest model to junior
|
||||
5. Consider model version numbers (higher = newer/better)
|
||||
6. Stable versions (no date) > snapshot versions (with date like 20250514)
|
||||
|
||||
@@ -69,16 +68,17 @@ Return ONLY a JSON object in this exact format (no markdown, no explanation):
|
||||
{
|
||||
"dev": {
|
||||
"junior": "provider/model-name",
|
||||
"medior": "provider/model-name",
|
||||
"mid": "provider/model-name",
|
||||
"senior": "provider/model-name"
|
||||
},
|
||||
"qa": {
|
||||
"reviewer": "provider/model-name",
|
||||
"tester": "provider/model-name"
|
||||
"junior": "provider/model-name",
|
||||
"mid": "provider/model-name",
|
||||
"senior": "provider/model-name"
|
||||
},
|
||||
"architect": {
|
||||
"opus": "provider/model-name",
|
||||
"sonnet": "provider/model-name"
|
||||
"junior": "provider/model-name",
|
||||
"senior": "provider/model-name"
|
||||
}
|
||||
}`;
|
||||
|
||||
@@ -131,17 +131,18 @@ Return ONLY a JSON object in this exact format (no markdown, no explanation):
|
||||
// Backfill architect if LLM didn't return it (graceful upgrade)
|
||||
if (!assignment.architect) {
|
||||
assignment.architect = {
|
||||
opus: assignment.dev?.senior ?? availableModels[0].model,
|
||||
sonnet: assignment.dev?.medior ?? availableModels[0].model,
|
||||
senior: assignment.dev?.senior ?? availableModels[0].model,
|
||||
junior: assignment.dev?.mid ?? availableModels[0].model,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
!assignment.dev?.junior ||
|
||||
!assignment.dev?.medior ||
|
||||
!assignment.dev?.mid ||
|
||||
!assignment.dev?.senior ||
|
||||
!assignment.qa?.reviewer ||
|
||||
!assignment.qa?.tester
|
||||
!assignment.qa?.junior ||
|
||||
!assignment.qa?.mid ||
|
||||
!assignment.qa?.senior
|
||||
) {
|
||||
console.error("Invalid assignment structure. Got:", assignment);
|
||||
throw new Error(`Invalid assignment structure from LLM. Missing fields in: ${JSON.stringify(Object.keys(assignment))}`);
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
export type ModelAssignment = {
|
||||
dev: {
|
||||
junior: string;
|
||||
medior: string;
|
||||
mid: string;
|
||||
senior: string;
|
||||
};
|
||||
qa: {
|
||||
reviewer: string;
|
||||
tester: string;
|
||||
junior: string;
|
||||
mid: string;
|
||||
senior: string;
|
||||
};
|
||||
architect: {
|
||||
opus: string;
|
||||
sonnet: string;
|
||||
junior: string;
|
||||
senior: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -43,9 +44,9 @@ export async function assignModels(
|
||||
if (authenticated.length === 1) {
|
||||
const model = authenticated[0].model;
|
||||
return {
|
||||
dev: { junior: model, medior: model, senior: model },
|
||||
qa: { reviewer: model, tester: model },
|
||||
architect: { opus: model, sonnet: model },
|
||||
dev: { junior: model, mid: model, senior: model },
|
||||
qa: { junior: model, mid: model, senior: model },
|
||||
architect: { junior: model, senior: model },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,12 +69,13 @@ export function formatAssignment(assignment: ModelAssignment): string {
|
||||
"| Role | Level | Model |",
|
||||
"|------|----------|--------------------------|",
|
||||
`| DEV | senior | ${assignment.dev.senior.padEnd(24)} |`,
|
||||
`| DEV | medior | ${assignment.dev.medior.padEnd(24)} |`,
|
||||
`| DEV | mid | ${assignment.dev.mid.padEnd(24)} |`,
|
||||
`| DEV | junior | ${assignment.dev.junior.padEnd(24)} |`,
|
||||
`| QA | reviewer | ${assignment.qa.reviewer.padEnd(24)} |`,
|
||||
`| QA | tester | ${assignment.qa.tester.padEnd(24)} |`,
|
||||
`| ARCH | opus | ${assignment.architect.opus.padEnd(24)} |`,
|
||||
`| ARCH | sonnet | ${assignment.architect.sonnet.padEnd(24)} |`,
|
||||
`| QA | senior | ${assignment.qa.senior.padEnd(24)} |`,
|
||||
`| QA | mid | ${assignment.qa.mid.padEnd(24)} |`,
|
||||
`| QA | junior | ${assignment.qa.junior.padEnd(24)} |`,
|
||||
`| ARCH | senior | ${assignment.architect.senior.padEnd(24)} |`,
|
||||
`| ARCH | junior | ${assignment.architect.junior.padEnd(24)} |`,
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user