Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0889635e66 | ||
|
|
2825bb1247 | ||
|
|
919ba00198 | ||
|
|
4e869cb11a | ||
|
|
b1688b9633 | ||
|
|
70413f5214 |
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
|||||||
cat > release_notes.md << EOF
|
cat > release_notes.md << EOF
|
||||||
Template release ${{ steps.get_tag.outputs.new_version }}
|
Template release ${{ steps.get_tag.outputs.new_version }}
|
||||||
|
|
||||||
Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, and opencode.
|
Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, opencode, and Windsurf.
|
||||||
|
|
||||||
Now includes per-script variants for POSIX shell (sh) and PowerShell (ps).
|
Now includes per-script variants for POSIX shell (sh) and PowerShell (ps).
|
||||||
|
|
||||||
@@ -98,6 +98,8 @@ jobs:
|
|||||||
- spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
- spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
||||||
- spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
- spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
||||||
- spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
- 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
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Generated release notes:"
|
echo "Generated release notes:"
|
||||||
@@ -122,6 +124,8 @@ jobs:
|
|||||||
spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||||
spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||||
spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
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 \
|
||||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||||
--notes-file release_notes.md
|
--notes-file release_notes.md
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -154,13 +154,16 @@ build_variant() {
|
|||||||
opencode)
|
opencode)
|
||||||
mkdir -p "$base_dir/.opencode/command"
|
mkdir -p "$base_dir/.opencode/command"
|
||||||
generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$script" ;;
|
generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$script" ;;
|
||||||
|
windsurf)
|
||||||
|
mkdir -p "$base_dir/.windsurf/workflows"
|
||||||
|
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
||||||
esac
|
esac
|
||||||
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
||||||
echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine agent list
|
# Determine agent list
|
||||||
ALL_AGENTS=(claude gemini copilot cursor qwen opencode)
|
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf)
|
||||||
ALL_SCRIPTS=(sh ps)
|
ALL_SCRIPTS=(sh ps)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
230
AGENTS.md
Normal file
230
AGENTS.md
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## About Spec Kit and Specify
|
||||||
|
|
||||||
|
**GitHub Spec Kit** is a comprehensive toolkit for implementing Spec-Driven Development (SDD) - a methodology that emphasizes creating clear specifications before implementation. The toolkit includes templates, scripts, and workflows that guide development teams through a structured approach to building software.
|
||||||
|
|
||||||
|
**Specify CLI** is the command-line interface that bootstraps projects with the Spec Kit framework. It sets up the necessary directory structures, templates, and AI agent integrations to support the Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
The toolkit supports multiple AI coding assistants, allowing teams to use their preferred tools while maintaining consistent project structure and development practices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
Specify supports multiple AI agents by generating agent-specific command files and directory structures when initializing projects. Each agent has its own conventions for:
|
||||||
|
|
||||||
|
- **Command file formats** (Markdown, TOML, etc.)
|
||||||
|
- **Directory structures** (`.claude/commands/`, `.windsurf/workflows/`, etc.)
|
||||||
|
- **Command invocation patterns** (slash commands, CLI tools, etc.)
|
||||||
|
- **Argument passing conventions** (`$ARGUMENTS`, `{{args}}`, etc.)
|
||||||
|
|
||||||
|
### Current Supported Agents
|
||||||
|
|
||||||
|
| Agent | Directory | Format | CLI Tool | Description |
|
||||||
|
|-------|-----------|---------|----------|-------------|
|
||||||
|
| **Claude Code** | `.claude/commands/` | Markdown | `claude` | Anthropic's Claude Code CLI |
|
||||||
|
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
|
||||||
|
| **GitHub Copilot** | `.github/prompts/` | Markdown | N/A (IDE-based) | GitHub Copilot in VS Code |
|
||||||
|
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
|
||||||
|
| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI |
|
||||||
|
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
|
||||||
|
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
|
||||||
|
|
||||||
|
### Step-by-Step Integration Guide
|
||||||
|
|
||||||
|
Follow these steps to add a new agent (using Windsurf as an example):
|
||||||
|
|
||||||
|
#### 1. Update AI_CHOICES Constant
|
||||||
|
|
||||||
|
Add the new agent to the `AI_CHOICES` dictionary in `src/specify_cli/__init__.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
AI_CHOICES = {
|
||||||
|
"copilot": "GitHub Copilot",
|
||||||
|
"claude": "Claude Code",
|
||||||
|
"gemini": "Gemini CLI",
|
||||||
|
"cursor": "Cursor",
|
||||||
|
"qwen": "Qwen Code",
|
||||||
|
"opencode": "opencode",
|
||||||
|
"windsurf": "Windsurf" # Add new agent here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Update CLI Help Text
|
||||||
|
|
||||||
|
Update all help text and examples to include the new agent:
|
||||||
|
|
||||||
|
- Command option help: `--ai` parameter description
|
||||||
|
- Function docstrings and examples
|
||||||
|
- Error messages with agent lists
|
||||||
|
|
||||||
|
#### 3. Update Release Package Script
|
||||||
|
|
||||||
|
Modify `.github/workflows/scripts/create-release-packages.sh`:
|
||||||
|
|
||||||
|
##### Add to ALL_AGENTS array:
|
||||||
|
```bash
|
||||||
|
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Add case statement for directory structure:
|
||||||
|
```bash
|
||||||
|
case $agent in
|
||||||
|
# ... existing cases ...
|
||||||
|
windsurf)
|
||||||
|
mkdir -p "$base_dir/.windsurf/workflows"
|
||||||
|
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
||||||
|
esac
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Update Agent Context Scripts
|
||||||
|
|
||||||
|
##### Bash script (`scripts/bash/update-agent-context.sh`):
|
||||||
|
|
||||||
|
Add file variable:
|
||||||
|
```bash
|
||||||
|
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to case statement:
|
||||||
|
```bash
|
||||||
|
case "$AGENT_TYPE" in
|
||||||
|
# ... existing cases ...
|
||||||
|
windsurf) update_agent_file "$WINDSURF_FILE" "Windsurf" ;;
|
||||||
|
"")
|
||||||
|
# ... existing checks ...
|
||||||
|
[ -f "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf";
|
||||||
|
# Update default creation condition
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
```
|
||||||
|
|
||||||
|
##### PowerShell script (`scripts/powershell/update-agent-context.ps1`):
|
||||||
|
|
||||||
|
Add file variable:
|
||||||
|
```powershell
|
||||||
|
$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md'
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to switch statement:
|
||||||
|
```powershell
|
||||||
|
switch ($AgentType) {
|
||||||
|
# ... existing cases ...
|
||||||
|
'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' }
|
||||||
|
'' {
|
||||||
|
foreach ($pair in @(
|
||||||
|
# ... existing pairs ...
|
||||||
|
@{file=$windsurfFile; name='Windsurf'}
|
||||||
|
)) {
|
||||||
|
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
||||||
|
}
|
||||||
|
# Update default creation condition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Update CLI Tool Checks (Optional)
|
||||||
|
|
||||||
|
For agents that require CLI tools, add checks in the `check()` command and agent validation:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# In check() command
|
||||||
|
tracker.add("windsurf", "Windsurf IDE (optional)")
|
||||||
|
windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", tracker)
|
||||||
|
|
||||||
|
# In init validation (only if CLI tool required)
|
||||||
|
elif selected_ai == "windsurf":
|
||||||
|
if not check_tool("windsurf", "Install from: https://windsurf.com/"):
|
||||||
|
console.print("[red]Error:[/red] Windsurf CLI is required for Windsurf projects")
|
||||||
|
agent_tool_missing = True
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Skip CLI checks for IDE-based agents (Copilot, Windsurf).
|
||||||
|
|
||||||
|
## Agent Categories
|
||||||
|
|
||||||
|
### CLI-Based Agents
|
||||||
|
Require a command-line tool to be installed:
|
||||||
|
- **Claude Code**: `claude` CLI
|
||||||
|
- **Gemini CLI**: `gemini` CLI
|
||||||
|
- **Cursor**: `cursor-agent` CLI
|
||||||
|
- **Qwen Code**: `qwen` CLI
|
||||||
|
- **opencode**: `opencode` CLI
|
||||||
|
|
||||||
|
### IDE-Based Agents
|
||||||
|
Work within integrated development environments:
|
||||||
|
- **GitHub Copilot**: Built into VS Code/compatible editors
|
||||||
|
- **Windsurf**: Built into Windsurf IDE
|
||||||
|
|
||||||
|
## Command File Formats
|
||||||
|
|
||||||
|
### Markdown Format
|
||||||
|
Used by: Claude, Cursor, opencode, Windsurf
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
description: "Command description"
|
||||||
|
---
|
||||||
|
|
||||||
|
Command content with {SCRIPT} and $ARGUMENTS placeholders.
|
||||||
|
```
|
||||||
|
|
||||||
|
### TOML Format
|
||||||
|
Used by: Gemini, Qwen
|
||||||
|
|
||||||
|
```toml
|
||||||
|
description = "Command description"
|
||||||
|
|
||||||
|
prompt = """
|
||||||
|
Command content with {SCRIPT} and {{args}} placeholders.
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Conventions
|
||||||
|
|
||||||
|
- **CLI agents**: Usually `.<agent-name>/commands/`
|
||||||
|
- **IDE agents**: Follow IDE-specific patterns:
|
||||||
|
- Copilot: `.github/prompts/`
|
||||||
|
- Cursor: `.cursor/commands/`
|
||||||
|
- Windsurf: `.windsurf/workflows/`
|
||||||
|
|
||||||
|
## Argument Patterns
|
||||||
|
|
||||||
|
Different agents use different argument placeholders:
|
||||||
|
- **Markdown/prompt-based**: `$ARGUMENTS`
|
||||||
|
- **TOML-based**: `{{args}}`
|
||||||
|
- **Script placeholders**: `{SCRIPT}` (replaced with actual script path)
|
||||||
|
- **Agent placeholders**: `__AGENT__` (replaced with agent name)
|
||||||
|
|
||||||
|
## Testing New Agent Integration
|
||||||
|
|
||||||
|
1. **Build test**: Run package creation script locally
|
||||||
|
2. **CLI test**: Test `specify init --ai <agent>` command
|
||||||
|
3. **File generation**: Verify correct directory structure and files
|
||||||
|
4. **Command validation**: Ensure generated commands work with the agent
|
||||||
|
5. **Context update**: Test agent context update scripts
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
1. **Forgetting update scripts**: Both bash and PowerShell scripts must be updated
|
||||||
|
2. **Missing CLI checks**: Only add for agents that actually have CLI tools
|
||||||
|
3. **Wrong argument format**: Use correct placeholder format for each agent type
|
||||||
|
4. **Directory naming**: Follow agent-specific conventions exactly
|
||||||
|
5. **Help text inconsistency**: Update all user-facing text consistently
|
||||||
|
|
||||||
|
## Future Considerations
|
||||||
|
|
||||||
|
When adding new agents:
|
||||||
|
- Consider the agent's native command/workflow patterns
|
||||||
|
- Ensure compatibility with the Spec-Driven Development process
|
||||||
|
- Document any special requirements or limitations
|
||||||
|
- Update this guide with lessons learned
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
||||||
@@ -4,7 +4,7 @@ REPO_ROOT=$(git rev-parse --show-toplevel)
|
|||||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||||
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
|
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
|
||||||
NEW_PLAN="$FEATURE_DIR/plan.md"
|
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"
|
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"
|
AGENT_TYPE="$1"
|
||||||
[ -f "$NEW_PLAN" ] || { echo "ERROR: No plan.md found at $NEW_PLAN"; exit 1; }
|
[ -f "$NEW_PLAN" ] || { echo "ERROR: No plan.md found at $NEW_PLAN"; exit 1; }
|
||||||
echo "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
echo "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||||
@@ -54,13 +54,15 @@ case "$AGENT_TYPE" in
|
|||||||
cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;;
|
cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;;
|
||||||
qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;;
|
qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;;
|
||||||
opencode) update_agent_file "$AGENTS_FILE" "opencode" ;;
|
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 "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"; \
|
||||||
[ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; \
|
[ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; \
|
||||||
[ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; \
|
[ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; \
|
||||||
[ -f "$CURSOR_FILE" ] && update_agent_file "$CURSOR_FILE" "Cursor IDE"; \
|
[ -f "$CURSOR_FILE" ] && update_agent_file "$CURSOR_FILE" "Cursor IDE"; \
|
||||||
[ -f "$QWEN_FILE" ] && update_agent_file "$QWEN_FILE" "Qwen Code"; \
|
[ -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" "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 ;;
|
[ -f "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf"; \
|
||||||
*) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode)"; exit 1 ;;
|
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 ;;
|
||||||
esac
|
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|windsurf]"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ $copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md'
|
|||||||
$cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc'
|
$cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc'
|
||||||
$qwenFile = Join-Path $repoRoot 'QWEN.md'
|
$qwenFile = Join-Path $repoRoot 'QWEN.md'
|
||||||
$agentsFile = Join-Path $repoRoot 'AGENTS.md'
|
$agentsFile = Join-Path $repoRoot 'AGENTS.md'
|
||||||
|
$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md'
|
||||||
|
|
||||||
Write-Output "=== Updating agent context files for feature $currentBranch ==="
|
Write-Output "=== Updating agent context files for feature $currentBranch ==="
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ switch ($AgentType) {
|
|||||||
'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' }
|
'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' }
|
||||||
'qwen' { Update-AgentFile $qwenFile 'Qwen Code' }
|
'qwen' { Update-AgentFile $qwenFile 'Qwen Code' }
|
||||||
'opencode' { Update-AgentFile $agentsFile 'opencode' }
|
'opencode' { Update-AgentFile $agentsFile 'opencode' }
|
||||||
|
'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' }
|
||||||
'' {
|
'' {
|
||||||
foreach ($pair in @(
|
foreach ($pair in @(
|
||||||
@{file=$claudeFile; name='Claude Code'},
|
@{file=$claudeFile; name='Claude Code'},
|
||||||
@@ -82,16 +84,17 @@ switch ($AgentType) {
|
|||||||
@{file=$copilotFile; name='GitHub Copilot'},
|
@{file=$copilotFile; name='GitHub Copilot'},
|
||||||
@{file=$cursorFile; name='Cursor IDE'},
|
@{file=$cursorFile; name='Cursor IDE'},
|
||||||
@{file=$qwenFile; name='Qwen Code'},
|
@{file=$qwenFile; name='Qwen Code'},
|
||||||
@{file=$agentsFile; name='opencode'}
|
@{file=$agentsFile; name='opencode'},
|
||||||
|
@{file=$windsurfFile; name='Windsurf'}
|
||||||
)) {
|
)) {
|
||||||
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
||||||
}
|
}
|
||||||
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile)) {
|
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile) -and -not (Test-Path $windsurfFile)) {
|
||||||
Write-Output 'No agent context files found. Creating Claude Code context file by default.'
|
Write-Output 'No agent context files found. Creating Claude Code context file by default.'
|
||||||
Update-AgentFile $claudeFile 'Claude Code'
|
Update-AgentFile $claudeFile 'Claude Code'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode or leave empty for all."; exit 1 }
|
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode, windsurf or leave empty for all."; exit 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Output ''
|
Write-Output ''
|
||||||
@@ -101,4 +104,4 @@ if ($newFramework) { Write-Output "- Added framework: $newFramework" }
|
|||||||
if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" }
|
if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" }
|
||||||
|
|
||||||
Write-Output ''
|
Write-Output ''
|
||||||
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode]'
|
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode|windsurf]'
|
||||||
|
|||||||
@@ -52,6 +52,19 @@ import truststore
|
|||||||
ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
client = httpx.Client(verify=ssl_context)
|
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")
|
||||||
|
|
||||||
|
def _github_auth_headers(cli_token: str | None = None) -> dict:
|
||||||
|
"""Headers for GitHub REST API requests.
|
||||||
|
- Uses Bearer auth if token present
|
||||||
|
"""
|
||||||
|
headers = {}
|
||||||
|
token = _github_token(cli_token)
|
||||||
|
if token:
|
||||||
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
|
return headers
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
AI_CHOICES = {
|
AI_CHOICES = {
|
||||||
"copilot": "GitHub Copilot",
|
"copilot": "GitHub Copilot",
|
||||||
@@ -59,7 +72,8 @@ AI_CHOICES = {
|
|||||||
"gemini": "Gemini CLI",
|
"gemini": "Gemini CLI",
|
||||||
"cursor": "Cursor",
|
"cursor": "Cursor",
|
||||||
"qwen": "Qwen Code",
|
"qwen": "Qwen Code",
|
||||||
"opencode": "opencode"
|
"opencode": "opencode",
|
||||||
|
"windsurf": "Windsurf"
|
||||||
}
|
}
|
||||||
# Add script type choices
|
# Add script type choices
|
||||||
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
||||||
@@ -77,7 +91,7 @@ BANNER = """
|
|||||||
╚══════╝╚═╝ ╚══════╝ ╚═════╝╚═╝╚═╝ ╚═╝
|
╚══════╝╚═╝ ╚══════╝ ╚═════╝╚═╝╚═╝ ╚═╝
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TAGLINE = "Spec-Driven Development Toolkit"
|
TAGLINE = "GitHub Spec Kit - Spec-Driven Development Toolkit"
|
||||||
class StepTracker:
|
class StepTracker:
|
||||||
"""Track and render hierarchical steps without emojis, similar to Claude Code tree output.
|
"""Track and render hierarchical steps without emojis, similar to Claude Code tree output.
|
||||||
Supports live auto-refresh via an attached refresh callback.
|
Supports live auto-refresh via an attached refresh callback.
|
||||||
@@ -418,7 +432,7 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
|
|||||||
os.chdir(original_cwd)
|
os.chdir(original_cwd)
|
||||||
|
|
||||||
|
|
||||||
def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None, debug: bool = False) -> Tuple[Path, dict]:
|
def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None, debug: bool = False, github_token: str = None) -> Tuple[Path, dict]:
|
||||||
repo_owner = "github"
|
repo_owner = "github"
|
||||||
repo_name = "spec-kit"
|
repo_name = "spec-kit"
|
||||||
if client is None:
|
if client is None:
|
||||||
@@ -429,7 +443,12 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
|
|||||||
api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest"
|
api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = client.get(api_url, timeout=30, follow_redirects=True)
|
response = client.get(
|
||||||
|
api_url,
|
||||||
|
timeout=30,
|
||||||
|
follow_redirects=True,
|
||||||
|
headers=_github_auth_headers(github_token) or None,
|
||||||
|
)
|
||||||
status = response.status_code
|
status = response.status_code
|
||||||
if status != 200:
|
if status != 200:
|
||||||
msg = f"GitHub API returned {status} for {api_url}"
|
msg = f"GitHub API returned {status} for {api_url}"
|
||||||
@@ -475,7 +494,14 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
|
|||||||
console.print(f"[cyan]Downloading template...[/cyan]")
|
console.print(f"[cyan]Downloading template...[/cyan]")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with client.stream("GET", download_url, timeout=60, follow_redirects=True) as response:
|
# 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,
|
||||||
|
) as response:
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
body_sample = response.text[:400]
|
body_sample = response.text[:400]
|
||||||
raise RuntimeError(f"Download failed with {response.status_code}\nHeaders: {response.headers}\nBody (truncated): {body_sample}")
|
raise RuntimeError(f"Download failed with {response.status_code}\nHeaders: {response.headers}\nBody (truncated): {body_sample}")
|
||||||
@@ -519,7 +545,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
|
|||||||
return zip_path, metadata
|
return zip_path, metadata
|
||||||
|
|
||||||
|
|
||||||
def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False) -> Path:
|
def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False, github_token: str = None) -> Path:
|
||||||
"""Download the latest release and extract it to create a new project.
|
"""Download the latest release and extract it to create a new project.
|
||||||
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
|
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
|
||||||
"""
|
"""
|
||||||
@@ -536,7 +562,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
|
|||||||
verbose=verbose and tracker is None,
|
verbose=verbose and tracker is None,
|
||||||
show_progress=(tracker is None),
|
show_progress=(tracker is None),
|
||||||
client=client,
|
client=client,
|
||||||
debug=debug
|
debug=debug,
|
||||||
|
github_token=github_token
|
||||||
)
|
)
|
||||||
if tracker:
|
if tracker:
|
||||||
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
|
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
|
||||||
@@ -724,20 +751,21 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
|
|||||||
@app.command()
|
@app.command()
|
||||||
def init(
|
def init(
|
||||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
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 windsurf"),
|
||||||
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
|
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"),
|
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"),
|
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
|
||||||
here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
|
here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
|
||||||
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
|
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
|
||||||
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
|
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
|
||||||
|
github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize a new Specify project from the latest template.
|
Initialize a new Specify project from the latest template.
|
||||||
|
|
||||||
This command will:
|
This command will:
|
||||||
1. Check that required tools are installed (git is optional)
|
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 Windsurf)
|
||||||
3. Download the appropriate template from GitHub
|
3. Download the appropriate template from GitHub
|
||||||
4. Extract the template to a new project directory or current directory
|
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)
|
5. Initialize a fresh git repository (if not --no-git and no existing repo)
|
||||||
@@ -750,7 +778,7 @@ def init(
|
|||||||
specify init my-project --ai copilot --no-git
|
specify init my-project --ai copilot --no-git
|
||||||
specify init my-project --ai cursor
|
specify init my-project --ai cursor
|
||||||
specify init my-project --ai qwen
|
specify init my-project --ai qwen
|
||||||
specify init my-project --ai opencode
|
specify init my-project --ai windsurf
|
||||||
specify init --ignore-agent-tools my-project
|
specify init --ignore-agent-tools my-project
|
||||||
specify init --here --ai claude
|
specify init --here --ai claude
|
||||||
specify init --here
|
specify init --here
|
||||||
@@ -897,7 +925,7 @@ def init(
|
|||||||
local_ssl_context = ssl_context if verify else False
|
local_ssl_context = ssl_context if verify else False
|
||||||
local_client = httpx.Client(verify=local_ssl_context)
|
local_client = httpx.Client(verify=local_ssl_context)
|
||||||
|
|
||||||
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug)
|
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 scripts are executable (POSIX)
|
# Ensure scripts are executable (POSIX)
|
||||||
ensure_executable_scripts(project_path, tracker=tracker)
|
ensure_executable_scripts(project_path, tracker=tracker)
|
||||||
@@ -951,15 +979,11 @@ def init(
|
|||||||
step_num = 2
|
step_num = 2
|
||||||
|
|
||||||
steps_lines.append(f"{step_num}. Start using slash commands with your AI agent:")
|
steps_lines.append(f"{step_num}. Start using slash commands with your AI agent:")
|
||||||
steps_lines.append(" [bold cyan]/constitution[/] - Establish project principles")
|
steps_lines.append(" 2.1 [bold cyan]/constitution[/] - Establish project principles")
|
||||||
step_num += 1
|
steps_lines.append(" 2.2 [bold cyan]/specify[/] - Create specifications")
|
||||||
steps_lines.append(f"{step_num}. [bold cyan]/specify[/] - Create specifications")
|
steps_lines.append(" 2.3 [bold cyan]/plan[/] - Create implementation plans")
|
||||||
step_num += 1
|
steps_lines.append(" 2.4 [bold cyan]/tasks[/] - Generate actionable tasks")
|
||||||
steps_lines.append(f"{step_num}. [bold cyan]/plan[/] - Create implementation plans")
|
steps_lines.append(" 2.5 [bold cyan]/implement[/] - Execute implementation")
|
||||||
step_num += 1
|
|
||||||
steps_lines.append(f"{step_num}. [bold cyan]/tasks[/] - Generate actionable tasks")
|
|
||||||
step_num += 1
|
|
||||||
steps_lines.append(f"{step_num}. [bold cyan]/implement[/] - Execute implementation")
|
|
||||||
|
|
||||||
steps_panel = Panel("\n".join(steps_lines), title="Next steps", border_style="cyan", padding=(1,2))
|
steps_panel = Panel("\n".join(steps_lines), title="Next steps", border_style="cyan", padding=(1,2))
|
||||||
console.print()
|
console.print()
|
||||||
@@ -979,6 +1003,7 @@ def check():
|
|||||||
tracker.add("qwen", "Qwen Code CLI")
|
tracker.add("qwen", "Qwen Code CLI")
|
||||||
tracker.add("code", "VS Code (for GitHub Copilot)")
|
tracker.add("code", "VS Code (for GitHub Copilot)")
|
||||||
tracker.add("cursor-agent", "Cursor IDE agent (optional)")
|
tracker.add("cursor-agent", "Cursor IDE agent (optional)")
|
||||||
|
tracker.add("windsurf", "Windsurf IDE (optional)")
|
||||||
tracker.add("opencode", "opencode")
|
tracker.add("opencode", "opencode")
|
||||||
|
|
||||||
git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker)
|
git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker)
|
||||||
@@ -989,6 +1014,7 @@ def check():
|
|||||||
if not code_ok:
|
if not code_ok:
|
||||||
code_ok = check_tool_for_tracker("code-insiders", "https://code.visualstudio.com/insiders/", tracker)
|
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)
|
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)
|
opencode_ok = check_tool_for_tracker("opencode", "https://opencode.ai/", tracker)
|
||||||
|
|
||||||
console.print(tracker.render())
|
console.print(tracker.render())
|
||||||
@@ -997,7 +1023,7 @@ def check():
|
|||||||
|
|
||||||
if not git_ok:
|
if not git_ok:
|
||||||
console.print("[dim]Tip: Install git for repository management[/dim]")
|
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 windsurf_ok or opencode_ok):
|
||||||
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ scripts:
|
|||||||
Given the feature description provided as an argument, do this:
|
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.
|
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.
|
||||||
2. Load `templates/spec-template.md` to understand required sections.
|
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.
|
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.
|
4. Report completion with branch name, spec file path, and readiness for the next phase.
|
||||||
|
|||||||
Reference in New Issue
Block a user