From b4fb6bba83682a0eb2683f27466ae5bd50c5fbe8 Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Thu, 4 Sep 2025 14:47:24 +0900 Subject: [PATCH 1/2] Add Codex CLI support with AGENTS.md and commands bootstrap --- README.md | 12 ++- scripts/update-agent-context.sh | 14 +++- src/specify_cli/__init__.py | 126 ++++++++++++++++++++++++++++---- 3 files changed, 131 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index a2bc464c..234c5505 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Our research and experimentation focus on: ## πŸ”§ Prerequisites - **Linux/macOS** (or WSL2 on Windows) -- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), or [Gemini CLI](https://github.com/google-gemini/gemini-cli) +- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Codex CLI](https://github.com/openai/codex) - [uv](https://docs.astral.sh/uv/) for package management - [Python 3.11+](https://www.python.org/downloads/) - [Git](https://git-scm.com/downloads) @@ -143,16 +143,24 @@ You will be prompted to select the AI agent you are using. You can also proactiv specify init --ai claude specify init --ai gemini specify init --ai copilot +specify init --ai codex # Or in current directory: specify init --here --ai claude +specify init --here --ai codex ``` -The CLI will check if you have Claude Code or Gemini CLI installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command: +The CLI will check if you have Claude Code, Gemini CLI, or Codex CLI installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command: ```bash specify init --ai claude --ignore-agent-tools ``` +> [!NOTE] +> Codex CLI specifics +> - When you run `specify init --ai codex`, the CLI ensures a `commands/` directory exists. If packaged templates are not available, it bootstraps minimal command files (`specify.md`, `plan.md`, `tasks.md`). +> - Codex reads project memory from `AGENTS.md`. If it does not exist yet, run `codex /init` inside the project. +> - To allow the bundled scripts under `scripts/` to run from Codex slash commands, open `codex /approvals` and enable β€œRun shell commands”. + ### **STEP 1:** Bootstrap the project Go to the project folder and run your AI agent. In our example, we're using `claude`. diff --git a/scripts/update-agent-context.sh b/scripts/update-agent-context.sh index 51fa640b..1886bca2 100755 --- a/scripts/update-agent-context.sh +++ b/scripts/update-agent-context.sh @@ -1,6 +1,6 @@ #!/bin/bash # Incrementally update agent context files based on new feature plan -# Supports: CLAUDE.md, GEMINI.md, and .github/copilot-instructions.md +# Supports: CLAUDE.md, GEMINI.md, AGENTS.md (Codex), and .github/copilot-instructions.md # O(1) operation - only reads current context file and new plan.md set -e @@ -14,6 +14,7 @@ NEW_PLAN="$FEATURE_DIR/plan.md" CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" GEMINI_FILE="$REPO_ROOT/GEMINI.md" COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md" +AGENTS_FILE="$REPO_ROOT/AGENTS.md" # Allow override via argument AGENT_TYPE="$1" @@ -197,11 +198,15 @@ case "$AGENT_TYPE" in "copilot") update_agent_file "$COPILOT_FILE" "GitHub Copilot" ;; + "codex") + update_agent_file "$AGENTS_FILE" "Codex CLI" + ;; "") # Update all existing files [ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code" [ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI" [ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot" + [ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "Codex CLI" # If no files exist, create based on current directory or ask user if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ]; then @@ -210,7 +215,7 @@ case "$AGENT_TYPE" in fi ;; *) - echo "ERROR: Unknown agent type '$AGENT_TYPE'. Use: claude, gemini, copilot, or leave empty for all." + echo "ERROR: Unknown agent type '$AGENT_TYPE'. Use: claude, gemini, copilot, codex, or leave empty for all." exit 1 ;; esac @@ -227,8 +232,9 @@ if [ ! -z "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ]; then fi echo "" -echo "Usage: $0 [claude|gemini|copilot]" +echo "Usage: $0 [claude|gemini|copilot|codex]" echo " - No argument: Update all existing agent context files" echo " - claude: Update only CLAUDE.md" echo " - gemini: Update only GEMINI.md" -echo " - copilot: Update only .github/copilot-instructions.md" \ No newline at end of file +echo " - copilot: Update only .github/copilot-instructions.md" +echo " - codex: Update only AGENTS.md" diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 75cd28f3..cbac1a4c 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -51,9 +51,54 @@ AI_CHOICES = { "copilot": "GitHub Copilot", "claude": "Claude Code", - "gemini": "Gemini CLI" + "gemini": "Gemini CLI", + "codex": "Codex CLI", } +# Embedded fallback command templates for Codex (used only if templates/commands is not found) +CODEX_CMD_SPECIFY = """--- +name: specify +description: "Start a new feature by creating a specification and feature branch." +--- + +Start a new feature by creating a specification and feature branch. + +Given the feature description provided as an argument, do this: + +1. Run the script `scripts/create-new-feature.sh --json "{ARGS}"` from the repo root and parse its JSON for BRANCH_NAME and SPEC_FILE. All future paths must be absolute. +2. Load `templates/spec-template.md` and create the initial specification at SPEC_FILE, filling in the placeholders with concrete details derived from the arguments while preserving headings/order. +3. Report completion with the new branch name and spec file path. +""" + +CODEX_CMD_PLAN = """--- +name: plan +description: "Plan how to implement the specified feature." +--- + +Plan how to implement the specified feature. + +Given the implementation details provided as an argument, do this: + +1. Run `scripts/setup-plan.sh --json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. Use absolute paths in all steps. +2. Read the feature specification (FEATURE_SPEC) and `memory/constitution.md`. +3. Copy `templates/plan-template.md` to IMPL_PLAN if not already present and fill in all sections using the specification and {ARGS} as Technical Context. +4. Ensure the plan includes phases and produces research.md, data-model.md (if needed), contracts/, quickstart.md as appropriate. +5. Report results with BRANCH and generated artifact paths. +""" + +CODEX_CMD_TASKS = """--- +name: tasks +description: "Break down the plan into executable tasks." +--- + +Break down the plan into executable tasks. + +1. Run `scripts/check-task-prerequisites.sh --json` and parse FEATURE_DIR and AVAILABLE_DOCS. +2. Read plan.md and any available docs to derive concrete tasks. +3. Use `templates/tasks-template.md` as the base, generating numbered tasks (T001, T002, …) with clear file paths and dependency notes. Mark tasks that can run in parallel with [P]. +4. Write the result to FEATURE_DIR/tasks.md. +""" + # ASCII Art Banner BANNER = """ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— @@ -405,23 +450,30 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb console.print(f"[red]Error fetching release information:[/red] {e}") raise typer.Exit(1) - # Find the template asset for the specified AI assistant + # Find the template asset for the specified AI assistant, with fallback for 'codex' -> 'copilot' pattern = f"spec-kit-template-{ai_assistant}" - matching_assets = [ - asset for asset in release_data.get("assets", []) - if pattern in asset["name"] and asset["name"].endswith(".zip") - ] - - if not matching_assets: + assets = release_data.get("assets", []) + matching_assets = [a for a in assets if pattern in a["name"] and a["name"].endswith(".zip")] + + asset = None + if matching_assets: + asset = matching_assets[0] + elif ai_assistant == "codex": + # Fallback to copilot template if codex-specific template is not published yet + fallback_pattern = "spec-kit-template-copilot" + fallback_assets = [a for a in assets if fallback_pattern in a["name"] and a["name"].endswith(".zip")] + if fallback_assets: + asset = fallback_assets[0] + if verbose: + console.print("[yellow]No 'codex' template found; falling back to 'copilot' template.[/yellow]") + + if asset is None: if verbose: console.print(f"[red]Error:[/red] No template found for AI assistant '{ai_assistant}'") console.print(f"[yellow]Available assets:[/yellow]") - for asset in release_data.get("assets", []): - console.print(f" - {asset['name']}") + for a in assets: + console.print(f" - {a['name']}") raise typer.Exit(1) - - # Use the first matching asset - asset = matching_assets[0] download_url = asset["browser_download_url"] filename = asset["name"] file_size = asset["size"] @@ -648,7 +700,7 @@ def init( This command will: 1. Check that required tools are installed (git is optional) - 2. Let you choose your AI assistant (Claude Code, Gemini CLI, or GitHub Copilot) + 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, or Codex CLI) 3. Download the appropriate template from GitHub 4. Extract the template to a new project directory or current directory 5. Initialize a fresh git repository (if not --no-git and no existing repo) @@ -659,8 +711,10 @@ def init( specify init my-project --ai claude specify init my-project --ai gemini specify init my-project --ai copilot --no-git + specify init my-project --ai codex specify init --ignore-agent-tools my-project specify init --here --ai claude + specify init --here --ai codex specify init --here """ # Show banner first @@ -737,6 +791,10 @@ def init( if not check_tool("gemini", "Install from: https://github.com/google-gemini/gemini-cli"): console.print("[red]Error:[/red] Gemini CLI is required for Gemini projects") agent_tool_missing = True + elif selected_ai == "codex": + if not check_tool("codex", "Install from: https://github.com/openai/codex"): + console.print("[red]Error:[/red] Codex CLI is required for Codex projects") + agent_tool_missing = True # GitHub Copilot check is not needed as it's typically available in supported IDEs if agent_tool_missing: @@ -765,6 +823,9 @@ def init( ("final", "Finalize") ]: tracker.add(key, label) + # Codex only: show progress for auto-adding commands/ + if selected_ai == "codex": + tracker.add("commands", "Add commands directory") # Use transient so live tree is replaced by the final static render (avoids duplicate output) with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live: @@ -772,6 +833,34 @@ def init( try: download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker) + # Codex only: if commands/ is missing, copy from template or bootstrap minimal ones + if selected_ai == "codex": + tracker.start("commands") + try: + target_cmds = project_path / "commands" + if not target_cmds.exists(): + # Search for templates/commands within the running package/repo + commands_src = None + for ancestor in Path(__file__).resolve().parents: + cand = ancestor / "templates" / "commands" + if cand.exists() and cand.is_dir(): + commands_src = cand + break + if commands_src is not None: + shutil.copytree(commands_src, target_cmds, dirs_exist_ok=True) + tracker.complete("commands", "added") + else: + # Fallback: embed minimal commands + target_cmds.mkdir(parents=True, exist_ok=True) + (target_cmds / "specify.md").write_text(CODEX_CMD_SPECIFY, encoding="utf-8") + (target_cmds / "plan.md").write_text(CODEX_CMD_PLAN, encoding="utf-8") + (target_cmds / "tasks.md").write_text(CODEX_CMD_TASKS, encoding="utf-8") + tracker.complete("commands", "bootstrapped") + else: + tracker.skip("commands", "already present") + except Exception as e: + tracker.error("commands", str(e)) + # Git step if not no_git: tracker.start("git") @@ -823,6 +912,12 @@ def init( steps_lines.append(" - See GEMINI.md for all available commands") elif selected_ai == "copilot": steps_lines.append(f"{step_num}. Open in Visual Studio Code and use [bold cyan]/specify[/], [bold cyan]/plan[/], [bold cyan]/tasks[/] commands with GitHub Copilot") + elif selected_ai == "codex": + steps_lines.append(f"{step_num}. Use / commands with Codex CLI") + steps_lines.append(" - Run codex /specify to create specifications") + steps_lines.append(" - Run codex /plan to create implementation plans") + steps_lines.append(" - Run codex /tasks to generate task list") + steps_lines.append(" - See AGENTS.md for available commands") step_num += 1 steps_lines.append(f"{step_num}. Update [bold magenta]CONSTITUTION.md[/bold magenta] with your project's non-negotiable principles") @@ -855,11 +950,12 @@ def check(): console.print("\n[cyan]Optional AI tools:[/cyan]") claude_ok = check_tool("claude", "Install from: https://docs.anthropic.com/en/docs/claude-code/setup") gemini_ok = check_tool("gemini", "Install from: https://github.com/google-gemini/gemini-cli") + codex_ok = check_tool("codex", "Install from: https://github.com/openai/codex") console.print("\n[green]βœ“ Specify CLI is ready to use![/green]") if not git_ok: console.print("[yellow]Consider installing git for repository management[/yellow]") - if not (claude_ok or gemini_ok): + if not (claude_ok or gemini_ok or codex_ok): console.print("[yellow]Consider installing an AI assistant for the best experience[/yellow]") From 614a3dd4150c7a2857d88fbd181bf11085e9c200 Mon Sep 17 00:00:00 2001 From: zvictor Date: Sat, 13 Sep 2025 17:44:57 +0200 Subject: [PATCH 2/2] Add Codex support to update-agent-context --- scripts/bash/update-agent-context.sh | 5 +++-- scripts/powershell/update-agent-context.ps1 | 6 ++++-- templates/plan-template.md | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 7742af3d..f2b87f6d 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -4,7 +4,7 @@ REPO_ROOT=$(git rev-parse --show-toplevel) CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH" NEW_PLAN="$FEATURE_DIR/plan.md" -CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md" +CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md" AGENT_TYPE="$1" [ -f "$NEW_PLAN" ] || { echo "ERROR: No plan.md found at $NEW_PLAN"; exit 1; } echo "=== Updating agent context files for feature $CURRENT_BRANCH ===" @@ -50,8 +50,9 @@ fi; mv "$temp_file" "$target_file" 2>/dev/null || true; echo "βœ… $agent_name co case "$AGENT_TYPE" in claude) update_agent_file "$CLAUDE_FILE" "Claude Code" ;; gemini) update_agent_file "$GEMINI_FILE" "Gemini CLI" ;; + codex) update_agent_file "$AGENTS_FILE" "Codex" ;; copilot) update_agent_file "$COPILOT_FILE" "GitHub Copilot" ;; - "") [ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"; [ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; [ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;; + "") [ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"; [ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; [ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "Codex"; [ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$AGENTS_FILE" ] && [ ! -f "$COPILOT_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;; *) echo "ERROR: Unknown agent type '$AGENT_TYPE'"; exit 1 ;; esac echo; echo "Summary of changes:"; [ -n "$NEW_LANG" ] && echo "- Added language: $NEW_LANG"; [ -n "$NEW_FRAMEWORK" ] && echo "- Added framework: $NEW_FRAMEWORK"; [ -n "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ] && echo "- Added database: $NEW_DB"; echo; echo "Usage: $0 [claude|gemini|copilot]" diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 7ac26a7d..4cc2b658 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -11,6 +11,7 @@ if (-not (Test-Path $newPlan)) { Write-Error "ERROR: No plan.md found at $newPla $claudeFile = Join-Path $repoRoot 'CLAUDE.md' $geminiFile = Join-Path $repoRoot 'GEMINI.md' +$agentsFile = Join-Path $repoRoot 'AGENTS.md' $copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md' Write-Output "=== Updating agent context files for feature $currentBranch ===" @@ -68,12 +69,13 @@ function Update-AgentFile($targetFile, $agentName) { switch ($AgentType) { 'claude' { Update-AgentFile $claudeFile 'Claude Code' } 'gemini' { Update-AgentFile $geminiFile 'Gemini CLI' } + 'codex' { Update-AgentFile $agentsFile 'Codex' } 'copilot' { Update-AgentFile $copilotFile 'GitHub Copilot' } '' { - foreach ($pair in @(@{file=$claudeFile; name='Claude Code'}, @{file=$geminiFile; name='Gemini CLI'}, @{file=$copilotFile; name='GitHub Copilot'})) { + foreach ($pair in @(@{file=$claudeFile; name='Claude Code'}, @{file=$geminiFile; name='Gemini CLI'}, @{file=$agentsFile; name='Codex'}, @{file=$copilotFile; name='GitHub Copilot'})) { if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name } } - if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile)) { + if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $agentsFile) -and -not (Test-Path $copilotFile)) { Write-Output 'No agent context files found. Creating Claude Code context file by default.' Update-AgentFile $claudeFile 'Claude Code' } diff --git a/templates/plan-template.md b/templates/plan-template.md index 93ab96b5..0fd9130a 100644 --- a/templates/plan-template.md +++ b/templates/plan-template.md @@ -19,7 +19,7 @@ β†’ Update Progress Tracking: Initial Constitution Check 4. Execute Phase 0 β†’ research.md β†’ If NEEDS CLARIFICATION remain: ERROR "Resolve unknowns" -5. Execute Phase 1 β†’ contracts, data-model.md, quickstart.md, agent-specific template file (e.g., `CLAUDE.md` for Claude Code, `.github/copilot-instructions.md` for GitHub Copilot, or `GEMINI.md` for Gemini CLI). +5. Execute Phase 1 β†’ contracts, data-model.md, quickstart.md, agent-specific template file (e.g., `CLAUDE.md` for Claude Code, `.github/copilot-instructions.md` for GitHub Copilot, `AGENTS.md` for Codex, or `GEMINI.md` for Gemini CLI). 6. Re-evaluate Constitution Check section β†’ If new violations: Refactor design, return to Phase 1 β†’ Update Progress Tracking: Post-Design Constitution Check