- Removed the deprecated tiers.ts file and migrated all related functionality to roles/index.js. - Updated tests and tools to reflect the new role structure, replacing references to "dev", "qa", and "architect" with "developer", "tester", and "architect". - Adjusted workflow configurations and state management to accommodate the new role naming conventions. - Enhanced project registration and health check tools to support dynamic role handling. - Updated task creation, update, and completion processes to align with the new role definitions. - Improved documentation and comments to clarify role responsibilities and usage.
255 lines
9.2 KiB
TypeScript
255 lines
9.2 KiB
TypeScript
/**
|
|
* Tests for projects.ts — worker state, migration, and accessors.
|
|
* Run with: npx tsx --test lib/projects.test.ts
|
|
*/
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
import { readProjects, getWorker, emptyWorkerState, writeProjects, type ProjectsData } from "./projects.js";
|
|
|
|
describe("readProjects migration", () => {
|
|
it("should migrate old format (dev/qa/architect fields) to workers map", async () => {
|
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "devclaw-proj-"));
|
|
const projDir = path.join(tmpDir, "projects");
|
|
await fs.mkdir(projDir, { recursive: true });
|
|
|
|
// Old format: hardcoded dev/qa/architect fields
|
|
const oldFormat = {
|
|
projects: {
|
|
"group-1": {
|
|
name: "test-project",
|
|
repo: "~/git/test",
|
|
groupName: "Test",
|
|
deployUrl: "",
|
|
baseBranch: "main",
|
|
deployBranch: "main",
|
|
dev: { active: true, issueId: "42", startTime: null, level: "mid", sessions: { mid: "key-1" } },
|
|
qa: { active: false, issueId: null, startTime: null, level: null, sessions: {} },
|
|
architect: { active: false, issueId: null, startTime: null, level: null, sessions: {} },
|
|
},
|
|
},
|
|
};
|
|
await fs.writeFile(path.join(projDir, "projects.json"), JSON.stringify(oldFormat), "utf-8");
|
|
|
|
const data = await readProjects(tmpDir);
|
|
const project = data.projects["group-1"];
|
|
|
|
// Should have workers map with migrated role keys
|
|
assert.ok(project.workers, "should have workers map");
|
|
assert.ok(project.workers.developer, "should have developer worker (migrated from dev)");
|
|
assert.ok(project.workers.tester, "should have tester worker (migrated from qa)");
|
|
assert.ok(project.workers.architect, "should have architect worker");
|
|
|
|
// Developer worker should be active with migrated level
|
|
assert.strictEqual(project.workers.developer.active, true);
|
|
assert.strictEqual(project.workers.developer.issueId, "42");
|
|
assert.strictEqual(project.workers.developer.level, "medior");
|
|
|
|
// Old fields should not exist on the object
|
|
assert.strictEqual((project as any).dev, undefined);
|
|
assert.strictEqual((project as any).qa, undefined);
|
|
assert.strictEqual((project as any).architect, undefined);
|
|
|
|
await fs.rm(tmpDir, { recursive: true });
|
|
});
|
|
|
|
it("should migrate old level names in old format", async () => {
|
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "devclaw-proj-"));
|
|
const projDir = path.join(tmpDir, "projects");
|
|
await fs.mkdir(projDir, { recursive: true });
|
|
|
|
const oldFormat = {
|
|
projects: {
|
|
"group-1": {
|
|
name: "legacy",
|
|
repo: "~/git/legacy",
|
|
groupName: "Legacy",
|
|
deployUrl: "",
|
|
baseBranch: "main",
|
|
deployBranch: "main",
|
|
dev: { active: false, issueId: null, startTime: null, level: "medior", sessions: { medior: "key-1" } },
|
|
qa: { active: false, issueId: null, startTime: null, level: "reviewer", sessions: { reviewer: "key-2" } },
|
|
architect: { active: false, issueId: null, startTime: null, level: "opus", sessions: { opus: "key-3" } },
|
|
},
|
|
},
|
|
};
|
|
await fs.writeFile(path.join(projDir, "projects.json"), JSON.stringify(oldFormat), "utf-8");
|
|
|
|
const data = await readProjects(tmpDir);
|
|
const project = data.projects["group-1"];
|
|
|
|
// Level names should be migrated (dev→developer, qa→tester, medior→medior, reviewer→medior)
|
|
assert.strictEqual(project.workers.developer.level, "medior");
|
|
assert.strictEqual(project.workers.tester.level, "medior");
|
|
assert.strictEqual(project.workers.architect.level, "senior");
|
|
|
|
// Session keys should be migrated
|
|
assert.strictEqual(project.workers.developer.sessions.medior, "key-1");
|
|
assert.strictEqual(project.workers.tester.sessions.medior, "key-2");
|
|
assert.strictEqual(project.workers.architect.sessions.senior, "key-3");
|
|
|
|
await fs.rm(tmpDir, { recursive: true });
|
|
});
|
|
|
|
it("should read new format (workers map) correctly", async () => {
|
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "devclaw-proj-"));
|
|
const projDir = path.join(tmpDir, "projects");
|
|
await fs.mkdir(projDir, { recursive: true });
|
|
|
|
const newFormat = {
|
|
projects: {
|
|
"group-1": {
|
|
name: "modern",
|
|
repo: "~/git/modern",
|
|
groupName: "Modern",
|
|
deployUrl: "",
|
|
baseBranch: "main",
|
|
deployBranch: "main",
|
|
workers: {
|
|
developer: { active: true, issueId: "10", startTime: null, level: "senior", sessions: { senior: "key-s" } },
|
|
tester: { active: false, issueId: null, startTime: null, level: null, sessions: {} },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
await fs.writeFile(path.join(projDir, "projects.json"), JSON.stringify(newFormat), "utf-8");
|
|
|
|
const data = await readProjects(tmpDir);
|
|
const project = data.projects["group-1"];
|
|
|
|
assert.ok(project.workers.developer);
|
|
assert.strictEqual(project.workers.developer.active, true);
|
|
assert.strictEqual(project.workers.developer.level, "senior");
|
|
assert.ok(project.workers.tester);
|
|
|
|
await fs.rm(tmpDir, { recursive: true });
|
|
});
|
|
|
|
it("should migrate old worker keys in new format", async () => {
|
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "devclaw-proj-"));
|
|
const projDir = path.join(tmpDir, "projects");
|
|
await fs.mkdir(projDir, { recursive: true });
|
|
|
|
// Workers map but with old role keys
|
|
const mixedFormat = {
|
|
projects: {
|
|
"group-1": {
|
|
name: "mixed",
|
|
repo: "~/git/mixed",
|
|
groupName: "Mixed",
|
|
deployUrl: "",
|
|
baseBranch: "main",
|
|
deployBranch: "main",
|
|
workers: {
|
|
dev: { active: true, issueId: "10", startTime: null, level: "mid", sessions: { mid: "key-m" } },
|
|
qa: { active: false, issueId: null, startTime: null, level: null, sessions: {} },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
await fs.writeFile(path.join(projDir, "projects.json"), JSON.stringify(mixedFormat), "utf-8");
|
|
|
|
const data = await readProjects(tmpDir);
|
|
const project = data.projects["group-1"];
|
|
|
|
// Old keys should be migrated
|
|
assert.ok(project.workers.developer, "dev should be migrated to developer");
|
|
assert.ok(project.workers.tester, "qa should be migrated to tester");
|
|
assert.strictEqual(project.workers.developer.level, "medior");
|
|
assert.strictEqual(project.workers.developer.sessions.medior, "key-m");
|
|
|
|
await fs.rm(tmpDir, { recursive: true });
|
|
});
|
|
});
|
|
|
|
describe("getWorker", () => {
|
|
it("should return worker from workers map", async () => {
|
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "devclaw-proj-"));
|
|
const projDir = path.join(tmpDir, "projects");
|
|
await fs.mkdir(projDir, { recursive: true });
|
|
|
|
const data: ProjectsData = {
|
|
projects: {
|
|
"g1": {
|
|
name: "test",
|
|
repo: "~/git/test",
|
|
groupName: "Test",
|
|
deployUrl: "",
|
|
baseBranch: "main",
|
|
deployBranch: "main",
|
|
workers: {
|
|
developer: { active: true, issueId: "5", startTime: null, level: "medior", sessions: {} },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const worker = getWorker(data.projects["g1"], "developer");
|
|
assert.strictEqual(worker.active, true);
|
|
assert.strictEqual(worker.issueId, "5");
|
|
|
|
await fs.rm(tmpDir, { recursive: true });
|
|
});
|
|
|
|
it("should return empty worker for unknown role", async () => {
|
|
const data: ProjectsData = {
|
|
projects: {
|
|
"g1": {
|
|
name: "test",
|
|
repo: "~/git/test",
|
|
groupName: "Test",
|
|
deployUrl: "",
|
|
baseBranch: "main",
|
|
deployBranch: "main",
|
|
workers: {},
|
|
},
|
|
},
|
|
};
|
|
|
|
const worker = getWorker(data.projects["g1"], "nonexistent");
|
|
assert.strictEqual(worker.active, false);
|
|
assert.strictEqual(worker.issueId, null);
|
|
});
|
|
});
|
|
|
|
describe("writeProjects round-trip", () => {
|
|
it("should preserve workers map through write/read cycle", async () => {
|
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "devclaw-proj-"));
|
|
const projDir = path.join(tmpDir, "projects");
|
|
await fs.mkdir(projDir, { recursive: true });
|
|
|
|
const data: ProjectsData = {
|
|
projects: {
|
|
"g1": {
|
|
name: "roundtrip",
|
|
repo: "~/git/rt",
|
|
groupName: "RT",
|
|
deployUrl: "",
|
|
baseBranch: "main",
|
|
deployBranch: "main",
|
|
workers: {
|
|
developer: emptyWorkerState(["junior", "medior", "senior"]),
|
|
tester: emptyWorkerState(["junior", "medior", "senior"]),
|
|
architect: emptyWorkerState(["junior", "senior"]),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
await writeProjects(tmpDir, data);
|
|
const loaded = await readProjects(tmpDir);
|
|
const project = loaded.projects["g1"];
|
|
|
|
assert.ok(project.workers.developer);
|
|
assert.ok(project.workers.tester);
|
|
assert.ok(project.workers.architect);
|
|
assert.strictEqual(project.workers.developer.sessions.junior, null);
|
|
assert.strictEqual(project.workers.developer.sessions.medior, null);
|
|
assert.strictEqual(project.workers.developer.sessions.senior, null);
|
|
|
|
await fs.rm(tmpDir, { recursive: true });
|
|
});
|
|
});
|