2026-06-29 12:47:44 +00:00
|
|
|
import { describe, test } from "node:test";
|
|
|
|
|
import { expect } from "expect";
|
2026-05-28 15:11:30 +00:00
|
|
|
import { isStudioRoleId, rolePackages, studioRoleIds, unknownStudioRoleMessage } from "../src/roles.js";
|
|
|
|
|
|
|
|
|
|
describe("Codex role packages", () => {
|
|
|
|
|
test("defines all canonical StudioRoleId packages", () => {
|
|
|
|
|
expect(studioRoleIds).toEqual([
|
|
|
|
|
"studio-orchestrator",
|
|
|
|
|
"producer",
|
|
|
|
|
"market-analyst",
|
|
|
|
|
"data-scientist",
|
|
|
|
|
"creative-director",
|
|
|
|
|
"senior-game-designer",
|
|
|
|
|
"game-designer",
|
|
|
|
|
"narrative-designer",
|
2026-06-17 20:19:36 +00:00
|
|
|
"writer",
|
|
|
|
|
"world-builder",
|
|
|
|
|
"level-designer",
|
2026-05-28 15:11:30 +00:00
|
|
|
"game-feel-designer",
|
2026-06-17 20:19:36 +00:00
|
|
|
"systems-designer",
|
|
|
|
|
"economy-designer",
|
2026-05-28 15:11:30 +00:00
|
|
|
"gameplay-programmer",
|
2026-06-17 20:19:36 +00:00
|
|
|
"ai-programmer",
|
|
|
|
|
"network-programmer",
|
|
|
|
|
"ui-programmer",
|
2026-05-28 15:11:30 +00:00
|
|
|
"engine-programmer",
|
2026-06-17 18:47:15 +10:00
|
|
|
"godot-specialist",
|
|
|
|
|
"unity-specialist",
|
|
|
|
|
"unreal-specialist",
|
2026-05-28 15:11:30 +00:00
|
|
|
"tools-programmer",
|
2026-06-17 20:19:36 +00:00
|
|
|
"technical-director",
|
|
|
|
|
"devops-engineer",
|
|
|
|
|
"security-engineer",
|
|
|
|
|
"performance-analyst",
|
2026-05-28 15:11:30 +00:00
|
|
|
"senior-game-artist",
|
|
|
|
|
"technical-artist",
|
2026-06-17 20:19:36 +00:00
|
|
|
"audio-director",
|
|
|
|
|
"sound-designer",
|
2026-05-28 15:11:30 +00:00
|
|
|
"ui-ux-designer",
|
2026-06-17 20:19:36 +00:00
|
|
|
"accessibility-specialist",
|
2026-05-28 15:11:30 +00:00
|
|
|
"qa-playtester",
|
2026-06-17 20:19:36 +00:00
|
|
|
"localization-lead",
|
|
|
|
|
"live-ops-designer",
|
|
|
|
|
"community-manager",
|
2026-05-28 15:11:30 +00:00
|
|
|
"release-manager"
|
|
|
|
|
]);
|
|
|
|
|
expect(Object.keys(rolePackages).sort()).toEqual([...studioRoleIds].sort());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("role packages have non-empty Codex contracts", () => {
|
|
|
|
|
for (const role of Object.values(rolePackages)) {
|
|
|
|
|
expect(role.displayName).toMatch(/\S/);
|
|
|
|
|
expect(role.systemPrompt).toMatch(/\S/);
|
|
|
|
|
expect(role.expectedOutputs.length).toBeGreaterThan(0);
|
|
|
|
|
expect(role.handoffTemplate).toMatch(/\S/);
|
|
|
|
|
expect(role.reviewChecklist.length).toBeGreaterThan(0);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-17 20:19:36 +00:00
|
|
|
|
|
|
|
|
test("role packages cover bounded specialist domain clusters", () => {
|
|
|
|
|
const clusters = {
|
|
|
|
|
audio: ["audio-director", "sound-designer"],
|
|
|
|
|
content: ["writer", "world-builder", "level-designer"],
|
|
|
|
|
systems: ["systems-designer", "economy-designer"],
|
|
|
|
|
operations: ["live-ops-designer", "community-manager", "localization-lead", "accessibility-specialist"],
|
|
|
|
|
technical: ["technical-director", "devops-engineer", "security-engineer", "performance-analyst", "network-programmer", "ai-programmer", "ui-programmer"]
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
for (const roles of Object.values(clusters)) {
|
|
|
|
|
for (const role of roles) {
|
|
|
|
|
expect(studioRoleIds).toContain(role);
|
|
|
|
|
expect(rolePackages[role].expectedOutputs.length).toBeGreaterThanOrEqual(2);
|
|
|
|
|
expect(rolePackages[role].reviewChecklist.length).toBeGreaterThanOrEqual(2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(rolePackages["audio-director"].systemPrompt).toContain("audio");
|
|
|
|
|
expect(rolePackages["network-programmer"].systemPrompt).toContain("network");
|
|
|
|
|
expect(rolePackages["accessibility-specialist"].systemPrompt).toContain("accessibility");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test("role packages expose structured compact role contracts", () => {
|
|
|
|
|
for (const role of Object.values(rolePackages)) {
|
|
|
|
|
expect(role.responsibilities.length).toBeGreaterThanOrEqual(2);
|
|
|
|
|
expect(role.inputsToInspect.length).toBeGreaterThanOrEqual(2);
|
|
|
|
|
expect(role.qualityGates.length).toBeGreaterThanOrEqual(2);
|
|
|
|
|
expect(role.stopConditions.length).toBeGreaterThanOrEqual(1);
|
|
|
|
|
expect(role.sharedFragments).toBeDefined();
|
|
|
|
|
expect(new Set(role.sharedFragments).size).toBe(role.sharedFragments.length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(rolePackages["gameplay-programmer"].outputSchema).toEqual(
|
|
|
|
|
expect.arrayContaining(["Changed files", "Implementation notes", "Verification evidence", "Risks or follow-ups"])
|
|
|
|
|
);
|
|
|
|
|
expect(rolePackages["qa-playtester"].outputSchema).toEqual(
|
|
|
|
|
expect.arrayContaining(["Issue ID", "Severity", "Reproduction steps", "Expected result", "Actual result", "Evidence"])
|
|
|
|
|
);
|
|
|
|
|
expect(rolePackages["release-manager"].outputSchema).toEqual(
|
|
|
|
|
expect.arrayContaining(["Release decision", "Blocking issues", "Warnings", "Validation evidence", "Rollback notes"])
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-28 15:11:30 +00:00
|
|
|
test("legacy underscore aliases are rejected with guidance", () => {
|
|
|
|
|
for (const alias of ["producer_agent", "qa_agent", "master_orchestrator"]) {
|
|
|
|
|
expect(isStudioRoleId(alias)).toBe(false);
|
|
|
|
|
expect(unknownStudioRoleMessage(alias)).toContain("Codex-native hyphenated role IDs");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|