Files
codex-game-studio/tests/prompt-surface-validation.test.ts
7e8846a7fb feat: add deterministic model tier routing (#14)
* feat: add deterministic model tier routing

Define Sol, Terra, and Luna policies with fixed fallbacks, explicit surface assignments, runtime precedence, and Luna escalation. Validate generated metadata and expose tier overrides across role, task, and orchestration commands.

* fix: infer model tiers from surface models

* fix: rebalance prompt model routing

* fix: decouple prompt effort routing

---------

Co-authored-by: MerlinH <merlinh221@gmail.com>
2026-07-10 18:49:20 +10:00

67 lines
5.5 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, test } from "node:test";
import { expect } from "expect";
import { validateTemplateSurfaces } from "../src/validation.js";
function makeRoot(): string {
const root = mkdtempSync(path.join(tmpdir(), "ogs-prompt-surface-"));
mkdirSync(path.join(root, ".codex", "agents"), { recursive: true });
mkdirSync(path.join(root, ".codex", "workflows"), { recursive: true });
mkdirSync(path.join(root, ".agents", "skills", "cgs-bugfix"), { recursive: true });
mkdirSync(path.join(root, ".agents", "skills", "cgs-standards-gameplay"), { recursive: true });
writeFileSync(path.join(root, "AGENTS.md"), "# Template\n\n.codex/agents game template guidance.\n");
writeFileSync(path.join(root, ".codex", "agents", "producer.toml"), `name = "producer"\ndescription = "Producer"\nmodel = "gpt-5.6-sol"\nmodel_reasoning_effort = "high"\n# source_reference = ".claude/agents/producer.md"\n# source_hash = "${"a".repeat(64)}"\n# primary_skills = ["cgs-bugfix"]\n# allowed_tool_categories = ["read", "edit", "shell"]\ndeveloper_instructions = """\n## Stop Conditions\n\nStop.\n## Use When\n\nUse.\n## Do Not Use When\n\nDo not.\n## Procedure\n\n1. Do work.\n## Handoff Contract\n\nReport evidence.\n"""\n`);
const skillBody = (name: string, model: string, effort: string, hash: string) => `---\nname: ${name}\ndescription: ${name}\nmodel: ${model}\nmodel_reasoning_effort: ${effort}\nargument-hint: describe target\nprimary-agent: producer\ntool-policy: read/edit/shell\nisolation: repository-root\nsource-reference: local\nsource-hash: ${hash}\n---\n\n# ${name}\n\n## Objective\n\nObjective.\n## Inputs\n\nInputs.\n## Arguments\n\nArgs.\n## Procedure\n\nProcedure.\n## Output Contract\n\nOutput.\n## Quality Gates\n\nQuality.\n## Decision Gates\n\nGates.\n## Handoff\n\nHandoff.\n`;
writeFileSync(path.join(root, ".agents", "skills", "cgs-bugfix", "SKILL.md"), skillBody("cgs-bugfix", "gpt-5.6-terra", "medium", "b".repeat(64)));
writeFileSync(path.join(root, ".agents", "skills", "cgs-standards-gameplay", "SKILL.md"), skillBody("cgs-standards-gameplay", "gpt-5.6-luna", "low", "c".repeat(64)));
writeFileSync(path.join(root, ".codex", "workflows", "bugfix.md"), `---\nmodel: gpt-5.6-terra\nmodel_reasoning_effort: medium\nprimary-agent: producer\nlinked-skills: [cgs-bugfix]\nphase: implement\nrisk: medium\nargument-hint: describe bug\nsource-reference: .codex/workflows/bugfix.md\nsource-hash: ${"d".repeat(64)}\n---\n\n# Bugfix\n\n## Purpose\n\nPurpose.\n## Inputs\n\nInputs.\n## Phase Gates\n\nGates.\n## Required Artifacts\n\nArtifacts.\n## Context Contract\n\nContext.\n## Output Contract\n\nOutput.\n## Stop Conditions\n\nStop.\n## Handoff\n\nHandoff.\n`);
return root;
}
describe("prompt surface validation", () => {
test("fails exact metadata regressions with stable diagnostics", () => {
const root = makeRoot();
writeFileSync(path.join(root, ".codex", "agents", "bad.toml"), `name = "bad"\ndescription = "Bad"\nmodel = "sonnet"\nmodel_reasoning_effort = "high"\n# source_reference = "local"\n# primary_skills = ["missing-skill"]\n# allowed_tool_categories = ["read"]\ndeveloper_instructions = """thin"""\n`);
const failures = validateTemplateSurfaces(root).filter((check) => check.status === "fail").map((check) => check.id);
expect(failures).toEqual(expect.arrayContaining(["prompt_surface.agent.bad.model", "prompt_surface.agent.bad.traceability", "prompt_surface.agent.bad.links", "prompt_surface.agent.bad.depth"]));
});
test("accepts independent model and reasoning-effort combinations but rejects invalid efforts", () => {
const root = makeRoot();
const workflow = path.join(root, ".codex", "workflows", "bugfix.md");
writeFileSync(workflow, readFileSync(workflow, "utf8").replace("model: gpt-5.6-terra", "model: gpt-5.6-sol"));
let failures = validateTemplateSurfaces(root).filter((check) => check.status === "fail").map((check) => check.id);
expect(failures).not.toContain("prompt_surface.workflow.bugfix.model");
writeFileSync(workflow, readFileSync(workflow, "utf8").replace("model_reasoning_effort: medium", "model_reasoning_effort: impossible"));
failures = validateTemplateSurfaces(root).filter((check) => check.status === "fail").map((check) => check.id);
expect(failures).toContain("prompt_surface.workflow.bugfix.model");
});
test("skill validation rejects duplicated generic adapter sections", () => {
const root = makeRoot();
const skill = path.join(root, ".agents", "skills", "cgs-bugfix", "SKILL.md");
writeFileSync(skill, `${readFileSync(skill, "utf8")}\n## Phased Procedure\n\nDuplicated generic boilerplate.\n`);
const failures = validateTemplateSurfaces(root).filter((check) => check.status === "fail").map((check) => check.id);
expect(failures).toContain("prompt_surface.skill.cgs-bugfix.concise");
});
test("optional OpenAI skill UI metadata paths resolve", () => {
for (const skill of ["cgs-prototype", "cgs-vertical-slice", "cgs-bugfix", "cgs-release-checklist", "cgs-help"]) {
const file = path.join(process.cwd(), ".agents", "skills", skill, "agents", "openai.yaml");
expect(existsSync(file)).toBe(true);
const body = readFileSync(file, "utf8");
expect(body).toContain("display_name:");
expect(body).toContain("default_prompt:");
expect(body).toContain("invocation_policy:");
expect(body).toContain("dependencies:");
}
});
});