diff --git a/AGENTS.md b/AGENTS.md index ec2f2de..fe42042 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 --exec`. Telemetry, planner/next, ownership enforcement, and parallel orchestration are future-only. diff --git a/README.md b/README.md index 4088727..aa9afd2 100644 --- a/README.md +++ b/README.md @@ -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//`. - 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 `: print a packaged template. -- `run `: prepare one bounded prompt packet for a project agent. +- `run `: prepare one bounded Codex prompt packet for a project agent; add `--exec` to invoke `codex exec` immediately. ## Project Layout diff --git a/docs/development-rules.md b/docs/development-rules.md index c5a4ac2..554cca5 100644 --- a/docs/development-rules.md +++ b/docs/development-rules.md @@ -15,4 +15,4 @@ npm run validate Keep generated projects under `projects//`. -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. diff --git a/docs/examples.md b/docs/examples.md index 42e4fd2..16dfe22 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -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//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: diff --git a/docs/known-upstream-differences.md b/docs/known-upstream-differences.md index 9882b2b..136e98e 100644 --- a/docs/known-upstream-differences.md +++ b/docs/known-upstream-differences.md @@ -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. diff --git a/docs/migration-from-claude.md b/docs/migration-from-claude.md index 3fe746a..6f50a6f 100644 --- a/docs/migration-from-claude.md +++ b/docs/migration-from-claude.md @@ -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//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. diff --git a/docs/workflow-validation.md b/docs/workflow-validation.md index 3a471bf..1e80ae5 100644 --- a/docs/workflow-validation.md +++ b/docs/workflow-validation.md @@ -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. diff --git a/src/agents.ts b/src/agents.ts index 5607b5c..642af69 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -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 --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. `; } diff --git a/src/cli.ts b/src/cli.ts index 68ed063..6ddf71c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -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("") .requiredOption("--project ", "project path") .requiredOption("--task ", "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 ", "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) => { diff --git a/src/runner.ts b/src/runner.ts index d8b0e75..d9eea6d 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -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 }; } diff --git a/src/validation.ts b/src/validation.ts index 1068dfe..b935fc8 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -98,7 +98,8 @@ export async function validateRepo(root = process.cwd()): Promise { 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); + }); });