refactor: implement dynamic role and level handling with migration support

This commit is contained in:
Lauren ten Hoor
2026-02-15 18:46:00 +08:00
parent 0e24a68882
commit a85f4fd33e
10 changed files with 278 additions and 227 deletions

View File

@@ -5,7 +5,8 @@
import fs from "node:fs/promises";
import path from "node:path";
import { homedir } from "node:os";
import { LEVEL_ALIASES, ROLE_ALIASES } from "./roles/index.js";
import { migrateProject } from "./migrations.js";
export type WorkerState = {
active: boolean;
issueId: string | null;
@@ -37,38 +38,6 @@ export type ProjectsData = {
projects: Record<string, Project>;
};
function migrateLevel(level: string | null, role: string): string | null {
if (!level) return null;
return LEVEL_ALIASES[role]?.[level] ?? level;
}
function migrateSessions(
sessions: Record<string, string | null>,
role: string,
): Record<string, string | null> {
const aliases = LEVEL_ALIASES[role];
if (!aliases) return sessions;
const migrated: Record<string, string | null> = {};
for (const [key, value] of Object.entries(sessions)) {
const newKey = aliases[key] ?? key;
migrated[newKey] = value;
}
return migrated;
}
function parseWorkerState(worker: Record<string, unknown>, role: string): WorkerState {
const level = (worker.level ?? worker.tier ?? null) as string | null;
const sessions = (worker.sessions as Record<string, string | null>) ?? {};
return {
active: worker.active as boolean,
issueId: worker.issueId as string | null,
startTime: worker.startTime as string | null,
level: migrateLevel(level, role),
sessions: migrateSessions(sessions, role),
};
}
/**
* Create a blank WorkerState with null sessions for given level names.
*/
@@ -105,36 +74,7 @@ export async function readProjects(workspaceDir: string): Promise<ProjectsData>
const data = JSON.parse(raw) as ProjectsData;
for (const project of Object.values(data.projects)) {
// Migrate old format: hardcoded dev/qa/architect fields → workers map
const raw = project as unknown as Record<string, unknown>;
if (!raw.workers && (raw.dev || raw.qa || raw.architect)) {
project.workers = {};
for (const role of ["dev", "qa", "architect"]) {
const canonical = ROLE_ALIASES[role] ?? role;
project.workers[canonical] = raw[role]
? parseWorkerState(raw[role] as Record<string, unknown>, role)
: emptyWorkerState([]);
}
// Clean up old fields from the in-memory object
delete raw.dev;
delete raw.qa;
delete raw.architect;
} else if (raw.workers) {
// New format: parse each worker with role-aware migration
const workers = raw.workers as Record<string, Record<string, unknown>>;
project.workers = {};
for (const [role, worker] of Object.entries(workers)) {
// Migrate old role keys (dev→developer, qa→tester)
const canonical = ROLE_ALIASES[role] ?? role;
project.workers[canonical] = parseWorkerState(worker, role);
}
} else {
project.workers = {};
}
if (!project.channel) {
project.channel = "telegram";
}
migrateProject(project);
}
return data;