Compare commits

...

25 Commits

Author SHA1 Message Date
Den Delimarsky 🌺
219ad02e4a Updating agent context files 2025-09-20 11:05:08 -07:00
Den Delimarsky
6f1970a0bd Merge pull request #14 from honjo-hiroaki-gtt/feat/add-codex-support
feat: Add Codex CLI support
2025-09-20 10:28:40 -07:00
Den Delimarsky 🌺
60b8d8fad2 Update update-agent-context.ps1 2025-09-20 10:26:22 -07:00
Den Delimarsky 🌺
2aa30cdbb2 Update package release 2025-09-20 10:13:20 -07:00
Den Delimarsky 🌺
d7d2c145c7 Update config 2025-09-20 10:10:23 -07:00
Den Delimarsky 🌺
caee341af9 Update __init__.py 2025-09-20 10:06:59 -07:00
Den Delimarsky 🌺
dc8fdc2dc8 Update __init__.py 2025-09-20 09:13:34 -07:00
Den Delimarsky 🌺
6a3ff650f1 Remove Codex-specific logic in the initialization script 2025-09-20 09:09:24 -07:00
Den Delimarsky
8784f39755 Merge branch 'main' into feat/add-codex-support 2025-09-20 08:54:00 -07:00
Den Delimarsky 🌺
fbacd0b0df Update version rev 2025-09-20 08:49:44 -07:00
Den Delimarsky 🌺
db9d97bcbd Update __init__.py 2025-09-20 08:46:32 -07:00
honjo-hiroaki-gtt
537332725b Resolve merge conflicts integrating Codexsupport with upstream Windsurf updates 2025-09-20 23:42:36 +09:00
honjo-hiroaki-gtt
65ccbb62ca Enhance Codex support by auto-syncing prompt files, allowing spec generation without git, and documenting clearer /specify usage. 2025-09-20 22:44:44 +09:00
Den Delimarsky 🌺
5659c869b5 Consistency tweaks 2025-09-20 00:24:06 -07:00
Den Delimarsky 🌺
d8bf98a88d Consistent step coloring 2025-09-20 00:21:01 -07:00
Den Delimarsky 🌺
e482072520 Update __init__.py 2025-09-20 00:17:10 -07:00
Den Delimarsky 🌺
aaa6df9653 Update __init__.py 2025-09-20 00:11:27 -07:00
Den Delimarsky 🌺
8c3e9db3bf Quick UI tweak 2025-09-19 18:24:29 -07:00
Den Delimarsky 🌺
d1d5c82a8e Update package release 2025-09-19 18:07:18 -07:00
honjo-hiroaki-gtt
3a0ae75bfb Limit workspace command seeding to Codex init and update Codex documentation accordingly. 2025-09-19 17:53:16 +09:00
honjo-hiroaki-gtt
312703260c Clarify Codex-specific README note with rationale for its different workflow. 2025-09-19 16:40:41 +09:00
honjo-hiroaki-gtt
286ad553fd Bump to 0.0.7 and document Codex support 2025-09-18 15:39:30 +09:00
honjo-hiroaki-gtt
a39185c8be Normalize Codex command templates to the scripts-based schema and auto-upgrade generated commands. 2025-09-18 15:39:30 +09:00
honjo-hiroaki-gtt
e29488d91f Fix remaining merge conflict markers in __init__.py 2025-09-18 15:39:30 +09:00
honjo-hiroaki-gtt
95fba17d20 Add Codex CLI support with AGENTS.md and commands bootstrap 2025-09-18 15:39:30 +09:00
11 changed files with 823 additions and 123 deletions

View File

@@ -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:

View File

@@ -6,7 +6,7 @@ set -euo pipefail
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
# 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)

View File

@@ -10,6 +10,10 @@ The toolkit supports multiple AI coding assistants, allowing teams to use their
---
## General practices
- Any changes to `__init__.py` for the Specify CLI require a version rev in `pyproject.toml` and addition of entries to `CHANGELOG.md`.
## Adding New Agent Support
This section explains how to add support for new AI agents/assistants to the Specify CLI. Use this guide as a reference when integrating new AI tools into the Spec-Driven Development workflow.

View File

@@ -5,7 +5,36 @@ All notable changes to the Specify CLI will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.0.11] - 2025-09-20
### Added
- Codex CLI support (thank you [@honjo-hiroaki-gtt](https://github.com/honjo-hiroaki-gtt) for the contribution in [#14](https://github.com/github/spec-kit/pull/14))
- Codex-aware context update tooling (Bash and PowerShell) so feature plans refresh `AGENTS.md` alongside existing assistants without manual edits.
## [0.0.10] - 2025-09-20
### Fixed
- Addressed [#378](https://github.com/github/spec-kit/issues/378) where a GitHub token may be attached to the request when it was empty.
## [0.0.9] - 2025-09-19
### Changed
- Improved agent selector UI with cyan highlighting for agent keys and gray parentheses for full names
## [0.0.8] - 2025-09-19
### Added
- 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
- Updated README with Windsurf examples and GitHub token usage
- Enhanced release workflow to include Windsurf templates
## [0.0.7] - 2025-09-18
@@ -40,4 +69,3 @@ N/A
### Changed
N/A

View File

@@ -101,20 +101,21 @@ The `specify` command supports the following options:
| Command | Description |
|-------------|----------------------------------------------------------------|
| `init` | Initialize a new Specify project from the latest template |
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `opencode`, `cursor-agent`) |
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`) |
### `specify init` Arguments & Options
| Argument/Option | Type | Description |
|------------------------|----------|------------------------------------------------------------------------------|
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`) |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `opencode`, or `cursor` |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, `qwen`, `opencode`, or `windsurf` |
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
| `--no-git` | Flag | Skip git repository initialization |
| `--here` | Flag | Initialize project in the current directory instead of creating a new one |
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
### Examples
@@ -128,6 +129,9 @@ specify init my-project --ai claude
# Initialize with Cursor support
specify init my-project --ai cursor
# Initialize with Windsurf support
specify init my-project --ai windsurf
# Initialize with PowerShell scripts (Windows/cross-platform)
specify init my-project --ai copilot --script ps
@@ -140,6 +144,9 @@ specify init my-project --ai gemini --no-git
# Enable debug output for troubleshooting
specify init my-project --ai claude --debug
# Use GitHub token for API requests (helpful for corporate environments)
specify init my-project --ai claude --github-token ghp_your_token_here
# Check system requirements
specify check
```
@@ -202,7 +209,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/), [Codex CLI](https://github.com/openai/codex), or [Windsurf](https://windsurf.com/)
- [uv](https://docs.astral.sh/uv/) for package management
- [Python 3.11+](https://www.python.org/downloads/)
- [Git](https://git-scm.com/downloads)
@@ -239,13 +246,17 @@ You will be prompted to select the AI agent you are using. You can also proactiv
specify init <project_name> --ai claude
specify init <project_name> --ai gemini
specify init <project_name> --ai copilot
specify init <project_name> --ai cursor
specify init <project_name> --ai qwen
specify init <project_name> --ai opencode
specify init <project_name> --ai codex
specify init <project_name> --ai windsurf
# 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, Cursor 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 <project_name> --ai claude --ignore-agent-tools

View File

@@ -1,6 +1,6 @@
[project]
name = "specify-cli"
version = "0.0.7"
version = "0.0.10"
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
requires-python = ">=3.11"
dependencies = [

View File

@@ -18,7 +18,21 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
exit 1
fi
# 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}"
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"

View File

@@ -1,68 +1,683 @@
#!/usr/bin/env bash
# Update agent context files with information from plan.md
#
# This script maintains AI agent context files by parsing feature specifications
# and updating agent-specific configuration files with project information.
#
# MAIN FUNCTIONS:
# 1. Environment Validation
# - Verifies git repository structure and branch information
# - Checks for required plan.md files and templates
# - Validates file permissions and accessibility
#
# 2. Plan Data Extraction
# - Parses plan.md files to extract project metadata
# - Identifies language/version, frameworks, databases, and project types
# - Handles missing or incomplete specification data gracefully
#
# 3. Agent File Management
# - Creates new agent context files from templates when needed
# - Updates existing agent files with new project information
# - Preserves manual additions and custom configurations
# - Supports multiple AI agent formats and directory structures
#
# 4. Content Generation
# - Generates language-specific build/test commands
# - Creates appropriate project directory structures
# - Updates technology stacks and recent changes sections
# - Maintains consistent formatting and timestamps
#
# 5. Multi-Agent Support
# - Handles agent-specific file paths and naming conventions
# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf
# - Can update single agents or all existing agent files
# - Creates default Claude file if no agent files exist
#
# Usage: ./update-agent-context.sh [agent_type]
# Agent types: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf
# Leave empty to update all existing agent files
set -e
# Enable strict error handling
set -u
set -o pipefail
#==============================================================================
# Configuration and Global Variables
#==============================================================================
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"; CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"; QWEN_FILE="$REPO_ROOT/QWEN.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md"; WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.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 ==="
NEW_LANG=$(grep "^**Language/Version**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Language\/Version**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
NEW_FRAMEWORK=$(grep "^**Primary Dependencies**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Primary Dependencies**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
NEW_DB=$(grep "^**Storage**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Storage**: //' | grep -v "N/A" | grep -v "NEEDS CLARIFICATION" || echo "")
NEW_PROJECT_TYPE=$(grep "^**Project Type**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Project Type**: //' || echo "")
update_agent_file() { local target_file="$1" agent_name="$2"; echo "Updating $agent_name context file: $target_file"; local temp_file=$(mktemp); if [ ! -f "$target_file" ]; then
echo "Creating new $agent_name context file..."; if [ -f "$REPO_ROOT/.specify/templates/agent-file-template.md" ]; then cp "$REPO_ROOT/.specify/templates/agent-file-template.md" "$temp_file"; else echo "ERROR: Template not found"; return 1; fi;
sed -i.bak "s/\[PROJECT NAME\]/$(basename $REPO_ROOT)/" "$temp_file"; sed -i.bak "s/\[DATE\]/$(date +%Y-%m-%d)/" "$temp_file"; sed -i.bak "s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/" "$temp_file";
if [[ "$NEW_PROJECT_TYPE" == *"web"* ]]; then sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|backend/\nfrontend/\ntests/|" "$temp_file"; else sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|src/\ntests/|" "$temp_file"; fi;
if [[ "$NEW_LANG" == *"Python"* ]]; then COMMANDS="cd src && pytest && ruff check ."; elif [[ "$NEW_LANG" == *"Rust"* ]]; then COMMANDS="cargo test && cargo clippy"; elif [[ "$NEW_LANG" == *"JavaScript"* ]] || [[ "$NEW_LANG" == *"TypeScript"* ]]; then COMMANDS="npm test && npm run lint"; else COMMANDS="# Add commands for $NEW_LANG"; fi; sed -i.bak "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$COMMANDS|" "$temp_file";
sed -i.bak "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$NEW_LANG: Follow standard conventions|" "$temp_file"; sed -i.bak "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK|" "$temp_file"; rm "$temp_file.bak";
AGENT_TYPE="${1:-}"
# Agent-specific file paths
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"
CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
QWEN_FILE="$REPO_ROOT/QWEN.md"
AGENTS_FILE="$REPO_ROOT/AGENTS.md"
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
# Template file
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
# Global variables for parsed plan data
NEW_LANG=""
NEW_FRAMEWORK=""
NEW_DB=""
NEW_PROJECT_TYPE=""
#==============================================================================
# Utility Functions
#==============================================================================
log_info() {
echo "INFO: $1"
}
log_success() {
echo "$1"
}
log_error() {
echo "ERROR: $1" >&2
}
log_warning() {
echo "WARNING: $1" >&2
}
# Cleanup function for temporary files
cleanup() {
local exit_code=$?
rm -f /tmp/agent_update_*_$$
rm -f /tmp/manual_additions_$$
exit $exit_code
}
# Set up cleanup trap
trap cleanup EXIT INT TERM
#==============================================================================
# Validation Functions
#==============================================================================
validate_environment() {
# Check if we're in a git repository
if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
log_error "Not in a git repository"
exit 1
fi
# Check if we have a current branch
if [[ -z "$CURRENT_BRANCH" ]]; then
log_error "Unable to determine current git branch"
exit 1
fi
# Check if plan.md exists
if [[ ! -f "$NEW_PLAN" ]]; then
log_error "No plan.md found at $NEW_PLAN"
log_info "Make sure you're on a feature branch with a corresponding spec directory"
exit 1
fi
# Check if template exists (needed for new files)
if [[ ! -f "$TEMPLATE_FILE" ]]; then
log_warning "Template file not found at $TEMPLATE_FILE"
log_warning "Creating new agent files will fail"
fi
}
#==============================================================================
# Plan Parsing Functions
#==============================================================================
extract_plan_field() {
local field_pattern="$1"
local plan_file="$2"
grep "^**${field_pattern}**: " "$plan_file" 2>/dev/null | \
head -1 | \
sed "s/^**${field_pattern}**: //" | \
grep -v "NEEDS CLARIFICATION" | \
grep -v "^N/A$" || echo ""
}
parse_plan_data() {
local plan_file="$1"
if [[ ! -f "$plan_file" ]]; then
log_error "Plan file not found: $plan_file"
return 1
fi
if [[ ! -r "$plan_file" ]]; then
log_error "Plan file is not readable: $plan_file"
return 1
fi
log_info "Parsing plan data from $plan_file"
NEW_LANG=$(extract_plan_field "Language/Version" "$plan_file")
NEW_FRAMEWORK=$(extract_plan_field "Primary Dependencies" "$plan_file")
NEW_DB=$(extract_plan_field "Storage" "$plan_file")
NEW_PROJECT_TYPE=$(extract_plan_field "Project Type" "$plan_file")
# Log what we found
if [[ -n "$NEW_LANG" ]]; then
log_info "Found language: $NEW_LANG"
else
echo "Updating existing $agent_name context file..."; manual_start=$(grep -n "<!-- MANUAL ADDITIONS START -->" "$target_file" | cut -d: -f1); manual_end=$(grep -n "<!-- MANUAL ADDITIONS END -->" "$target_file" | cut -d: -f1); if [ -n "$manual_start" ] && [ -n "$manual_end" ]; then sed -n "${manual_start},${manual_end}p" "$target_file" > /tmp/manual_additions.txt; fi;
python3 - "$target_file" <<'EOF'
import re,sys,datetime
target=sys.argv[1]
with open(target) as f: content=f.read()
NEW_LANG="'$NEW_LANG'";NEW_FRAMEWORK="'$NEW_FRAMEWORK'";CURRENT_BRANCH="'$CURRENT_BRANCH'";NEW_DB="'$NEW_DB'";NEW_PROJECT_TYPE="'$NEW_PROJECT_TYPE'"
# Tech section
m=re.search(r'## Active Technologies\n(.*?)\n\n',content, re.DOTALL)
if m:
existing=m.group(1)
additions=[]
if '$NEW_LANG' and '$NEW_LANG' not in existing: additions.append(f"- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)")
if '$NEW_DB' and '$NEW_DB' not in existing and '$NEW_DB'!='N/A': additions.append(f"- $NEW_DB ($CURRENT_BRANCH)")
if additions:
new_block=existing+"\n"+"\n".join(additions)
content=content.replace(m.group(0),f"## Active Technologies\n{new_block}\n\n")
# Recent changes
m2=re.search(r'## Recent Changes\n(.*?)(\n\n|$)',content, re.DOTALL)
if m2:
lines=[l for l in m2.group(1).strip().split('\n') if l]
lines.insert(0,f"- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK")
lines=lines[:3]
content=re.sub(r'## Recent Changes\n.*?(\n\n|$)', '## Recent Changes\n'+"\n".join(lines)+'\n\n', content, flags=re.DOTALL)
content=re.sub(r'Last updated: \d{4}-\d{2}-\d{2}', 'Last updated: '+datetime.datetime.now().strftime('%Y-%m-%d'), content)
open(target+'.tmp','w').write(content)
EOF
mv "$target_file.tmp" "$target_file"; if [ -f /tmp/manual_additions.txt ]; then sed -i.bak '/<!-- MANUAL ADDITIONS START -->/,/<!-- MANUAL ADDITIONS END -->/d' "$target_file"; cat /tmp/manual_additions.txt >> "$target_file"; rm /tmp/manual_additions.txt "$target_file.bak"; fi;
fi; mv "$temp_file" "$target_file" 2>/dev/null || true; echo "$agent_name context file updated successfully"; }
case "$AGENT_TYPE" in
claude) update_agent_file "$CLAUDE_FILE" "Claude Code" ;;
gemini) update_agent_file "$GEMINI_FILE" "Gemini CLI" ;;
copilot) update_agent_file "$COPILOT_FILE" "GitHub Copilot" ;;
cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;;
qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;;
opencode) update_agent_file "$AGENTS_FILE" "opencode" ;;
windsurf) update_agent_file "$WINDSURF_FILE" "Windsurf" ;;
"") [ -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 "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf"; \
if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ] && [ ! -f "$WINDSURF_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;;
*) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode|windsurf)"; exit 1 ;;
log_warning "No language information found in plan"
fi
if [[ -n "$NEW_FRAMEWORK" ]]; then
log_info "Found framework: $NEW_FRAMEWORK"
fi
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
log_info "Found database: $NEW_DB"
fi
if [[ -n "$NEW_PROJECT_TYPE" ]]; then
log_info "Found project type: $NEW_PROJECT_TYPE"
fi
}
#==============================================================================
# Template and Content Generation Functions
#==============================================================================
get_project_structure() {
local project_type="$1"
if [[ "$project_type" == *"web"* ]]; then
echo "backend/
frontend/
tests/"
else
echo "src/
tests/"
fi
}
get_commands_for_language() {
local lang="$1"
case "$lang" in
*"Python"*)
echo "cd src && pytest && ruff check ."
;;
*"Rust"*)
echo "cargo test && cargo clippy"
;;
*"JavaScript"*|*"TypeScript"*)
echo "npm test && npm run lint"
;;
*)
echo "# Add commands for $lang"
;;
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|windsurf]"
}
get_language_conventions() {
local lang="$1"
echo "$lang: Follow standard conventions"
}
create_new_agent_file() {
local target_file="$1"
local temp_file="$2"
local project_name="$3"
local current_date="$4"
if [[ ! -f "$TEMPLATE_FILE" ]]; then
log_error "Template not found at $TEMPLATE_FILE"
return 1
fi
if [[ ! -r "$TEMPLATE_FILE" ]]; then
log_error "Template file is not readable: $TEMPLATE_FILE"
return 1
fi
log_info "Creating new agent context file from template..."
if ! cp "$TEMPLATE_FILE" "$temp_file"; then
log_error "Failed to copy template file"
return 1
fi
# Replace template placeholders
local project_structure
project_structure=$(get_project_structure "$NEW_PROJECT_TYPE")
local commands
commands=$(get_commands_for_language "$NEW_LANG")
local language_conventions
language_conventions=$(get_language_conventions "$NEW_LANG")
# Perform substitutions with error checking
local substitutions=(
"s/\[PROJECT NAME\]/$project_name/"
"s/\[DATE\]/$current_date/"
"s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/"
"s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|"
"s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|"
"s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|"
"s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK|"
)
for substitution in "${substitutions[@]}"; do
if ! sed -i.bak "$substitution" "$temp_file"; then
log_error "Failed to perform substitution: $substitution"
rm -f "$temp_file" "$temp_file.bak"
return 1
fi
done
# Clean up backup files
rm -f "$temp_file.bak"
return 0
}
update_active_technologies() {
local target_file="$1"
local temp_file="$2"
# Find the Active Technologies section and add new entries
local tech_section_start
tech_section_start=$(grep -n "## Active Technologies" "$target_file" | cut -d: -f1)
if [[ -z "$tech_section_start" ]]; then
return 0 # No Active Technologies section found
fi
# Find the end of the Active Technologies section (next ## heading or empty line)
local tech_section_end
tech_section_end=$(tail -n +$((tech_section_start + 1)) "$target_file" | grep -n "^## \|^$" | head -1 | cut -d: -f1)
if [[ -n "$tech_section_end" ]]; then
tech_section_end=$((tech_section_start + tech_section_end))
else
tech_section_end=$(wc -l < "$target_file")
fi
# Extract existing technologies section
local existing_tech
existing_tech=$(sed -n "${tech_section_start},${tech_section_end}p" "$target_file")
# Build list of new additions
local additions=()
if [[ -n "$NEW_LANG" ]] && ! echo "$existing_tech" | grep -q "$NEW_LANG"; then
additions+=("- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)")
fi
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && ! echo "$existing_tech" | grep -q "$NEW_DB"; then
additions+=("- $NEW_DB ($CURRENT_BRANCH)")
fi
# If we have additions, update the section
if [[ ${#additions[@]} -gt 0 ]]; then
{
# Copy everything before the Active Technologies section
head -n $((tech_section_start)) "$target_file"
# Copy existing tech section content
sed -n "$((tech_section_start + 1)),$((tech_section_end - 1))p" "$target_file"
# Add new technologies
printf '%s\n' "${additions[@]}"
echo
# Copy everything after the Active Technologies section
tail -n +$((tech_section_end + 1)) "$target_file"
} > "$temp_file"
else
cp "$target_file" "$temp_file"
fi
}
update_recent_changes() {
local temp_file="$1"
local temp_file2="$2"
# Find Recent Changes section
local changes_section_start
changes_section_start=$(grep -n "## Recent Changes" "$temp_file" | cut -d: -f1)
if [[ -z "$changes_section_start" ]]; then
return 0 # No Recent Changes section found
fi
# Find the end of the Recent Changes section
local changes_section_end
changes_section_end=$(tail -n +$((changes_section_start + 1)) "$temp_file" | grep -n "^## \|^$" | head -1 | cut -d: -f1)
if [[ -n "$changes_section_end" ]]; then
changes_section_end=$((changes_section_start + changes_section_end))
else
changes_section_end=$(wc -l < "$temp_file")
fi
# Extract existing changes, keep only non-empty lines, and limit to 2 (so we can add 1 new one)
local existing_changes=()
while IFS= read -r line; do
if [[ -n "$line" ]] && [[ "$line" == "- "* ]]; then
existing_changes+=("$line")
fi
done < <(sed -n "$((changes_section_start + 1)),$((changes_section_end - 1))p" "$temp_file")
# Keep only the first 2 existing changes
existing_changes=("${existing_changes[@]:0:2}")
# Create updated Recent Changes section
{
# Copy everything before Recent Changes
head -n "$changes_section_start" "$temp_file"
# Add new change at the top
echo "- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK"
# Add existing changes (up to 2)
printf '%s\n' "${existing_changes[@]}"
echo
# Copy everything after Recent Changes section
tail -n +$((changes_section_end + 1)) "$temp_file"
} > "$temp_file2"
}
update_last_updated() {
local temp_file="$1"
local current_date="$2"
# Update the "Last updated" timestamp
sed -i.bak "s/Last updated: [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/Last updated: $current_date/" "$temp_file"
rm -f "$temp_file.bak"
}
preserve_manual_additions() {
local target_file="$1"
local temp_file="$2"
# Check if there are manual additions to preserve
local manual_start manual_end
manual_start=$(grep -n "<!-- MANUAL ADDITIONS START -->" "$target_file" 2>/dev/null | cut -d: -f1 || echo "")
manual_end=$(grep -n "<!-- MANUAL ADDITIONS END -->" "$target_file" 2>/dev/null | cut -d: -f1 || echo "")
if [[ -n "$manual_start" ]] && [[ -n "$manual_end" ]]; then
# Extract manual additions
local manual_file="/tmp/manual_additions_$$"
sed -n "${manual_start},${manual_end}p" "$target_file" > "$manual_file"
# Remove any existing manual additions from temp file
sed -i.bak '/<!-- MANUAL ADDITIONS START -->/,/<!-- MANUAL ADDITIONS END -->/d' "$temp_file"
rm -f "$temp_file.bak"
# Append preserved manual additions
cat "$manual_file" >> "$temp_file"
rm -f "$manual_file"
fi
}
update_existing_agent_file() {
local target_file="$1"
local current_date="$2"
log_info "Updating existing agent context file..."
local temp_file1="/tmp/agent_update_1_$$"
local temp_file2="/tmp/agent_update_2_$$"
# Step 1: Update Active Technologies section
update_active_technologies "$target_file" "$temp_file1"
# Step 2: Update Recent Changes section
update_recent_changes "$temp_file1" "$temp_file2"
# Step 3: Update timestamp
update_last_updated "$temp_file2" "$current_date"
# Step 4: Preserve manual additions
preserve_manual_additions "$target_file" "$temp_file2"
# Move the final result to target
mv "$temp_file2" "$target_file"
# Cleanup
rm -f "$temp_file1"
}
#==============================================================================
# Main Agent File Update Function
#==============================================================================
update_agent_file() {
local target_file="$1"
local agent_name="$2"
if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then
log_error "update_agent_file requires target_file and agent_name parameters"
return 1
fi
log_info "Updating $agent_name context file: $target_file"
local project_name
project_name=$(basename "$REPO_ROOT")
local current_date
current_date=$(date +%Y-%m-%d)
# Create directory if it doesn't exist
local target_dir
target_dir=$(dirname "$target_file")
if [[ ! -d "$target_dir" ]]; then
if ! mkdir -p "$target_dir"; then
log_error "Failed to create directory: $target_dir"
return 1
fi
fi
if [[ ! -f "$target_file" ]]; then
# Create new file from template
local temp_file
temp_file=$(mktemp) || {
log_error "Failed to create temporary file"
return 1
}
if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then
if mv "$temp_file" "$target_file"; then
log_success "Created new $agent_name context file"
else
log_error "Failed to move temporary file to $target_file"
rm -f "$temp_file"
return 1
fi
else
log_error "Failed to create new agent file"
rm -f "$temp_file"
return 1
fi
else
# Update existing file
if [[ ! -r "$target_file" ]]; then
log_error "Cannot read existing file: $target_file"
return 1
fi
if [[ ! -w "$target_file" ]]; then
log_error "Cannot write to existing file: $target_file"
return 1
fi
if update_existing_agent_file "$target_file" "$current_date"; then
log_success "Updated existing $agent_name context file"
else
log_error "Failed to update existing agent file"
return 1
fi
fi
return 0
}
#==============================================================================
# Agent Selection and Processing
#==============================================================================
update_specific_agent() {
local agent_type="$1"
case "$agent_type" in
claude)
update_agent_file "$CLAUDE_FILE" "Claude Code"
;;
gemini)
update_agent_file "$GEMINI_FILE" "Gemini CLI"
;;
copilot)
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
;;
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"
;;
windsurf)
update_agent_file "$WINDSURF_FILE" "Windsurf"
;;
*)
log_error "Unknown agent type '$agent_type'"
log_error "Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf"
exit 1
;;
esac
}
update_all_existing_agents() {
local found_agent=false
# Check each possible agent file and update if it exists
if [[ -f "$CLAUDE_FILE" ]]; then
update_agent_file "$CLAUDE_FILE" "Claude Code"
found_agent=true
fi
if [[ -f "$GEMINI_FILE" ]]; then
update_agent_file "$GEMINI_FILE" "Gemini CLI"
found_agent=true
fi
if [[ -f "$COPILOT_FILE" ]]; then
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
found_agent=true
fi
if [[ -f "$CURSOR_FILE" ]]; then
update_agent_file "$CURSOR_FILE" "Cursor IDE"
found_agent=true
fi
if [[ -f "$QWEN_FILE" ]]; then
update_agent_file "$QWEN_FILE" "Qwen Code"
found_agent=true
fi
if [[ -f "$AGENTS_FILE" ]]; then
update_agent_file "$AGENTS_FILE" "Codex/opencode"
found_agent=true
fi
if [[ -f "$WINDSURF_FILE" ]]; then
update_agent_file "$WINDSURF_FILE" "Windsurf"
found_agent=true
fi
# If no agent files exist, create a default Claude file
if [[ "$found_agent" == false ]]; then
log_info "No existing agent files found, creating default Claude file..."
update_agent_file "$CLAUDE_FILE" "Claude Code"
fi
}
print_summary() {
echo
log_info "Summary of changes:"
if [[ -n "$NEW_LANG" ]]; then
echo " - Added language: $NEW_LANG"
fi
if [[ -n "$NEW_FRAMEWORK" ]]; then
echo " - Added framework: $NEW_FRAMEWORK"
fi
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
echo " - Added database: $NEW_DB"
fi
echo
log_info "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf]"
}
#==============================================================================
# Main Execution
#==============================================================================
main() {
# Validate environment before proceeding
validate_environment
log_info "=== Updating agent context files for feature $CURRENT_BRANCH ==="
# Parse the plan file to extract project information
if ! parse_plan_data "$NEW_PLAN"; then
log_error "Failed to parse plan data"
exit 1
fi
# Process based on agent type argument
local success=true
if [[ -z "$AGENT_TYPE" ]]; then
# No specific agent provided - update all existing agent files
log_info "No agent specified, updating all existing agent files..."
if ! update_all_existing_agents; then
success=false
fi
else
# Specific agent provided - update only that agent
log_info "Updating specific agent: $AGENT_TYPE"
if ! update_specific_agent "$AGENT_TYPE"; then
success=false
fi
fi
# Print summary
print_summary
if [[ "$success" == true ]]; then
log_success "Agent context update completed successfully"
exit 0
else
log_error "Agent context update completed with errors"
exit 1
fi
}
# Execute main function if script is run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi

View File

@@ -66,7 +66,7 @@ function Update-AgentFile($targetFile, $agentName) {
}
$content = [regex]::Replace($content, 'Last updated: \d{4}-\d{2}-\d{2}', "Last updated: $(Get-Date -Format 'yyyy-MM-dd')")
$content | Set-Content $targetFile -Encoding UTF8
Write-Output " $agentName context file updated successfully"
Write-Output " $agentName context file updated successfully"
}
switch ($AgentType) {
@@ -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]'

View File

@@ -53,17 +53,13 @@ ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client = httpx.Client(verify=ssl_context)
def _github_token(cli_token: str | None = None) -> str | None:
return cli_token or os.getenv("GH_TOKEN") or os.getenv("GITHUB_TOKEN")
"""Return sanitized GitHub token (cli arg takes precedence) or None."""
return ((cli_token or os.getenv("GH_TOKEN") or os.getenv("GITHUB_TOKEN") or "").strip()) or None
def _github_auth_headers(cli_token: str | None = None) -> dict:
"""Headers for GitHub REST API requests.
- Uses Bearer auth if token present
"""
headers = {}
"""Return Authorization header dict only when a non-empty token exists."""
token = _github_token(cli_token)
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
return {"Authorization": f"Bearer {token}"} if token else {}
# Constants
AI_CHOICES = {
@@ -73,7 +69,8 @@ AI_CHOICES = {
"cursor": "Cursor",
"qwen": "Qwen Code",
"opencode": "opencode",
"windsurf": "Windsurf"
"codex": "Codex CLI",
"windsurf": "Windsurf",
}
# Add script type choices
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
@@ -142,7 +139,7 @@ class StepTracker:
pass
def render(self):
tree = Tree(f"[bold cyan]{self.title}[/bold cyan]", guide_style="grey50")
tree = Tree(f"[cyan]{self.title}[/cyan]", guide_style="grey50")
for step in self.steps:
label = step["label"]
detail_text = step["detail"].strip() if step["detail"] else ""
@@ -235,14 +232,14 @@ def select_with_arrows(options: dict, prompt_text: str = "Select an option", def
def create_selection_panel():
"""Create the selection panel with current selection highlighted."""
table = Table.grid(padding=(0, 2))
table.add_column(style="bright_cyan", justify="left", width=3)
table.add_column(style="cyan", justify="left", width=3)
table.add_column(style="white", justify="left")
for i, key in enumerate(option_keys):
if i == selected_index:
table.add_row("", f"[bright_cyan]{key}: {options[key]}[/bright_cyan]")
table.add_row("", f"[cyan]{key}[/cyan] [dim]({options[key]})[/dim]")
else:
table.add_row(" ", f"[white]{key}: {options[key]}[/white]")
table.add_row(" ", f"[cyan]{key}[/cyan] [dim]({options[key]})[/dim]")
table.add_row("", "")
table.add_row("", "[dim]Use ↑/↓ to navigate, Enter to select, Esc to cancel[/dim]")
@@ -447,7 +444,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
api_url,
timeout=30,
follow_redirects=True,
headers=_github_auth_headers(github_token) or None,
headers=_github_auth_headers(github_token),
)
status = response.status_code
if status != 200:
@@ -465,20 +462,21 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
raise typer.Exit(1)
# 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 = [
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:
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 = matching_assets[0] if matching_assets else None
if asset is None:
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 first matching asset
asset = matching_assets[0]
download_url = asset["browser_download_url"]
filename = asset["name"]
file_size = asset["size"]
@@ -488,19 +486,17 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
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,
timeout=60,
follow_redirects=True,
headers=_github_auth_headers(github_token) or None,
headers=_github_auth_headers(github_token),
) as response:
if response.status_code != 200:
body_sample = response.text[:400]
@@ -747,11 +743,10 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
for f in failures:
console.print(f" - {f}")
@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, opencode or windsurf"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, codex, or windsurf"),
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"),
@@ -765,7 +760,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, opencode or Windsurf)
2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, or Windsurf)
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)
@@ -778,9 +773,12 @@ def init(
specify init my-project --ai copilot --no-git
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 my-project --ai windsurf
specify init --ignore-agent-tools my-project
specify init --here --ai claude
specify init --here --ai codex
specify init --here
"""
# Show banner first
@@ -818,12 +816,21 @@ def init(
console.print(f"[red]Error:[/red] Directory '{project_name}' already exists")
raise typer.Exit(1)
console.print(Panel.fit(
"[bold cyan]Specify Project Setup[/bold cyan]\n"
f"{'Initializing in current directory:' if here else 'Creating new project:'} [green]{project_path.name}[/green]"
+ (f"\n[dim]Path: {project_path}[/dim]" if here else ""),
border_style="cyan"
))
# Create formatted setup info with column alignment
current_dir = Path.cwd()
setup_lines = [
"[cyan]Specify Project Setup[/cyan]",
"",
f"{'Project':<15} [green]{project_path.name}[/green]",
f"{'Working Path':<15} [dim]{current_dir}[/dim]",
]
# Add target path only if different from working dir
if not here:
setup_lines.append(f"{'Target Path':<15} [dim]{project_path}[/dim]")
console.print(Panel("\n".join(setup_lines), border_style="cyan", padding=(1, 2)))
# Check git only if we might need it (not --no-git)
# Only set to True if the user wants it and the tool is available
@@ -866,6 +873,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:
@@ -979,11 +990,11 @@ def init(
step_num = 2
steps_lines.append(f"{step_num}. Start using slash commands with your AI agent:")
steps_lines.append(" 2.1 [bold cyan]/constitution[/] - Establish project principles")
steps_lines.append(" 2.2 [bold cyan]/specify[/] - Create specifications")
steps_lines.append(" 2.3 [bold cyan]/plan[/] - Create implementation plans")
steps_lines.append(" 2.4 [bold cyan]/tasks[/] - Generate actionable tasks")
steps_lines.append(" 2.5 [bold cyan]/implement[/] - Execute implementation")
steps_lines.append(" 2.1 [cyan]/constitution[/] - Establish project principles")
steps_lines.append(" 2.2 [cyan]/specify[/] - Create specifications")
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")
steps_panel = Panel("\n".join(steps_lines), title="Next steps", border_style="cyan", padding=(1,2))
console.print()
@@ -1005,6 +1016,7 @@ def check():
tracker.add("cursor-agent", "Cursor IDE agent (optional)")
tracker.add("windsurf", "Windsurf IDE (optional)")
tracker.add("opencode", "opencode")
tracker.add("codex", "Codex CLI")
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)
@@ -1016,6 +1028,7 @@ def check():
cursor_ok = check_tool_for_tracker("cursor-agent", "https://cursor.sh/", tracker)
windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", 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)
console.print(tracker.render())
@@ -1023,7 +1036,7 @@ def check():
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 windsurf_ok or opencode_ok):
if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or opencode_ok or codex_ok):
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")

View File

@@ -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 an 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.
**IMPORTANT** You must only ever run this script once. The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for.