feat(migration): implement workspace layout migration and testing
- Added `migrate-layout.ts` to handle migration from old workspace layouts to the new `devclaw/` structure. - Introduced `migrate-layout.test.ts` for comprehensive tests covering various migration scenarios. - Updated `workspace.ts` to ensure default files are created post-migration, including `workflow.yaml` and role-specific prompts. - Refactored role instruction handling to accommodate new directory structure. - Enhanced project registration to scaffold prompt files in the new `devclaw/projects/<project>/prompts/` directory. - Adjusted setup tool descriptions and logic to reflect changes in file structure. - Updated templates to align with the new workflow configuration and role instructions.
This commit is contained in:
@@ -31,9 +31,9 @@ describe("architect tiers", () => {
|
||||
assert.strictEqual(getDefaultModel("architect", "junior"), "anthropic/claude-sonnet-4-5");
|
||||
});
|
||||
|
||||
it("should resolve architect model from config", () => {
|
||||
const config = { models: { architect: { senior: "custom/model" } } };
|
||||
assert.strictEqual(resolveModel("architect", "senior", config), "custom/model");
|
||||
it("should resolve architect model from resolved role config", () => {
|
||||
const resolvedRole = { models: { senior: "custom/model" }, levels: ["junior", "senior"], defaultLevel: "junior", emoji: {}, completionResults: [] as string[], enabled: true };
|
||||
assert.strictEqual(resolveModel("architect", "senior", resolvedRole), "custom/model");
|
||||
});
|
||||
|
||||
it("should have architect emoji", () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { dispatchTask } from "../dispatch.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { requireWorkspaceDir, resolveProject, resolveProvider, getPluginConfig } from "../tool-helpers.js";
|
||||
import { loadWorkflow, getActiveLabel, getQueueLabels } from "../workflow.js";
|
||||
import { loadConfig } from "../config/index.js";
|
||||
import { selectLevel } from "../model-selector.js";
|
||||
import { resolveModel } from "../roles/index.js";
|
||||
|
||||
@@ -123,7 +124,9 @@ Example:
|
||||
const level = complexity === "complex"
|
||||
? selectLevel(title, "system-wide " + description, role).level
|
||||
: selectLevel(title, description, role).level;
|
||||
const model = resolveModel(role, level, pluginConfig);
|
||||
const resolvedConfig = await loadConfig(workspaceDir, project.name);
|
||||
const resolvedRole = resolvedConfig.roles[role];
|
||||
const model = resolveModel(role, level, resolvedRole);
|
||||
|
||||
if (dryRun) {
|
||||
return jsonResult({
|
||||
|
||||
@@ -26,7 +26,7 @@ export function createOnboardTool(api: OpenClawPluginApi) {
|
||||
const mode = params.mode ? (params.mode as "first-run" | "reconfigure")
|
||||
: configured && hasWorkspace ? "reconfigure" : "first-run";
|
||||
|
||||
const instructions = mode === "first-run" ? buildOnboardToolContext() : buildReconfigContext(api.pluginConfig as Record<string, unknown>);
|
||||
const instructions = mode === "first-run" ? buildOnboardToolContext() : buildReconfigContext();
|
||||
|
||||
return jsonResult({
|
||||
success: true, mode, configured, instructions,
|
||||
|
||||
@@ -16,18 +16,19 @@ import { createProvider } from "../providers/index.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { getAllRoleIds, getLevelsForRole } from "../roles/index.js";
|
||||
import { DEFAULT_ROLE_INSTRUCTIONS } from "../templates.js";
|
||||
import { DATA_DIR } from "../setup/migrate-layout.js";
|
||||
|
||||
/**
|
||||
* Scaffold project-specific prompt files for all registered roles.
|
||||
* Returns true if files were created, false if they already existed.
|
||||
*/
|
||||
async function scaffoldPromptFiles(workspaceDir: string, projectName: string): Promise<boolean> {
|
||||
const projectDir = path.join(workspaceDir, "projects", "roles", projectName);
|
||||
await fs.mkdir(projectDir, { recursive: true });
|
||||
const promptsDir = path.join(workspaceDir, DATA_DIR, "projects", projectName, "prompts");
|
||||
await fs.mkdir(promptsDir, { recursive: true });
|
||||
|
||||
let created = false;
|
||||
for (const role of getAllRoleIds()) {
|
||||
const filePath = path.join(projectDir, `${role}.md`);
|
||||
const filePath = path.join(promptsDir, `${role}.md`);
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch {
|
||||
|
||||
@@ -14,7 +14,7 @@ export function createSetupTool(api: OpenClawPluginApi) {
|
||||
return (ctx: ToolContext) => ({
|
||||
name: "setup",
|
||||
label: "Setup",
|
||||
description: `Execute DevClaw setup. Creates AGENTS.md, HEARTBEAT.md, projects/projects.json, and model level config. Optionally creates a new agent with channel binding. Called after onboard collects configuration.`,
|
||||
description: `Execute DevClaw setup. Creates AGENTS.md, HEARTBEAT.md, devclaw/projects.json, devclaw/prompts/, and model level config. Optionally creates a new agent with channel binding. Called after onboard collects configuration.`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user