mirror of
https://github.com/merlinhu1/codex-game-studio.git
synced 2026-08-25 07:54:34 +02:00
156 lines
5.3 KiB
TypeScript
156 lines
5.3 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { packageAssetPath } from "./paths.js";
|
|
import type { AgentName } from "./config.js";
|
|
|
|
export type TemplateId =
|
|
| "gdd"
|
|
| "feature_spec"
|
|
| "handoff"
|
|
| "analytics_setup"
|
|
| "engine_setup"
|
|
| "market_analysis"
|
|
| "project_config";
|
|
|
|
export type TemplateInfo = {
|
|
id: TemplateId;
|
|
category: string;
|
|
path: string;
|
|
roles: AgentName[];
|
|
tags: string[];
|
|
requiredSections: string[];
|
|
};
|
|
|
|
export const templateRegistry: Record<TemplateId, TemplateInfo> = {
|
|
gdd: {
|
|
id: "gdd",
|
|
category: "design",
|
|
path: "templates/gdd_template.md",
|
|
roles: ["game-designer", "creative-director"],
|
|
tags: ["design", "gdd"],
|
|
requiredSections: ["# Purpose", "# Inputs", "# Outputs", "# Validation"]
|
|
},
|
|
feature_spec: {
|
|
id: "feature_spec",
|
|
category: "design",
|
|
path: "templates/feature_spec_template.md",
|
|
roles: ["senior-game-designer", "game-designer", "gameplay-programmer"],
|
|
tags: ["feature", "spec", "design"],
|
|
requiredSections: ["# Purpose", "# Inputs", "# Outputs", "# Validation"]
|
|
},
|
|
handoff: {
|
|
id: "handoff",
|
|
category: "coordination",
|
|
path: "templates/handoff_template.md",
|
|
roles: ["studio-orchestrator", "creative-director", "producer"],
|
|
tags: ["handoff", "coordination"],
|
|
requiredSections: ["# Purpose", "# Inputs", "# Outputs", "# Validation"]
|
|
},
|
|
analytics_setup: {
|
|
id: "analytics_setup",
|
|
category: "analytics",
|
|
path: "templates/analytics_setup_template.md",
|
|
roles: ["data-scientist", "producer"],
|
|
tags: ["analytics", "metrics"],
|
|
requiredSections: ["# Purpose", "# Inputs", "# Outputs", "# Validation"]
|
|
},
|
|
engine_setup: {
|
|
id: "engine_setup",
|
|
category: "engine",
|
|
path: "templates/engine_setup_template.md",
|
|
roles: ["gameplay-programmer", "engine-programmer", "technical-artist"],
|
|
tags: ["engine", "setup"],
|
|
requiredSections: ["# Purpose", "# Inputs", "# Outputs", "# Validation"]
|
|
},
|
|
market_analysis: {
|
|
id: "market_analysis",
|
|
category: "market",
|
|
path: "templates/market_analysis_template.md",
|
|
roles: ["market-analyst", "producer"],
|
|
tags: ["market", "competitors"],
|
|
requiredSections: ["# Purpose", "# Inputs", "# Outputs", "# Validation"]
|
|
},
|
|
project_config: {
|
|
id: "project_config",
|
|
category: "config",
|
|
path: "templates/project_config_template.json",
|
|
roles: ["producer", "creative-director"],
|
|
tags: ["config", "setup"],
|
|
requiredSections: []
|
|
}
|
|
};
|
|
|
|
export function listTemplates(): TemplateInfo[] {
|
|
return Object.values(templateRegistry);
|
|
}
|
|
|
|
export function readTemplate(id: TemplateId): string {
|
|
const info = templateRegistry[id];
|
|
if (!info) throw new Error(`Unknown template "${id}"`);
|
|
return readFileSync(packageAssetPath(info.path), "utf8");
|
|
}
|
|
|
|
export function formatTemplateShow(id: TemplateId): string {
|
|
const info = templateRegistry[id];
|
|
if (!info) throw new Error(`Unknown template "${id}"`);
|
|
return [
|
|
`ID: ${info.id}`,
|
|
`Category: ${info.category}`,
|
|
`Path: ${info.path}`,
|
|
`Roles: ${info.roles.join(", ")}`,
|
|
`Tags: ${info.tags.join(", ")}`,
|
|
"",
|
|
readTemplate(id)
|
|
].join("\n");
|
|
}
|
|
|
|
export function templatePath(id: TemplateId): string {
|
|
return packageAssetPath(templateRegistry[id].path);
|
|
}
|
|
|
|
function sectionHasContent(body: string, section: string): boolean {
|
|
const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
const match = new RegExp(`^${escaped}\\s*$`, "m").exec(body);
|
|
if (!match) return false;
|
|
const start = match.index + match[0].length;
|
|
const rest = body.slice(start);
|
|
const nextHeading = rest.search(/^#/m);
|
|
const content = nextHeading === -1 ? rest : rest.slice(0, nextHeading);
|
|
return content.trim().length > 0;
|
|
}
|
|
|
|
export function validateTemplateFiles(): string[] {
|
|
const failures: string[] = [];
|
|
for (const info of listTemplates()) {
|
|
const fullPath = path.resolve(packageAssetPath(info.path));
|
|
if (!existsSync(fullPath)) {
|
|
failures.push(`Missing template ${info.id}: ${info.path}`);
|
|
continue;
|
|
}
|
|
const body = readFileSync(fullPath, "utf8");
|
|
for (const section of info.requiredSections) {
|
|
if (!sectionHasContent(body, section)) failures.push(`Template ${info.id} missing non-empty ${section}`);
|
|
}
|
|
if (info.id === "project_config") JSON.parse(body);
|
|
}
|
|
return failures;
|
|
}
|
|
|
|
export function selectTemplates(agent: AgentName, task: string): TemplateId[] {
|
|
const lower = task.toLowerCase();
|
|
const selected = new Set<TemplateId>();
|
|
if (/(handoff|coordination|coordinate)/.test(lower)) selected.add("handoff");
|
|
if ((agent === "producer" || agent === "market-analyst") && /(market|competitor|audience)/.test(lower)) selected.add("market_analysis");
|
|
if ((agent === "producer" || agent === "data-scientist") && /(analytics|metric|telemetry)/.test(lower)) selected.add("analytics_setup");
|
|
if ((agent === "senior-game-designer" || agent === "game-designer" || agent === "gameplay-programmer") && /(design|spec|gdd|feature)/.test(lower)) {
|
|
selected.add("gdd");
|
|
selected.add("feature_spec");
|
|
}
|
|
if (/(engine|setup|project config|initialize|init)/.test(lower)) {
|
|
selected.add("engine_setup");
|
|
selected.add("project_config");
|
|
}
|
|
if (agent === "qa-playtester" && /(spec review|review spec)/.test(lower)) selected.add("feature_spec");
|
|
return [...selected];
|
|
}
|