Compare commits

...

37 Commits

Author SHA1 Message Date
Den Delimarsky
ed5dbf197f Merge pull request #828 from github/localden/updates
Update clarify.md
2025-10-10 13:19:10 -07:00
den (work)
df4d7fa062 Update clarify.md 2025-10-10 13:09:11 -07:00
Den Delimarsky
b4ecd14ffa Merge pull request #800 from technoch1ef/patch-1
feat: add documentation for upgrading specify installation
2025-10-10 13:03:59 -07:00
Den Delimarsky
26fde7cfda Merge pull request #827 from github/localden/updates
Update vscode-settings.json
2025-10-10 12:30:16 -07:00
den (work)
8abc812c57 Update vscode-settings.json 2025-10-10 12:29:46 -07:00
Den Delimarsky
940714df0a Merge pull request #826 from github/localden/updates
Hot Fix
2025-10-10 11:58:36 -07:00
den (work)
f393ae9825 Update instructions and bug fix 2025-10-10 11:58:10 -07:00
Den Delimarsky
97dee3e4bf Merge pull request #825 from github/localden/updates
Spec Kit Updates (0.0.19)
2025-10-10 11:50:47 -07:00
den (work)
aec568949c Update __init__.py 2025-10-10 11:49:51 -07:00
den (work)
ed9044345b Consolidate Cursor naming 2025-10-10 11:45:51 -07:00
den (work)
058ee510a7 Update CHANGELOG.md 2025-10-10 11:35:48 -07:00
den (work)
e91aca54ee Git errors are now highlighted. 2025-10-10 11:35:05 -07:00
den (work)
9c87fdd5bb Update __init__.py 2025-10-10 11:22:57 -07:00
den (work)
301a556110 Merge branch 'localden/updates' of https://github.com/github/spec-kit into localden/updates 2025-10-10 11:15:22 -07:00
den (work)
bb9ec8e638 Refactor agent configuration 2025-10-10 11:12:54 -07:00
Den Delimarsky
e83d2c777d Update src/specify_cli/__init__.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 11:07:45 -07:00
Den Delimarsky
68809bdacb Update scripts/powershell/update-agent-context.ps1
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 11:07:21 -07:00
Den Delimarsky
3cc545243b Update AGENTS.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 11:07:12 -07:00
Den Delimarsky
9ef389baba Update templates/commands/implement.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 11:05:38 -07:00
Den Delimarsky
426ac8ab2e Update templates/commands/implement.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 11:05:16 -07:00
den (work)
4de1b6b6c3 Merge branch 'localden/updates' of https://github.com/github/spec-kit into localden/updates 2025-10-10 11:04:28 -07:00
den (work)
199c63901f Update CHANGELOG.md 2025-10-10 11:04:25 -07:00
Den Delimarsky
369ed643d7 Merge pull request #462 from lispking/codebuddy
feat: support codebuddy ai
2025-10-10 11:04:10 -07:00
Den Delimarsky
6c947cc8d8 Merge branch 'main' into codebuddy 2025-10-10 11:03:13 -07:00
den (work)
07d506feb5 Update changelog 2025-10-10 10:53:21 -07:00
Den Delimarsky
0124a0f32e Merge pull request #816 from sigent-amazon/feature/add-ignore-file-verification
Add ignore file(s) verification step to /speckit.implement command
2025-10-10 10:51:48 -07:00
Den Delimarsky
e7936c3fd0 Merge pull request #808 from hsin19/feature/escape-toml-backslashes
fix: correctly escape backslashes in TOML slash command outputs
2025-10-10 10:47:26 -07:00
den (work)
583d556677 Update plan.md 2025-10-10 10:43:31 -07:00
Simon Gent
72ed39d8a1 Add ignore file verification step to /speckit.implement command
- Added step 4 for project setup verification
- Detects and creates/verifies ignore files based on project setup
- Includes patterns for .gitignore, .dockerignore, .eslintignore, .prettierignore, .npmignore, .terraformignore, .helmignore
- Provides technology-specific patterns (Node.js, Python, Java, C#/.NET, Go)
- Includes tool-specific patterns (Docker, ESLint, Prettier, Terraform)
- Renumbered subsequent steps 5-9
2025-10-10 10:11:06 +01:00
Eric Yeh
7c4c1edd85 Escape backslashes in TOML outputs 2025-10-10 06:09:38 +08:00
Oleksandr Ovcharov
5846a38c68 add how to upgrade specify installation 2025-10-09 16:19:36 +02:00
King
aa599b8af1 Merge branch 'main' into codebuddy 2025-09-26 09:12:09 +08:00
King
2b2f5a7c2a Merge branch 'main' into codebuddy 2025-09-23 12:00:46 +08:00
lispking
8b09559690 update CodeBuddy to international site 2025-09-23 05:22:35 +08:00
lispking
318b76de50 feat: support codebuddy ai 2025-09-22 14:34:43 +08:00
King
a85fdd4051 Merge branch 'main' into codebuddy 2025-09-22 14:23:00 +08:00
lispking
92621bca7d feat: support codebuddy ai 2025-09-22 14:20:31 +08:00
13 changed files with 390 additions and 232 deletions

View File

@@ -38,6 +38,8 @@ gh release create "$VERSION" \
.genreleases/spec-kit-template-auggie-ps-"$VERSION".zip \ .genreleases/spec-kit-template-auggie-ps-"$VERSION".zip \
.genreleases/spec-kit-template-roo-sh-"$VERSION".zip \ .genreleases/spec-kit-template-roo-sh-"$VERSION".zip \
.genreleases/spec-kit-template-roo-ps-"$VERSION".zip \ .genreleases/spec-kit-template-roo-ps-"$VERSION".zip \
.genreleases/spec-kit-template-codebuddy-sh-"$VERSION".zip \
.genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \
.genreleases/spec-kit-template-q-sh-"$VERSION".zip \ .genreleases/spec-kit-template-q-sh-"$VERSION".zip \
.genreleases/spec-kit-template-q-ps-"$VERSION".zip \ .genreleases/spec-kit-template-q-ps-"$VERSION".zip \
--title "Spec Kit Templates - $VERSION_NO_V" \ --title "Spec Kit Templates - $VERSION_NO_V" \

View File

@@ -91,6 +91,7 @@ generate_commands() {
case $ext in case $ext in
toml) toml)
body=$(printf '%s\n' "$body" | sed 's/\\/\\\\/g')
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/speckit.$name.$ext" ;; { echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/speckit.$name.$ext" ;;
md) md)
echo "$body" > "$output_dir/speckit.$name.$ext" ;; echo "$body" > "$output_dir/speckit.$name.$ext" ;;
@@ -176,6 +177,10 @@ build_variant() {
roo) roo)
mkdir -p "$base_dir/.roo/commands" mkdir -p "$base_dir/.roo/commands"
generate_commands roo md "\$ARGUMENTS" "$base_dir/.roo/commands" "$script" ;; generate_commands roo md "\$ARGUMENTS" "$base_dir/.roo/commands" "$script" ;;
codebuddy)
mkdir -p "$base_dir/.codebuddy/commands"
generate_commands codebuddy md "\$ARGUMENTS" "$base_dir/.codebuddy/commands" "$script" ;;
q) q)
mkdir -p "$base_dir/.amazonq/prompts" mkdir -p "$base_dir/.amazonq/prompts"
generate_commands q md "\$ARGUMENTS" "$base_dir/.amazonq/prompts" "$script" ;; generate_commands q md "\$ARGUMENTS" "$base_dir/.amazonq/prompts" "$script" ;;
@@ -185,10 +190,9 @@ build_variant() {
} }
# Determine agent list # Determine agent list
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf codex kilocode auggie roo q) ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf codex kilocode auggie roo codebuddy q)
ALL_SCRIPTS=(sh ps) ALL_SCRIPTS=(sh ps)
norm_list() { norm_list() {
# convert comma+space separated -> space separated unique while preserving order of first occurrence # convert comma+space separated -> space separated unique while preserving order of first occurrence
tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?" ":"") $i)}}}END{printf("\n")}' tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?" ":"") $i)}}}END{printf("\n")}'

130
AGENTS.md
View File

@@ -37,56 +37,57 @@ Specify supports multiple AI agents by generating agent-specific command files a
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI | | **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI | | **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI |
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI | | **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
| **Codex CLI** | `.codex/commands/` | Markdown | `codex` | Codex CLI |
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows | | **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
| **Kilo Code** | `.kilocode/rules/` | Markdown | N/A (IDE-based) | Kilo Code IDE |
| **Auggie CLI** | `.augment/rules/` | Markdown | `auggie` | Auggie CLI |
| **Roo Code** | `.roo/rules/` | Markdown | N/A (IDE-based) | Roo Code IDE |
| **CodeBuddy** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy |
| **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI | | **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI |
### Step-by-Step Integration Guide ### Step-by-Step Integration Guide
Follow these steps to add a new agent (using Windsurf as an example): Follow these steps to add a new agent (using a hypothetical new agent as an example):
#### 1. Update AI_CHOICES Constant #### 1. Add to AGENT_CONFIG
Add the new agent to the `AI_CHOICES` dictionary in `src/specify_cli/__init__.py`: **IMPORTANT**: Use the actual CLI tool name as the key, not a shortened version.
Add the new agent to the `AGENT_CONFIG` dictionary in `src/specify_cli/__init__.py`. This is the **single source of truth** for all agent metadata:
```python ```python
AI_CHOICES = { AGENT_CONFIG = {
"copilot": "GitHub Copilot", # ... existing agents ...
"claude": "Claude Code", "new-agent-cli": { # Use the ACTUAL CLI tool name (what users type in terminal)
"gemini": "Gemini CLI", "name": "New Agent Display Name",
"cursor": "Cursor", "folder": ".newagent/", # Directory for agent files
"qwen": "Qwen Code", "install_url": "https://example.com/install", # URL for installation docs (or None if IDE-based)
"opencode": "opencode", "requires_cli": True, # True if CLI tool required, False for IDE-based agents
"windsurf": "Windsurf", },
"q": "Amazon Q Developer CLI" # Add new agent here
} }
``` ```
Also update the `agent_folder_map` in the same file to include the new agent's folder for the security notice: **Key Design Principle**: The dictionary key should match the actual executable name that users install. For example:
- ✅ Use `"cursor-agent"` because the CLI tool is literally called `cursor-agent`
- ❌ Don't use `"cursor"` as a shortcut if the tool is `cursor-agent`
```python This eliminates the need for special-case mappings throughout the codebase.
agent_folder_map = {
"claude": ".claude/", **Field Explanations**:
"gemini": ".gemini/", - `name`: Human-readable display name shown to users
"cursor": ".cursor/", - `folder`: Directory where agent-specific files are stored (relative to project root)
"qwen": ".qwen/", - `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
"opencode": ".opencode/", - `requires_cli`: Whether the agent requires a CLI tool check during initialization
"codex": ".codex/",
"windsurf": ".windsurf/",
"kilocode": ".kilocode/",
"auggie": ".auggie/",
"copilot": ".github/",
"q": ".amazonq/" # Add new agent folder here
}
```
#### 2. Update CLI Help Text #### 2. Update CLI Help Text
Update all help text and examples to include the new agent: Update the `--ai` parameter help text in the `init()` command to include the new agent:
- Command option help: `--ai` parameter description ```python
- Function docstrings and examples ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, new-agent-cli, or q"),
- Error messages with agent lists ```
Also update any function docstrings, examples, and error messages that list available agents.
#### 3. Update README Documentation #### 3. Update README Documentation
@@ -190,17 +191,65 @@ elif selected_ai == "windsurf":
agent_tool_missing = True agent_tool_missing = True
``` ```
**Note**: Skip CLI checks for IDE-based agents (Copilot, Windsurf). **Note**: CLI tool checks are now handled automatically based on the `requires_cli` field in AGENT_CONFIG. No additional code changes needed in the `check()` or `init()` commands - they automatically loop through AGENT_CONFIG and check tools as needed.
## Important Design Decisions
### Using Actual CLI Tool Names as Keys
**CRITICAL**: When adding a new agent to AGENT_CONFIG, always use the **actual executable name** as the dictionary key, not a shortened or convenient version.
**Why this matters:**
- The `check_tool()` function uses `shutil.which(tool)` to find executables in the system PATH
- If the key doesn't match the actual CLI tool name, you'll need special-case mappings throughout the codebase
- This creates unnecessary complexity and maintenance burden
**Example - The Cursor Lesson:**
**Wrong approach** (requires special-case mapping):
```python
AGENT_CONFIG = {
"cursor": { # Shorthand that doesn't match the actual tool
"name": "Cursor",
# ...
}
}
# Then you need special cases everywhere:
cli_tool = agent_key
if agent_key == "cursor":
cli_tool = "cursor-agent" # Map to the real tool name
```
**Correct approach** (no mapping needed):
```python
AGENT_CONFIG = {
"cursor-agent": { # Matches the actual executable name
"name": "Cursor",
# ...
}
}
# No special cases needed - just use agent_key directly!
```
**Benefits of this approach:**
- Eliminates special-case logic scattered throughout the codebase
- Makes the code more maintainable and easier to understand
- Reduces the chance of bugs when adding new agents
- Tool checking "just works" without additional mappings
## Agent Categories ## Agent Categories
### CLI-Based Agents ### CLI-Based Agents
Require a command-line tool to be installed: Require a command-line tool to be installed:
- **Claude Code**: `claude` CLI - **Claude Code**: `claude` CLI
- **Gemini CLI**: `gemini` CLI - **Gemini CLI**: `gemini` CLI
- **Cursor**: `cursor-agent` CLI - **Cursor**: `cursor-agent` CLI
- **Qwen Code**: `qwen` CLI - **Qwen Code**: `qwen` CLI
- **opencode**: `opencode` CLI - **opencode**: `opencode` CLI
- **CodeBuddy**: `codebuddy` CLI
### IDE-Based Agents ### IDE-Based Agents
Work within integrated development environments: Work within integrated development environments:
@@ -257,19 +306,22 @@ Different agents use different argument placeholders:
## Common Pitfalls ## Common Pitfalls
1. **Forgetting update scripts**: Both bash and PowerShell scripts must be updated 1. **Using shorthand keys instead of actual CLI tool names**: Always use the actual executable name as the AGENT_CONFIG key (e.g., `"cursor-agent"` not `"cursor"`). This prevents the need for special-case mappings throughout the codebase.
2. **Missing CLI checks**: Only add for agents that actually have CLI tools 2. **Forgetting update scripts**: Both bash and PowerShell scripts must be updated when adding new agents.
3. **Wrong argument format**: Use correct placeholder format for each agent type 3. **Incorrect `requires_cli` value**: Set to `True` only for agents that actually have CLI tools to check; set to `False` for IDE-based agents.
4. **Directory naming**: Follow agent-specific conventions exactly 4. **Wrong argument format**: Use correct placeholder format for each agent type (`$ARGUMENTS` for Markdown, `{{args}}` for TOML).
5. **Help text inconsistency**: Update all user-facing text consistently 5. **Directory naming**: Follow agent-specific conventions exactly (check existing agents for patterns).
6. **Help text inconsistency**: Update all user-facing text consistently (help strings, docstrings, README, error messages).
## Future Considerations ## Future Considerations
When adding new agents: When adding new agents:
- Consider the agent's native command/workflow patterns - Consider the agent's native command/workflow patterns
- Ensure compatibility with the Spec-Driven Development process - Ensure compatibility with the Spec-Driven Development process
- Document any special requirements or limitations - Document any special requirements or limitations
- Update this guide with lessons learned - Update this guide with lessons learned
- Verify the actual CLI tool name before adding to AGENT_CONFIG
--- ---

View File

@@ -7,6 +7,19 @@ All notable changes to the Specify CLI and templates are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.19] - 2025-10-10
### Added
- Support for CodeBuddy (thank you to [@lispking](https://github.com/lispking) for the contribution).
- You can now see Git-sourced errors in the Specify CLI.
### Changed
- Fixed the path to the constitution in `plan.md` (thank you to [@lyzno1](https://github.com/lyzno1) for spotting).
- Fixed backslash escapes in generated TOML files for Gemini (thank you to [@hsin19](https://github.com/hsin19) for the contribution).
- Implementation command now ensures that the correct ignore files are added (thank you to [@sigent-amazon](https://github.com/sigent-amazon) for the contribution).
## [0.0.18] - 2025-10-06 ## [0.0.18] - 2025-10-06
### Added ### Added

View File

@@ -61,6 +61,12 @@ specify init <PROJECT_NAME>
specify check specify check
``` ```
To upgrade specify run:
```bash
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
```
#### Option 2: One-time Usage #### Option 2: One-time Usage
Run directly without installing: Run directly without installing:
@@ -137,6 +143,7 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
| [Windsurf](https://windsurf.com/) | ✅ | | | [Windsurf](https://windsurf.com/) | ✅ | |
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | | | [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | |
| [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | | | [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | |
| [CodeBuddy](https://www.codebuddy.ai/) | ✅ | |
| [Roo Code](https://roocode.com/) | ✅ | | | [Roo Code](https://roocode.com/) | ✅ | |
| [Codex CLI](https://github.com/openai/codex) | ✅ | | | [Codex CLI](https://github.com/openai/codex) | ✅ | |
| [Amazon Q Developer CLI](https://aws.amazon.com/developer/learning/q-developer-cli/) | ⚠️ | Amazon Q Developer CLI [does not support](https://github.com/aws/amazon-q-developer-cli/issues/3064) custom arguments for slash commands. | | [Amazon Q Developer CLI](https://aws.amazon.com/developer/learning/q-developer-cli/) | ⚠️ | Amazon Q Developer CLI [does not support](https://github.com/aws/amazon-q-developer-cli/issues/3064) custom arguments for slash commands. |
@@ -157,7 +164,7 @@ The `specify` command supports the following options:
| Argument/Option | Type | Description | | Argument/Option | Type | Description |
|------------------------|----------|------------------------------------------------------------------------------| |------------------------|----------|------------------------------------------------------------------------------|
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) | | `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, or `q` | | `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, or `q` |
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) | | `--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 | | `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
| `--no-git` | Flag | Skip git repository initialization | | `--no-git` | Flag | Skip git repository initialization |
@@ -177,7 +184,7 @@ specify init my-project
specify init my-project --ai claude specify init my-project --ai claude
# Initialize with Cursor support # Initialize with Cursor support
specify init my-project --ai cursor specify init my-project --ai cursor-agent
# Initialize with Windsurf support # Initialize with Windsurf support
specify init my-project --ai windsurf specify init my-project --ai windsurf

View File

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

View File

@@ -69,6 +69,7 @@ WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md" KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md"
AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md" AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
CODEBUDDY_FILE="$REPO_ROOT/.codebuddy/rules/specify-rules.md"
Q_FILE="$REPO_ROOT/AGENTS.md" Q_FILE="$REPO_ROOT/AGENTS.md"
# Template file # Template file
@@ -581,6 +582,9 @@ update_specific_agent() {
roo) roo)
update_agent_file "$ROO_FILE" "Roo Code" update_agent_file "$ROO_FILE" "Roo Code"
;; ;;
codebuddy)
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy"
;;
q) q)
update_agent_file "$Q_FILE" "Amazon Q Developer CLI" update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
;; ;;
@@ -646,6 +650,11 @@ update_all_existing_agents() {
found_agent=true found_agent=true
fi fi
if [[ -f "$CODEBUDDY_FILE" ]]; then
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy"
found_agent=true
fi
if [[ -f "$Q_FILE" ]]; then if [[ -f "$Q_FILE" ]]; then
update_agent_file "$Q_FILE" "Amazon Q Developer CLI" update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
found_agent=true found_agent=true
@@ -674,7 +683,8 @@ print_summary() {
fi fi
echo echo
log_info "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|q]"
log_info "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|q]"
} }
#============================================================================== #==============================================================================

View File

@@ -25,7 +25,7 @@ Relies on common helper functions in common.ps1
#> #>
param( param(
[Parameter(Position=0)] [Parameter(Position=0)]
[ValidateSet('claude','gemini','copilot','cursor','qwen','opencode','codex','windsurf','kilocode','auggie','roo','q')] [ValidateSet('claude','gemini','copilot','cursor','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','q')]
[string]$AgentType [string]$AgentType
) )
@@ -54,6 +54,7 @@ $WINDSURF_FILE = Join-Path $REPO_ROOT '.windsurf/rules/specify-rules.md'
$KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md' $KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md'
$AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md' $AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md'
$ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md' $ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md'
$CODEBUDDY_FILE = Join-Path $REPO_ROOT '.codebuddy/rules/specify-rules.md'
$Q_FILE = Join-Path $REPO_ROOT 'AGENTS.md' $Q_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md' $TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
@@ -377,8 +378,9 @@ function Update-SpecificAgent {
'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' } 'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' }
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' } 'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' } 'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy' }
'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' } 'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' }
default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|q'; return $false } default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|q'; return $false }
} }
} }
@@ -395,6 +397,7 @@ function Update-AllExistingAgents {
if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true } if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true }
if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true } if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true }
if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true } if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true }
if (Test-Path $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy')) { $ok = $false }; $found = $true }
if (Test-Path $Q_FILE) { if (-not (Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI')) { $ok = $false }; $found = $true } if (Test-Path $Q_FILE) { if (-not (Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI')) { $ok = $false }; $found = $true }
if (-not $found) { if (-not $found) {
Write-Info 'No existing agent files found, creating default Claude file...' Write-Info 'No existing agent files found, creating default Claude file...'
@@ -410,7 +413,7 @@ function Print-Summary {
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" } if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" } if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
Write-Host '' Write-Host ''
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|q]' Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|q]'
} }
function Main { function Main {

View File

@@ -64,19 +64,86 @@ def _github_auth_headers(cli_token: str | None = None) -> dict:
token = _github_token(cli_token) token = _github_token(cli_token)
return {"Authorization": f"Bearer {token}"} if token else {} return {"Authorization": f"Bearer {token}"} if token else {}
AI_CHOICES = { # Agent configuration with name, folder, install URL, and CLI tool requirement
"copilot": "GitHub Copilot", AGENT_CONFIG = {
"claude": "Claude Code", "copilot": {
"gemini": "Gemini CLI", "name": "GitHub Copilot",
"cursor": "Cursor", "folder": ".github/",
"qwen": "Qwen Code", "install_url": None, # IDE-based, no CLI check needed
"opencode": "opencode", "requires_cli": False,
"codex": "Codex CLI", },
"windsurf": "Windsurf", "claude": {
"kilocode": "Kilo Code", "name": "Claude Code",
"auggie": "Auggie CLI", "folder": ".claude/",
"roo": "Roo Code", "install_url": "https://docs.anthropic.com/en/docs/claude-code/setup",
"q": "Amazon Q Developer CLI", "requires_cli": True,
},
"gemini": {
"name": "Gemini CLI",
"folder": ".gemini/",
"install_url": "https://github.com/google-gemini/gemini-cli",
"requires_cli": True,
},
"cursor-agent": {
"name": "Cursor",
"folder": ".cursor/",
"install_url": None, # IDE-based
"requires_cli": False,
},
"qwen": {
"name": "Qwen Code",
"folder": ".qwen/",
"install_url": "https://github.com/QwenLM/qwen-code",
"requires_cli": True,
},
"opencode": {
"name": "opencode",
"folder": ".opencode/",
"install_url": "https://opencode.ai",
"requires_cli": True,
},
"codex": {
"name": "Codex CLI",
"folder": ".codex/",
"install_url": "https://github.com/openai/codex",
"requires_cli": True,
},
"windsurf": {
"name": "Windsurf",
"folder": ".windsurf/",
"install_url": None, # IDE-based
"requires_cli": False,
},
"kilocode": {
"name": "Kilo Code",
"folder": ".kilocode/",
"install_url": None, # IDE-based
"requires_cli": False,
},
"auggie": {
"name": "Auggie CLI",
"folder": ".augment/",
"install_url": "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli",
"requires_cli": True,
},
"codebuddy": {
"name": "CodeBuddy",
"folder": ".codebuddy/",
"install_url": "https://www.codebuddy.ai",
"requires_cli": True,
},
"roo": {
"name": "Roo Code",
"folder": ".roo/",
"install_url": None, # IDE-based
"requires_cli": False,
},
"q": {
"name": "Amazon Q Developer CLI",
"folder": ".amazonq/",
"install_url": "https://aws.amazon.com/developer/learning/q-developer-cli/",
"requires_cli": True,
},
} }
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
@@ -131,7 +198,7 @@ class StepTracker:
s["detail"] = detail s["detail"] = detail
self._maybe_refresh() self._maybe_refresh()
return return
# If not present, add it
self.steps.append({"key": key, "label": key, "status": status, "detail": detail}) self.steps.append({"key": key, "label": key, "status": status, "detail": detail})
self._maybe_refresh() self._maybe_refresh()
@@ -148,7 +215,6 @@ class StepTracker:
label = step["label"] label = step["label"]
detail_text = step["detail"].strip() if step["detail"] else "" detail_text = step["detail"].strip() if step["detail"] else ""
# Circles (unchanged styling)
status = step["status"] status = step["status"]
if status == "done": if status == "done":
symbol = "[green]●[/green]" symbol = "[green]●[/green]"
@@ -272,7 +338,6 @@ def select_with_arrows(options: dict, prompt_text: str = "Select an option", def
console.print("\n[red]Selection failed.[/red]") console.print("\n[red]Selection failed.[/red]")
raise typer.Exit(1) raise typer.Exit(1)
# Suppress explicit selection print; tracker / later logic will report consolidated status
return selected_key return selected_key
console = Console() console = Console()
@@ -296,7 +361,6 @@ app = typer.Typer(
def show_banner(): def show_banner():
"""Display the ASCII art banner.""" """Display the ASCII art banner."""
# Create gradient effect with different colors
banner_lines = BANNER.strip().split('\n') banner_lines = BANNER.strip().split('\n')
colors = ["bright_blue", "blue", "cyan", "bright_cyan", "white", "bright_white"] colors = ["bright_blue", "blue", "cyan", "bright_cyan", "white", "bright_white"]
@@ -312,8 +376,6 @@ def show_banner():
@app.callback() @app.callback()
def callback(ctx: typer.Context): def callback(ctx: typer.Context):
"""Show banner when no subcommand is provided.""" """Show banner when no subcommand is provided."""
# Show banner only when no subcommand and no help flag
# (help is handled by BannerGroup)
if ctx.invoked_subcommand is None and "--help" not in sys.argv and "-h" not in sys.argv: if ctx.invoked_subcommand is None and "--help" not in sys.argv and "-h" not in sys.argv:
show_banner() show_banner()
console.print(Align.center("[dim]Run 'specify --help' for usage information[/dim]")) console.print(Align.center("[dim]Run 'specify --help' for usage information[/dim]"))
@@ -337,18 +399,16 @@ def run_command(cmd: list[str], check_return: bool = True, capture: bool = False
raise raise
return None return None
def check_tool_for_tracker(tool: str, tracker: StepTracker) -> bool: def check_tool(tool: str, tracker: StepTracker = None) -> bool:
"""Check if a tool is installed and update tracker.""" """Check if a tool is installed. Optionally update tracker.
if shutil.which(tool):
tracker.complete(tool, "available")
return True
else:
tracker.error(tool, "not found")
return False
def check_tool(tool: str, install_hint: str) -> bool: Args:
"""Check if a tool is installed.""" tool: Name of the tool to check
tracker: Optional StepTracker to update with results
Returns:
True if tool is found, False otherwise
"""
# Special handling for Claude CLI after `claude migrate-installer` # Special handling for Claude CLI after `claude migrate-installer`
# See: https://github.com/github/spec-kit/issues/123 # See: https://github.com/github/spec-kit/issues/123
# The migrate-installer command REMOVES the original executable from PATH # The migrate-installer command REMOVES the original executable from PATH
@@ -356,12 +416,19 @@ def check_tool(tool: str, install_hint: str) -> bool:
# This path should be prioritized over other claude executables in PATH # This path should be prioritized over other claude executables in PATH
if tool == "claude": if tool == "claude":
if CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file(): if CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file():
if tracker:
tracker.complete(tool, "available")
return True return True
if shutil.which(tool): found = shutil.which(tool) is not None
return True
else: if tracker:
return False if found:
tracker.complete(tool, "available")
else:
tracker.error(tool, "not found")
return found
def is_git_repo(path: Path = None) -> bool: def is_git_repo(path: Path = None) -> bool:
"""Check if the specified path is inside a git repository.""" """Check if the specified path is inside a git repository."""
@@ -383,26 +450,38 @@ def is_git_repo(path: Path = None) -> bool:
except (subprocess.CalledProcessError, FileNotFoundError): except (subprocess.CalledProcessError, FileNotFoundError):
return False return False
def init_git_repo(project_path: Path, quiet: bool = False) -> bool: def init_git_repo(project_path: Path, quiet: bool = False) -> Tuple[bool, Optional[str]]:
"""Initialize a git repository in the specified path. """Initialize a git repository in the specified path.
quiet: if True suppress console output (tracker handles status)
Args:
project_path: Path to initialize git repository in
quiet: if True suppress console output (tracker handles status)
Returns:
Tuple of (success: bool, error_message: Optional[str])
""" """
try: try:
original_cwd = Path.cwd() original_cwd = Path.cwd()
os.chdir(project_path) os.chdir(project_path)
if not quiet: if not quiet:
console.print("[cyan]Initializing git repository...[/cyan]") console.print("[cyan]Initializing git repository...[/cyan]")
subprocess.run(["git", "init"], check=True, capture_output=True) subprocess.run(["git", "init"], check=True, capture_output=True, text=True)
subprocess.run(["git", "add", "."], check=True, capture_output=True) subprocess.run(["git", "add", "."], check=True, capture_output=True, text=True)
subprocess.run(["git", "commit", "-m", "Initial commit from Specify template"], check=True, capture_output=True) subprocess.run(["git", "commit", "-m", "Initial commit from Specify template"], check=True, capture_output=True, text=True)
if not quiet: if not quiet:
console.print("[green]✓[/green] Git repository initialized") console.print("[green]✓[/green] Git repository initialized")
return True return True, None
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
error_msg = f"Command: {' '.join(e.cmd)}\nExit code: {e.returncode}"
if e.stderr:
error_msg += f"\nError: {e.stderr.strip()}"
elif e.stdout:
error_msg += f"\nOutput: {e.stdout.strip()}"
if not quiet: if not quiet:
console.print(f"[red]Error initializing git repository:[/red] {e}") console.print(f"[red]Error initializing git repository:[/red] {e}")
return False return False, error_msg
finally: finally:
os.chdir(original_cwd) os.chdir(original_cwd)
@@ -438,7 +517,6 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
console.print(Panel(str(e), title="Fetch Error", border_style="red")) console.print(Panel(str(e), title="Fetch Error", border_style="red"))
raise typer.Exit(1) raise typer.Exit(1)
# Find the template asset for the specified AI assistant
assets = release_data.get("assets", []) assets = release_data.get("assets", [])
pattern = f"spec-kit-template-{ai_assistant}-{script_type}" pattern = f"spec-kit-template-{ai_assistant}-{script_type}"
matching_assets = [ matching_assets = [
@@ -523,7 +601,6 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
""" """
current_dir = Path.cwd() current_dir = Path.cwd()
# Step: fetch + download combined
if tracker: if tracker:
tracker.start("fetch", "contacting GitHub API") tracker.start("fetch", "contacting GitHub API")
try: try:
@@ -556,12 +633,10 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
console.print("Extracting template...") console.print("Extracting template...")
try: try:
# Create project directory only if not using current directory
if not is_current_dir: if not is_current_dir:
project_path.mkdir(parents=True) project_path.mkdir(parents=True)
with zipfile.ZipFile(zip_path, 'r') as zip_ref: with zipfile.ZipFile(zip_path, 'r') as zip_ref:
# List all files in the ZIP for debugging
zip_contents = zip_ref.namelist() zip_contents = zip_ref.namelist()
if tracker: if tracker:
tracker.start("zip-list") tracker.start("zip-list")
@@ -569,13 +644,11 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
elif verbose: elif verbose:
console.print(f"[cyan]ZIP contains {len(zip_contents)} items[/cyan]") console.print(f"[cyan]ZIP contains {len(zip_contents)} items[/cyan]")
# For current directory, extract to a temp location first
if is_current_dir: if is_current_dir:
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir) temp_path = Path(temp_dir)
zip_ref.extractall(temp_path) zip_ref.extractall(temp_path)
# Check what was extracted
extracted_items = list(temp_path.iterdir()) extracted_items = list(temp_path.iterdir())
if tracker: if tracker:
tracker.start("extracted-summary") tracker.start("extracted-summary")
@@ -583,7 +656,6 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
elif verbose: elif verbose:
console.print(f"[cyan]Extracted {len(extracted_items)} items to temp location[/cyan]") console.print(f"[cyan]Extracted {len(extracted_items)} items to temp location[/cyan]")
# Handle GitHub-style ZIP with a single root directory
source_dir = temp_path source_dir = temp_path
if len(extracted_items) == 1 and extracted_items[0].is_dir(): if len(extracted_items) == 1 and extracted_items[0].is_dir():
source_dir = extracted_items[0] source_dir = extracted_items[0]
@@ -593,14 +665,12 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
elif verbose: elif verbose:
console.print(f"[cyan]Found nested directory structure[/cyan]") console.print(f"[cyan]Found nested directory structure[/cyan]")
# Copy contents to current directory
for item in source_dir.iterdir(): for item in source_dir.iterdir():
dest_path = project_path / item.name dest_path = project_path / item.name
if item.is_dir(): if item.is_dir():
if dest_path.exists(): if dest_path.exists():
if verbose and not tracker: if verbose and not tracker:
console.print(f"[yellow]Merging directory:[/yellow] {item.name}") console.print(f"[yellow]Merging directory:[/yellow] {item.name}")
# Recursively copy directory contents
for sub_item in item.rglob('*'): for sub_item in item.rglob('*'):
if sub_item.is_file(): if sub_item.is_file():
rel_path = sub_item.relative_to(item) rel_path = sub_item.relative_to(item)
@@ -616,10 +686,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
if verbose and not tracker: if verbose and not tracker:
console.print(f"[cyan]Template files merged into current directory[/cyan]") console.print(f"[cyan]Template files merged into current directory[/cyan]")
else: else:
# Extract directly to project directory (original behavior)
zip_ref.extractall(project_path) zip_ref.extractall(project_path)
# Check what was extracted
extracted_items = list(project_path.iterdir()) extracted_items = list(project_path.iterdir())
if tracker: if tracker:
tracker.start("extracted-summary") tracker.start("extracted-summary")
@@ -629,16 +697,14 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
for item in extracted_items: for item in extracted_items:
console.print(f" - {item.name} ({'dir' if item.is_dir() else 'file'})") console.print(f" - {item.name} ({'dir' if item.is_dir() else 'file'})")
# Handle GitHub-style ZIP with a single root directory
if len(extracted_items) == 1 and extracted_items[0].is_dir(): if len(extracted_items) == 1 and extracted_items[0].is_dir():
# Move contents up one level
nested_dir = extracted_items[0] nested_dir = extracted_items[0]
temp_move_dir = project_path.parent / f"{project_path.name}_temp" temp_move_dir = project_path.parent / f"{project_path.name}_temp"
# Move the nested directory contents to temp location
shutil.move(str(nested_dir), str(temp_move_dir)) shutil.move(str(nested_dir), str(temp_move_dir))
# Remove the now-empty project directory
project_path.rmdir() project_path.rmdir()
# Rename temp directory to project directory
shutil.move(str(temp_move_dir), str(project_path)) shutil.move(str(temp_move_dir), str(project_path))
if tracker: if tracker:
tracker.add("flatten", "Flatten nested directory") tracker.add("flatten", "Flatten nested directory")
@@ -654,7 +720,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
console.print(f"[red]Error extracting template:[/red] {e}") console.print(f"[red]Error extracting template:[/red] {e}")
if debug: if debug:
console.print(Panel(str(e), title="Extraction Error", border_style="red")) console.print(Panel(str(e), title="Extraction Error", border_style="red"))
# Clean up project directory if created and not current directory
if not is_current_dir and project_path.exists(): if not is_current_dir and project_path.exists():
shutil.rmtree(project_path) shutil.rmtree(project_path)
raise typer.Exit(1) raise typer.Exit(1)
@@ -664,7 +730,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
finally: finally:
if tracker: if tracker:
tracker.add("cleanup", "Remove temporary archive") tracker.add("cleanup", "Remove temporary archive")
# Clean up downloaded ZIP file
if zip_path.exists(): if zip_path.exists():
zip_path.unlink() zip_path.unlink()
if tracker: if tracker:
@@ -722,7 +788,7 @@ 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, or use '.' for current directory)"), project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, codex, windsurf, kilocode, auggie or q"), ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, or q"),
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"),
@@ -737,7 +803,7 @@ def init(
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, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, or Amazon Q Developer CLI) 2. Let you choose your AI assistant
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)
@@ -752,13 +818,13 @@ def init(
specify init . # Initialize in current directory (interactive AI selection) specify init . # Initialize in current directory (interactive AI selection)
specify init --here --ai claude # Alternative syntax for current directory specify init --here --ai claude # Alternative syntax for current directory
specify init --here --ai codex specify init --here --ai codex
specify init --here --ai codebuddy
specify init --here specify init --here
specify init --here --force # Skip confirmation when current directory not empty specify init --here --force # Skip confirmation when current directory not empty
""" """
show_banner() show_banner()
# Handle '.' as shorthand for current directory (equivalent to --here)
if project_name == ".": if project_name == ".":
here = True here = True
project_name = None # Clear project_name to use existing validation logic project_name = None # Clear project_name to use existing validation logic
@@ -809,91 +875,57 @@ def init(
f"{'Working Path':<15} [dim]{current_dir}[/dim]", f"{'Working Path':<15} [dim]{current_dir}[/dim]",
] ]
# Add target path only if different from working dir
if not here: if not here:
setup_lines.append(f"{'Target Path':<15} [dim]{project_path}[/dim]") setup_lines.append(f"{'Target Path':<15} [dim]{project_path}[/dim]")
console.print(Panel("\n".join(setup_lines), border_style="cyan", padding=(1, 2))) 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
should_init_git = False should_init_git = False
if not no_git: if not no_git:
should_init_git = check_tool("git", "https://git-scm.com/downloads") should_init_git = check_tool("git")
if not should_init_git: if not should_init_git:
console.print("[yellow]Git not found - will skip repository initialization[/yellow]") console.print("[yellow]Git not found - will skip repository initialization[/yellow]")
if ai_assistant: if ai_assistant:
if ai_assistant not in AI_CHOICES: if ai_assistant not in AGENT_CONFIG:
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AI_CHOICES.keys())}") console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
raise typer.Exit(1) raise typer.Exit(1)
selected_ai = ai_assistant selected_ai = ai_assistant
else: else:
# Use arrow-key selection interface # Create options dict for selection (agent_key: display_name)
ai_choices = {key: config["name"] for key, config in AGENT_CONFIG.items()}
selected_ai = select_with_arrows( selected_ai = select_with_arrows(
AI_CHOICES, ai_choices,
"Choose your AI assistant:", "Choose your AI assistant:",
"copilot" "copilot"
) )
# Check agent tools unless ignored
if not ignore_agent_tools: if not ignore_agent_tools:
agent_tool_missing = False agent_config = AGENT_CONFIG.get(selected_ai)
install_url = "" if agent_config and agent_config["requires_cli"]:
if selected_ai == "claude": install_url = agent_config["install_url"]
if not check_tool("claude", "https://docs.anthropic.com/en/docs/claude-code/setup"): if not check_tool(selected_ai):
install_url = "https://docs.anthropic.com/en/docs/claude-code/setup" error_panel = Panel(
agent_tool_missing = True f"[cyan]{selected_ai}[/cyan] not found\n"
elif selected_ai == "gemini": f"Install from: [cyan]{install_url}[/cyan]\n"
if not check_tool("gemini", "https://github.com/google-gemini/gemini-cli"): f"{agent_config['name']} is required to continue with this project type.\n\n"
install_url = "https://github.com/google-gemini/gemini-cli" "Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check",
agent_tool_missing = True title="[red]Agent Detection Error[/red]",
elif selected_ai == "qwen": border_style="red",
if not check_tool("qwen", "https://github.com/QwenLM/qwen-code"): padding=(1, 2)
install_url = "https://github.com/QwenLM/qwen-code" )
agent_tool_missing = True console.print()
elif selected_ai == "opencode": console.print(error_panel)
if not check_tool("opencode", "https://opencode.ai"): raise typer.Exit(1)
install_url = "https://opencode.ai"
agent_tool_missing = True
elif selected_ai == "codex":
if not check_tool("codex", "https://github.com/openai/codex"):
install_url = "https://github.com/openai/codex"
agent_tool_missing = True
elif selected_ai == "auggie":
if not check_tool("auggie", "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"):
install_url = "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"
agent_tool_missing = True
elif selected_ai == "q":
if not check_tool("q", "https://github.com/aws/amazon-q-developer-cli"):
install_url = "https://aws.amazon.com/developer/learning/q-developer-cli/"
agent_tool_missing = True
# GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs
if agent_tool_missing:
error_panel = Panel(
f"[cyan]{selected_ai}[/cyan] not found\n"
f"Install with: [cyan]{install_url}[/cyan]\n"
f"{AI_CHOICES[selected_ai]} is required to continue with this project type.\n\n"
"Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check",
title="[red]Agent Detection Error[/red]",
border_style="red",
padding=(1, 2)
)
console.print()
console.print(error_panel)
raise typer.Exit(1)
# Determine script type (explicit, interactive, or OS default)
if script_type: if script_type:
if script_type not in SCRIPT_TYPE_CHOICES: if script_type not in SCRIPT_TYPE_CHOICES:
console.print(f"[red]Error:[/red] Invalid script type '{script_type}'. Choose from: {', '.join(SCRIPT_TYPE_CHOICES.keys())}") console.print(f"[red]Error:[/red] Invalid script type '{script_type}'. Choose from: {', '.join(SCRIPT_TYPE_CHOICES.keys())}")
raise typer.Exit(1) raise typer.Exit(1)
selected_script = script_type selected_script = script_type
else: else:
# Auto-detect default
default_script = "ps" if os.name == "nt" else "sh" default_script = "ps" if os.name == "nt" else "sh"
# Provide interactive selection similar to AI if stdin is a TTY
if sys.stdin.isatty(): if sys.stdin.isatty():
selected_script = select_with_arrows(SCRIPT_TYPE_CHOICES, "Choose script type (or press Enter)", default_script) selected_script = select_with_arrows(SCRIPT_TYPE_CHOICES, "Choose script type (or press Enter)", default_script)
else: else:
@@ -902,12 +934,10 @@ def init(
console.print(f"[cyan]Selected AI assistant:[/cyan] {selected_ai}") console.print(f"[cyan]Selected AI assistant:[/cyan] {selected_ai}")
console.print(f"[cyan]Selected script type:[/cyan] {selected_script}") console.print(f"[cyan]Selected script type:[/cyan] {selected_script}")
# Download and set up project
# New tree-based progress (no emojis); include earlier substeps
tracker = StepTracker("Initialize Specify Project") tracker = StepTracker("Initialize Specify Project")
# Flag to allow suppressing legacy headings
sys._specify_tracker_active = True sys._specify_tracker_active = True
# Pre steps recorded as completed before live rendering
tracker.add("precheck", "Check required tools") tracker.add("precheck", "Check required tools")
tracker.complete("precheck", "ok") tracker.complete("precheck", "ok")
tracker.add("ai-select", "Select AI assistant") tracker.add("ai-select", "Select AI assistant")
@@ -927,30 +957,31 @@ def init(
]: ]:
tracker.add(key, label) tracker.add(key, label)
# Use transient so live tree is replaced by the final static render (avoids duplicate output) # Track git error message outside Live context so it persists
git_error_message = None
with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live: with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live:
tracker.attach_refresh(lambda: live.update(tracker.render())) tracker.attach_refresh(lambda: live.update(tracker.render()))
try: try:
# Create a httpx client with verify based on skip_tls
verify = not skip_tls verify = not skip_tls
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, github_token=github_token) 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_executable_scripts(project_path, tracker=tracker) ensure_executable_scripts(project_path, tracker=tracker)
# Git step
if not no_git: if not no_git:
tracker.start("git") tracker.start("git")
if is_git_repo(project_path): if is_git_repo(project_path):
tracker.complete("git", "existing repo detected") tracker.complete("git", "existing repo detected")
elif should_init_git: elif should_init_git:
if init_git_repo(project_path, quiet=True): success, error_msg = init_git_repo(project_path, quiet=True)
if success:
tracker.complete("git", "initialized") tracker.complete("git", "initialized")
else: else:
tracker.error("git", "init failed") tracker.error("git", "init failed")
git_error_message = error_msg
else: else:
tracker.skip("git", "git not available") tracker.skip("git", "git not available")
else: else:
@@ -973,31 +1004,32 @@ def init(
shutil.rmtree(project_path) shutil.rmtree(project_path)
raise typer.Exit(1) raise typer.Exit(1)
finally: finally:
# Force final render
pass pass
# Final static tree (ensures finished state visible after Live context ends)
console.print(tracker.render()) console.print(tracker.render())
console.print("\n[bold green]Project ready.[/bold green]") console.print("\n[bold green]Project ready.[/bold green]")
# Agent folder security notice # Show git error details if initialization failed
agent_folder_map = { if git_error_message:
"claude": ".claude/", console.print()
"gemini": ".gemini/", git_error_panel = Panel(
"cursor": ".cursor/", f"[yellow]Warning:[/yellow] Git repository initialization failed\n\n"
"qwen": ".qwen/", f"{git_error_message}\n\n"
"opencode": ".opencode/", f"[dim]You can initialize git manually later with:[/dim]\n"
"codex": ".codex/", f"[cyan]cd {project_path if not here else '.'}[/cyan]\n"
"windsurf": ".windsurf/", f"[cyan]git init[/cyan]\n"
"kilocode": ".kilocode/", f"[cyan]git add .[/cyan]\n"
"auggie": ".augment/", f"[cyan]git commit -m \"Initial commit\"[/cyan]",
"copilot": ".github/", title="[red]Git Initialization Failed[/red]",
"roo": ".roo/", border_style="red",
"q": ".amazonq/" padding=(1, 2)
} )
console.print(git_error_panel)
if selected_ai in agent_folder_map: # Agent folder security notice
agent_folder = agent_folder_map[selected_ai] agent_config = AGENT_CONFIG.get(selected_ai)
if agent_config:
agent_folder = agent_config["folder"]
security_notice = Panel( security_notice = Panel(
f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n" f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.", f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
@@ -1008,7 +1040,6 @@ def init(
console.print() console.print()
console.print(security_notice) console.print(security_notice)
# Boxed "Next steps" section
steps_lines = [] steps_lines = []
if not here: if not here:
steps_lines.append(f"1. Go to the project folder: [cyan]cd {project_name}[/cyan]") steps_lines.append(f"1. Go to the project folder: [cyan]cd {project_name}[/cyan]")
@@ -1061,32 +1092,21 @@ def check():
tracker = StepTracker("Check Available Tools") tracker = StepTracker("Check Available Tools")
tracker.add("git", "Git version control") tracker.add("git", "Git version control")
tracker.add("claude", "Claude Code CLI") git_ok = check_tool("git", tracker=tracker)
tracker.add("gemini", "Gemini CLI")
tracker.add("qwen", "Qwen Code CLI")
tracker.add("code", "Visual Studio Code")
tracker.add("code-insiders", "Visual Studio Code Insiders")
tracker.add("cursor-agent", "Cursor IDE agent")
tracker.add("windsurf", "Windsurf IDE")
tracker.add("kilocode", "Kilo Code IDE")
tracker.add("opencode", "opencode")
tracker.add("codex", "Codex CLI")
tracker.add("auggie", "Auggie CLI")
tracker.add("q", "Amazon Q Developer CLI")
git_ok = check_tool_for_tracker("git", tracker) agent_results = {}
claude_ok = check_tool_for_tracker("claude", tracker) for agent_key, agent_config in AGENT_CONFIG.items():
gemini_ok = check_tool_for_tracker("gemini", tracker) agent_name = agent_config["name"]
qwen_ok = check_tool_for_tracker("qwen", tracker)
code_ok = check_tool_for_tracker("code", tracker) tracker.add(agent_key, agent_name)
code_insiders_ok = check_tool_for_tracker("code-insiders", tracker) agent_results[agent_key] = check_tool(agent_key, tracker=tracker)
cursor_ok = check_tool_for_tracker("cursor-agent", tracker)
windsurf_ok = check_tool_for_tracker("windsurf", tracker) # Check VS Code variants (not in agent config)
kilocode_ok = check_tool_for_tracker("kilocode", tracker) tracker.add("code", "Visual Studio Code")
opencode_ok = check_tool_for_tracker("opencode", tracker) code_ok = check_tool("code", tracker=tracker)
codex_ok = check_tool_for_tracker("codex", tracker)
auggie_ok = check_tool_for_tracker("auggie", tracker) tracker.add("code-insiders", "Visual Studio Code Insiders")
q_ok = check_tool_for_tracker("q", tracker) code_insiders_ok = check_tool("code-insiders", tracker=tracker)
console.print(tracker.render()) console.print(tracker.render())
@@ -1094,7 +1114,8 @@ 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 windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok or q_ok):
if not any(agent_results.values()):
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]")
def main(): def main():

View File

@@ -97,7 +97,15 @@ Execution steps:
4. Sequential questioning loop (interactive): 4. Sequential questioning loop (interactive):
- Present EXACTLY ONE question at a time. - Present EXACTLY ONE question at a time.
- For multiplechoice questions render options as a Markdown table: - For multiplechoice questions:
* **Analyze all options** and determine the **most suitable option** based on:
- Best practices for the project type
- Common patterns in similar implementations
- Risk reduction (security, performance, maintainability)
- Alignment with any explicit project goals or constraints visible in the spec
* Present your **recommended option prominently** at the top with clear reasoning (1-2 sentences explaining why this is the best choice).
* Format as: `**Recommended:** Option [X] - <reasoning>`
* Then render all options as a Markdown table:
| Option | Description | | Option | Description |
|--------|-------------| |--------|-------------|
@@ -106,9 +114,14 @@ Execution steps:
| C | <Option C description> | (add D/E as needed up to 5) | C | <Option C description> | (add D/E as needed up to 5)
| Short | Provide a different short answer (<=5 words) | (Include only if free-form alternative is appropriate) | Short | Provide a different short answer (<=5 words) | (Include only if free-form alternative is appropriate)
- For shortanswer style (no meaningful discrete options), output a single line after the question: `Format: Short answer (<=5 words)`. * After the table, add: `You can reply with the option letter (e.g., "A"), accept the recommendation by saying "yes" or "recommended", or provide your own short answer.`
- For shortanswer style (no meaningful discrete options):
* Provide your **suggested answer** based on best practices and context.
* Format as: `**Suggested:** <your proposed answer> - <brief reasoning>`
* Then output: `Format: Short answer (<=5 words). You can accept the suggestion by saying "yes" or "suggested", or provide your own answer.`
- After the user answers: - After the user answers:
* Validate the answer maps to one option or fits the <=5 word constraint. * If the user replies with "yes", "recommended", or "suggested", use your previously stated recommendation/suggestion as the answer.
* Otherwise, validate the answer maps to one option or fits the <=5 word constraint.
* If ambiguous, ask for a quick disambiguation (count still belongs to same question; do not advance). * If ambiguous, ask for a quick disambiguation (count still belongs to same question; do not advance).
* Once satisfactory, record it in working memory (do not yet write to disk) and move to the next queued question. * Once satisfactory, record it in working memory (do not yet write to disk) and move to the next queued question.
- Stop asking further questions when: - Stop asking further questions when:

View File

@@ -54,27 +54,60 @@ You **MUST** consider the user input before proceeding (if not empty).
- **IF EXISTS**: Read research.md for technical decisions and constraints - **IF EXISTS**: Read research.md for technical decisions and constraints
- **IF EXISTS**: Read quickstart.md for integration scenarios - **IF EXISTS**: Read quickstart.md for integration scenarios
4. Parse tasks.md structure and extract: 4. **Project Setup Verification**:
- **REQUIRED**: Create/verify ignore files based on actual project setup:
**Detection & Creation Logic**:
- Check if the following command succeeds to determine if the repository is a git repo (create/verify .gitignore if so):
```sh
git rev-parse --git-dir 2>/dev/null
```
- Check if Dockerfile* exists or Docker in plan.md → create/verify .dockerignore
- Check if .eslintrc* or eslint.config.* exists → create/verify .eslintignore
- Check if .prettierrc* exists → create/verify .prettierignore
- Check if .npmrc or package.json exists → create/verify .npmignore (if publishing)
- Check if terraform files (*.tf) exist → create/verify .terraformignore
- Check if .helmignore needed (helm charts present) → create/verify .helmignore
**If ignore file already exists**: Verify it contains essential patterns, append missing critical patterns only
**If ignore file missing**: Create with full pattern set for detected technology
**Common Patterns by Technology** (from plan.md tech stack):
- **Node.js/JavaScript**: `node_modules/`, `dist/`, `build/`, `*.log`, `.env*`
- **Python**: `__pycache__/`, `*.pyc`, `.venv/`, `venv/`, `dist/`, `*.egg-info/`
- **Java**: `target/`, `*.class`, `*.jar`, `.gradle/`, `build/`
- **C#/.NET**: `bin/`, `obj/`, `*.user`, `*.suo`, `packages/`
- **Go**: `*.exe`, `*.test`, `vendor/`, `*.out`
- **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/`
**Tool-Specific Patterns**:
- **Docker**: `node_modules/`, `.git/`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/`
- **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js`
- **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
- **Terraform**: `.terraform/`, `*.tfstate*`, `*.tfvars`, `.terraform.lock.hcl`
5. Parse tasks.md structure and extract:
- **Task phases**: Setup, Tests, Core, Integration, Polish - **Task phases**: Setup, Tests, Core, Integration, Polish
- **Task dependencies**: Sequential vs parallel execution rules - **Task dependencies**: Sequential vs parallel execution rules
- **Task details**: ID, description, file paths, parallel markers [P] - **Task details**: ID, description, file paths, parallel markers [P]
- **Execution flow**: Order and dependency requirements - **Execution flow**: Order and dependency requirements
5. Execute implementation following the task plan: 6. Execute implementation following the task plan:
- **Phase-by-phase execution**: Complete each phase before moving to the next - **Phase-by-phase execution**: Complete each phase before moving to the next
- **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
- **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
- **File-based coordination**: Tasks affecting the same files must run sequentially - **File-based coordination**: Tasks affecting the same files must run sequentially
- **Validation checkpoints**: Verify each phase completion before proceeding - **Validation checkpoints**: Verify each phase completion before proceeding
6. Implementation execution rules: 7. Implementation execution rules:
- **Setup first**: Initialize project structure, dependencies, configuration - **Setup first**: Initialize project structure, dependencies, configuration
- **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios - **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios
- **Core development**: Implement models, services, CLI commands, endpoints - **Core development**: Implement models, services, CLI commands, endpoints
- **Integration work**: Database connections, middleware, logging, external services - **Integration work**: Database connections, middleware, logging, external services
- **Polish and validation**: Unit tests, performance optimization, documentation - **Polish and validation**: Unit tests, performance optimization, documentation
7. Progress tracking and error handling: 8. Progress tracking and error handling:
- Report progress after each completed task - Report progress after each completed task
- Halt execution if any non-parallel task fails - Halt execution if any non-parallel task fails
- For parallel tasks [P], continue with successful tasks, report failed ones - For parallel tasks [P], continue with successful tasks, report failed ones
@@ -82,7 +115,7 @@ You **MUST** consider the user input before proceeding (if not empty).
- Suggest next steps if implementation cannot proceed - Suggest next steps if implementation cannot proceed
- **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file. - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
8. Completion validation: 9. Completion validation:
- Verify all required tasks are completed - Verify all required tasks are completed
- Check that implemented features match the original specification - Check that implemented features match the original specification
- Validate that tests pass and coverage meets requirements - Validate that tests pass and coverage meets requirements

View File

@@ -20,7 +20,7 @@ You **MUST** consider the user input before proceeding (if not empty).
1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). 1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
2. **Load context**: Read FEATURE_SPEC and `.specify/memory/constitution.md`. Load IMPL_PLAN template (already copied). 2. **Load context**: Read FEATURE_SPEC and `/memory/constitution.md`. Load IMPL_PLAN template (already copied).
3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to: 3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to:
- Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION") - Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION")

View File

@@ -8,6 +8,6 @@
}, },
"chat.tools.terminal.autoApprove": { "chat.tools.terminal.autoApprove": {
".specify/scripts/bash/": true, ".specify/scripts/bash/": true,
".specify/scripts/ps/": true ".specify/scripts/powershell/": true
} }
} }