From 95fba17d204aad170d2a496e5f310998e670d347 Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Thu, 4 Sep 2025 14:47:24 +0900 Subject: [PATCH 01/13] Add Codex CLI support with AGENTS.md and commands bootstrap --- README.md | 13 ++- scripts/bash/update-agent-context.sh | 7 +- src/specify_cli/__init__.py | 124 ++++++++++++++++++++++++--- 3 files changed, 127 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3c65440..b62e52a 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,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), [Cursor](https://cursor.sh/), [Qwen CLI](https://github.com/QwenLM/qwen-code) or [opencode](https://opencode.ai/) +- 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), [Cursor](https://cursor.sh/), [Qwen CLI](https://github.com/QwenLM/qwen-code), [opencode](https://opencode.ai/), 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) @@ -207,18 +207,27 @@ 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 cursor specify init --ai qwen specify init --ai opencode +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, Gemini CLI, Qwen CLI or opencode 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, Qwen CLI, opencode, 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 249a061..b186a2f 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -54,13 +54,14 @@ case "$AGENT_TYPE" in cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;; qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;; opencode) update_agent_file "$AGENTS_FILE" "opencode" ;; + codex) update_agent_file "$AGENTS_FILE" "Codex CLI" ;; "") [ -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 "$CURSOR_FILE" ] && update_agent_file "$CURSOR_FILE" "Cursor IDE"; \ [ -f "$QWEN_FILE" ] && update_agent_file "$QWEN_FILE" "Qwen Code"; \ - [ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "opencode"; \ + [ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "Codex/opencode"; \ if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;; - *) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode)"; exit 1 ;; + *) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode|codex)"; 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|cursor|qwen|opencode]" +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|cursor|qwen|opencode|codex]" diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 5d5fdd4..04329d4 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -59,7 +59,8 @@ AI_CHOICES = { "gemini": "Gemini CLI", "cursor": "Cursor", "qwen": "Qwen Code", - "opencode": "opencode" + "opencode": "opencode", + "codex": "Codex CLI", } # Add script type choices SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} @@ -67,6 +68,50 @@ SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} # 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 packaged templates are unavailable) +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 = """ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— @@ -445,21 +490,34 @@ 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 Codex) + assets = release_data.get("assets", []) pattern = f"spec-kit-template-{ai_assistant}-{script_type}" matching_assets = [ - asset for asset in release_data.get("assets", []) + asset for asset in assets if pattern in asset["name"] and asset["name"].endswith(".zip") ] - - if not matching_assets: + + asset = matching_assets[0] if matching_assets else None + + if asset is None and ai_assistant == "codex": + fallback_pattern = f"spec-kit-template-copilot-{script_type}" + fallback_assets = [ + asset for asset in assets + if fallback_pattern in asset["name"] and asset["name"].endswith(".zip") + ] + if fallback_assets: + asset = fallback_assets[0] + if verbose: + console.print("[yellow]No Codex-specific template found; falling back to GitHub Copilot template.[/yellow]") + + if asset is None: console.print(f"[red]No matching release asset found[/red] for pattern: [bold]{pattern}[/bold]") - asset_names = [a.get('name','?') for a in release_data.get('assets', [])] + asset_names = [a.get('name','?') for a in assets] console.print(Panel("\n".join(asset_names) or "(no assets)", title="Available Assets", border_style="yellow")) raise typer.Exit(1) - - # Use the first matching asset - asset = matching_assets[0] + + # Use the resolved asset download_url = asset["browser_download_url"] filename = asset["name"] file_size = asset["size"] @@ -724,7 +782,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), - ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen or opencode"), + ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, or codex"), script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"), ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"), no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"), @@ -737,7 +795,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, Cursor, Qwen Code or opencode) + 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, 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) @@ -751,8 +809,10 @@ def init( specify init my-project --ai cursor specify init my-project --ai qwen specify init my-project --ai opencode + 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 @@ -838,6 +898,10 @@ def init( if not check_tool("opencode", "Install from: https://opencode.ai"): console.print("[red]Error:[/red] opencode CLI is required for opencode 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 and Cursor checks are not needed as they're typically available in supported IDEs if agent_tool_missing: @@ -888,6 +952,9 @@ def init( ]: tracker.add(key, label) + if selected_ai == "codex": + tracker.add("commands", "Ensure Codex commands") + # 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: tracker.attach_refresh(lambda: live.update(tracker.render())) @@ -902,6 +969,31 @@ def init( # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, tracker=tracker) + if selected_ai == "codex": + tracker.start("commands") + try: + target_cmds = project_path / "commands" + if not target_cmds.exists(): + commands_src = None + for ancestor in Path(__file__).resolve().parents: + candidate = ancestor / "templates" / "commands" + if candidate.exists() and candidate.is_dir(): + commands_src = candidate + break + if commands_src is not None: + shutil.copytree(commands_src, target_cmds, dirs_exist_ok=True) + tracker.complete("commands", "added") + else: + 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 codex_error: + tracker.error("commands", str(codex_error)) + # Git step if not no_git: tracker.start("git") @@ -975,6 +1067,12 @@ def init( steps_lines.append(" - Use /specify to create specifications") steps_lines.append(" - Use /plan to create implementation plans") steps_lines.append(" - Use /tasks to generate tasks") + 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 lists") + steps_lines.append(" - See AGENTS.md for bundled commands") # Removed script variant step (scripts are transparent to users) step_num += 1 @@ -1004,6 +1102,7 @@ def check(): tracker.add("code", "VS Code (for GitHub Copilot)") tracker.add("cursor-agent", "Cursor IDE agent (optional)") tracker.add("opencode", "opencode") + tracker.add("codex", "Codex CLI") # Check each tool git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker) @@ -1016,6 +1115,7 @@ def check(): code_ok = check_tool_for_tracker("code-insiders", "https://code.visualstudio.com/insiders/", tracker) cursor_ok = check_tool_for_tracker("cursor-agent", "https://cursor.sh/", tracker) opencode_ok = check_tool_for_tracker("opencode", "https://opencode.ai/", tracker) + codex_ok = check_tool_for_tracker("codex", "https://github.com/openai/codex", tracker) # Render the final tree console.print(tracker.render()) @@ -1026,7 +1126,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 or cursor_ok or qwen_ok or opencode_ok): + if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or opencode_ok or codex_ok): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") From e29488d91f9ef8cc50ff81f74eb66b220f8a17c1 Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Tue, 16 Sep 2025 18:11:35 +0900 Subject: [PATCH 02/13] Fix remaining merge conflict markers in __init__.py --- src/specify_cli/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 04329d4..934405b 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -512,8 +512,11 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri console.print("[yellow]No Codex-specific template found; falling back to GitHub Copilot template.[/yellow]") if asset is None: - console.print(f"[red]No matching release asset found[/red] for pattern: [bold]{pattern}[/bold]") - asset_names = [a.get('name','?') for a in assets] + console.print( + f"[red]No matching release asset found[/red] for AI assistant " + f"[bold]{ai_assistant}[/bold] (pattern: {pattern})" + ) + asset_names = [a.get('name', '?') for a in assets] console.print(Panel("\n".join(asset_names) or "(no assets)", title="Available Assets", border_style="yellow")) raise typer.Exit(1) @@ -1119,14 +1122,15 @@ def check(): # Render the final tree console.print(tracker.render()) - + # Summary console.print("\n[bold green]Specify CLI is ready to use![/bold green]") - + # Recommendations if not git_ok: console.print("[dim]Tip: Install git for repository management[/dim]") if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or opencode_ok or codex_ok): + console.print("[yellow]Consider installing an AI assistant for the best experience[/yellow]") console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") From a39185c8bed2a95d436cc577aa5758686d8303be Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Wed, 17 Sep 2025 01:42:19 +0900 Subject: [PATCH 03/13] Normalize Codex command templates to the scripts-based schema and auto-upgrade generated commands. --- src/specify_cli/__init__.py | 170 ++++++++++++++++++++++++++++++------ templates/commands/plan.md | 4 +- templates/commands/tasks.md | 2 +- 3 files changed, 145 insertions(+), 31 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 934405b..f28443d 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -70,48 +70,154 @@ CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude" # Embedded fallback command templates for Codex (used only if packaged templates are unavailable) CODEX_CMD_SPECIFY = """--- -name: specify -description: "Start a new feature by creating a specification and feature branch." +description: Create or update the feature specification from a natural language feature description. +scripts: + sh: scripts/bash/create-new-feature.sh --json "{ARGS}" + ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}" --- -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. +1. Run the script `{SCRIPT}` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute. +2. Load `templates/spec-template.md` to understand required sections. +3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings. +4. Report completion with branch name, spec file path, and readiness for the next phase. + +Note: The script creates and checks out the new branch and initializes the spec file before writing. """ CODEX_CMD_PLAN = """--- -name: plan -description: "Plan how to implement the specified feature." +description: Execute the implementation planning workflow using the plan template to generate design artifacts. +scripts: + sh: scripts/bash/setup-plan.sh --json + ps: scripts/powershell/setup-plan.ps1 -Json --- -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. +1. Run `{SCRIPT}` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute. +2. Read and analyze the feature specification to understand: + - The feature requirements and user stories + - Functional and non-functional requirements + - Success criteria and acceptance criteria + - Any technical constraints or dependencies mentioned + +3. Read the constitution at `memory/constitution.md` to understand constitutional requirements. + +4. Execute the implementation plan template: + - Load `templates/plan-template.md` (already copied to IMPL_PLAN path) + - Set Input path to FEATURE_SPEC + - Run the Execution Flow (main) function steps 1-9 + - The template is self-contained and executable + - Follow error handling and gate checks as specified + - Let the template guide artifact generation in $SPECS_DIR: + * Phase 0 generates research.md + * Phase 1 generates data-model.md, contracts/, quickstart.md + * Phase 2 generates tasks.md + - Incorporate user-provided details from arguments into Technical Context: {ARGS} + - Update Progress Tracking as you complete each phase + +5. Verify execution completed: + - Check Progress Tracking shows all phases complete + - Ensure all required artifacts were generated + - Confirm no ERROR states in execution + +6. Report results with branch name, file paths, and generated artifacts. + +Use absolute paths with the repository root for all file operations to avoid path issues. """ CODEX_CMD_TASKS = """--- -name: tasks -description: "Break down the plan into executable tasks." +description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts. +scripts: + sh: scripts/bash/check-task-prerequisites.sh --json + ps: scripts/powershell/check-task-prerequisites.ps1 -Json --- -Break down the plan into executable tasks. +Given the context provided as an argument, do this: -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. +1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. +2. Load and analyze available design documents: + - Always read plan.md for tech stack and libraries + - IF EXISTS: Read data-model.md for entities + - IF EXISTS: Read contracts/ for API endpoints + - IF EXISTS: Read research.md for technical decisions + - IF EXISTS: Read quickstart.md for test scenarios + + Note: Not all projects have all documents. For example: + - CLI tools might not have contracts/ + - Simple libraries might not need data-model.md + - Generate tasks based on what's available + +3. Generate tasks following the template: + - Use `templates/tasks-template.md` as the base + - Replace example tasks with actual tasks based on: + * **Setup tasks**: Project init, dependencies, linting + * **Test tasks [P]**: One per contract, one per integration scenario + * **Core tasks**: One per entity, service, CLI command, endpoint + * **Integration tasks**: DB connections, middleware, logging + * **Polish tasks [P]**: Unit tests, performance, docs + +4. Task generation rules: + - Each contract file β†’ contract test task marked [P] + - Each entity in data-model β†’ model creation task marked [P] + - Each endpoint β†’ implementation task (not parallel if shared files) + - Each user story β†’ integration test marked [P] + - Different files = can be parallel [P] + - Same file = sequential (no [P]) + +5. Order tasks by dependencies: + - Setup before everything + - Tests before implementation (TDD) + - Models before services + - Services before endpoints + - Core before integration + - Everything before polish + +6. Include parallel execution examples: + - Group [P] tasks that can run together + - Show actual Task agent commands + +7. Create FEATURE_DIR/tasks.md with: + - Correct feature name from implementation plan + - Numbered tasks (T001, T002, etc.) + - Clear file paths for each task + - Dependency notes + - Parallel execution guidance + +Context for task generation: {ARGS} + +The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context. """ + +# Utility to ensure Codex command templates use the modern schema (with scripts mapping) +def ensure_codex_command_templates_current(commands_dir: Path) -> None: + expected = { + "specify.md": CODEX_CMD_SPECIFY, + "plan.md": CODEX_CMD_PLAN, + "tasks.md": CODEX_CMD_TASKS, + } + + def needs_upgrade(content: str) -> bool: + # Old templates lacked the scripts: mapping and {SCRIPT} placeholder + return "scripts:" not in content or "{SCRIPT}" not in content + + for filename, template_text in expected.items(): + target_file = commands_dir / filename + if target_file.exists(): + try: + current = target_file.read_text(encoding="utf-8") + except Exception: + target_file.write_text(template_text, encoding="utf-8") + else: + if needs_upgrade(current): + target_file.write_text(template_text, encoding="utf-8") + else: + target_file.parent.mkdir(parents=True, exist_ok=True) + target_file.write_text(template_text, encoding="utf-8") + + # ASCII Art Banner BANNER = """ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— @@ -972,6 +1078,7 @@ def init( # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, tracker=tracker) + # Codex only: if commands/ is missing, copy from template (with embedded fallback) if selected_ai == "codex": tracker.start("commands") try: @@ -985,13 +1092,20 @@ def init( break if commands_src is not None: shutil.copytree(commands_src, target_cmds, dirs_exist_ok=True) + ensure_codex_command_templates_current(target_cmds) tracker.complete("commands", "added") else: - 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") + template_commands = project_path / ".specify" / "templates" / "commands" + if template_commands.exists(): + shutil.copytree(template_commands, target_cmds, dirs_exist_ok=True) + ensure_codex_command_templates_current(target_cmds) + tracker.complete("commands", "added from template") + else: + 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 minimal") else: tracker.skip("commands", "already present") except Exception as codex_error: diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 18a0b5c..6315d0e 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -14,10 +14,10 @@ Given the implementation details provided as an argument, do this: - Success criteria and acceptance criteria - Any technical constraints or dependencies mentioned -3. Read the constitution at `/memory/constitution.md` to understand constitutional requirements. +3. Read the constitution at `memory/constitution.md` to understand constitutional requirements. 4. Execute the implementation plan template: - - Load `/templates/plan-template.md` (already copied to IMPL_PLAN path) + - Load `templates/plan-template.md` (already copied to IMPL_PLAN path) - Set Input path to FEATURE_SPEC - Run the Execution Flow (main) function steps 1-9 - The template is self-contained and executable diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 29b4cd2..fe89b95 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -21,7 +21,7 @@ Given the context provided as an argument, do this: - Generate tasks based on what's available 3. Generate tasks following the template: - - Use `/templates/tasks-template.md` as the base + - Use `templates/tasks-template.md` as the base - Replace example tasks with actual tasks based on: * **Setup tasks**: Project init, dependencies, linting * **Test tasks [P]**: One per contract, one per integration scenario From 286ad553fda6a587409cab711299bbd53f8df7c6 Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Thu, 18 Sep 2025 10:36:48 +0900 Subject: [PATCH 04/13] Bump to 0.0.7 and document Codex support --- CHANGELOG.md | 8 +++++++- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c763a3..e9cca15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.7] - 2025-09-18 + +### Added + +- Codex CLI support across the Specify CLI, documentation, and bootstrap scripts, including automatic command template provisioning and upgrades. +- Codex-aware updates to agent context automation so `update-agent-context.sh` can manage Codex project state alongside existing assistants. + ## [0.0.6] - 2025-09-17 ### Added @@ -32,4 +39,3 @@ N/A ### Changed N/A - diff --git a/pyproject.toml b/pyproject.toml index 3daa03d..7971881 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.0.6" +version = "0.0.7" description = "Setup tool for Specify spec-driven development projects" requires-python = ">=3.11" dependencies = [ From 312703260c46d4d800825383d5c6f7429ec77cd3 Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Fri, 19 Sep 2025 16:40:41 +0900 Subject: [PATCH 05/13] Clarify Codex-specific README note with rationale for its different workflow. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b62e52a..b9e32c8 100644 --- a/README.md +++ b/README.md @@ -224,9 +224,9 @@ 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”. +> - Codex CLI loads slash commands from the project workspace instead of from an IDE bundle, so `specify init --ai codex` creates a `commands/` directory (and seeds `specify.md`, `plan.md`, `tasks.md` if templates are missing) to match that expectation. +> - Codex persists its working memory in `AGENTS.md`; if you do not see that file yet, run `codex /init` once inside the project to generate it. +> - To let Codex trigger the helper scripts under `scripts/` through slash commands, open `codex /approvals` and enable β€œRun shell commands”. ### **STEP 1:** Bootstrap the project From 3a0ae75bfbf97eca7c318c8de3ce7fde59371eba Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Fri, 19 Sep 2025 17:53:16 +0900 Subject: [PATCH 06/13] Limit workspace command seeding to Codex init and update Codex documentation accordingly. --- README.md | 2 +- src/specify_cli/__init__.py | 121 +++++++++++++++++++++++------------- 2 files changed, 78 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index b9e32c8..cca5efc 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ specify init --ai claude --ignore-agent-tools > [!NOTE] > Codex CLI specifics -> - Codex CLI loads slash commands from the project workspace instead of from an IDE bundle, so `specify init --ai codex` creates a `commands/` directory (and seeds `specify.md`, `plan.md`, `tasks.md` if templates are missing) to match that expectation. +> - `specify init --ai codex` ensures a workspace-level `commands/` directory exists (seeding `specify.md`, `plan.md`, `tasks.md` if necessary) because Codex CLI loads slash commands from the repo itself. > - Codex persists its working memory in `AGENTS.md`; if you do not see that file yet, run `codex /init` once inside the project to generate it. > - To let Codex trigger the helper scripts under `scripts/` through slash commands, open `codex /approvals` and enable β€œRun shell commands”. diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index f28443d..842909c 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -68,8 +68,8 @@ SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} # 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 packaged templates are unavailable) -CODEX_CMD_SPECIFY = """--- +# Embedded fallback command templates (used if packaged templates are unavailable) +COMMAND_TEMPLATE_SPECIFY = """--- description: Create or update the feature specification from a natural language feature description. scripts: sh: scripts/bash/create-new-feature.sh --json "{ARGS}" @@ -86,7 +86,7 @@ Given the feature description provided as an argument, do this: Note: The script creates and checks out the new branch and initializes the spec file before writing. """ -CODEX_CMD_PLAN = """--- +COMMAND_TEMPLATE_PLAN = """--- description: Execute the implementation planning workflow using the plan template to generate design artifacts. scripts: sh: scripts/bash/setup-plan.sh --json @@ -127,7 +127,7 @@ Given the implementation details provided as an argument, do this: Use absolute paths with the repository root for all file operations to avoid path issues. """ -CODEX_CMD_TASKS = """--- +COMMAND_TEMPLATE_TASKS = """--- description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts. scripts: sh: scripts/bash/check-task-prerequisites.sh --json @@ -191,12 +191,12 @@ The tasks.md should be immediately executable - each task must be specific enoug """ -# Utility to ensure Codex command templates use the modern schema (with scripts mapping) -def ensure_codex_command_templates_current(commands_dir: Path) -> None: +# Utility to ensure command templates use the modern schema (with scripts mapping) +def ensure_command_templates_current(commands_dir: Path) -> None: expected = { - "specify.md": CODEX_CMD_SPECIFY, - "plan.md": CODEX_CMD_PLAN, - "tasks.md": CODEX_CMD_TASKS, + "specify.md": COMMAND_TEMPLATE_SPECIFY, + "plan.md": COMMAND_TEMPLATE_PLAN, + "tasks.md": COMMAND_TEMPLATE_TASKS, } def needs_upgrade(content: str) -> bool: @@ -888,6 +888,68 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = console.print(f" - {f}") +def ensure_workspace_commands(project_path: Path, tracker: StepTracker | None = None) -> None: + """Ensure a workspace-level commands/ directory exists and has up-to-date templates.""" + if tracker: + tracker.start("commands") + + commands_dir = project_path / "commands" + seeded_from: str | None = None + + try: + existed = commands_dir.exists() + if not existed: + commands_dir.mkdir(parents=True, exist_ok=True) + + try: + is_empty = not any(commands_dir.iterdir()) + except FileNotFoundError: + is_empty = True + + should_seed = not existed or is_empty + + if should_seed: + candidates: list[tuple[str, Path]] = [] + + template_commands = project_path / ".specify" / "templates" / "commands" + if template_commands.exists() and template_commands.is_dir(): + candidates.append(("release bundle", template_commands)) + + packaged_commands = None + for ancestor in Path(__file__).resolve().parents: + candidate = ancestor / "templates" / "commands" + if candidate.exists() and candidate.is_dir(): + packaged_commands = candidate + break + if packaged_commands is not None: + candidates.append(("packaged defaults", packaged_commands)) + + for label, source in candidates: + try: + shutil.copytree(source, commands_dir, dirs_exist_ok=True) + seeded_from = label + break + except Exception: + continue + + if seeded_from is None: + seeded_from = "embedded defaults" + + ensure_command_templates_current(commands_dir) + + detail = "verified" if seeded_from is None else seeded_from + if tracker: + tracker.complete("commands", detail) + else: + if seeded_from: + console.print(f"[cyan]Seeded workspace commands from {seeded_from}[/cyan]") + except Exception as exc: + if tracker: + tracker.error("commands", str(exc)) + else: + console.print(f"[yellow]Warning: could not ensure commands directory ({exc})[/yellow]") + + @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), @@ -1054,7 +1116,7 @@ def init( ("extract", "Extract template"), ("zip-list", "Archive contents"), ("extracted-summary", "Extraction summary"), - ("chmod", "Ensure scripts executable"), + ("chmod", "Ensure scripts executable"), ("cleanup", "Cleanup"), ("git", "Initialize git repository"), ("final", "Finalize") @@ -1062,7 +1124,7 @@ def init( tracker.add(key, label) if selected_ai == "codex": - tracker.add("commands", "Ensure Codex commands") + tracker.add("commands", "Ensure workspace commands") # 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: @@ -1075,42 +1137,13 @@ def init( download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug) + # Ensure /commands directory for Codex CLI workspaces only + if selected_ai == "codex": + ensure_workspace_commands(project_path, tracker=tracker) + # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, tracker=tracker) - # Codex only: if commands/ is missing, copy from template (with embedded fallback) - if selected_ai == "codex": - tracker.start("commands") - try: - target_cmds = project_path / "commands" - if not target_cmds.exists(): - commands_src = None - for ancestor in Path(__file__).resolve().parents: - candidate = ancestor / "templates" / "commands" - if candidate.exists() and candidate.is_dir(): - commands_src = candidate - break - if commands_src is not None: - shutil.copytree(commands_src, target_cmds, dirs_exist_ok=True) - ensure_codex_command_templates_current(target_cmds) - tracker.complete("commands", "added") - else: - template_commands = project_path / ".specify" / "templates" / "commands" - if template_commands.exists(): - shutil.copytree(template_commands, target_cmds, dirs_exist_ok=True) - ensure_codex_command_templates_current(target_cmds) - tracker.complete("commands", "added from template") - else: - 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 minimal") - else: - tracker.skip("commands", "already present") - except Exception as codex_error: - tracker.error("commands", str(codex_error)) - # Git step if not no_git: tracker.start("git") From 65ccbb62ca1749744f8843c7d52e0cd89439d8cf Mon Sep 17 00:00:00 2001 From: honjo-hiroaki-gtt Date: Sat, 20 Sep 2025 22:44:44 +0900 Subject: [PATCH 07/13] Enhance Codex support by auto-syncing prompt files, allowing spec generation without git, and documenting clearer /specify usage. --- README.md | 6 +- scripts/bash/create-new-feature.sh | 22 +++++- src/specify_cli/__init__.py | 120 +++++++++++++++++++++++++++++ templates/commands/specify.md | 4 +- 4 files changed, 147 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cca5efc..a85e710 100644 --- a/README.md +++ b/README.md @@ -224,9 +224,11 @@ specify init --ai claude --ignore-agent-tools > [!NOTE] > Codex CLI specifics -> - `specify init --ai codex` ensures a workspace-level `commands/` directory exists (seeding `specify.md`, `plan.md`, `tasks.md` if necessary) because Codex CLI loads slash commands from the repo itself. +> - `specify init --ai codex` seeds `commands/*.md` in your repo and automatically mirrors them into `${CODEX_HOME:-~/.codex}/prompts` so Codex picks up `/specify`, `/plan`, and `/tasks` immediately. +> - If Codex was running during installation, restart the CLI once so it reloads the refreshed slash commands. > - Codex persists its working memory in `AGENTS.md`; if you do not see that file yet, run `codex /init` once inside the project to generate it. -> - To let Codex trigger the helper scripts under `scripts/` through slash commands, open `codex /approvals` and enable β€œRun shell commands”. +> - When Codex is configured to run helper scripts, open `codex /approvals` and enable β€œRun shell commands”. +> - If you point `CODEX_HOME` at the project (for per-repo isolation) the installer adds ignore rules for Codex session artifacts to `.gitignore`; adjust them as needed for your workflow. ### **STEP 1:** Bootstrap the project diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index bc4b406..575e714 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -18,7 +18,21 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then exit 1 fi -REPO_ROOT=$(git rev-parse --show-toplevel) +# Resolve repository root. Prefer git information when available, but fall back +# to the script location so the workflow still functions in repositories that +# were initialised with --no-git. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FALLBACK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +if git rev-parse --show-toplevel >/dev/null 2>&1; then + REPO_ROOT=$(git rev-parse --show-toplevel) + HAS_GIT=true +else + REPO_ROOT="$FALLBACK_ROOT" + HAS_GIT=false +fi + +cd "$REPO_ROOT" + SPECS_DIR="$REPO_ROOT/specs" mkdir -p "$SPECS_DIR" @@ -40,7 +54,11 @@ BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/ WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//') BRANCH_NAME="${FEATURE_NUM}-${WORDS}" -git checkout -b "$BRANCH_NAME" +if [ "$HAS_GIT" = true ]; then + git checkout -b "$BRANCH_NAME" +else + >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" +fi FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" mkdir -p "$FEATURE_DIR" diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 842909c..fdac7d8 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -950,6 +950,124 @@ def ensure_workspace_commands(project_path: Path, tracker: StepTracker | None = console.print(f"[yellow]Warning: could not ensure commands directory ({exc})[/yellow]") +def _resolve_codex_home() -> Path: + env_value = os.environ.get("CODEX_HOME") + if env_value: + return Path(env_value).expanduser() + return Path.home() / ".codex" + + +def _ensure_gitignore_entries(project_path: Path, entries: list[str]) -> None: + if not entries: + return + + gitignore_path = project_path / ".gitignore" + + existing_text = "" + existing: set[str] = set() + if gitignore_path.exists(): + try: + existing_text = gitignore_path.read_text(encoding="utf-8") + existing = {line.strip() for line in existing_text.splitlines()} + except Exception: + return + + new_entries = [entry for entry in entries if entry not in existing] + if not new_entries: + return + + try: + with gitignore_path.open("a", encoding="utf-8") as fh: + if existing_text and not existing_text.endswith("\n"): + fh.write("\n") + for entry in new_entries: + fh.write(f"{entry}\n") + except Exception: + return + + +def sync_codex_prompts(project_path: Path, tracker: StepTracker | None = None) -> None: + if tracker: + tracker.start("codex-prompts") + + commands_dir = project_path / "commands" + if not commands_dir.is_dir(): + if tracker: + tracker.skip("codex-prompts", "no commands directory") + return + + try: + codex_home = _resolve_codex_home() + prompts_dir = (codex_home / "prompts").expanduser() + prompts_dir.mkdir(parents=True, exist_ok=True) + + if not os.access(prompts_dir, os.W_OK): + raise PermissionError(f"Codex prompts directory not writable: {prompts_dir}") + + expected: set[str] = set() + copied = 0 + skipped = 0 + + for source in sorted(commands_dir.glob("*.md")): + if not source.is_file(): + continue + dest_name = source.name + dest_path = prompts_dir / dest_name + expected.add(dest_name) + + data = source.read_bytes() + if dest_path.exists(): + try: + if dest_path.read_bytes() == data: + skipped += 1 + continue + except Exception: + pass + dest_path.write_bytes(data) + copied += 1 + + # Clean up any legacy spec-kit-prefixed prompts from earlier installer versions + for legacy in prompts_dir.glob("spec-kit-*.md"): + try: + legacy.unlink() + except Exception: + continue + + detail_bits = [] + if copied: + detail_bits.append(f"{copied} updated") + if skipped: + detail_bits.append(f"{skipped} unchanged") + detail = ", ".join(detail_bits) if detail_bits else "ok" + + if tracker: + tracker.complete("codex-prompts", detail) + + # If CODEX_HOME lives inside this project, make sure generated files stay untracked + try: + codex_home_relative = codex_home.resolve().relative_to(project_path.resolve()) + except Exception: + return + + codex_prefix = codex_home_relative.as_posix() + if codex_prefix == ".": + return + ignore_entries = [ + f"{codex_prefix}/*.json", + f"{codex_prefix}/*.jsonl", + f"{codex_prefix}/*.toml", + f"{codex_prefix}/log", + f"{codex_prefix}/sessions", + ] + _ensure_gitignore_entries(project_path, ignore_entries) + + except Exception as exc: + if tracker: + tracker.error("codex-prompts", str(exc)) + else: + console.print(f"[yellow]Warning: could not sync Codex prompts ({exc})[/yellow]") + + @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), @@ -1125,6 +1243,7 @@ def init( if selected_ai == "codex": tracker.add("commands", "Ensure workspace commands") + tracker.add("codex-prompts", "Sync Codex prompts") # 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: @@ -1140,6 +1259,7 @@ def init( # Ensure /commands directory for Codex CLI workspaces only if selected_ai == "codex": ensure_workspace_commands(project_path, tracker=tracker) + sync_codex_prompts(project_path, tracker=tracker) # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, tracker=tracker) diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 41b8f6f..2ea4f78 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -5,7 +5,9 @@ scripts: ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}" --- -Given the feature description provided as an argument, do this: +The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided a truly empty command. + +Given that feature description, do this: 1. Run the script `{SCRIPT}` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute. 2. Load `templates/spec-template.md` to understand required sections. From 6a3ff650f12431659e4966f94d01ce99c3e2cf35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 09:09:24 -0700 Subject: [PATCH 08/13] Remove Codex-specific logic in the initialization script --- CHANGELOG.md | 2 +- src/specify_cli/__init__.py | 342 ------------------------------------ 2 files changed, 1 insertion(+), 343 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65eaab5..3f49434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Windsurf IDE support as additional AI assistant option +- Windsurf IDE support as additional AI assistant option (thank you [@raedkit](https://github.com/raedkit) for the work in [#151](https://github.com/github/spec-kit/pull/151)) - GitHub token support for API requests to handle corporate environments and rate limiting (contributed by [@zryfish](https://github.com/@zryfish) in [#243](https://github.com/github/spec-kit/pull/243)) ### Changed diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index e026104..b898f38 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -78,156 +78,6 @@ SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} # Claude CLI local installation path after migrate-installer CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude" -# Embedded fallback command templates (used if packaged templates are unavailable) -COMMAND_TEMPLATE_SPECIFY = """--- -description: Create or update the feature specification from a natural language feature description. -scripts: - sh: scripts/bash/create-new-feature.sh --json "{ARGS}" - ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}" ---- - -Given the feature description provided as an argument, do this: - -1. Run the script `{SCRIPT}` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute. -2. Load `templates/spec-template.md` to understand required sections. -3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings. -4. Report completion with branch name, spec file path, and readiness for the next phase. - -Note: The script creates and checks out the new branch and initializes the spec file before writing. -""" - -COMMAND_TEMPLATE_PLAN = """--- -description: Execute the implementation planning workflow using the plan template to generate design artifacts. -scripts: - sh: scripts/bash/setup-plan.sh --json - ps: scripts/powershell/setup-plan.ps1 -Json ---- - -Given the implementation details provided as an argument, do this: - -1. Run `{SCRIPT}` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute. -2. Read and analyze the feature specification to understand: - - The feature requirements and user stories - - Functional and non-functional requirements - - Success criteria and acceptance criteria - - Any technical constraints or dependencies mentioned - -3. Read the constitution at `memory/constitution.md` to understand constitutional requirements. - -4. Execute the implementation plan template: - - Load `templates/plan-template.md` (already copied to IMPL_PLAN path) - - Set Input path to FEATURE_SPEC - - Run the Execution Flow (main) function steps 1-9 - - The template is self-contained and executable - - Follow error handling and gate checks as specified - - Let the template guide artifact generation in $SPECS_DIR: - * Phase 0 generates research.md - * Phase 1 generates data-model.md, contracts/, quickstart.md - * Phase 2 generates tasks.md - - Incorporate user-provided details from arguments into Technical Context: {ARGS} - - Update Progress Tracking as you complete each phase - -5. Verify execution completed: - - Check Progress Tracking shows all phases complete - - Ensure all required artifacts were generated - - Confirm no ERROR states in execution - -6. Report results with branch name, file paths, and generated artifacts. - -Use absolute paths with the repository root for all file operations to avoid path issues. -""" - -COMMAND_TEMPLATE_TASKS = """--- -description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts. -scripts: - sh: scripts/bash/check-task-prerequisites.sh --json - ps: scripts/powershell/check-task-prerequisites.ps1 -Json ---- - -Given the context provided as an argument, do this: - -1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. -2. Load and analyze available design documents: - - Always read plan.md for tech stack and libraries - - IF EXISTS: Read data-model.md for entities - - IF EXISTS: Read contracts/ for API endpoints - - IF EXISTS: Read research.md for technical decisions - - IF EXISTS: Read quickstart.md for test scenarios - - Note: Not all projects have all documents. For example: - - CLI tools might not have contracts/ - - Simple libraries might not need data-model.md - - Generate tasks based on what's available - -3. Generate tasks following the template: - - Use `templates/tasks-template.md` as the base - - Replace example tasks with actual tasks based on: - * **Setup tasks**: Project init, dependencies, linting - * **Test tasks [P]**: One per contract, one per integration scenario - * **Core tasks**: One per entity, service, CLI command, endpoint - * **Integration tasks**: DB connections, middleware, logging - * **Polish tasks [P]**: Unit tests, performance, docs - -4. Task generation rules: - - Each contract file β†’ contract test task marked [P] - - Each entity in data-model β†’ model creation task marked [P] - - Each endpoint β†’ implementation task (not parallel if shared files) - - Each user story β†’ integration test marked [P] - - Different files = can be parallel [P] - - Same file = sequential (no [P]) - -5. Order tasks by dependencies: - - Setup before everything - - Tests before implementation (TDD) - - Models before services - - Services before endpoints - - Core before integration - - Everything before polish - -6. Include parallel execution examples: - - Group [P] tasks that can run together - - Show actual Task agent commands - -7. Create FEATURE_DIR/tasks.md with: - - Correct feature name from implementation plan - - Numbered tasks (T001, T002, etc.) - - Clear file paths for each task - - Dependency notes - - Parallel execution guidance - -Context for task generation: {ARGS} - -The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context. -""" - - -# Utility to ensure command templates use the modern schema (with scripts mapping) -def ensure_command_templates_current(commands_dir: Path) -> None: - expected = { - "specify.md": COMMAND_TEMPLATE_SPECIFY, - "plan.md": COMMAND_TEMPLATE_PLAN, - "tasks.md": COMMAND_TEMPLATE_TASKS, - } - - def needs_upgrade(content: str) -> bool: - # Old templates lacked the scripts: mapping and {SCRIPT} placeholder - return "scripts:" not in content or "{SCRIPT}" not in content - - for filename, template_text in expected.items(): - target_file = commands_dir / filename - if target_file.exists(): - try: - current = target_file.read_text(encoding="utf-8") - except Exception: - target_file.write_text(template_text, encoding="utf-8") - else: - if needs_upgrade(current): - target_file.write_text(template_text, encoding="utf-8") - else: - target_file.parent.mkdir(parents=True, exist_ok=True) - target_file.write_text(template_text, encoding="utf-8") - - # ASCII Art Banner BANNER = """ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— @@ -910,187 +760,6 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = for f in failures: console.print(f" - {f}") - -def ensure_workspace_commands(project_path: Path, tracker: StepTracker | None = None) -> None: - """Ensure a workspace-level commands/ directory exists and has up-to-date templates.""" - if tracker: - tracker.start("commands") - - commands_dir = project_path / "commands" - seeded_from: str | None = None - - try: - existed = commands_dir.exists() - if not existed: - commands_dir.mkdir(parents=True, exist_ok=True) - - try: - is_empty = not any(commands_dir.iterdir()) - except FileNotFoundError: - is_empty = True - - should_seed = not existed or is_empty - - if should_seed: - candidates: list[tuple[str, Path]] = [] - - template_commands = project_path / ".specify" / "templates" / "commands" - if template_commands.exists() and template_commands.is_dir(): - candidates.append(("release bundle", template_commands)) - - packaged_commands = None - for ancestor in Path(__file__).resolve().parents: - candidate = ancestor / "templates" / "commands" - if candidate.exists() and candidate.is_dir(): - packaged_commands = candidate - break - if packaged_commands is not None: - candidates.append(("packaged defaults", packaged_commands)) - - for label, source in candidates: - try: - shutil.copytree(source, commands_dir, dirs_exist_ok=True) - seeded_from = label - break - except Exception: - continue - - if seeded_from is None: - seeded_from = "embedded defaults" - - ensure_command_templates_current(commands_dir) - - detail = "verified" if seeded_from is None else seeded_from - if tracker: - tracker.complete("commands", detail) - else: - if seeded_from: - console.print(f"[cyan]Seeded workspace commands from {seeded_from}[/cyan]") - except Exception as exc: - if tracker: - tracker.error("commands", str(exc)) - else: - console.print(f"[yellow]Warning: could not ensure commands directory ({exc})[/yellow]") - - -def _resolve_codex_home() -> Path: - env_value = os.environ.get("CODEX_HOME") - if env_value: - return Path(env_value).expanduser() - return Path.home() / ".codex" - - -def _ensure_gitignore_entries(project_path: Path, entries: list[str]) -> None: - if not entries: - return - - gitignore_path = project_path / ".gitignore" - - existing_text = "" - existing: set[str] = set() - if gitignore_path.exists(): - try: - existing_text = gitignore_path.read_text(encoding="utf-8") - existing = {line.strip() for line in existing_text.splitlines()} - except Exception: - return - - new_entries = [entry for entry in entries if entry not in existing] - if not new_entries: - return - - try: - with gitignore_path.open("a", encoding="utf-8") as fh: - if existing_text and not existing_text.endswith("\n"): - fh.write("\n") - for entry in new_entries: - fh.write(f"{entry}\n") - except Exception: - return - - -def sync_codex_prompts(project_path: Path, tracker: StepTracker | None = None) -> None: - if tracker: - tracker.start("codex-prompts") - - commands_dir = project_path / "commands" - if not commands_dir.is_dir(): - if tracker: - tracker.skip("codex-prompts", "no commands directory") - return - - try: - codex_home = _resolve_codex_home() - prompts_dir = (codex_home / "prompts").expanduser() - prompts_dir.mkdir(parents=True, exist_ok=True) - - if not os.access(prompts_dir, os.W_OK): - raise PermissionError(f"Codex prompts directory not writable: {prompts_dir}") - - expected: set[str] = set() - copied = 0 - skipped = 0 - - for source in sorted(commands_dir.glob("*.md")): - if not source.is_file(): - continue - dest_name = source.name - dest_path = prompts_dir / dest_name - expected.add(dest_name) - - data = source.read_bytes() - if dest_path.exists(): - try: - if dest_path.read_bytes() == data: - skipped += 1 - continue - except Exception: - pass - dest_path.write_bytes(data) - copied += 1 - - # Clean up any legacy spec-kit-prefixed prompts from earlier installer versions - for legacy in prompts_dir.glob("spec-kit-*.md"): - try: - legacy.unlink() - except Exception: - continue - - detail_bits = [] - if copied: - detail_bits.append(f"{copied} updated") - if skipped: - detail_bits.append(f"{skipped} unchanged") - detail = ", ".join(detail_bits) if detail_bits else "ok" - - if tracker: - tracker.complete("codex-prompts", detail) - - # If CODEX_HOME lives inside this project, make sure generated files stay untracked - try: - codex_home_relative = codex_home.resolve().relative_to(project_path.resolve()) - except Exception: - return - - codex_prefix = codex_home_relative.as_posix() - if codex_prefix == ".": - return - ignore_entries = [ - f"{codex_prefix}/*.json", - f"{codex_prefix}/*.jsonl", - f"{codex_prefix}/*.toml", - f"{codex_prefix}/log", - f"{codex_prefix}/sessions", - ] - _ensure_gitignore_entries(project_path, ignore_entries) - - except Exception as exc: - if tracker: - tracker.error("codex-prompts", str(exc)) - else: - console.print(f"[yellow]Warning: could not sync Codex prompts ({exc})[/yellow]") - - @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), @@ -1275,10 +944,6 @@ def init( ]: tracker.add(key, label) - if selected_ai == "codex": - tracker.add("commands", "Ensure workspace commands") - tracker.add("codex-prompts", "Sync Codex prompts") - # 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: tracker.attach_refresh(lambda: live.update(tracker.render())) @@ -1290,11 +955,6 @@ def init( download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token) - # Ensure /commands directory for Codex CLI workspaces only - if selected_ai == "codex": - ensure_workspace_commands(project_path, tracker=tracker) - sync_codex_prompts(project_path, tracker=tracker) - # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, tracker=tracker) @@ -1352,8 +1012,6 @@ def init( steps_lines.append(" 2.3 [cyan]/plan[/] - Create implementation plans") steps_lines.append(" 2.4 [cyan]/tasks[/] - Generate actionable tasks") steps_lines.append(" 2.5 [cyan]/implement[/] - Execute implementation") - if selected_ai == "codex": - steps_lines.append(" 2.6 [cyan]Codex CLI[/] - Restart Codex if slash commands are missing; commands mirror into AGENTS.md") steps_panel = Panel("\n".join(steps_lines), title="Next steps", border_style="cyan", padding=(1,2)) console.print() From dc8fdc2dc8f2cd78300683cea75574833489af4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 09:13:34 -0700 Subject: [PATCH 09/13] Update __init__.py --- src/specify_cli/__init__.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index b898f38..8610ec0 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -461,7 +461,7 @@ 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 (with fallback for Codex) + # Find the template asset for the specified AI assistant assets = release_data.get("assets", []) pattern = f"spec-kit-template-{ai_assistant}-{script_type}" matching_assets = [ @@ -471,17 +471,6 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri asset = matching_assets[0] if matching_assets else None - if asset is None and ai_assistant == "codex": - fallback_pattern = f"spec-kit-template-copilot-{script_type}" - fallback_assets = [ - asset for asset in assets - if fallback_pattern in asset["name"] and asset["name"].endswith(".zip") - ] - if fallback_assets: - asset = fallback_assets[0] - if verbose: - console.print("[yellow]No Codex-specific template found; falling back to GitHub Copilot template.[/yellow]") - if asset is None: console.print( f"[red]No matching release asset found[/red] for AI assistant " From caee341af9a54d9d40906f6c50be9b12dfa0336a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 10:06:59 -0700 Subject: [PATCH 10/13] Update __init__.py --- src/specify_cli/__init__.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 8610ec0..4eb36fb 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -472,15 +472,11 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri asset = matching_assets[0] if matching_assets else None if asset is None: - console.print( - f"[red]No matching release asset found[/red] for AI assistant " - f"[bold]{ai_assistant}[/bold] (pattern: {pattern})" - ) + console.print(f"[red]No matching release asset found[/red] for [bold]{ai_assistant}[/bold] (expected pattern: [bold]{pattern}[/bold])") asset_names = [a.get('name', '?') for a in assets] console.print(Panel("\n".join(asset_names) or "(no assets)", title="Available Assets", border_style="yellow")) raise typer.Exit(1) - # Use the resolved asset download_url = asset["browser_download_url"] filename = asset["name"] file_size = asset["size"] @@ -489,14 +485,12 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri console.print(f"[cyan]Found template:[/cyan] {filename}") console.print(f"[cyan]Size:[/cyan] {file_size:,} bytes") console.print(f"[cyan]Release:[/cyan] {release_data['tag_name']}") - - # Download the file + zip_path = download_dir / filename if verbose: console.print(f"[cyan]Downloading template...[/cyan]") try: - # Include auth header for initial GitHub request; it won't leak across cross-origin redirects with client.stream( "GET", download_url, From d7d2c145c7c6ce18912e8ddee4538540e0c4f931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 10:10:23 -0700 Subject: [PATCH 11/13] Update config --- README.md | 8 -------- templates/commands/plan.md | 4 ++-- templates/commands/specify.md | 2 +- templates/commands/tasks.md | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3680be7..b2dbca5 100644 --- a/README.md +++ b/README.md @@ -262,14 +262,6 @@ The CLI will check if you have Claude Code, Gemini CLI, Cursor CLI, Qwen CLI, op specify init --ai claude --ignore-agent-tools ``` -> [!NOTE] -> Codex CLI specifics -> - `specify init --ai codex` seeds `commands/*.md` in your repo and automatically mirrors them into `${CODEX_HOME:-~/.codex}/prompts` so Codex picks up `/specify`, `/plan`, and `/tasks` immediately. -> - If Codex was running during installation, restart the CLI once so it reloads the refreshed slash commands. -> - Codex persists its working memory in `AGENTS.md`; if you do not see that file yet, run `codex /init` once inside the project to generate it. -> - When Codex is configured to run helper scripts, open `codex /approvals` and enable β€œRun shell commands”. -> - If you point `CODEX_HOME` at the project (for per-repo isolation) the installer adds ignore rules for Codex session artifacts to `.gitignore`; adjust them as needed for your workflow. - ### **STEP 1:** Establish project principles Go to the project folder and run your AI agent. In our example, we're using `claude`. diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 6315d0e..18a0b5c 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -14,10 +14,10 @@ Given the implementation details provided as an argument, do this: - Success criteria and acceptance criteria - Any technical constraints or dependencies mentioned -3. Read the constitution at `memory/constitution.md` to understand constitutional requirements. +3. Read the constitution at `/memory/constitution.md` to understand constitutional requirements. 4. Execute the implementation plan template: - - Load `templates/plan-template.md` (already copied to IMPL_PLAN path) + - Load `/templates/plan-template.md` (already copied to IMPL_PLAN path) - Set Input path to FEATURE_SPEC - Run the Execution Flow (main) function steps 1-9 - The template is self-contained and executable diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 8544b3f..9a6a3b3 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -5,7 +5,7 @@ scripts: ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}" --- -The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided a truly empty command. +The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided an empty command. Given that feature description, do this: diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index fe89b95..29b4cd2 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -21,7 +21,7 @@ Given the context provided as an argument, do this: - Generate tasks based on what's available 3. Generate tasks following the template: - - Use `templates/tasks-template.md` as the base + - Use `/templates/tasks-template.md` as the base - Replace example tasks with actual tasks based on: * **Setup tasks**: Project init, dependencies, linting * **Test tasks [P]**: One per contract, one per integration scenario From 2aa30cdbb21f5e54f91ab3c4ffdca731a4c42840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 10:13:20 -0700 Subject: [PATCH 12/13] Update package release --- .github/workflows/release.yml | 6 +++++- .github/workflows/scripts/create-release-packages.sh | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c12f0ff..675ee3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,7 +81,7 @@ jobs: cat > release_notes.md << EOF Template release ${{ steps.get_tag.outputs.new_version }} - Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, opencode, and Windsurf. + Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, opencode, Windsurf, and Codex. Now includes per-script variants for POSIX shell (sh) and PowerShell (ps). @@ -100,6 +100,8 @@ jobs: - spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip - spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip - spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip + - spec-kit-template-codex-sh-${{ steps.get_tag.outputs.new_version }}.zip + - spec-kit-template-codex-ps-${{ steps.get_tag.outputs.new_version }}.zip EOF echo "Generated release notes:" @@ -126,6 +128,8 @@ jobs: spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \ spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip \ spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + spec-kit-template-codex-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + spec-kit-template-codex-ps-${{ steps.get_tag.outputs.new_version }}.zip \ --title "Spec Kit Templates - $VERSION_NO_V" \ --notes-file release_notes.md env: diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index 8d7eaf2..05b5cce 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -6,7 +6,7 @@ set -euo pipefail # Usage: .github/workflows/scripts/create-release-packages.sh # Version argument should include leading 'v'. # Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built. -# AGENTS : space or comma separated subset of: claude gemini copilot qwen opencode (default: all) +# AGENTS : space or comma separated subset of: claude gemini copilot cursor qwen opencode windsurf codex (default: all) # SCRIPTS : space or comma separated subset of: sh ps (default: both) # Examples: # AGENTS=claude SCRIPTS=sh $0 v0.2.0 @@ -157,13 +157,16 @@ build_variant() { windsurf) mkdir -p "$base_dir/.windsurf/workflows" generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;; + codex) + mkdir -p "$base_dir/.codex/commands" + generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/commands" "$script" ;; esac ( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . ) echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" } # Determine agent list -ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf) +ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf codex) ALL_SCRIPTS=(sh ps) From 60b8d8fad232a2b7c30d1e84ebcf3c9b8ec0f8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 10:26:22 -0700 Subject: [PATCH 13/13] Update update-agent-context.ps1 --- scripts/powershell/update-agent-context.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 0a12728..133b454 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -77,6 +77,7 @@ switch ($AgentType) { 'qwen' { Update-AgentFile $qwenFile 'Qwen Code' } 'opencode' { Update-AgentFile $agentsFile 'opencode' } 'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' } + 'codex' { Update-AgentFile $agentsFile 'Codex CLI' } '' { foreach ($pair in @( @{file=$claudeFile; name='Claude Code'}, @@ -85,7 +86,8 @@ switch ($AgentType) { @{file=$cursorFile; name='Cursor IDE'}, @{file=$qwenFile; name='Qwen Code'}, @{file=$agentsFile; name='opencode'}, - @{file=$windsurfFile; name='Windsurf'} + @{file=$windsurfFile; name='Windsurf'}, + @{file=$agentsFile; name='Codex CLI'} )) { if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name } } @@ -94,7 +96,7 @@ switch ($AgentType) { Update-AgentFile $claudeFile 'Claude Code' } } - Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode, windsurf or leave empty for all."; exit 1 } + Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode, windsurf, codex or leave empty for all."; exit 1 } } Write-Output '' @@ -104,4 +106,4 @@ if ($newFramework) { Write-Output "- Added framework: $newFramework" } if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" } Write-Output '' -Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode|windsurf]' +Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode|windsurf|codex]'