mirror of
https://github.com/merlinhu1/codex-game-studio.git
synced 2026-08-25 07:54:34 +02:00
feat: add Open GameStudio Codex port
This commit is contained in:
@@ -12,4 +12,4 @@ Do not load all agents or all templates for a single role task.
|
||||
|
||||
`src/agents.ts` is the single owner for generated project `AGENTS.md`.
|
||||
|
||||
Direct Codex execution, telemetry, planner/next, ownership enforcement, and parallel orchestration are future-only.
|
||||
Direct Codex execution is a first-class path via `open-gamestudio run <agent> --exec`. Telemetry, planner/next, ownership enforcement, and parallel orchestration are future-only.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Open GameStudio is a Node/TypeScript CLI package for creating and managing local, agent-assisted game projects. It provides project scaffolding, engine-aware configuration, base agent prompts, reusable templates, bounded prompt packets, and validation gates that keep generated project artifacts predictable.
|
||||
|
||||
The package is an agent workflow layer for game making. It does not host a service, execute assistants directly, or require a specific model provider. The current CLI prepares the project structure and prompt context that Codex or another agent can use from your own environment.
|
||||
The package is a Codex-first agent workflow layer for game making. It keeps project state local and inspectable while integrating directly with `codex exec` for role-specific work. The CLI prepares bounded prompt packets, invokes Codex when requested, and preserves deterministic validation gates around generated artifacts.
|
||||
|
||||
## Why This Exists
|
||||
|
||||
@@ -11,12 +11,12 @@ Open GameStudio started as a port motivated by a simple need: make the game-stud
|
||||
Claude Game Studio deserves real kudos for proving that role-based game-development workflows can be practical and useful. Open GameStudio is inspired by that idea, but it is an independent implementation with different priorities:
|
||||
|
||||
- CLI and package first, with deterministic npm scripts for local development.
|
||||
- Provider and model agnostic, with no direct Claude, Codex, or assistant lock-in.
|
||||
- Codex-native by default, with direct `codex exec` integration for role-specific work.
|
||||
- Generated game projects live under `projects/<slug>/`.
|
||||
- Engine configs, templates, and base agents are package assets.
|
||||
- Validation is explicit and hard-failing instead of advisory.
|
||||
- Prompt packets are prepared for external agents instead of spawning a provider process.
|
||||
- Direct Codex execution, telemetry, planner/`next`, ownership enforcement, changed-file tracking, and parallel orchestration are future-only.
|
||||
- Prompt packets are optimized for Codex and can be executed directly with `--exec`.
|
||||
- Telemetry, planner/`next`, ownership enforcement, changed-file tracking, and parallel orchestration are future-only.
|
||||
|
||||
The goal is not to clone another tool. The goal is to make the workflow contract inspectable, portable, and easy to run in normal developer tooling.
|
||||
|
||||
@@ -76,14 +76,20 @@ npm run validate
|
||||
npm run validate -- --project projects/my-game
|
||||
```
|
||||
|
||||
Prepare a bounded prompt packet for an agent:
|
||||
Run a project agent through Codex:
|
||||
|
||||
```sh
|
||||
npm run build --silent
|
||||
node dist/cli.js run market_analyst --project projects/my-game --task "Create the initial market overview." --exec
|
||||
```
|
||||
|
||||
Inspect the generated prompt packet without executing Codex:
|
||||
|
||||
```sh
|
||||
node dist/cli.js run market_analyst --project projects/my-game --task "Create the initial market overview." --dry-run
|
||||
```
|
||||
|
||||
The `run` command prepares context and output paths. In this build, you execute Codex or another agent separately and point it at the generated prompt packet.
|
||||
The `run` command writes a bounded prompt packet and, with `--exec`, immediately invokes `codex exec` in the project root. Use `--dry-run` or `--print-prompt` when you want to inspect the exact Codex context first.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
@@ -94,7 +100,7 @@ The `run` command prepares context and output paths. In this build, you execute
|
||||
- `validate`: run repository or project validation and exit nonzero on failure.
|
||||
- `templates list`: list packaged template IDs.
|
||||
- `templates show <template-id>`: print a packaged template.
|
||||
- `run <agent>`: prepare one bounded prompt packet for a project agent.
|
||||
- `run <agent>`: prepare one bounded Codex prompt packet for a project agent; add `--exec` to invoke `codex exec` immediately.
|
||||
|
||||
## Project Layout
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ npm run validate
|
||||
|
||||
Keep generated projects under `projects/<slug>/`.
|
||||
|
||||
The first build intentionally excludes planner commands, direct Codex execution, telemetry, parallel orchestration, changed-file tracking, and ownership enforcement.
|
||||
The first build includes direct Codex execution through `run --exec`. It still intentionally excludes planner commands, telemetry, parallel orchestration, changed-file tracking, and ownership enforcement.
|
||||
|
||||
+4
-4
@@ -8,16 +8,16 @@ npm exec open-gamestudio -- status --project projects/my-game
|
||||
npm exec open-gamestudio -- validate --project projects/my-game
|
||||
```
|
||||
|
||||
Prepare a bounded prompt packet:
|
||||
Run a role agent directly through Codex:
|
||||
|
||||
```bash
|
||||
npm exec open-gamestudio -- run market_analyst --project projects/my-game --task "Create the initial market overview."
|
||||
npm exec open-gamestudio -- run market_analyst --project projects/my-game --task "Create the initial market overview." --exec
|
||||
```
|
||||
|
||||
Manual external Codex command; `open-gamestudio` does not spawn Codex in the first build:
|
||||
Inspect the bounded Codex prompt packet first:
|
||||
|
||||
```bash
|
||||
codex exec --cd projects/my-game "Read .gamestudio/runs/<run-id>/prompt.md and perform the requested task."
|
||||
npm exec open-gamestudio -- run market_analyst --project projects/my-game --task "Create the initial market overview." --dry-run
|
||||
```
|
||||
|
||||
Discover templates:
|
||||
|
||||
@@ -12,4 +12,6 @@ Legacy validation depended on Python and shell assumptions. This port is TypeScr
|
||||
|
||||
Intentional omissions for the first build: no interactive `menu`, no `startover`, no generated `project_orchestrator.md`, no exact `template_info.md`, no eager competitor reports during init, and no upstream license/authorship/citation parity documents.
|
||||
|
||||
Future-only features are not implemented in this build: planner/`next`, telemetry, direct Codex execution, parallel orchestration, changed-file tracking, prompt-size metrics, and hard output-ownership enforcement.
|
||||
Codex-native difference: `run --exec` invokes `codex exec` directly against the generated bounded prompt packet instead of requiring a separate manual command.
|
||||
|
||||
Future-only features are not implemented in this build: planner/`next`, telemetry, parallel orchestration, changed-file tracking, prompt-size metrics, and hard output-ownership enforcement.
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
# Migration From Claude-Oriented Game Studio
|
||||
|
||||
Use the canonical TypeScript CLI:
|
||||
Use the canonical TypeScript CLI with direct Codex execution:
|
||||
|
||||
```bash
|
||||
npm exec open-gamestudio -- init --name "My Game" --engine godot --mode prototype --non-interactive --competitor "Mini Metro"
|
||||
npm exec open-gamestudio -- run market_analyst --project projects/my-game --task "Create the initial market overview."
|
||||
npm exec open-gamestudio -- run market_analyst --project projects/my-game --task "Create the initial market overview." --exec
|
||||
```
|
||||
|
||||
Manual external Codex command; `open-gamestudio` does not spawn Codex in the first build:
|
||||
|
||||
```bash
|
||||
codex exec --cd projects/my-game "Read .gamestudio/runs/<run-id>/prompt.md and perform the requested task."
|
||||
```
|
||||
For inspection-only runs, omit `--exec` or add `--dry-run` to view the generated Codex prompt packet and metadata path before execution.
|
||||
|
||||
Intentional differences: no interactive menu, no `startover`, no exact `template_info.md`, no eager competitor reports during init, and no generated `project_orchestrator.md`.
|
||||
|
||||
Future-only features are not implemented: `open-gamestudio next`, `run --exec`, telemetry, parallel orchestration, changed-file tracking, and ownership enforcement.
|
||||
Future-only features are not implemented: `open-gamestudio next`, telemetry, parallel orchestration, changed-file tracking, and ownership enforcement.
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
Validation exits nonzero when any check fails.
|
||||
|
||||
Repo validation checks package scripts, build output, NodeNext import specifiers, package assets, engine configs, base agents, templates, package packing, and installed-bin asset loading.
|
||||
Repo validation checks package scripts, build output, NodeNext import specifiers, package assets, engine configs, base agents, templates, package packing, installed-bin asset loading, and direct Codex execution exposure.
|
||||
|
||||
Project validation checks schema-valid config, active agents, engine source root, engine project file, materialized agents, project `AGENTS.md` provenance and config hash, market seed, starter GDD, timeline sections, and read-only `status`/`resume` behavior.
|
||||
|
||||
Absence checks:
|
||||
CLI surface checks:
|
||||
|
||||
```bash
|
||||
npm exec open-gamestudio -- run --help | grep -- "--exec"
|
||||
! npm exec open-gamestudio -- --help | grep -E " next|telemetry"
|
||||
! npm exec open-gamestudio -- run --help | grep -- "--exec"
|
||||
```
|
||||
|
||||
No generated `project_orchestrator.md` is required or produced.
|
||||
|
||||
+2
-1
@@ -58,7 +58,8 @@ ${config.team.active_agents.map((agent) => `- ${agent}: .gamestudio/agents/${age
|
||||
# Rules
|
||||
|
||||
Use bounded context. Load the current role prompt, project config, engine overlay, and task-relevant templates only.
|
||||
Do not use direct Codex execution, telemetry, planner/next, parallel orchestration, or ownership enforcement in this first build.
|
||||
Codex execution is first-class: prefer \`open-gamestudio run <agent> --exec\` when you want the toolkit to invoke Codex directly; use \`--dry-run\` or \`--print-prompt\` only for inspection.
|
||||
Do not use telemetry, planner/next, parallel orchestration, or ownership enforcement in this build.
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -4,7 +4,7 @@ import path from "node:path";
|
||||
import { formatTemplateShow, listTemplates, templateRegistry, type TemplateId } from "./templates.js";
|
||||
import { freezeProject, initProject, resumeProject, statusProject } from "./projects.js";
|
||||
import { runValidation } from "./validation.js";
|
||||
import { prepareRun } from "./runner.js";
|
||||
import { executeCodexRun, prepareRun } from "./runner.js";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
@@ -84,12 +84,13 @@ templates
|
||||
|
||||
program
|
||||
.command("run")
|
||||
.description("Prepare one bounded prompt packet for a project agent")
|
||||
.description("Prepare one bounded prompt packet for a project agent, with optional direct Codex execution")
|
||||
.argument("<agent>")
|
||||
.requiredOption("--project <path>", "project path")
|
||||
.requiredOption("--task <text>", "task text")
|
||||
.option("--print-prompt", "print deterministic prompt body")
|
||||
.option("--dry-run", "print selected context and output paths")
|
||||
.option("--exec", "execute the prompt immediately with codex exec")
|
||||
.option("--include-artifact <relative-path>", "include one project artifact", (value, previous: string[] = []) => [...previous, value], [])
|
||||
.option("--allow-broad-context", "explicitly allow broader context discovery")
|
||||
.action((agent, opts) => {
|
||||
@@ -98,10 +99,21 @@ program
|
||||
task: opts.task,
|
||||
printPrompt: opts.printPrompt,
|
||||
dryRun: opts.dryRun,
|
||||
exec: opts.exec,
|
||||
includeArtifact: opts.includeArtifact,
|
||||
allowBroadContext: opts.allowBroadContext
|
||||
});
|
||||
console.log(result.output);
|
||||
if (!opts.exec) return;
|
||||
const execution = executeCodexRun(result);
|
||||
if (execution.stdout) process.stdout.write(execution.stdout);
|
||||
if (execution.stderr) process.stderr.write(execution.stderr);
|
||||
if (execution.error) {
|
||||
console.error(execution.error.message);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (execution.status !== 0) process.exitCode = execution.status ?? 1;
|
||||
});
|
||||
|
||||
program.parseAsync().catch((error: unknown) => {
|
||||
|
||||
+40
-2
@@ -1,3 +1,4 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { agentNameSchema, readProjectConfig, type AgentName } from "./config.js";
|
||||
@@ -11,6 +12,8 @@ export type RunOptions = {
|
||||
task: string;
|
||||
printPrompt?: boolean;
|
||||
dryRun?: boolean;
|
||||
exec?: boolean;
|
||||
codexBin?: string;
|
||||
includeArtifact?: string[];
|
||||
allowBroadContext?: boolean;
|
||||
};
|
||||
@@ -19,15 +22,46 @@ export type PreparedRun = {
|
||||
prompt: string;
|
||||
promptPath: string;
|
||||
metadataPath: string;
|
||||
projectRoot: string;
|
||||
contextFiles: string[];
|
||||
codexCommand: { command: string; args: string[]; display: string };
|
||||
output: string;
|
||||
};
|
||||
|
||||
export type CodexExecutionResult = {
|
||||
status: number | null;
|
||||
signal: NodeJS.Signals | null;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
function requireTask(task: string): string {
|
||||
if (!task || !task.trim()) throw new Error("--task is required and must be non-empty");
|
||||
return task.trim();
|
||||
}
|
||||
|
||||
export function codexExecInvocation(projectRoot: string, promptPath: string, codexBin = "codex"): { command: string; args: string[]; display: string } {
|
||||
const promptRelative = path.relative(projectRoot, promptPath);
|
||||
const taskPrompt = `Read ${promptRelative} and perform the requested task.`;
|
||||
const args = ["exec", "--cd", projectRoot, taskPrompt];
|
||||
return { command: codexBin, args, display: [codexBin, ...args.map((arg) => JSON.stringify(arg))].join(" ") };
|
||||
}
|
||||
|
||||
export function executeCodexRun(run: PreparedRun): CodexExecutionResult {
|
||||
const result = spawnSync(run.codexCommand.command, run.codexCommand.args, {
|
||||
cwd: run.projectRoot,
|
||||
encoding: "utf8"
|
||||
});
|
||||
return {
|
||||
status: result.status,
|
||||
signal: result.signal,
|
||||
stdout: result.stdout ?? "",
|
||||
stderr: result.stderr ?? "",
|
||||
error: result.error
|
||||
};
|
||||
}
|
||||
|
||||
function safeArtifact(projectRoot: string, artifact: string): string {
|
||||
if (path.isAbsolute(artifact)) throw new Error("--include-artifact must be relative");
|
||||
const full = path.resolve(projectRoot, artifact);
|
||||
@@ -41,6 +75,7 @@ function safeArtifact(projectRoot: string, artifact: string): string {
|
||||
let runSequence = 0;
|
||||
|
||||
export function prepareRun(agentInput: string, options: RunOptions, cwd = process.cwd()): PreparedRun {
|
||||
if (options.exec && (options.printPrompt || options.dryRun)) throw new Error("--exec cannot be combined with --print-prompt or --dry-run");
|
||||
const agent = agentNameSchema.parse(agentInput) as AgentName;
|
||||
const task = requireTask(options.task);
|
||||
const projectRoot = resolveProjectRoot(options.project, cwd);
|
||||
@@ -112,10 +147,13 @@ export function prepareRun(agentInput: string, options: RunOptions, cwd = proces
|
||||
2
|
||||
)}\n`
|
||||
);
|
||||
const codexCommand = codexExecInvocation(projectRoot, promptPath, options.codexBin ?? process.env.OPEN_GAMESTUDIO_CODEX_BIN ?? "codex");
|
||||
const output = options.printPrompt
|
||||
? prompt
|
||||
: options.dryRun
|
||||
? `Prompt cache: ${promptPath}\nMetadata: ${metadataPath}\nContext files:\n${contextFiles.map((f) => `- ${f}`).join("\n")}\nValidation: npm run validate -- --project ${path.relative(cwd, projectRoot) || "."}`
|
||||
: `Prompt cache written: ${promptPath}\nNext manual command: codex exec --cd ${projectRoot} "Read ${path.relative(projectRoot, promptPath)} and perform the requested task."`;
|
||||
return { prompt, promptPath, metadataPath, contextFiles, output };
|
||||
: options.exec
|
||||
? `Prompt cache written: ${promptPath}\nExecuting Codex: ${codexCommand.display}`
|
||||
: `Prompt cache written: ${promptPath}\nNext Codex command: ${codexCommand.display}`;
|
||||
return { prompt, promptPath, metadataPath, projectRoot, contextFiles, codexCommand, output };
|
||||
}
|
||||
|
||||
+2
-1
@@ -98,7 +98,8 @@ export async function validateRepo(root = process.cwd()): Promise<ValidationChec
|
||||
}
|
||||
}
|
||||
const help = readFileSync(path.join(root, "src", "cli.ts"), "utf8");
|
||||
for (const forbidden of ["next", "--exec", "telemetry", "parallel orchestration", "ownership enforcement"]) {
|
||||
checks.push(help.includes('.option("--exec"') ? pass("codex.exec", "direct codex exec option exposed") : fail("codex.exec", "run command must expose --exec for direct Codex integration"));
|
||||
for (const forbidden of ["next", "telemetry", "parallel orchestration", "ownership enforcement"]) {
|
||||
checks.push(!help.includes(`command("${forbidden}`) && !help.includes(`option("${forbidden}`) ? pass(`future.absent.${forbidden}`, `${forbidden} not exposed`) : fail(`future.absent.${forbidden}`, `${forbidden} must not be exposed`));
|
||||
}
|
||||
return checks;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { existsSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { chmodSync, existsSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { initProject } from "../src/projects.js";
|
||||
import { prepareRun } from "../src/runner.js";
|
||||
import { codexExecInvocation, executeCodexRun, prepareRun } from "../src/runner.js";
|
||||
|
||||
describe("bounded runner", () => {
|
||||
test("requires non-empty task", () => {
|
||||
@@ -73,4 +73,28 @@ describe("bounded runner", () => {
|
||||
expect(a).toContain("# Template: analytics_setup");
|
||||
expect(a).toContain("documentation/technical/analytics/analytics-plan.md");
|
||||
});
|
||||
|
||||
test("builds a direct codex exec invocation for prepared prompts", () => {
|
||||
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-run-"));
|
||||
const { projectRoot } = initProject({ name: "Codex Exec Game", engine: "godot", mode: "prototype", nonInteractive: true }, cwd);
|
||||
const result = prepareRun("qa_agent", { project: projectRoot, task: "Review validation readiness", exec: true, codexBin: "codex-test" }, cwd);
|
||||
expect(result.output).toContain("Executing Codex:");
|
||||
expect(result.codexCommand.command).toBe("codex-test");
|
||||
expect(result.codexCommand.args.slice(0, 3)).toEqual(["exec", "--cd", projectRoot]);
|
||||
expect(result.codexCommand.args[3]).toMatch(/Read \.gamestudio\/runs\/.+prompt\.md and perform the requested task\./);
|
||||
expect(codexExecInvocation(projectRoot, result.promptPath, "codex-test").display).toContain("codex-test");
|
||||
expect(() => prepareRun("qa_agent", { project: projectRoot, task: "x", exec: true, dryRun: true }, cwd)).toThrow(/--exec cannot be combined/);
|
||||
});
|
||||
|
||||
test("can execute a codex-compatible binary with argument isolation", () => {
|
||||
const cwd = mkdtempSync(path.join(tmpdir(), "ogs-run-"));
|
||||
const { projectRoot } = initProject({ name: "Stub Codex Game", engine: "godot", mode: "prototype", nonInteractive: true }, cwd);
|
||||
const stub = path.join(cwd, "codex-stub.mjs");
|
||||
writeFileSync(stub, "#!/usr/bin/env node\nconsole.log(JSON.stringify(process.argv.slice(2)));\n");
|
||||
chmodSync(stub, 0o755);
|
||||
const result = prepareRun("qa_agent", { project: projectRoot, task: "Review validation readiness", exec: true, codexBin: stub }, cwd);
|
||||
const execution = executeCodexRun(result);
|
||||
expect(execution.status).toBe(0);
|
||||
expect(JSON.parse(execution.stdout)).toEqual(result.codexCommand.args);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user