mirror of
https://github.com/github/spec-kit.git
synced 2026-01-31 13:03:36 +00:00
Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3ada747cf | ||
|
|
cbc8ab020c | ||
|
|
e0e62f6757 | ||
|
|
6c22085214 | ||
|
|
02c1549f80 | ||
|
|
ab14090813 | ||
|
|
546e9d6617 | ||
|
|
ea90d02c41 | ||
|
|
3e85f46465 | ||
|
|
015440838a | ||
|
|
7050a3151c | ||
|
|
9e84f46e56 | ||
|
|
5e32de1f3f | ||
|
|
5558b24475 | ||
|
|
f892b9e1cb | ||
|
|
a66af9b7f5 | ||
|
|
a5fdd53a3e | ||
|
|
ed0fa8fffe | ||
|
|
098380a46f | ||
|
|
74f7e508a4 | ||
|
|
8130d98bcc | ||
|
|
315269d9a8 | ||
|
|
71c2c63d55 | ||
|
|
b37a9516d0 | ||
|
|
b009773d5c | ||
|
|
a8514da3e8 | ||
|
|
9d4e8e9eb9 | ||
|
|
f3c77e2f4f | ||
|
|
ecec4bc5e0 | ||
|
|
900bc2ed68 | ||
|
|
03c7021270 | ||
|
|
3b000fce4d | ||
|
|
c59595d065 | ||
|
|
1c16a68df2 | ||
|
|
39bf3e4d9a | ||
|
|
045696641a | ||
|
|
41690cd1d4 | ||
|
|
e45c469709 | ||
|
|
8c9e586662 | ||
|
|
ce844c6259 | ||
|
|
84b46cd1b9 | ||
|
|
0cca67fcd2 | ||
|
|
66fc4c292d | ||
|
|
2baae57b26 | ||
|
|
514b0548fe | ||
|
|
be7db635cc | ||
|
|
a945077b8d | ||
|
|
7b55522213 | ||
|
|
7ca792509b | ||
|
|
4522fb4c44 | ||
|
|
36ff7e6505 | ||
|
|
defb1870da | ||
|
|
b61f04c898 | ||
|
|
64745162df | ||
|
|
97df98b9a0 | ||
|
|
36383b411f | ||
|
|
3e476c2ba6 | ||
|
|
654a00aac9 | ||
|
|
4690d13f88 | ||
|
|
e7bb98de42 | ||
|
|
d4f5c75519 | ||
|
|
09f57a87fa | ||
|
|
b702fcbbc0 | ||
|
|
2c1de4202e | ||
|
|
e65660ffc3 | ||
|
|
f7ae5781b7 | ||
|
|
d09552fc63 | ||
|
|
ed5dbf197f | ||
|
|
df4d7fa062 | ||
|
|
b4ecd14ffa | ||
|
|
26fde7cfda | ||
|
|
8abc812c57 | ||
|
|
940714df0a | ||
|
|
f393ae9825 | ||
|
|
97dee3e4bf | ||
|
|
aec568949c | ||
|
|
ed9044345b | ||
|
|
058ee510a7 | ||
|
|
e91aca54ee | ||
|
|
9c87fdd5bb | ||
|
|
301a556110 | ||
|
|
bb9ec8e638 | ||
|
|
e83d2c777d | ||
|
|
68809bdacb | ||
|
|
3cc545243b | ||
|
|
9ef389baba | ||
|
|
426ac8ab2e | ||
|
|
4de1b6b6c3 | ||
|
|
199c63901f | ||
|
|
369ed643d7 | ||
|
|
6c947cc8d8 | ||
|
|
07d506feb5 | ||
|
|
0124a0f32e | ||
|
|
e7936c3fd0 | ||
|
|
583d556677 | ||
|
|
72ed39d8a1 | ||
|
|
7c4c1edd85 | ||
|
|
5846a38c68 | ||
|
|
47e5f7c2e2 | ||
|
|
aa599b8af1 | ||
|
|
2b2f5a7c2a | ||
|
|
8b09559690 | ||
|
|
318b76de50 | ||
|
|
a85fdd4051 | ||
|
|
92621bca7d |
77
.devcontainer/devcontainer.json
Normal file
77
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/python
|
||||||
|
{
|
||||||
|
"name": "SpecKitDevContainer",
|
||||||
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/python:3.13-trixie", // based on Debian "Trixie" (13)
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||||
|
"installZsh": true,
|
||||||
|
"installOhMyZsh": true,
|
||||||
|
"installOhMyZshConfig": true,
|
||||||
|
"upgradePackages": true,
|
||||||
|
"username": "devcontainer",
|
||||||
|
"userUid": "automatic",
|
||||||
|
"userGid": "automatic"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/dotnet:2": {
|
||||||
|
"version": "lts"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/git:1": {
|
||||||
|
"ppa": true,
|
||||||
|
"version": "latest"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/node": {
|
||||||
|
"version": "lts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
"forwardPorts": [
|
||||||
|
8080 // for Spec-Kit documentation site
|
||||||
|
],
|
||||||
|
"containerUser": "devcontainer",
|
||||||
|
"updateRemoteUserUID": true,
|
||||||
|
"postCreateCommand": "chmod +x ./.devcontainer/post-create.sh && ./.devcontainer/post-create.sh",
|
||||||
|
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"mhutchie.git-graph",
|
||||||
|
"eamodio.gitlens",
|
||||||
|
"anweber.reveal-button",
|
||||||
|
"chrisdias.promptboost",
|
||||||
|
// Github Copilot
|
||||||
|
"GitHub.copilot",
|
||||||
|
"GitHub.copilot-chat",
|
||||||
|
// Codex
|
||||||
|
"openai.chatgpt",
|
||||||
|
// Kilo Code
|
||||||
|
"kilocode.Kilo-Code",
|
||||||
|
// Roo Code
|
||||||
|
"RooVeterinaryInc.roo-cline",
|
||||||
|
// Amazon Developer Q
|
||||||
|
"AmazonWebServices.amazon-q-vscode",
|
||||||
|
// Claude Code
|
||||||
|
"anthropic.claude-code"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"debug.javascript.autoAttachFilter": "disabled", // fix running commands in integrated terminal
|
||||||
|
|
||||||
|
// Specify settings for Github Copilot
|
||||||
|
"git.autofetch": true,
|
||||||
|
"chat.promptFilesRecommendations": {
|
||||||
|
"speckit.constitution": true,
|
||||||
|
"speckit.specify": true,
|
||||||
|
"speckit.plan": true,
|
||||||
|
"speckit.tasks": true,
|
||||||
|
"speckit.implement": true
|
||||||
|
},
|
||||||
|
"chat.tools.terminal.autoApprove": {
|
||||||
|
".specify/scripts/bash/": true,
|
||||||
|
".specify/scripts/powershell/": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
.devcontainer/post-create.sh
Executable file
100
.devcontainer/post-create.sh
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit immediately on error, treat unset variables as an error, and fail if any command in a pipeline fails.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Function to run a command and show logs only on error
|
||||||
|
run_command() {
|
||||||
|
local command_to_run="$*"
|
||||||
|
local output
|
||||||
|
local exit_code
|
||||||
|
|
||||||
|
# Capture all output (stdout and stderr)
|
||||||
|
output=$(eval "$command_to_run" 2>&1) || exit_code=$?
|
||||||
|
exit_code=${exit_code:-0}
|
||||||
|
|
||||||
|
if [ $exit_code -ne 0 ]; then
|
||||||
|
echo -e "\033[0;31m[ERROR] Command failed (Exit Code $exit_code): $command_to_run\033[0m" >&2
|
||||||
|
echo -e "\033[0;31m$output\033[0m" >&2
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installing CLI-based AI Agents
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Copilot CLI..."
|
||||||
|
run_command "npm install -g @github/copilot@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Claude CLI..."
|
||||||
|
run_command "npm install -g @anthropic-ai/claude-code@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Codex CLI..."
|
||||||
|
run_command "npm install -g @openai/codex@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Gemini CLI..."
|
||||||
|
run_command "npm install -g @google/gemini-cli@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Augie CLI..."
|
||||||
|
run_command "npm install -g @augmentcode/auggie@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Qwen Code CLI..."
|
||||||
|
run_command "npm install -g @qwen-code/qwen-code@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing OpenCode CLI..."
|
||||||
|
run_command "npm install -g opencode-ai@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Amazon Q CLI..."
|
||||||
|
# 👉🏾 https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-verify-download.html
|
||||||
|
|
||||||
|
run_command "curl --proto '=https' --tlsv1.2 -sSf 'https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip' -o 'q.zip'"
|
||||||
|
run_command "curl --proto '=https' --tlsv1.2 -sSf 'https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip.sig' -o 'q.zip.sig'"
|
||||||
|
cat > amazonq-public-key.asc << 'EOF'
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mDMEZig60RYJKwYBBAHaRw8BAQdAy/+G05U5/EOA72WlcD4WkYn5SInri8pc4Z6D
|
||||||
|
BKNNGOm0JEFtYXpvbiBRIENMSSBUZWFtIDxxLWNsaUBhbWF6b24uY29tPoiZBBMW
|
||||||
|
CgBBFiEEmvYEF+gnQskUPgPsUNx6jcJMVmcFAmYoOtECGwMFCQPCZwAFCwkIBwIC
|
||||||
|
IgIGFQoJCAsCBBYCAwECHgcCF4AACgkQUNx6jcJMVmef5QD/QWWEGG/cOnbDnp68
|
||||||
|
SJXuFkwiNwlH2rPw9ZRIQMnfAS0A/0V6ZsGB4kOylBfc7CNfzRFGtovdBBgHqA6P
|
||||||
|
zQ/PNscGuDgEZig60RIKKwYBBAGXVQEFAQEHQC4qleONMBCq3+wJwbZSr0vbuRba
|
||||||
|
D1xr4wUPn4Avn4AnAwEIB4h+BBgWCgAmFiEEmvYEF+gnQskUPgPsUNx6jcJMVmcF
|
||||||
|
AmYoOtECGwwFCQPCZwAACgkQUNx6jcJMVmchMgEA6l3RveCM0YHAGQaSFMkguoAo
|
||||||
|
vK6FgOkDawgP0NPIP2oA/jIAO4gsAntuQgMOsPunEdDeji2t+AhV02+DQIsXZpoB
|
||||||
|
=f8yY
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
EOF
|
||||||
|
run_command "gpg --batch --import amazonq-public-key.asc"
|
||||||
|
run_command "gpg --verify q.zip.sig q.zip"
|
||||||
|
run_command "unzip -q q.zip"
|
||||||
|
run_command "chmod +x ./q/install.sh"
|
||||||
|
run_command "./q/install.sh --no-confirm"
|
||||||
|
run_command "rm -rf ./q q.zip q.zip.sig amazonq-public-key.asc"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing CodeBuddy CLI..."
|
||||||
|
run_command "npm install -g @tencent-ai/codebuddy-code@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
# Installing UV (Python package manager)
|
||||||
|
echo -e "\n🐍 Installing UV - Python Package Manager..."
|
||||||
|
run_command "pipx install uv"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
# Installing DocFx (for documentation site)
|
||||||
|
echo -e "\n📚 Installing DocFx..."
|
||||||
|
run_command "dotnet tool update -g docfx"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🧹 Cleaning cache..."
|
||||||
|
run_command "sudo apt-get autoclean"
|
||||||
|
run_command "sudo apt-get clean"
|
||||||
|
|
||||||
|
echo "✅ Setup completed. Happy coding! 🚀"
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1,2 +1,3 @@
|
|||||||
# Global code owner
|
# Global code owner
|
||||||
* @localden
|
* @localden
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/docs.yml
vendored
1
.github/workflows/docs.yml
vendored
@@ -65,3 +65,4 @@ jobs:
|
|||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -57,3 +57,4 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
chmod +x .github/workflows/scripts/update-version.sh
|
chmod +x .github/workflows/scripts/update-version.sh
|
||||||
.github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }}
|
.github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }}
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ if gh release view "$VERSION" >/dev/null 2>&1; then
|
|||||||
else
|
else
|
||||||
echo "exists=false" >> $GITHUB_OUTPUT
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
echo "Release $VERSION does not exist, proceeding..."
|
echo "Release $VERSION does not exist, proceeding..."
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ gh release create "$VERSION" \
|
|||||||
.genreleases/spec-kit-template-claude-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-claude-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-gemini-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-gemini-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-gemini-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-gemini-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-cursor-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-cursor-agent-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-cursor-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-cursor-agent-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-opencode-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-opencode-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-opencode-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-opencode-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-qwen-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-qwen-sh-"$VERSION".zip \
|
||||||
@@ -38,7 +38,9 @@ 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" \
|
||||||
--notes-file release_notes.md
|
--notes-file release_notes.md
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
|||||||
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
||||||
# Version argument should include leading 'v'.
|
# Version argument should include leading 'v'.
|
||||||
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
||||||
# AGENTS : space or comma separated subset of: claude gemini copilot cursor qwen opencode windsurf codex (default: all)
|
# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex (default: all)
|
||||||
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
||||||
# Examples:
|
# Examples:
|
||||||
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
||||||
@@ -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" ;;
|
||||||
@@ -132,7 +133,7 @@ build_variant() {
|
|||||||
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
|
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
|
||||||
|
|
||||||
# NOTE: We substitute {ARGS} internally. Outward tokens differ intentionally:
|
# NOTE: We substitute {ARGS} internally. Outward tokens differ intentionally:
|
||||||
# * Markdown/prompt (claude, copilot, cursor, opencode): $ARGUMENTS
|
# * Markdown/prompt (claude, copilot, cursor-agent, opencode): $ARGUMENTS
|
||||||
# * TOML (gemini, qwen): {{args}}
|
# * TOML (gemini, qwen): {{args}}
|
||||||
# This keeps formats readable without extra abstraction.
|
# This keeps formats readable without extra abstraction.
|
||||||
|
|
||||||
@@ -151,9 +152,9 @@ build_variant() {
|
|||||||
mkdir -p "$base_dir/.vscode"
|
mkdir -p "$base_dir/.vscode"
|
||||||
[[ -f templates/vscode-settings.json ]] && cp templates/vscode-settings.json "$base_dir/.vscode/settings.json"
|
[[ -f templates/vscode-settings.json ]] && cp templates/vscode-settings.json "$base_dir/.vscode/settings.json"
|
||||||
;;
|
;;
|
||||||
cursor)
|
cursor-agent)
|
||||||
mkdir -p "$base_dir/.cursor/commands"
|
mkdir -p "$base_dir/.cursor/commands"
|
||||||
generate_commands cursor md "\$ARGUMENTS" "$base_dir/.cursor/commands" "$script" ;;
|
generate_commands cursor-agent md "\$ARGUMENTS" "$base_dir/.cursor/commands" "$script" ;;
|
||||||
qwen)
|
qwen)
|
||||||
mkdir -p "$base_dir/.qwen/commands"
|
mkdir -p "$base_dir/.qwen/commands"
|
||||||
generate_commands qwen toml "{{args}}" "$base_dir/.qwen/commands" "$script"
|
generate_commands qwen toml "{{args}}" "$base_dir/.qwen/commands" "$script"
|
||||||
@@ -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-agent 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")}'
|
||||||
@@ -233,3 +237,4 @@ done
|
|||||||
|
|
||||||
echo "Archives in $GENRELEASES_DIR:"
|
echo "Archives in $GENRELEASES_DIR:"
|
||||||
ls -1 "$GENRELEASES_DIR"/spec-kit-template-*-"${NEW_VERSION}".zip
|
ls -1 "$GENRELEASES_DIR"/spec-kit-template-*-"${NEW_VERSION}".zip
|
||||||
|
|
||||||
|
|||||||
@@ -33,4 +33,4 @@ This is the latest set of releases that you can use with your agent of choice. W
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Generated release notes:"
|
echo "Generated release notes:"
|
||||||
cat release_notes.md
|
cat release_notes.md
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ PATCH=$((PATCH + 1))
|
|||||||
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
||||||
|
|
||||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "New version will be: $NEW_VERSION"
|
echo "New version will be: $NEW_VERSION"
|
||||||
|
|||||||
2
.github/workflows/scripts/update-version.sh
vendored
2
.github/workflows/scripts/update-version.sh
vendored
@@ -20,4 +20,4 @@ if [ -f "pyproject.toml" ]; then
|
|||||||
echo "Updated pyproject.toml version to $PYTHON_VERSION (for release artifacts only)"
|
echo "Updated pyproject.toml version to $PYTHON_VERSION (for release artifacts only)"
|
||||||
else
|
else
|
||||||
echo "Warning: pyproject.toml not found, skipping version update"
|
echo "Warning: pyproject.toml not found, skipping version update"
|
||||||
fi
|
fi
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -42,4 +42,4 @@ env/
|
|||||||
# Spec Kit-specific files
|
# Spec Kit-specific files
|
||||||
.genreleases/
|
.genreleases/
|
||||||
*.zip
|
*.zip
|
||||||
sdd-*/
|
sdd-*/
|
||||||
|
|||||||
181
AGENTS.md
181
AGENTS.md
@@ -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 CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
|
||||||
| **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
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ Modify `.github/workflows/scripts/create-release-packages.sh`:
|
|||||||
|
|
||||||
##### Add to ALL_AGENTS array:
|
##### Add to ALL_AGENTS array:
|
||||||
```bash
|
```bash
|
||||||
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf q)
|
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf q)
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Add case statement for directory structure:
|
##### Add case statement for directory structure:
|
||||||
@@ -190,17 +191,111 @@ 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
|
||||||
|
|
||||||
|
#### 7. Update Devcontainer files (Optional)
|
||||||
|
|
||||||
|
For agents that have VS Code extensions or require CLI installation, update the devcontainer configuration files:
|
||||||
|
|
||||||
|
##### VS Code Extension-based Agents
|
||||||
|
|
||||||
|
For agents available as VS Code extensions, add them to `.devcontainer/devcontainer.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
// ... existing extensions ...
|
||||||
|
// [New Agent Name]
|
||||||
|
"[New Agent Extension ID]"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### CLI-based Agents
|
||||||
|
|
||||||
|
For agents that require CLI tools, add installation commands to `.devcontainer/post-create.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Existing installations...
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing [New Agent Name] CLI..."
|
||||||
|
# run_command "npm install -g [agent-cli-package]@latest" # Example for node-based CLI
|
||||||
|
# or other installation instructions (must be non-interactive and compatible with Linux Debian "Trixie" or later)...
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quick Tips:**
|
||||||
|
|
||||||
|
- **Extension-based agents**: Add to the `extensions` array in `devcontainer.json`
|
||||||
|
- **CLI-based agents**: Add installation scripts to `post-create.sh`
|
||||||
|
- **Hybrid agents**: May require both extension and CLI installation
|
||||||
|
- **Test thoroughly**: Ensure installations work in the devcontainer environment
|
||||||
|
|
||||||
## 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
|
||||||
|
- **Amazon Q Developer CLI**: `q` CLI
|
||||||
|
- **CodeBuddy CLI**: `codebuddy` CLI
|
||||||
|
|
||||||
### IDE-Based Agents
|
### IDE-Based Agents
|
||||||
Work within integrated development environments:
|
Work within integrated development environments:
|
||||||
@@ -257,20 +352,24 @@ 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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
||||||
|
|
||||||
|
|||||||
37
CHANGELOG.md
37
CHANGELOG.md
@@ -7,6 +7,42 @@ 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.20] - 2025-10-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Intelligent Branch Naming**: `create-new-feature` scripts now support `--short-name` parameter for custom branch names
|
||||||
|
- When `--short-name` provided: Uses the custom name directly (cleaned and formatted)
|
||||||
|
- When omitted: Automatically generates meaningful names using stop word filtering and length-based filtering
|
||||||
|
- Filters out common stop words (I, want, to, the, for, etc.)
|
||||||
|
- Removes words shorter than 3 characters (unless they're uppercase acronyms)
|
||||||
|
- Takes 3-4 most meaningful words from the description
|
||||||
|
- **Enforces GitHub's 244-byte branch name limit** with automatic truncation and warnings
|
||||||
|
- Examples:
|
||||||
|
- "I want to create user authentication" → `001-create-user-authentication`
|
||||||
|
- "Implement OAuth2 integration for API" → `001-implement-oauth2-integration-api`
|
||||||
|
- "Fix payment processing bug" → `001-fix-payment-processing`
|
||||||
|
- Very long descriptions are automatically truncated at word boundaries to stay within limits
|
||||||
|
- Designed for AI agents to provide semantic short names while maintaining standalone usability
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Enhanced help documentation for `create-new-feature.sh` and `create-new-feature.ps1` scripts with examples
|
||||||
|
- Branch names now validated against GitHub's 244-byte limit with automatic truncation if needed
|
||||||
|
|
||||||
|
## [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
|
||||||
@@ -130,3 +166,4 @@ N/A
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
N/A
|
N/A
|
||||||
|
|
||||||
|
|||||||
@@ -71,4 +71,4 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi
|
|||||||
available at [http://contributor-covenant.org/version/1/4][version]
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
[homepage]: http://contributor-covenant.org
|
[homepage]: http://contributor-covenant.org
|
||||||
[version]: http://contributor-covenant.org/version/1/4/
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
|
|||||||
@@ -13,6 +13,23 @@ These are one time installations required to be able to test your changes locall
|
|||||||
1. Install [Git](https://git-scm.com/downloads)
|
1. Install [Git](https://git-scm.com/downloads)
|
||||||
1. Have an [AI coding agent available](README.md#-supported-ai-agents)
|
1. Have an [AI coding agent available](README.md#-supported-ai-agents)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>💡 Hint if you are using <code>VSCode</code> or <code>GitHub Codespaces</code> as your IDE</b></summary>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Provided you have [Docker](https://docker.com) installed on your machine, you can leverage [Dev Containers](https://containers.dev) through this [VSCode extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), to easily set up your development environment, with aforementioned tools already installed and configured, thanks to the `.devcontainer/devcontainer.json` file (located at the root of the project).
|
||||||
|
|
||||||
|
To do so, simply:
|
||||||
|
|
||||||
|
- Checkout the repo
|
||||||
|
- Open it with VSCode
|
||||||
|
- Open the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) and select "Dev Containers: Open Folder in Container..."
|
||||||
|
|
||||||
|
On [GitHub Codespaces](https://github.com/features/codespaces) it's even simpler, as it leverages the `.devcontainer/devcontainer.json` automatically upon opening the codespace.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Submitting a pull request
|
## Submitting a pull request
|
||||||
|
|
||||||
>[!NOTE]
|
>[!NOTE]
|
||||||
@@ -108,3 +125,4 @@ Please be respectful to maintainers and disclose AI assistance.
|
|||||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||||
- [GitHub Help](https://help.github.com)
|
- [GitHub Help](https://help.github.com)
|
||||||
|
|
||||||
|
|||||||
1
LICENSE
1
LICENSE
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|||||||
67
README.md
67
README.md
@@ -5,7 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>An effort to allow organizations to focus on product scenarios rather than writing undifferentiated code with the help of Spec-Driven Development.</strong>
|
<strong>An open source toolkit that allows you to focus on product scenarios and predictable outcomes instead of vibe coding every piece from scratch.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -20,16 +20,16 @@
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
|
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
|
||||||
- [⚡ Get started](#-get-started)
|
- [⚡ Get Started](#-get-started)
|
||||||
- [📽️ Video Overview](#️-video-overview)
|
- [📽️ Video Overview](#️-video-overview)
|
||||||
- [🤖 Supported AI Agents](#-supported-ai-agents)
|
- [🤖 Supported AI Agents](#-supported-ai-agents)
|
||||||
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
||||||
- [📚 Core philosophy](#-core-philosophy)
|
- [📚 Core Philosophy](#-core-philosophy)
|
||||||
- [🌟 Development phases](#-development-phases)
|
- [🌟 Development Phases](#-development-phases)
|
||||||
- [🎯 Experimental goals](#-experimental-goals)
|
- [🎯 Experimental Goals](#-experimental-goals)
|
||||||
- [🔧 Prerequisites](#-prerequisites)
|
- [🔧 Prerequisites](#-prerequisites)
|
||||||
- [📖 Learn more](#-learn-more)
|
- [📖 Learn More](#-learn-more)
|
||||||
- [📋 Detailed process](#-detailed-process)
|
- [📋 Detailed Process](#-detailed-process)
|
||||||
- [🔍 Troubleshooting](#-troubleshooting)
|
- [🔍 Troubleshooting](#-troubleshooting)
|
||||||
- [👥 Maintainers](#-maintainers)
|
- [👥 Maintainers](#-maintainers)
|
||||||
- [💬 Support](#-support)
|
- [💬 Support](#-support)
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
|
|
||||||
Spec-Driven Development **flips the script** on traditional software development. For decades, code has been king — specifications were just scaffolding we built and discarded once the "real work" of coding began. Spec-Driven Development changes this: **specifications become executable**, directly generating working implementations rather than just guiding them.
|
Spec-Driven Development **flips the script** on traditional software development. For decades, code has been king — specifications were just scaffolding we built and discarded once the "real work" of coding began. Spec-Driven Development changes this: **specifications become executable**, directly generating working implementations rather than just guiding them.
|
||||||
|
|
||||||
## ⚡ Get started
|
## ⚡ Get Started
|
||||||
|
|
||||||
### 1. Install Specify
|
### 1. Install Specify CLI
|
||||||
|
|
||||||
Choose your preferred installation method:
|
Choose your preferred installation method:
|
||||||
|
|
||||||
@@ -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:
|
||||||
@@ -78,6 +84,8 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME
|
|||||||
|
|
||||||
### 2. Establish project principles
|
### 2. Establish project principles
|
||||||
|
|
||||||
|
Launch your AI assistant in the project directory. The `/speckit.*` commands are available in the assistant.
|
||||||
|
|
||||||
Use the **`/speckit.constitution`** command to create your project's governing principles and development guidelines that will guide all subsequent development.
|
Use the **`/speckit.constitution`** command to create your project's governing principles and development guidelines that will guide all subsequent development.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -137,6 +145,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 CLI](https://www.codebuddy.ai/cli) | ✅ | |
|
||||||
| [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 +166,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-agent`, `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 +186,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
|
||||||
@@ -240,7 +249,7 @@ Additional commands for enhanced quality and validation:
|
|||||||
|------------------|------------------------------------------------------------------------------------------------|
|
|------------------|------------------------------------------------------------------------------------------------|
|
||||||
| `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.<br/>**Must be set in the context of the agent you're working with prior to using `/speckit.plan` or follow-up commands. |
|
| `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.<br/>**Must be set in the context of the agent you're working with prior to using `/speckit.plan` or follow-up commands. |
|
||||||
|
|
||||||
## 📚 Core philosophy
|
## 📚 Core Philosophy
|
||||||
|
|
||||||
Spec-Driven Development is a structured process that emphasizes:
|
Spec-Driven Development is a structured process that emphasizes:
|
||||||
|
|
||||||
@@ -249,7 +258,7 @@ Spec-Driven Development is a structured process that emphasizes:
|
|||||||
- **Multi-step refinement** rather than one-shot code generation from prompts
|
- **Multi-step refinement** rather than one-shot code generation from prompts
|
||||||
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
|
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
|
||||||
|
|
||||||
## 🌟 Development phases
|
## 🌟 Development Phases
|
||||||
|
|
||||||
| Phase | Focus | Key Activities |
|
| Phase | Focus | Key Activities |
|
||||||
|-------|-------|----------------|
|
|-------|-------|----------------|
|
||||||
@@ -257,7 +266,7 @@ Spec-Driven Development is a structured process that emphasizes:
|
|||||||
| **Creative Exploration** | Parallel implementations | <ul><li>Explore diverse solutions</li><li>Support multiple technology stacks & architectures</li><li>Experiment with UX patterns</li></ul> |
|
| **Creative Exploration** | Parallel implementations | <ul><li>Explore diverse solutions</li><li>Support multiple technology stacks & architectures</li><li>Experiment with UX patterns</li></ul> |
|
||||||
| **Iterative Enhancement** ("Brownfield") | Brownfield modernization | <ul><li>Add features iteratively</li><li>Modernize legacy systems</li><li>Adapt processes</li></ul> |
|
| **Iterative Enhancement** ("Brownfield") | Brownfield modernization | <ul><li>Add features iteratively</li><li>Modernize legacy systems</li><li>Adapt processes</li></ul> |
|
||||||
|
|
||||||
## 🎯 Experimental goals
|
## 🎯 Experimental Goals
|
||||||
|
|
||||||
Our research and experimentation focus on:
|
Our research and experimentation focus on:
|
||||||
|
|
||||||
@@ -285,22 +294,22 @@ Our research and experimentation focus on:
|
|||||||
|
|
||||||
## 🔧 Prerequisites
|
## 🔧 Prerequisites
|
||||||
|
|
||||||
- **Linux/macOS** (or WSL2 on Windows)
|
- **Linux/macOS/Windows**
|
||||||
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Cursor](https://cursor.sh/), [Qwen CLI](https://github.com/QwenLM/qwen-code), [opencode](https://opencode.ai/), [Codex CLI](https://github.com/openai/codex), [Windsurf](https://windsurf.com/), or [Amazon Q Developer CLI](https://aws.amazon.com/developer/learning/q-developer-cli/)
|
- [Supported](#-supported-ai-agents) AI coding agent.
|
||||||
- [uv](https://docs.astral.sh/uv/) for package management
|
- [uv](https://docs.astral.sh/uv/) for package management
|
||||||
- [Python 3.11+](https://www.python.org/downloads/)
|
- [Python 3.11+](https://www.python.org/downloads/)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
|
||||||
If you encounter issues with an agent, please open an issue so we can refine the integration.
|
If you encounter issues with an agent, please open an issue so we can refine the integration.
|
||||||
|
|
||||||
## 📖 Learn more
|
## 📖 Learn More
|
||||||
|
|
||||||
- **[Complete Spec-Driven Development Methodology](./spec-driven.md)** - Deep dive into the full process
|
- **[Complete Spec-Driven Development Methodology](./spec-driven.md)** - Deep dive into the full process
|
||||||
- **[Detailed Walkthrough](#-detailed-process)** - Step-by-step implementation guide
|
- **[Detailed Walkthrough](#-detailed-process)** - Step-by-step implementation guide
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Detailed process
|
## 📋 Detailed Process
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click to expand the detailed step-by-step walkthrough</summary>
|
<summary>Click to expand the detailed step-by-step walkthrough</summary>
|
||||||
@@ -538,7 +547,26 @@ You can also ask Claude Code (if you have the [GitHub CLI](https://docs.github.c
|
|||||||
>[!NOTE]
|
>[!NOTE]
|
||||||
>Before you have the agent implement it, it's also worth prompting Claude Code to cross-check the details to see if there are any over-engineered pieces (remember - it can be over-eager). If over-engineered components or decisions exist, you can ask Claude Code to resolve them. Ensure that Claude Code follows the [constitution](base/memory/constitution.md) as the foundational piece that it must adhere to when establishing the plan.
|
>Before you have the agent implement it, it's also worth prompting Claude Code to cross-check the details to see if there are any over-engineered pieces (remember - it can be over-eager). If over-engineered components or decisions exist, you can ask Claude Code to resolve them. Ensure that Claude Code follows the [constitution](base/memory/constitution.md) as the foundational piece that it must adhere to when establishing the plan.
|
||||||
|
|
||||||
### STEP 6: Implementation
|
### **STEP 6:** Generate task breakdown with /speckit.tasks
|
||||||
|
|
||||||
|
With the implementation plan validated, you can now break down the plan into specific, actionable tasks that can be executed in the correct order. Use the `/speckit.tasks` command to automatically generate a detailed task breakdown from your implementation plan:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/speckit.tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
This step creates a `tasks.md` file in your feature specification directory that contains:
|
||||||
|
|
||||||
|
- **Task breakdown organized by user story** - Each user story becomes a separate implementation phase with its own set of tasks
|
||||||
|
- **Dependency management** - Tasks are ordered to respect dependencies between components (e.g., models before services, services before endpoints)
|
||||||
|
- **Parallel execution markers** - Tasks that can run in parallel are marked with `[P]` to optimize development workflow
|
||||||
|
- **File path specifications** - Each task includes the exact file paths where implementation should occur
|
||||||
|
- **Test-driven development structure** - If tests are requested, test tasks are included and ordered to be written before implementation
|
||||||
|
- **Checkpoint validation** - Each user story phase includes checkpoints to validate independent functionality
|
||||||
|
|
||||||
|
The generated tasks.md provides a clear roadmap for the `/speckit.implement` command, ensuring systematic implementation that maintains code quality and allows for incremental delivery of user stories.
|
||||||
|
|
||||||
|
### **STEP 7:** Implementation
|
||||||
|
|
||||||
Once ready, use the `/speckit.implement` command to execute your implementation plan:
|
Once ready, use the `/speckit.implement` command to execute your implementation plan:
|
||||||
|
|
||||||
@@ -597,3 +625,4 @@ This project is heavily influenced by and based on the work and research of [Joh
|
|||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](./LICENSE) file for the full terms.
|
This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](./LICENSE) file for the full terms.
|
||||||
|
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ This information will help us triage your report more quickly.
|
|||||||
|
|
||||||
## Policy
|
## Policy
|
||||||
|
|
||||||
See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms)
|
See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms)
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ For help or questions about using this project, please:
|
|||||||
## GitHub Support Policy
|
## GitHub Support Policy
|
||||||
|
|
||||||
Support for this project is limited to the resources listed above.
|
Support for this project is limited to the resources listed above.
|
||||||
|
|
||||||
|
|||||||
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
@@ -6,3 +6,4 @@ obj/
|
|||||||
# Temporary files
|
# Temporary files
|
||||||
*.tmp
|
*.tmp
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
|||||||
@@ -31,3 +31,4 @@ To build the documentation locally:
|
|||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
Documentation is automatically built and deployed to GitHub Pages when changes are pushed to the `main` branch. The workflow is defined in `.github/workflows/docs.yml`.
|
Documentation is automatically built and deployed to GitHub Pages when changes are pushed to the `main` branch. The workflow is defined in `.github/workflows/docs.yml`.
|
||||||
|
|
||||||
|
|||||||
@@ -68,3 +68,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,3 +60,4 @@ Please see our [Contributing Guide](https://github.com/github/spec-kit/blob/main
|
|||||||
## Support
|
## Support
|
||||||
|
|
||||||
For support, please check our [Support Guide](https://github.com/github/spec-kit/blob/main/SUPPORT.md) or open an issue on GitHub.
|
For support, please check our [Support Guide](https://github.com/github/spec-kit/blob/main/SUPPORT.md) or open an issue on GitHub.
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
||||||
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Codebuddy CLI](https://www.codebuddy.ai/cli) or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
||||||
- [uv](https://docs.astral.sh/uv/) for package management
|
- [uv](https://docs.astral.sh/uv/) for package management
|
||||||
- [Python 3.11+](https://www.python.org/downloads/)
|
- [Python 3.11+](https://www.python.org/downloads/)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
@@ -34,6 +34,7 @@ You can proactively specify your AI agent during initialization:
|
|||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai claude
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai claude
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai gemini
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai gemini
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai copilot
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai copilot
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai codebuddy
|
||||||
```
|
```
|
||||||
|
|
||||||
### Specify Script Type (Shell vs PowerShell)
|
### Specify Script Type (Shell vs PowerShell)
|
||||||
@@ -86,3 +87,4 @@ git config --global credential.helper manager
|
|||||||
echo "Cleaning up..."
|
echo "Cleaning up..."
|
||||||
rm gcm-linux_amd64.2.6.1.deb
|
rm gcm-linux_amd64.2.6.1.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -166,3 +166,4 @@ rm -rf .venv dist build *.egg-info
|
|||||||
- Open a PR when satisfied
|
- Open a PR when satisfied
|
||||||
- (Optional) Tag a release once changes land in `main`
|
- (Optional) Tag a release once changes land in `main`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -120,3 +120,4 @@ implement specs/002-create-taskify/plan.md
|
|||||||
- Read the complete methodology for in-depth guidance
|
- Read the complete methodology for in-depth guidance
|
||||||
- Check out more examples in the repository
|
- Check out more examples in the repository
|
||||||
- Explore the source code on GitHub
|
- Explore the source code on GitHub
|
||||||
|
|
||||||
|
|||||||
@@ -15,3 +15,4 @@
|
|||||||
items:
|
items:
|
||||||
- name: Local Development
|
- name: Local Development
|
||||||
href: local-development.md
|
href: local-development.md
|
||||||
|
|
||||||
|
|||||||
@@ -47,4 +47,4 @@
|
|||||||
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->
|
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->
|
||||||
|
|
||||||
**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
|
**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
|
||||||
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
|
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.0.18"
|
version = "0.0.20"
|
||||||
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 = [
|
||||||
@@ -21,3 +21,4 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/specify_cli"]
|
packages = ["src/specify_cli"]
|
||||||
|
|
||||||
|
|||||||
@@ -163,4 +163,4 @@ else
|
|||||||
if $INCLUDE_TASKS; then
|
if $INCLUDE_TASKS; then
|
||||||
check_file "$TASKS" "tasks.md"
|
check_file "$TASKS" "tasks.md"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -19,21 +19,21 @@ get_current_branch() {
|
|||||||
echo "$SPECIFY_FEATURE"
|
echo "$SPECIFY_FEATURE"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Then check git if available
|
# Then check git if available
|
||||||
if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
|
if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
|
||||||
git rev-parse --abbrev-ref HEAD
|
git rev-parse --abbrev-ref HEAD
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For non-git repos, try to find the latest feature directory
|
# For non-git repos, try to find the latest feature directory
|
||||||
local repo_root=$(get_repo_root)
|
local repo_root=$(get_repo_root)
|
||||||
local specs_dir="$repo_root/specs"
|
local specs_dir="$repo_root/specs"
|
||||||
|
|
||||||
if [[ -d "$specs_dir" ]]; then
|
if [[ -d "$specs_dir" ]]; then
|
||||||
local latest_feature=""
|
local latest_feature=""
|
||||||
local highest=0
|
local highest=0
|
||||||
|
|
||||||
for dir in "$specs_dir"/*; do
|
for dir in "$specs_dir"/*; do
|
||||||
if [[ -d "$dir" ]]; then
|
if [[ -d "$dir" ]]; then
|
||||||
local dirname=$(basename "$dir")
|
local dirname=$(basename "$dir")
|
||||||
@@ -47,13 +47,13 @@ get_current_branch() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -n "$latest_feature" ]]; then
|
if [[ -n "$latest_feature" ]]; then
|
||||||
echo "$latest_feature"
|
echo "$latest_feature"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "main" # Final fallback
|
echo "main" # Final fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,35 +65,77 @@ has_git() {
|
|||||||
check_feature_branch() {
|
check_feature_branch() {
|
||||||
local branch="$1"
|
local branch="$1"
|
||||||
local has_git_repo="$2"
|
local has_git_repo="$2"
|
||||||
|
|
||||||
# For non-git repos, we can't enforce branch naming but still provide output
|
# For non-git repos, we can't enforce branch naming but still provide output
|
||||||
if [[ "$has_git_repo" != "true" ]]; then
|
if [[ "$has_git_repo" != "true" ]]; then
|
||||||
echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
|
echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
||||||
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
||||||
echo "Feature branches should be named like: 001-feature-name" >&2
|
echo "Feature branches should be named like: 001-feature-name" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
get_feature_dir() { echo "$1/specs/$2"; }
|
get_feature_dir() { echo "$1/specs/$2"; }
|
||||||
|
|
||||||
|
# Find feature directory by numeric prefix instead of exact branch match
|
||||||
|
# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
|
||||||
|
find_feature_dir_by_prefix() {
|
||||||
|
local repo_root="$1"
|
||||||
|
local branch_name="$2"
|
||||||
|
local specs_dir="$repo_root/specs"
|
||||||
|
|
||||||
|
# Extract numeric prefix from branch (e.g., "004" from "004-whatever")
|
||||||
|
if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
|
||||||
|
# If branch doesn't have numeric prefix, fall back to exact match
|
||||||
|
echo "$specs_dir/$branch_name"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local prefix="${BASH_REMATCH[1]}"
|
||||||
|
|
||||||
|
# Search for directories in specs/ that start with this prefix
|
||||||
|
local matches=()
|
||||||
|
if [[ -d "$specs_dir" ]]; then
|
||||||
|
for dir in "$specs_dir"/"$prefix"-*; do
|
||||||
|
if [[ -d "$dir" ]]; then
|
||||||
|
matches+=("$(basename "$dir")")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle results
|
||||||
|
if [[ ${#matches[@]} -eq 0 ]]; then
|
||||||
|
# No match found - return the branch name path (will fail later with clear error)
|
||||||
|
echo "$specs_dir/$branch_name"
|
||||||
|
elif [[ ${#matches[@]} -eq 1 ]]; then
|
||||||
|
# Exactly one match - perfect!
|
||||||
|
echo "$specs_dir/${matches[0]}"
|
||||||
|
else
|
||||||
|
# Multiple matches - this shouldn't happen with proper naming convention
|
||||||
|
echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2
|
||||||
|
echo "Please ensure only one spec directory exists per numeric prefix." >&2
|
||||||
|
echo "$specs_dir/$branch_name" # Return something to avoid breaking the script
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
get_feature_paths() {
|
get_feature_paths() {
|
||||||
local repo_root=$(get_repo_root)
|
local repo_root=$(get_repo_root)
|
||||||
local current_branch=$(get_current_branch)
|
local current_branch=$(get_current_branch)
|
||||||
local has_git_repo="false"
|
local has_git_repo="false"
|
||||||
|
|
||||||
if has_git; then
|
if has_git; then
|
||||||
has_git_repo="true"
|
has_git_repo="true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
|
# Use prefix-based lookup to support multiple branches per spec
|
||||||
|
local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch")
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
REPO_ROOT='$repo_root'
|
REPO_ROOT='$repo_root'
|
||||||
CURRENT_BRANCH='$current_branch'
|
CURRENT_BRANCH='$current_branch'
|
||||||
@@ -111,3 +153,4 @@ EOF
|
|||||||
|
|
||||||
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||||
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,52 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
JSON_MODE=false
|
JSON_MODE=false
|
||||||
|
SHORT_NAME=""
|
||||||
ARGS=()
|
ARGS=()
|
||||||
for arg in "$@"; do
|
i=1
|
||||||
|
while [ $i -le $# ]; do
|
||||||
|
arg="${!i}"
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--json) JSON_MODE=true ;;
|
--json)
|
||||||
--help|-h) echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
|
JSON_MODE=true
|
||||||
*) ARGS+=("$arg") ;;
|
;;
|
||||||
|
--short-name)
|
||||||
|
if [ $((i + 1)) -gt $# ]; then
|
||||||
|
echo 'Error: --short-name requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
i=$((i + 1))
|
||||||
|
next_arg="${!i}"
|
||||||
|
# Check if the next argument is another option (starts with --)
|
||||||
|
if [[ "$next_arg" == --* ]]; then
|
||||||
|
echo 'Error: --short-name requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SHORT_NAME="$next_arg"
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--json] [--short-name <name>] <feature_description>"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --json Output in JSON format"
|
||||||
|
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
|
echo " --help, -h Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
||||||
|
echo " $0 'Implement OAuth2 integration for API'"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ARGS+=("$arg")
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
i=$((i + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
FEATURE_DESCRIPTION="${ARGS[*]}"
|
FEATURE_DESCRIPTION="${ARGS[*]}"
|
||||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||||
echo "Usage: $0 [--json] <feature_description>" >&2
|
echo "Usage: $0 [--json] [--short-name <name>] <feature_description>" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -67,9 +101,84 @@ fi
|
|||||||
NEXT=$((HIGHEST + 1))
|
NEXT=$((HIGHEST + 1))
|
||||||
FEATURE_NUM=$(printf "%03d" "$NEXT")
|
FEATURE_NUM=$(printf "%03d" "$NEXT")
|
||||||
|
|
||||||
BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
|
# Function to generate branch name with stop word filtering and length filtering
|
||||||
WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
|
generate_branch_name() {
|
||||||
BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
|
local description="$1"
|
||||||
|
|
||||||
|
# Common stop words to filter out
|
||||||
|
local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$"
|
||||||
|
|
||||||
|
# Convert to lowercase and split into words
|
||||||
|
local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
|
||||||
|
|
||||||
|
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
|
||||||
|
local meaningful_words=()
|
||||||
|
for word in $clean_name; do
|
||||||
|
# Skip empty words
|
||||||
|
[ -z "$word" ] && continue
|
||||||
|
|
||||||
|
# Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
|
||||||
|
if ! echo "$word" | grep -qiE "$stop_words"; then
|
||||||
|
if [ ${#word} -ge 3 ]; then
|
||||||
|
meaningful_words+=("$word")
|
||||||
|
elif echo "$description" | grep -q "\b${word^^}\b"; then
|
||||||
|
# Keep short words if they appear as uppercase in original (likely acronyms)
|
||||||
|
meaningful_words+=("$word")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If we have meaningful words, use first 3-4 of them
|
||||||
|
if [ ${#meaningful_words[@]} -gt 0 ]; then
|
||||||
|
local max_words=3
|
||||||
|
if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
|
||||||
|
|
||||||
|
local result=""
|
||||||
|
local count=0
|
||||||
|
for word in "${meaningful_words[@]}"; do
|
||||||
|
if [ $count -ge $max_words ]; then break; fi
|
||||||
|
if [ -n "$result" ]; then result="$result-"; fi
|
||||||
|
result="$result$word"
|
||||||
|
count=$((count + 1))
|
||||||
|
done
|
||||||
|
echo "$result"
|
||||||
|
else
|
||||||
|
# Fallback to original logic if no meaningful words found
|
||||||
|
echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate branch name
|
||||||
|
if [ -n "$SHORT_NAME" ]; then
|
||||||
|
# Use provided short name, just clean it up
|
||||||
|
BRANCH_SUFFIX=$(echo "$SHORT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
|
||||||
|
else
|
||||||
|
# Generate from description with smart filtering
|
||||||
|
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
|
||||||
|
fi
|
||||||
|
|
||||||
|
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||||
|
|
||||||
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
|
# Validate and truncate if necessary
|
||||||
|
MAX_BRANCH_LENGTH=244
|
||||||
|
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
|
||||||
|
# Calculate how much we need to trim from suffix
|
||||||
|
# Account for: feature number (3) + hyphen (1) = 4 chars
|
||||||
|
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
|
||||||
|
|
||||||
|
# Truncate suffix at word boundary if possible
|
||||||
|
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
|
||||||
|
# Remove trailing hyphen if truncation created one
|
||||||
|
TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
|
||||||
|
|
||||||
|
ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
|
||||||
|
BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
|
||||||
|
|
||||||
|
>&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
|
||||||
|
>&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
|
||||||
|
>&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$HAS_GIT" = true ]; then
|
if [ "$HAS_GIT" = true ]; then
|
||||||
git checkout -b "$BRANCH_NAME"
|
git checkout -b "$BRANCH_NAME"
|
||||||
|
|||||||
@@ -58,3 +58,4 @@ else
|
|||||||
echo "BRANCH: $CURRENT_BRANCH"
|
echo "BRANCH: $CURRENT_BRANCH"
|
||||||
echo "HAS_GIT: $HAS_GIT"
|
echo "HAS_GIT: $HAS_GIT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
# - Creates default Claude file if no agent files exist
|
# - Creates default Claude file if no agent files exist
|
||||||
#
|
#
|
||||||
# Usage: ./update-agent-context.sh [agent_type]
|
# Usage: ./update-agent-context.sh [agent_type]
|
||||||
# Agent types: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|q
|
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|q
|
||||||
# Leave empty to update all existing agent files
|
# Leave empty to update all existing agent files
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
@@ -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.md"
|
||||||
Q_FILE="$REPO_ROOT/AGENTS.md"
|
Q_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
|
|
||||||
# Template file
|
# Template file
|
||||||
@@ -249,7 +250,7 @@ get_commands_for_language() {
|
|||||||
echo "cargo test && cargo clippy"
|
echo "cargo test && cargo clippy"
|
||||||
;;
|
;;
|
||||||
*"JavaScript"*|*"TypeScript"*)
|
*"JavaScript"*|*"TypeScript"*)
|
||||||
echo "npm test && npm run lint"
|
echo "npm test \\&\\& npm run lint"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "# Add commands for $lang"
|
echo "# Add commands for $lang"
|
||||||
@@ -557,7 +558,7 @@ update_specific_agent() {
|
|||||||
copilot)
|
copilot)
|
||||||
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
|
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
|
||||||
;;
|
;;
|
||||||
cursor)
|
cursor-agent)
|
||||||
update_agent_file "$CURSOR_FILE" "Cursor IDE"
|
update_agent_file "$CURSOR_FILE" "Cursor IDE"
|
||||||
;;
|
;;
|
||||||
qwen)
|
qwen)
|
||||||
@@ -581,12 +582,15 @@ 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 CLI"
|
||||||
|
;;
|
||||||
q)
|
q)
|
||||||
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown agent type '$agent_type'"
|
log_error "Unknown agent type '$agent_type'"
|
||||||
log_error "Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|q"
|
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|q"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -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 CLI"
|
||||||
|
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-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|q]"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
@@ -726,3 +736,4 @@ main() {
|
|||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
main "$@"
|
main "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -145,4 +145,4 @@ if ($Json) {
|
|||||||
if ($IncludeTasks) {
|
if ($IncludeTasks) {
|
||||||
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
|
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,3 +134,4 @@ function Test-DirHasFiles {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,39 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[switch]$Json,
|
[switch]$Json,
|
||||||
|
[string]$ShortName,
|
||||||
|
[switch]$Help,
|
||||||
[Parameter(ValueFromRemainingArguments = $true)]
|
[Parameter(ValueFromRemainingArguments = $true)]
|
||||||
[string[]]$FeatureDescription
|
[string[]]$FeatureDescription
|
||||||
)
|
)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
if ($Help) {
|
||||||
|
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] <feature description>"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Options:"
|
||||||
|
Write-Host " -Json Output in JSON format"
|
||||||
|
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
|
Write-Host " -Help Show this help message"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Examples:"
|
||||||
|
Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'"
|
||||||
|
Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if feature description provided
|
||||||
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
||||||
Write-Error "Usage: ./create-new-feature.ps1 [-Json] <feature description>"
|
Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] <feature description>"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
||||||
|
|
||||||
# Resolve repository root. Prefer git information when available, but fall back
|
# Resolve repository root. Prefer git information when available, but fall back
|
||||||
# to searching for repository markers so the workflow still functions in repositories that
|
# to searching for repository markers so the workflow still functions in repositories that
|
||||||
# were initialised with --no-git.
|
# were initialized with --no-git.
|
||||||
function Find-RepositoryRoot {
|
function Find-RepositoryRoot {
|
||||||
param(
|
param(
|
||||||
[string]$StartDir,
|
[string]$StartDir,
|
||||||
@@ -72,9 +91,82 @@ if (Test-Path $specsDir) {
|
|||||||
$next = $highest + 1
|
$next = $highest + 1
|
||||||
$featureNum = ('{0:000}' -f $next)
|
$featureNum = ('{0:000}' -f $next)
|
||||||
|
|
||||||
$branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
|
# Function to generate branch name with stop word filtering and length filtering
|
||||||
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
|
function Get-BranchName {
|
||||||
$branchName = "$featureNum-$([string]::Join('-', $words))"
|
param([string]$Description)
|
||||||
|
|
||||||
|
# Common stop words to filter out
|
||||||
|
$stopWords = @(
|
||||||
|
'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from',
|
||||||
|
'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
|
||||||
|
'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall',
|
||||||
|
'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their',
|
||||||
|
'want', 'need', 'add', 'get', 'set'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert to lowercase and extract words (alphanumeric only)
|
||||||
|
$cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' '
|
||||||
|
$words = $cleanName -split '\s+' | Where-Object { $_ }
|
||||||
|
|
||||||
|
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
|
||||||
|
$meaningfulWords = @()
|
||||||
|
foreach ($word in $words) {
|
||||||
|
# Skip stop words
|
||||||
|
if ($stopWords -contains $word) { continue }
|
||||||
|
|
||||||
|
# Keep words that are length >= 3 OR appear as uppercase in original (likely acronyms)
|
||||||
|
if ($word.Length -ge 3) {
|
||||||
|
$meaningfulWords += $word
|
||||||
|
} elseif ($Description -match "\b$($word.ToUpper())\b") {
|
||||||
|
# Keep short words if they appear as uppercase in original (likely acronyms)
|
||||||
|
$meaningfulWords += $word
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# If we have meaningful words, use first 3-4 of them
|
||||||
|
if ($meaningfulWords.Count -gt 0) {
|
||||||
|
$maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 }
|
||||||
|
$result = ($meaningfulWords | Select-Object -First $maxWords) -join '-'
|
||||||
|
return $result
|
||||||
|
} else {
|
||||||
|
# Fallback to original logic if no meaningful words found
|
||||||
|
$result = $Description.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
|
||||||
|
$fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3
|
||||||
|
return [string]::Join('-', $fallbackWords)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate branch name
|
||||||
|
if ($ShortName) {
|
||||||
|
# Use provided short name, just clean it up
|
||||||
|
$branchSuffix = $ShortName.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
|
||||||
|
} else {
|
||||||
|
# Generate from description with smart filtering
|
||||||
|
$branchSuffix = Get-BranchName -Description $featureDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
$branchName = "$featureNum-$branchSuffix"
|
||||||
|
|
||||||
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
|
# Validate and truncate if necessary
|
||||||
|
$maxBranchLength = 244
|
||||||
|
if ($branchName.Length -gt $maxBranchLength) {
|
||||||
|
# Calculate how much we need to trim from suffix
|
||||||
|
# Account for: feature number (3) + hyphen (1) = 4 chars
|
||||||
|
$maxSuffixLength = $maxBranchLength - 4
|
||||||
|
|
||||||
|
# Truncate suffix
|
||||||
|
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
|
||||||
|
# Remove trailing hyphen if truncation created one
|
||||||
|
$truncatedSuffix = $truncatedSuffix -replace '-$', ''
|
||||||
|
|
||||||
|
$originalBranchName = $branchName
|
||||||
|
$branchName = "$featureNum-$truncatedSuffix"
|
||||||
|
|
||||||
|
Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit"
|
||||||
|
Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)"
|
||||||
|
Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)"
|
||||||
|
}
|
||||||
|
|
||||||
if ($hasGit) {
|
if ($hasGit) {
|
||||||
try {
|
try {
|
||||||
@@ -115,3 +207,4 @@ if ($Json) {
|
|||||||
Write-Output "HAS_GIT: $hasGit"
|
Write-Output "HAS_GIT: $hasGit"
|
||||||
Write-Output "SPECIFY_FEATURE environment variable set to: $branchName"
|
Write-Output "SPECIFY_FEATURE environment variable set to: $branchName"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,3 +59,4 @@ if ($Json) {
|
|||||||
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
||||||
Write-Output "HAS_GIT: $($paths.HAS_GIT)"
|
Write-Output "HAS_GIT: $($paths.HAS_GIT)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Mirrors the behavior of scripts/bash/update-agent-context.sh:
|
|||||||
2. Plan Data Extraction
|
2. Plan Data Extraction
|
||||||
3. Agent File Management (create from template or update existing)
|
3. Agent File Management (create from template or update existing)
|
||||||
4. Content Generation (technology stack, recent changes, timestamp)
|
4. Content Generation (technology stack, recent changes, timestamp)
|
||||||
5. Multi-Agent Support (claude, gemini, copilot, cursor, qwen, opencode, codex, windsurf, kilocode, auggie, roo, q)
|
5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, roo, q)
|
||||||
|
|
||||||
.PARAMETER AgentType
|
.PARAMETER AgentType
|
||||||
Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist).
|
Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist).
|
||||||
@@ -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-agent','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.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'
|
||||||
@@ -369,7 +370,7 @@ function Update-SpecificAgent {
|
|||||||
'claude' { Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code' }
|
'claude' { Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code' }
|
||||||
'gemini' { Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI' }
|
'gemini' { Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI' }
|
||||||
'copilot' { Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot' }
|
'copilot' { Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot' }
|
||||||
'cursor' { Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE' }
|
'cursor-agent' { Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE' }
|
||||||
'qwen' { Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code' }
|
'qwen' { Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code' }
|
||||||
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
|
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
|
||||||
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
|
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
|
||||||
@@ -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 CLI' }
|
||||||
'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-agent|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 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 (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-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|q]'
|
||||||
}
|
}
|
||||||
|
|
||||||
function Main {
|
function Main {
|
||||||
@@ -431,3 +434,4 @@ function Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Main
|
Main
|
||||||
|
|
||||||
|
|||||||
@@ -401,3 +401,4 @@ By embedding these principles into the specification and planning process, SDD e
|
|||||||
This isn't about replacing developers or automating creativity. It's about amplifying human capability by automating mechanical translation. It's about creating a tight feedback loop where specifications, research, and code evolve together, each iteration bringing deeper understanding and better alignment between intent and implementation.
|
This isn't about replacing developers or automating creativity. It's about amplifying human capability by automating mechanical translation. It's about creating a tight feedback loop where specifications, research, and code evolve together, each iteration bringing deeper understanding and better alignment between intent and implementation.
|
||||||
|
|
||||||
Software development needs better tools for maintaining alignment between intent and implementation. SDD provides the methodology for achieving this alignment through executable specifications that generate code rather than merely guiding it.
|
Software development needs better tools for maintaining alignment between intent and implementation. SDD provides the methodology for achieving this alignment through executable specifications that generate code rather than merely guiding it.
|
||||||
|
|
||||||
|
|||||||
@@ -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/cli",
|
||||||
|
"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:
|
|
||||||
"""Check if a tool is installed."""
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
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,29 +450,108 @@ 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)
|
||||||
|
|
||||||
|
def handle_vscode_settings(sub_item, dest_file, rel_path, verbose=False, tracker=None) -> None:
|
||||||
|
"""Handle merging or copying of .vscode/settings.json files."""
|
||||||
|
def log(message, color="green"):
|
||||||
|
if verbose and not tracker:
|
||||||
|
console.print(f"[{color}]{message}[/] {rel_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(sub_item, 'r', encoding='utf-8') as f:
|
||||||
|
new_settings = json.load(f)
|
||||||
|
|
||||||
|
if dest_file.exists():
|
||||||
|
merged = merge_json_files(dest_file, new_settings, verbose=verbose and not tracker)
|
||||||
|
with open(dest_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(merged, f, indent=4)
|
||||||
|
f.write('\n')
|
||||||
|
log("Merged:", "green")
|
||||||
|
else:
|
||||||
|
shutil.copy2(sub_item, dest_file)
|
||||||
|
log("Copied (no existing settings.json):", "blue")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Warning: Could not merge, copying instead: {e}", "yellow")
|
||||||
|
shutil.copy2(sub_item, dest_file)
|
||||||
|
|
||||||
|
def merge_json_files(existing_path: Path, new_content: dict, verbose: bool = False) -> dict:
|
||||||
|
"""Merge new JSON content into existing JSON file.
|
||||||
|
|
||||||
|
Performs a deep merge where:
|
||||||
|
- New keys are added
|
||||||
|
- Existing keys are preserved unless overwritten by new content
|
||||||
|
- Nested dictionaries are merged recursively
|
||||||
|
- Lists and other values are replaced (not merged)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
existing_path: Path to existing JSON file
|
||||||
|
new_content: New JSON content to merge in
|
||||||
|
verbose: Whether to print merge details
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Merged JSON content as dict
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(existing_path, 'r', encoding='utf-8') as f:
|
||||||
|
existing_content = json.load(f)
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
|
# If file doesn't exist or is invalid, just use new content
|
||||||
|
return new_content
|
||||||
|
|
||||||
|
def deep_merge(base: dict, update: dict) -> dict:
|
||||||
|
"""Recursively merge update dict into base dict."""
|
||||||
|
result = base.copy()
|
||||||
|
for key, value in update.items():
|
||||||
|
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
||||||
|
# Recursively merge nested dictionaries
|
||||||
|
result[key] = deep_merge(result[key], value)
|
||||||
|
else:
|
||||||
|
# Add new key or replace existing value
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
merged = deep_merge(existing_content, new_content)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
console.print(f"[cyan]Merged JSON file:[/cyan] {existing_path.name}")
|
||||||
|
|
||||||
|
return merged
|
||||||
|
|
||||||
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]:
|
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"
|
||||||
@@ -438,7 +584,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 +668,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 +700,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 +711,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 +723,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,20 +732,22 @@ 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)
|
||||||
dest_file = dest_path / rel_path
|
dest_file = dest_path / rel_path
|
||||||
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copy2(sub_item, dest_file)
|
# Special handling for .vscode/settings.json - merge instead of overwrite
|
||||||
|
if dest_file.name == "settings.json" and dest_file.parent.name == ".vscode":
|
||||||
|
handle_vscode_settings(sub_item, dest_file, rel_path, verbose, tracker)
|
||||||
|
else:
|
||||||
|
shutil.copy2(sub_item, dest_file)
|
||||||
else:
|
else:
|
||||||
shutil.copytree(item, dest_path)
|
shutil.copytree(item, dest_path)
|
||||||
else:
|
else:
|
||||||
@@ -616,10 +757,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 +768,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 +791,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 +801,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 +859,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 +874,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 +889,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 +946,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 +1005,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 +1028,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 +1075,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]")
|
||||||
|
|
||||||
|
# Show git error details if initialization failed
|
||||||
|
if git_error_message:
|
||||||
|
console.print()
|
||||||
|
git_error_panel = Panel(
|
||||||
|
f"[yellow]Warning:[/yellow] Git repository initialization failed\n\n"
|
||||||
|
f"{git_error_message}\n\n"
|
||||||
|
f"[dim]You can initialize git manually later with:[/dim]\n"
|
||||||
|
f"[cyan]cd {project_path if not here else '.'}[/cyan]\n"
|
||||||
|
f"[cyan]git init[/cyan]\n"
|
||||||
|
f"[cyan]git add .[/cyan]\n"
|
||||||
|
f"[cyan]git commit -m \"Initial commit\"[/cyan]",
|
||||||
|
title="[red]Git Initialization Failed[/red]",
|
||||||
|
border_style="red",
|
||||||
|
padding=(1, 2)
|
||||||
|
)
|
||||||
|
console.print(git_error_panel)
|
||||||
|
|
||||||
# Agent folder security notice
|
# Agent folder security notice
|
||||||
agent_folder_map = {
|
agent_config = AGENT_CONFIG.get(selected_ai)
|
||||||
"claude": ".claude/",
|
if agent_config:
|
||||||
"gemini": ".gemini/",
|
agent_folder = agent_config["folder"]
|
||||||
"cursor": ".cursor/",
|
|
||||||
"qwen": ".qwen/",
|
|
||||||
"opencode": ".opencode/",
|
|
||||||
"codex": ".codex/",
|
|
||||||
"windsurf": ".windsurf/",
|
|
||||||
"kilocode": ".kilocode/",
|
|
||||||
"auggie": ".augment/",
|
|
||||||
"copilot": ".github/",
|
|
||||||
"roo": ".roo/",
|
|
||||||
"q": ".amazonq/"
|
|
||||||
}
|
|
||||||
|
|
||||||
if selected_ai in agent_folder_map:
|
|
||||||
agent_folder = agent_folder_map[selected_ai]
|
|
||||||
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 +1111,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 +1163,28 @@ 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)
|
requires_cli = agent_config["requires_cli"]
|
||||||
code_ok = check_tool_for_tracker("code", tracker)
|
|
||||||
code_insiders_ok = check_tool_for_tracker("code-insiders", tracker)
|
tracker.add(agent_key, agent_name)
|
||||||
cursor_ok = check_tool_for_tracker("cursor-agent", tracker)
|
|
||||||
windsurf_ok = check_tool_for_tracker("windsurf", tracker)
|
if requires_cli:
|
||||||
kilocode_ok = check_tool_for_tracker("kilocode", tracker)
|
agent_results[agent_key] = check_tool(agent_key, tracker=tracker)
|
||||||
opencode_ok = check_tool_for_tracker("opencode", tracker)
|
else:
|
||||||
codex_ok = check_tool_for_tracker("codex", tracker)
|
# IDE-based agent - skip CLI check and mark as optional
|
||||||
auggie_ok = check_tool_for_tracker("auggie", tracker)
|
tracker.skip(agent_key, "IDE-based, no CLI check")
|
||||||
q_ok = check_tool_for_tracker("q", tracker)
|
agent_results[agent_key] = False # Don't count IDE agents as "found"
|
||||||
|
|
||||||
|
# Check VS Code variants (not in agent config)
|
||||||
|
tracker.add("code", "Visual Studio Code")
|
||||||
|
code_ok = check_tool("code", tracker=tracker)
|
||||||
|
|
||||||
|
tracker.add("code-insiders", "Visual Studio Code Insiders")
|
||||||
|
code_insiders_ok = check_tool("code-insiders", tracker=tracker)
|
||||||
|
|
||||||
console.print(tracker.render())
|
console.print(tracker.render())
|
||||||
|
|
||||||
@@ -1094,7 +1192,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():
|
||||||
@@ -1102,3 +1201,4 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ Auto-generated from all feature plans. Last updated: [DATE]
|
|||||||
[LAST 3 FEATURES AND WHAT THEY ADDED]
|
[LAST 3 FEATURES AND WHAT THEY ADDED]
|
||||||
|
|
||||||
<!-- MANUAL ADDITIONS START -->
|
<!-- MANUAL ADDITIONS START -->
|
||||||
<!-- MANUAL ADDITIONS END -->
|
<!-- MANUAL ADDITIONS END -->
|
||||||
|
|||||||
@@ -38,3 +38,4 @@
|
|||||||
- Add comments or findings inline
|
- Add comments or findings inline
|
||||||
- Link to relevant resources or documentation
|
- Link to relevant resources or documentation
|
||||||
- Items are numbered sequentially for easy reference
|
- Items are numbered sequentially for easy reference
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/tasks` has successfully produced a complete `tasks.md`.
|
Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/speckit.tasks` has successfully produced a complete `tasks.md`.
|
||||||
|
|
||||||
## Operating Constraints
|
## Operating Constraints
|
||||||
|
|
||||||
**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
|
**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
|
||||||
|
|
||||||
**Constitution Authority**: The project constitution (`/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/analyze`.
|
**Constitution Authority**: The project constitution (`/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/speckit.analyze`.
|
||||||
|
|
||||||
## Execution Steps
|
## Execution Steps
|
||||||
|
|
||||||
@@ -157,9 +157,9 @@ Output a Markdown report (no file writes) with the following structure:
|
|||||||
|
|
||||||
At end of report, output a concise Next Actions block:
|
At end of report, output a concise Next Actions block:
|
||||||
|
|
||||||
- If CRITICAL issues exist: Recommend resolving before `/implement`
|
- If CRITICAL issues exist: Recommend resolving before `/speckit.implement`
|
||||||
- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions
|
- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions
|
||||||
- Provide explicit command suggestions: e.g., "Run /specify with refinement", "Run /plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'"
|
- Provide explicit command suggestions: e.g., "Run /speckit.specify with refinement", "Run /speckit.plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'"
|
||||||
|
|
||||||
### 8. Offer Remediation
|
### 8. Offer Remediation
|
||||||
|
|
||||||
@@ -185,3 +185,4 @@ Ask the user: "Would you like me to suggest concrete remediation edits for the t
|
|||||||
## Context
|
## Context
|
||||||
|
|
||||||
{ARGS}
|
{ARGS}
|
||||||
|
|
||||||
|
|||||||
@@ -288,3 +288,4 @@ Sample items:
|
|||||||
- Correct: Validation of requirement quality
|
- Correct: Validation of requirement quality
|
||||||
- Wrong: "Does it do X?"
|
- Wrong: "Does it do X?"
|
||||||
- Correct: "Is X clearly specified?"
|
- Correct: "Is X clearly specified?"
|
||||||
|
|
||||||
|
|||||||
@@ -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 multiple‑choice questions render options as a Markdown table:
|
- For multiple‑choice 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 short‑answer 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 short‑answer 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:
|
||||||
@@ -164,3 +177,4 @@ Behavior rules:
|
|||||||
- If quota reached with unresolved high-impact categories remaining, explicitly flag them under Deferred with rationale.
|
- If quota reached with unresolved high-impact categories remaining, explicitly flag them under Deferred with rationale.
|
||||||
|
|
||||||
Context for prioritization: {ARGS}
|
Context for prioritization: {ARGS}
|
||||||
|
|
||||||
|
|||||||
@@ -75,3 +75,4 @@ If the user supplies partial updates (e.g., only one principle revision), still
|
|||||||
If critical info missing (e.g., ratification date truly unknown), insert `TODO(<FIELD_NAME>): explanation` and include in the Sync Impact Report under deferred items.
|
If critical info missing (e.g., ratification date truly unknown), insert `TODO(<FIELD_NAME>): explanation` and include in the Sync Impact Report under deferred items.
|
||||||
|
|
||||||
Do not create a new template; always operate on the existing `/memory/constitution.md` file.
|
Do not create a new template; always operate on the existing `/memory/constitution.md` file.
|
||||||
|
|
||||||
|
|||||||
@@ -54,27 +54,69 @@ 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/TypeScript**: `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`
|
||||||
|
- **Ruby**: `.bundle/`, `log/`, `tmp/`, `*.gem`, `vendor/bundle/`
|
||||||
|
- **PHP**: `vendor/`, `*.log`, `*.cache`, `*.env`
|
||||||
|
- **Rust**: `target/`, `debug/`, `release/`, `*.rs.bk`, `*.rlib`, `*.prof*`, `.idea/`, `*.log`, `.env*`
|
||||||
|
- **Kotlin**: `build/`, `out/`, `.gradle/`, `.idea/`, `*.class`, `*.jar`, `*.iml`, `*.log`, `.env*`
|
||||||
|
- **C++**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.so`, `*.a`, `*.exe`, `*.dll`, `.idea/`, `*.log`, `.env*`
|
||||||
|
- **C**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.a`, `*.so`, `*.exe`, `Makefile`, `config.log`, `.idea/`, `*.log`, `.env*`
|
||||||
|
- **Swift**: `.build/`, `DerivedData/`, `*.swiftpm/`, `Packages/`
|
||||||
|
- **R**: `.Rproj.user/`, `.Rhistory`, `.RData`, `.Ruserdata`, `*.Rproj`, `packrat/`, `renv/`
|
||||||
|
- **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`
|
||||||
|
- **Kubernetes/k8s**: `*.secret.yaml`, `secrets/`, `.kube/`, `kubeconfig*`, `*.key`, `*.crt`
|
||||||
|
|
||||||
|
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,11 +124,12 @@ 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
|
||||||
- Confirm the implementation follows the technical plan
|
- Confirm the implementation follows the technical plan
|
||||||
- Report final status with summary of completed work
|
- Report final status with summary of completed work
|
||||||
|
|
||||||
Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/tasks` first to regenerate the task list.
|
Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/speckit.tasks` first to regenerate the task list.
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -84,3 +84,4 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|||||||
|
|
||||||
- Use absolute paths
|
- Use absolute paths
|
||||||
- ERROR on gate failures or unresolved clarifications
|
- ERROR on gate failures or unresolved clarifications
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,32 @@ The text the user typed after `/speckit.specify` in the triggering message **is*
|
|||||||
|
|
||||||
Given that feature description, do this:
|
Given that feature description, do this:
|
||||||
|
|
||||||
1. Run the script `{SCRIPT}` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute.
|
1. **Generate a concise short name** (2-4 words) for the branch:
|
||||||
**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. 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").
|
- Analyze the feature description and extract the most meaningful keywords
|
||||||
2. Load `templates/spec-template.md` to understand required sections.
|
- Create a 2-4 word short name that captures the essence of the feature
|
||||||
|
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||||
|
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||||
|
- Keep it concise but descriptive enough to understand the feature at a glance
|
||||||
|
- Examples:
|
||||||
|
- "I want to add user authentication" → "user-auth"
|
||||||
|
- "Implement OAuth2 integration for the API" → "oauth2-api-integration"
|
||||||
|
- "Create a dashboard for analytics" → "analytics-dashboard"
|
||||||
|
- "Fix payment processing timeout bug" → "fix-payment-timeout"
|
||||||
|
|
||||||
3. Follow this execution flow:
|
2. Run the script `{SCRIPT}` from repo root **with the short-name argument** and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute.
|
||||||
|
|
||||||
|
**IMPORTANT**:
|
||||||
|
|
||||||
|
- Append the short-name argument to the `{SCRIPT}` command with the 2-4 word short name you created in step 1
|
||||||
|
- Bash: `--short-name "your-generated-short-name"`
|
||||||
|
- PowerShell: `-ShortName "your-generated-short-name"`
|
||||||
|
- 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")
|
||||||
|
- 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
|
||||||
|
|
||||||
|
3. Load `templates/spec-template.md` to understand required sections.
|
||||||
|
|
||||||
|
4. Follow this execution flow:
|
||||||
|
|
||||||
1. Parse user description from Input
|
1. Parse user description from Input
|
||||||
If empty: ERROR "No feature description provided"
|
If empty: ERROR "No feature description provided"
|
||||||
@@ -49,9 +70,9 @@ Given that feature description, do this:
|
|||||||
7. Identify Key Entities (if data involved)
|
7. Identify Key Entities (if data involved)
|
||||||
8. Return: SUCCESS (spec ready for planning)
|
8. Return: SUCCESS (spec ready for planning)
|
||||||
|
|
||||||
4. 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.
|
5. 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.
|
||||||
|
|
||||||
5. **Specification Quality Validation**: After writing the initial spec, validate it against quality criteria:
|
6. **Specification Quality Validation**: After writing the initial spec, validate it against quality criteria:
|
||||||
|
|
||||||
a. **Create Spec Quality Checklist**: Generate a checklist file at `FEATURE_DIR/checklists/requirements.md` using the checklist template structure with these validation items:
|
a. **Create Spec Quality Checklist**: Generate a checklist file at `FEATURE_DIR/checklists/requirements.md` using the checklist template structure with these validation items:
|
||||||
|
|
||||||
@@ -143,7 +164,7 @@ Given that feature description, do this:
|
|||||||
|
|
||||||
d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status
|
d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status
|
||||||
|
|
||||||
6. Report completion with branch name, spec file path, checklist results, and readiness for the next phase (`/speckit.clarify` or `/speckit.plan`).
|
7. Report completion with branch name, spec file path, checklist results, and readiness for the next phase (`/speckit.clarify` or `/speckit.plan`).
|
||||||
|
|
||||||
**NOTE:** The script creates and checks out the new branch and initializes the spec file before writing.
|
**NOTE:** The script creates and checks out the new branch and initializes the spec file before writing.
|
||||||
|
|
||||||
@@ -209,3 +230,4 @@ Success criteria must be:
|
|||||||
- "Database can handle 1000 TPS" (implementation detail, use user-facing metric)
|
- "Database can handle 1000 TPS" (implementation detail, use user-facing metric)
|
||||||
- "React components render efficiently" (framework-specific)
|
- "React components render efficiently" (framework-specific)
|
||||||
- "Redis cache hit rate above 80%" (technology-specific)
|
- "Redis cache hit rate above 80%" (technology-specific)
|
||||||
|
|
||||||
|
|||||||
@@ -22,27 +22,13 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|||||||
- **Optional**: data-model.md (entities), contracts/ (API endpoints), research.md (decisions), quickstart.md (test scenarios)
|
- **Optional**: data-model.md (entities), contracts/ (API endpoints), research.md (decisions), quickstart.md (test scenarios)
|
||||||
- Note: Not all projects have all documents. Generate tasks based on what's available.
|
- Note: Not all projects have all documents. Generate tasks based on what's available.
|
||||||
|
|
||||||
3. **Execute task generation workflow** (follow the template structure):
|
3. **Execute task generation workflow**:
|
||||||
- Load plan.md and extract tech stack, libraries, project structure
|
- Load plan.md and extract tech stack, libraries, project structure
|
||||||
- **Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)**
|
- Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)
|
||||||
- If data-model.md exists: Extract entities → map to user stories
|
- If data-model.md exists: Extract entities and map to user stories
|
||||||
- If contracts/ exists: Each file → map endpoints to user stories
|
- If contracts/ exists: Map endpoints to user stories
|
||||||
- If research.md exists: Extract decisions → generate setup tasks
|
- If research.md exists: Extract decisions for setup tasks
|
||||||
- **Generate tasks ORGANIZED BY USER STORY**:
|
- Generate tasks organized by user story (see Task Generation Rules below)
|
||||||
- Setup tasks (shared infrastructure needed by all stories)
|
|
||||||
- **Foundational tasks (prerequisites that must complete before ANY user story can start)**
|
|
||||||
- For each user story (in priority order P1, P2, P3...):
|
|
||||||
- Group all tasks needed to complete JUST that story
|
|
||||||
- Include models, services, endpoints, UI components specific to that story
|
|
||||||
- Mark which tasks are [P] parallelizable
|
|
||||||
- If tests requested: Include tests specific to that story
|
|
||||||
- Polish/Integration tasks (cross-cutting concerns)
|
|
||||||
- **Tests are OPTIONAL**: Only generate test tasks if explicitly requested in the feature spec or user asks for TDD approach
|
|
||||||
- Apply task rules:
|
|
||||||
- Different files = mark [P] for parallel
|
|
||||||
- Same file = sequential (no [P])
|
|
||||||
- If tests requested: Tests before implementation (TDD order)
|
|
||||||
- Number tasks sequentially (T001, T002...)
|
|
||||||
- Generate dependency graph showing user story completion order
|
- Generate dependency graph showing user story completion order
|
||||||
- Create parallel execution examples per user story
|
- Create parallel execution examples per user story
|
||||||
- Validate task completeness (each user story has all needed tasks, independently testable)
|
- Validate task completeness (each user story has all needed tasks, independently testable)
|
||||||
@@ -52,12 +38,9 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|||||||
- Phase 1: Setup tasks (project initialization)
|
- Phase 1: Setup tasks (project initialization)
|
||||||
- Phase 2: Foundational tasks (blocking prerequisites for all user stories)
|
- Phase 2: Foundational tasks (blocking prerequisites for all user stories)
|
||||||
- Phase 3+: One phase per user story (in priority order from spec.md)
|
- Phase 3+: One phase per user story (in priority order from spec.md)
|
||||||
- Each phase includes: story goal, independent test criteria, tests (if requested), implementation tasks
|
- Each phase includes: story goal, independent test criteria, tests (if requested), implementation tasks
|
||||||
- Clear [Story] labels (US1, US2, US3...) for each task
|
|
||||||
- [P] markers for parallelizable tasks within each story
|
|
||||||
- Checkpoint markers after each story phase
|
|
||||||
- Final Phase: Polish & cross-cutting concerns
|
- Final Phase: Polish & cross-cutting concerns
|
||||||
- Numbered tasks (T001, T002...) in execution order
|
- All tasks must follow the strict checklist format (see Task Generation Rules below)
|
||||||
- Clear file paths for each task
|
- Clear file paths for each task
|
||||||
- Dependencies section showing story completion order
|
- Dependencies section showing story completion order
|
||||||
- Parallel execution examples per story
|
- Parallel execution examples per story
|
||||||
@@ -69,6 +52,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
|||||||
- Parallel opportunities identified
|
- Parallel opportunities identified
|
||||||
- Independent test criteria for each story
|
- Independent test criteria for each story
|
||||||
- Suggested MVP scope (typically just User Story 1)
|
- Suggested MVP scope (typically just User Story 1)
|
||||||
|
- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
|
||||||
|
|
||||||
Context for task generation: {ARGS}
|
Context for task generation: {ARGS}
|
||||||
|
|
||||||
@@ -76,10 +60,44 @@ The tasks.md should be immediately executable - each task must be specific enoug
|
|||||||
|
|
||||||
## Task Generation Rules
|
## Task Generation Rules
|
||||||
|
|
||||||
**IMPORTANT**: Tests are optional. Only generate test tasks if the user explicitly requested testing or TDD approach in the feature specification.
|
|
||||||
|
|
||||||
**CRITICAL**: Tasks MUST be organized by user story to enable independent implementation and testing.
|
**CRITICAL**: Tasks MUST be organized by user story to enable independent implementation and testing.
|
||||||
|
|
||||||
|
**Tests are OPTIONAL**: Only generate test tasks if explicitly requested in the feature specification or if user requests TDD approach.
|
||||||
|
|
||||||
|
### Checklist Format (REQUIRED)
|
||||||
|
|
||||||
|
Every task MUST strictly follow this format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
- [ ] [TaskID] [P?] [Story?] Description with file path
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format Components**:
|
||||||
|
|
||||||
|
1. **Checkbox**: ALWAYS start with `- [ ]` (markdown checkbox)
|
||||||
|
2. **Task ID**: Sequential number (T001, T002, T003...) in execution order
|
||||||
|
3. **[P] marker**: Include ONLY if task is parallelizable (different files, no dependencies on incomplete tasks)
|
||||||
|
4. **[Story] label**: REQUIRED for user story phase tasks only
|
||||||
|
- Format: [US1], [US2], [US3], etc. (maps to user stories from spec.md)
|
||||||
|
- Setup phase: NO story label
|
||||||
|
- Foundational phase: NO story label
|
||||||
|
- User Story phases: MUST have story label
|
||||||
|
- Polish phase: NO story label
|
||||||
|
5. **Description**: Clear action with exact file path
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
|
||||||
|
- ✅ CORRECT: `- [ ] T001 Create project structure per implementation plan`
|
||||||
|
- ✅ CORRECT: `- [ ] T005 [P] Implement authentication middleware in src/middleware/auth.py`
|
||||||
|
- ✅ CORRECT: `- [ ] T012 [P] [US1] Create User model in src/models/user.py`
|
||||||
|
- ✅ CORRECT: `- [ ] T014 [US1] Implement UserService in src/services/user_service.py`
|
||||||
|
- ❌ WRONG: `- [ ] Create User model` (missing ID and Story label)
|
||||||
|
- ❌ WRONG: `T001 [US1] Create model` (missing checkbox)
|
||||||
|
- ❌ WRONG: `- [ ] [US1] Create User model` (missing Task ID)
|
||||||
|
- ❌ WRONG: `- [ ] T001 [US1] Create model` (missing file path)
|
||||||
|
|
||||||
|
### Task Organization
|
||||||
|
|
||||||
1. **From User Stories (spec.md)** - PRIMARY ORGANIZATION:
|
1. **From User Stories (spec.md)** - PRIMARY ORGANIZATION:
|
||||||
- Each user story (P1, P2, P3...) gets its own phase
|
- Each user story (P1, P2, P3...) gets its own phase
|
||||||
- Map all related components to their story:
|
- Map all related components to their story:
|
||||||
@@ -94,22 +112,22 @@ The tasks.md should be immediately executable - each task must be specific enoug
|
|||||||
- If tests requested: Each contract → contract test task [P] before implementation in that story's phase
|
- If tests requested: Each contract → contract test task [P] before implementation in that story's phase
|
||||||
|
|
||||||
3. **From Data Model**:
|
3. **From Data Model**:
|
||||||
- Map each entity → to the user story(ies) that need it
|
- Map each entity to the user story(ies) that need it
|
||||||
- If entity serves multiple stories: Put in earliest story or Setup phase
|
- If entity serves multiple stories: Put in earliest story or Setup phase
|
||||||
- Relationships → service layer tasks in appropriate story phase
|
- Relationships → service layer tasks in appropriate story phase
|
||||||
|
|
||||||
4. **From Setup/Infrastructure**:
|
4. **From Setup/Infrastructure**:
|
||||||
- Shared infrastructure → Setup phase (Phase 1)
|
- Shared infrastructure → Setup phase (Phase 1)
|
||||||
- Foundational/blocking tasks → Foundational phase (Phase 2)
|
- Foundational/blocking tasks → Foundational phase (Phase 2)
|
||||||
- Examples: Database schema setup, authentication framework, core libraries, base configurations
|
|
||||||
- These MUST complete before any user story can be implemented
|
|
||||||
- Story-specific setup → within that story's phase
|
- Story-specific setup → within that story's phase
|
||||||
|
|
||||||
5. **Ordering**:
|
### Phase Structure
|
||||||
- Phase 1: Setup (project initialization)
|
|
||||||
- Phase 2: Foundational (blocking prerequisites - must complete before user stories)
|
- **Phase 1**: Setup (project initialization)
|
||||||
- Phase 3+: User Stories in priority order (P1, P2, P3...)
|
- **Phase 2**: Foundational (blocking prerequisites - MUST complete before user stories)
|
||||||
- Within each story: Tests (if requested) → Models → Services → Endpoints → Integration
|
- **Phase 3+**: User Stories in priority order (P1, P2, P3...)
|
||||||
- Final Phase: Polish & Cross-Cutting Concerns
|
- Within each story: Tests (if requested) → Models → Services → Endpoints → Integration
|
||||||
- Each user story phase should be a complete, independently testable increment
|
- Each phase should be a complete, independently testable increment
|
||||||
|
- **Final Phase**: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -102,3 +102,4 @@ directories captured above]
|
|||||||
|-----------|------------|-------------------------------------|
|
|-----------|------------|-------------------------------------|
|
||||||
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||||
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||||
|
|
||||||
|
|||||||
@@ -113,3 +113,4 @@
|
|||||||
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
|
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
|
||||||
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
|
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
|
||||||
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
|
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
|
||||||
|
|
||||||
|
|||||||
@@ -248,3 +248,4 @@ With multiple developers:
|
|||||||
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
|
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
},
|
},
|
||||||
"chat.tools.terminal.autoApprove": {
|
"chat.tools.terminal.autoApprove": {
|
||||||
".specify/scripts/bash/": true,
|
".specify/scripts/bash/": true,
|
||||||
".specify/scripts/ps/": true
|
".specify/scripts/powershell/": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user