diff --git a/README.md b/README.md index 153d1dbe61..dc16e7ab58 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,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/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Cursor](https://cursor.sh/) +- 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), [Codex CLI](https://github.com/openai/codex) or [Cursor](https://cursor.sh/) - [uv](https://docs.astral.sh/uv/) for package management - [Python 3.11+](https://www.python.org/downloads/) - [Git](https://git-scm.com/downloads) @@ -200,16 +200,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/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 7742af3d4d..f2b87f6df6 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 7ac26a7d81..4cc2b658e0 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/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 88c166998b..bdc4ead805 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -57,7 +57,8 @@ "copilot": "GitHub Copilot", "claude": "Claude Code", "gemini": "Gemini CLI", - "cursor": "Cursor" + "cursor": "Cursor", + "codex": "Codex CLI", } # Add script type choices SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} @@ -65,6 +66,50 @@ # Claude CLI local installation path after migrate-installer CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude" +# 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 = """ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— @@ -443,12 +488,21 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri console.print(Panel(str(e), title="Fetch Error", border_style="red")) 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 'new assistant' -> 'copilot' pattern = f"spec-kit-template-{ai_assistant}-{script_type}" + assets = release_data.get("assets", []) matching_assets = [ asset for asset in release_data.get("assets", []) if pattern in asset["name"] and asset["name"].endswith(".zip") ] + + if not matching_assets and not ai_assistant == "copilot": + 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: + matching_assets = fallback_assets + if verbose: + console.print(f"[yellow]No '{ai_assistant}' template found; falling back to 'copilot' template.[/yellow]") if not matching_assets: console.print(f"[red]No matching release asset found[/red] for pattern: [bold]{pattern}[/bold]") @@ -735,7 +789,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, GitHub Copilot, or Cursor) + 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Codex, or Cursor) 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) @@ -746,9 +800,11 @@ 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 my-project --ai cursor specify init --ignore-agent-tools my-project specify init --here --ai claude + specify init --here --ai codex specify init --here """ # Show banner first @@ -825,6 +881,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 if agent_tool_missing: console.print("\n[red]Required AI tool is missing![/red]") @@ -873,6 +933,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: @@ -888,6 +951,34 @@ def init( # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, 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") @@ -950,6 +1041,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") # Removed script variant step (scripts are transparent to users) step_num += 1 @@ -982,6 +1079,7 @@ def check(): git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker) claude_ok = check_tool_for_tracker("claude", "https://docs.anthropic.com/en/docs/claude-code/setup", tracker) gemini_ok = check_tool_for_tracker("gemini", "https://github.com/google-gemini/gemini-cli", tracker) + codex_ok = check_tool_for_tracker("codex", "https://github.com/openai/codex", tracker) # Check for VS Code (code or code-insiders) code_ok = check_tool_for_tracker("code", "https://code.visualstudio.com/", tracker) if not code_ok: @@ -997,7 +1095,7 @@ def check(): # Recommendations if not git_ok: console.print("[dim]Tip: Install git for repository management[/dim]") - if not (claude_ok or gemini_ok): + if not (claude_ok or gemini_ok or codex_ok): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") diff --git a/templates/plan-template.md b/templates/plan-template.md index 93ab96b5d5..0fd9130a92 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