Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb9bc613a5 | ||
|
|
ebd99bc11b | ||
|
|
8bd155b9f5 | ||
|
|
f6e4714ba0 | ||
|
|
919bda2355 | ||
|
|
1414bcb1b2 | ||
|
|
ec47ecfd16 | ||
|
|
c5f7582470 | ||
|
|
87d4998b9d | ||
|
|
10e56aa67c | ||
|
|
cc686c6621 | ||
|
|
8b49d5d0fb | ||
|
|
66688dffa3 | ||
|
|
b18ef208cb | ||
|
|
5828e58f84 | ||
|
|
dd57e9d444 | ||
|
|
558e682865 | ||
|
|
c5e0c1840b | ||
|
|
63bc6b495d | ||
|
|
70b3db27db | ||
|
|
6e94588615 | ||
|
|
ad9c93c13b | ||
|
|
f979b64338 | ||
|
|
b1591282f6 | ||
|
|
856680e3bc | ||
|
|
60b015a094 | ||
|
|
0c2b367ba0 | ||
|
|
6b8b1a8b93 | ||
|
|
0e6f513c14 | ||
|
|
6f81f7d6a0 | ||
|
|
c875bd0f30 | ||
|
|
736e282562 | ||
|
|
542751fcd1 | ||
|
|
6c83e9ff66 | ||
|
|
a55448057b | ||
|
|
88cded5c4d | ||
|
|
0ad2f169d2 | ||
|
|
fa3171ca6e | ||
|
|
117ec67e47 | ||
|
|
5bd7027526 | ||
|
|
ec7d87f121 | ||
|
|
85e5eedef8 | ||
|
|
0a5b1ac538 | ||
|
|
eaf4caa231 | ||
|
|
c29e419b4f | ||
|
|
af3cf934e5 | ||
|
|
5787bb5537 | ||
|
|
0c419e5198 | ||
|
|
d605d1e008 | ||
|
|
57024454bf | ||
|
|
1ae6b55c87 | ||
|
|
bfeb40cebc | ||
|
|
22b7098edb | ||
|
|
38ad8b0bac | ||
|
|
445902f2f0 | ||
|
|
20f6c9dede | ||
|
|
4cb63ed6f1 | ||
|
|
020fd27352 | ||
|
|
60ee3a75b5 | ||
|
|
b1858498d4 | ||
|
|
4b66f216e9 | ||
|
|
5c9d9a40ac | ||
|
|
ee6b83c1dd | ||
|
|
b31ca19962 | ||
|
|
15917c2094 | ||
|
|
f89361cd3d | ||
|
|
fe4de3ca45 | ||
|
|
0f0e19da33 | ||
|
|
21b3dbf904 | ||
|
|
708e887022 | ||
|
|
73a9af70a4 | ||
|
|
78e6c9953c | ||
|
|
e21820fb92 | ||
|
|
51705217d4 | ||
|
|
e979ef0c7c | ||
|
|
5d1a174a95 | ||
|
|
f13eb86c0f | ||
|
|
6e2af26867 | ||
|
|
24ba30444e | ||
|
|
584175351a | ||
|
|
167038ca3c | ||
|
|
9140e9b009 | ||
|
|
fc8eb0434a | ||
|
|
fd61b8742d | ||
|
|
4591cf7df6 | ||
|
|
03ee3401e7 | ||
|
|
4b98c20f5d | ||
|
|
e566e90708 | ||
|
|
a3b6e4455a | ||
|
|
3259b845ed | ||
|
|
9d01e9d7b6 | ||
|
|
e786a9df64 | ||
|
|
51b8459122 | ||
|
|
fa56dbd8b0 | ||
|
|
793e5176ef | ||
|
|
29eb082e2a | ||
|
|
c96f6e1a1b |
67
.github/workflows/docs.yml
vendored
Normal file
67
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# Build and deploy DocFX documentation to GitHub Pages
|
||||
name: Deploy Documentation to Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for git info
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.x'
|
||||
|
||||
- name: Setup DocFX
|
||||
run: dotnet tool install -g docfx
|
||||
|
||||
- name: Build with DocFX
|
||||
run: |
|
||||
cd docs
|
||||
docfx docfx.json
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: 'docs/_site'
|
||||
|
||||
# Deploy job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
191
.github/workflows/manual-release.yml
vendored
191
.github/workflows/manual-release.yml
vendored
@@ -1,191 +0,0 @@
|
||||
name: Manual Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_bump:
|
||||
description: 'Version bump type'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
|
||||
jobs:
|
||||
manual_release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Calculate new version
|
||||
id: version
|
||||
run: |
|
||||
# Get the latest tag, or use v0.0.0 if no tags exist
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Extract version number
|
||||
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
||||
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
||||
MAJOR=${VERSION_PARTS[0]:-0}
|
||||
MINOR=${VERSION_PARTS[1]:-0}
|
||||
PATCH=${VERSION_PARTS[2]:-0}
|
||||
|
||||
# Increment based on input
|
||||
case "${{ github.event.inputs.version_bump }}" in
|
||||
"major")
|
||||
MAJOR=$((MAJOR + 1))
|
||||
MINOR=0
|
||||
PATCH=0
|
||||
;;
|
||||
"minor")
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
;;
|
||||
"patch")
|
||||
PATCH=$((PATCH + 1))
|
||||
;;
|
||||
esac
|
||||
|
||||
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "New version will be: $NEW_VERSION (was $LATEST_TAG)"
|
||||
|
||||
- name: Create release package
|
||||
run: |
|
||||
# Create base package directory structure
|
||||
mkdir -p sdd-package-base
|
||||
|
||||
# Copy common folders to base
|
||||
echo "Packaging SDD common components..."
|
||||
|
||||
if [ -d "memory" ]; then
|
||||
cp -r memory sdd-package-base/
|
||||
echo "✓ Copied memory folder ($(find memory -type f | wc -l) files)"
|
||||
else
|
||||
echo "⚠️ memory folder not found"
|
||||
fi
|
||||
|
||||
if [ -d "scripts" ]; then
|
||||
cp -r scripts sdd-package-base/
|
||||
echo "✓ Copied scripts folder ($(find scripts -type f | wc -l) files)"
|
||||
else
|
||||
echo "⚠️ scripts folder not found"
|
||||
fi
|
||||
|
||||
# Create Claude Code package
|
||||
echo "Creating Claude Code package..."
|
||||
mkdir -p sdd-claude-package
|
||||
cp -r sdd-package-base/* sdd-claude-package/
|
||||
if [ -d "agent_templates/claude" ]; then
|
||||
cp -r agent_templates/claude sdd-claude-package/.claude
|
||||
echo "✓ Added Claude Code commands ($(find agent_templates/claude -type f | wc -l) files)"
|
||||
else
|
||||
echo "⚠️ agent_templates/claude folder not found"
|
||||
fi
|
||||
|
||||
# Create Gemini CLI package
|
||||
echo "Creating Gemini CLI package..."
|
||||
mkdir -p sdd-gemini-package
|
||||
cp -r sdd-package-base/* sdd-gemini-package/
|
||||
if [ -d "agent_templates/gemini" ]; then
|
||||
cp -r agent_templates/gemini sdd-gemini-package/.gemini
|
||||
# Move GEMINI.md to root for easier access
|
||||
if [ -f "sdd-gemini-package/.gemini/GEMINI.md" ]; then
|
||||
mv sdd-gemini-package/.gemini/GEMINI.md sdd-gemini-package/GEMINI.md
|
||||
echo "✓ Moved GEMINI.md to root of Gemini package"
|
||||
fi
|
||||
# Remove empty .gemini folder if it only contained GEMINI.md
|
||||
if [ -d "sdd-gemini-package/.gemini" ] && [ -z "$(find sdd-gemini-package/.gemini -type f)" ]; then
|
||||
rm -rf sdd-gemini-package/.gemini
|
||||
echo "✓ Removed empty .gemini folder"
|
||||
fi
|
||||
echo "✓ Added Gemini CLI commands ($(find agent_templates/gemini -type f | wc -l) files)"
|
||||
else
|
||||
echo "⚠️ agent_templates/gemini folder not found"
|
||||
fi
|
||||
|
||||
# Create GitHub Copilot package
|
||||
echo "Creating GitHub Copilot package..."
|
||||
mkdir -p sdd-copilot-package
|
||||
cp -r sdd-package-base/* sdd-copilot-package/
|
||||
if [ -d "agent_templates/copilot" ]; then
|
||||
mkdir -p sdd-copilot-package/.github
|
||||
cp -r agent_templates/copilot/* sdd-copilot-package/.github/
|
||||
echo "✓ Added Copilot instructions to .github ($(find agent_templates/copilot -type f | wc -l) files)"
|
||||
else
|
||||
echo "⚠️ agent_templates/copilot folder not found"
|
||||
fi
|
||||
|
||||
# Create archive files for each package
|
||||
echo "Creating archive files..."
|
||||
cd sdd-claude-package && zip -r ../spec-kit-template-claude-${{ steps.version.outputs.new_version }}.zip . && cd ..
|
||||
|
||||
cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${{ steps.version.outputs.new_version }}.zip . && cd ..
|
||||
|
||||
cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${{ steps.version.outputs.new_version }}.zip . && cd ..
|
||||
|
||||
echo ""
|
||||
echo "📦 Packages created:"
|
||||
echo "Claude: $(ls -lh spec-kit-template-claude-*.zip | awk '{print $5}')"
|
||||
echo "Gemini: $(ls -lh spec-kit-template-gemini-*.zip | awk '{print $5}')"
|
||||
echo "Copilot: $(ls -lh spec-kit-template-copilot-*.zip | awk '{print $5}')"
|
||||
echo "Copilot: $(ls -lh sdd-template-copilot-*.zip | awk '{print $5}')"
|
||||
|
||||
- name: Generate detailed release notes
|
||||
run: |
|
||||
LAST_TAG=${{ steps.version.outputs.latest_tag }}
|
||||
|
||||
# Get commit range
|
||||
if [ "$LAST_TAG" = "v0.0.0" ]; then
|
||||
COMMIT_RANGE="HEAD~10..HEAD"
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" $COMMIT_RANGE 2>/dev/null || echo "- Initial release")
|
||||
else
|
||||
COMMIT_RANGE="$LAST_TAG..HEAD"
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" $COMMIT_RANGE 2>/dev/null || echo "- No changes since last release")
|
||||
fi
|
||||
|
||||
# Count files in each directory
|
||||
CLAUDE_COUNT=$(find agent_templates/claude -type f 2>/dev/null | wc -l || echo "0")
|
||||
GEMINI_COUNT=$(find agent_templates/gemini -type f 2>/dev/null | wc -l || echo "0")
|
||||
COPILOT_COUNT=$(find agent_templates/copilot -type f 2>/dev/null | wc -l || echo "0")
|
||||
MEMORY_COUNT=$(find memory -type f 2>/dev/null | wc -l || echo "0")
|
||||
SCRIPTS_COUNT=$(find scripts -type f 2>/dev/null | wc -l || echo "0")
|
||||
|
||||
cat > release_notes.md << EOF
|
||||
Template release ${{ steps.version.outputs.new_version }}
|
||||
|
||||
Updated specification-driven development templates for GitHub Copilot, Claude Code, and Gemini CLI.
|
||||
|
||||
Download the template for your preferred AI assistant:
|
||||
- spec-kit-template-copilot-${{ steps.version.outputs.new_version }}.zip
|
||||
- spec-kit-template-claude-${{ steps.version.outputs.new_version }}.zip
|
||||
- spec-kit-template-gemini-${{ steps.version.outputs.new_version }}.zip
|
||||
|
||||
Changes since $LAST_TAG:
|
||||
$COMMITS
|
||||
EOF
|
||||
|
||||
- name: Create GitHub Release
|
||||
run: |
|
||||
# Remove 'v' prefix from version for release title
|
||||
VERSION_NO_V=${{ steps.version.outputs.new_version }}
|
||||
VERSION_NO_V=${VERSION_NO_V#v}
|
||||
|
||||
gh release create ${{ steps.version.outputs.new_version }} \
|
||||
spec-kit-template-copilot-${{ steps.version.outputs.new_version }}.zip \
|
||||
spec-kit-template-claude-${{ steps.version.outputs.new_version }}.zip \
|
||||
spec-kit-template-gemini-${{ steps.version.outputs.new_version }}.zip \
|
||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||
--notes-file release_notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
324
.github/workflows/release.yml
vendored
324
.github/workflows/release.yml
vendored
@@ -3,219 +3,137 @@ name: Create Release
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'memory/**'
|
||||
- 'scripts/**'
|
||||
- 'templates/**'
|
||||
- '.github/workflows/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest tag
|
||||
id: get_tag
|
||||
run: |
|
||||
# Get the latest tag, or use v0.0.0 if no tags exist
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
# Extract version number and increment
|
||||
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
||||
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
||||
MAJOR=${VERSION_PARTS[0]:-0}
|
||||
MINOR=${VERSION_PARTS[1]:-0}
|
||||
PATCH=${VERSION_PARTS[2]:-0}
|
||||
|
||||
# Increment patch version
|
||||
PATCH=$((PATCH + 1))
|
||||
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
||||
|
||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "New version will be: $NEW_VERSION"
|
||||
|
||||
- name: Check if release already exists
|
||||
id: check_release
|
||||
run: |
|
||||
if gh release view ${{ steps.get_tag.outputs.new_version }} >/dev/null 2>&1; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
echo "Release ${{ steps.get_tag.outputs.new_version }} already exists, skipping..."
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "Release ${{ steps.get_tag.outputs.new_version }} does not exist, proceeding..."
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create release package
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
run: |
|
||||
# Create base package directory structure
|
||||
mkdir -p sdd-package-base
|
||||
|
||||
# Copy common folders to base
|
||||
if [ -d "memory" ]; then
|
||||
cp -r memory sdd-package-base/
|
||||
echo "Copied memory folder"
|
||||
fi
|
||||
|
||||
if [ -d "scripts" ]; then
|
||||
cp -r scripts sdd-package-base/
|
||||
echo "Copied scripts folder"
|
||||
fi
|
||||
|
||||
if [ -d "templates" ]; then
|
||||
mkdir -p sdd-package-base/templates
|
||||
# Copy templates folder but exclude the commands directory
|
||||
find templates -type f -not -path "templates/commands/*" -exec cp --parents {} sdd-package-base/ \;
|
||||
echo "Copied templates folder (excluding commands directory)"
|
||||
fi
|
||||
|
||||
# Generate command files for each agent from source templates
|
||||
generate_commands() {
|
||||
local agent=$1
|
||||
local ext=$2
|
||||
local arg_format=$3
|
||||
local output_dir=$4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get latest tag
|
||||
id: get_tag
|
||||
run: |
|
||||
# Get the latest tag, or use v0.0.0 if no tags exist
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
mkdir -p "$output_dir"
|
||||
# Extract version number and increment
|
||||
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
||||
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
||||
MAJOR=${VERSION_PARTS[0]:-0}
|
||||
MINOR=${VERSION_PARTS[1]:-0}
|
||||
PATCH=${VERSION_PARTS[2]:-0}
|
||||
|
||||
for template in templates/commands/*.md; do
|
||||
if [[ -f "$template" ]]; then
|
||||
name=$(basename "$template" .md)
|
||||
description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r')
|
||||
content=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template" | sed "s/{ARGS}/$arg_format/g")
|
||||
|
||||
case $ext in
|
||||
"toml")
|
||||
{
|
||||
echo "description = \"$description\""
|
||||
echo ""
|
||||
echo "prompt = \"\"\""
|
||||
echo "$content"
|
||||
echo "\"\"\""
|
||||
} > "$output_dir/$name.$ext"
|
||||
;;
|
||||
"md")
|
||||
echo "$content" > "$output_dir/$name.$ext"
|
||||
;;
|
||||
"prompt.md")
|
||||
{
|
||||
echo "# $(echo "$description" | sed 's/\. .*//')"
|
||||
echo ""
|
||||
echo "$content"
|
||||
} > "$output_dir/$name.$ext"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Create Claude Code package
|
||||
mkdir -p sdd-claude-package
|
||||
cp -r sdd-package-base/* sdd-claude-package/
|
||||
mkdir -p sdd-claude-package/.claude/commands
|
||||
generate_commands "claude" "md" "\$ARGUMENTS" "sdd-claude-package/.claude/commands"
|
||||
echo "Created Claude Code package"
|
||||
|
||||
# Create Gemini CLI package
|
||||
mkdir -p sdd-gemini-package
|
||||
cp -r sdd-package-base/* sdd-gemini-package/
|
||||
mkdir -p sdd-gemini-package/.gemini/commands
|
||||
generate_commands "gemini" "toml" "{{args}}" "sdd-gemini-package/.gemini/commands"
|
||||
if [ -f "agent_templates/gemini/GEMINI.md" ]; then
|
||||
cp agent_templates/gemini/GEMINI.md sdd-gemini-package/GEMINI.md
|
||||
fi
|
||||
echo "Created Gemini CLI package"
|
||||
|
||||
# Create GitHub Copilot package
|
||||
mkdir -p sdd-copilot-package
|
||||
cp -r sdd-package-base/* sdd-copilot-package/
|
||||
mkdir -p sdd-copilot-package/.github/prompts
|
||||
generate_commands "copilot" "prompt.md" "\$ARGUMENTS" "sdd-copilot-package/.github/prompts"
|
||||
echo "Created GitHub Copilot package"
|
||||
|
||||
# Create archive files for each package
|
||||
cd sdd-claude-package && zip -r ../spec-kit-template-claude-${{ steps.get_tag.outputs.new_version }}.zip . && cd ..
|
||||
|
||||
cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${{ steps.get_tag.outputs.new_version }}.zip . && cd ..
|
||||
|
||||
cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${{ steps.get_tag.outputs.new_version }}.zip . && cd ..
|
||||
|
||||
# List contents for verification
|
||||
echo "Claude package contents:"
|
||||
unzip -l spec-kit-template-claude-${{ steps.get_tag.outputs.new_version }}.zip | head -10
|
||||
echo "Gemini package contents:"
|
||||
unzip -l spec-kit-template-gemini-${{ steps.get_tag.outputs.new_version }}.zip | head -10
|
||||
echo "Copilot package contents:"
|
||||
unzip -l spec-kit-template-copilot-${{ steps.get_tag.outputs.new_version }}.zip | head -10
|
||||
|
||||
- name: Generate release notes
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
id: release_notes
|
||||
run: |
|
||||
# Get commits since last tag
|
||||
LAST_TAG=${{ steps.get_tag.outputs.latest_tag }}
|
||||
if [ "$LAST_TAG" = "v0.0.0" ]; then
|
||||
# Check how many commits we have and use that as the limit
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
if [ "$COMMIT_COUNT" -gt 10 ]; then
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~10..HEAD)
|
||||
# Increment patch version
|
||||
PATCH=$((PATCH + 1))
|
||||
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
||||
|
||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "New version will be: $NEW_VERSION"
|
||||
- name: Check if release already exists
|
||||
id: check_release
|
||||
run: |
|
||||
if gh release view ${{ steps.get_tag.outputs.new_version }} >/dev/null 2>&1; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
echo "Release ${{ steps.get_tag.outputs.new_version }} already exists, skipping..."
|
||||
else
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~$COMMIT_COUNT..HEAD 2>/dev/null || git log --oneline --pretty=format:"- %s")
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "Release ${{ steps.get_tag.outputs.new_version }} does not exist, proceeding..."
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create release package variants
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
run: |
|
||||
chmod +x .github/workflows/scripts/create-release-packages.sh
|
||||
.github/workflows/scripts/create-release-packages.sh ${{ steps.get_tag.outputs.new_version }}
|
||||
- name: Generate release notes
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
id: release_notes
|
||||
run: |
|
||||
# Get commits since last tag
|
||||
LAST_TAG=${{ steps.get_tag.outputs.latest_tag }}
|
||||
if [ "$LAST_TAG" = "v0.0.0" ]; then
|
||||
# Check how many commits we have and use that as the limit
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
if [ "$COMMIT_COUNT" -gt 10 ]; then
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~10..HEAD)
|
||||
else
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~$COMMIT_COUNT..HEAD 2>/dev/null || git log --oneline --pretty=format:"- %s")
|
||||
fi
|
||||
else
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" $LAST_TAG..HEAD)
|
||||
fi
|
||||
|
||||
# Create release notes
|
||||
cat > release_notes.md << EOF
|
||||
Template release ${{ steps.get_tag.outputs.new_version }}
|
||||
|
||||
Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, and opencode.
|
||||
|
||||
Now includes per-script variants for POSIX shell (sh) and PowerShell (ps).
|
||||
|
||||
Download the template for your preferred AI assistant + script type:
|
||||
- spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
EOF
|
||||
|
||||
echo "Generated release notes:"
|
||||
cat release_notes.md
|
||||
- name: Create GitHub Release
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
run: |
|
||||
# Remove 'v' prefix from version for release title
|
||||
VERSION_NO_V=${{ steps.get_tag.outputs.new_version }}
|
||||
VERSION_NO_V=${VERSION_NO_V#v}
|
||||
|
||||
gh release create ${{ steps.get_tag.outputs.new_version }} \
|
||||
spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||
--notes-file release_notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Update version in pyproject.toml (for release artifacts only)
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
run: |
|
||||
# Update version in pyproject.toml (remove 'v' prefix for Python versioning)
|
||||
VERSION=${{ steps.get_tag.outputs.new_version }}
|
||||
PYTHON_VERSION=${VERSION#v}
|
||||
|
||||
if [ -f "pyproject.toml" ]; then
|
||||
sed -i "s/version = \".*\"/version = \"$PYTHON_VERSION\"/" pyproject.toml
|
||||
echo "Updated pyproject.toml version to $PYTHON_VERSION (for release artifacts only)"
|
||||
fi
|
||||
else
|
||||
COMMITS=$(git log --oneline --pretty=format:"- %s" $LAST_TAG..HEAD)
|
||||
fi
|
||||
|
||||
# Create release notes
|
||||
cat > release_notes.md << EOF
|
||||
Template release ${{ steps.get_tag.outputs.new_version }}
|
||||
|
||||
Updated specification-driven development templates for GitHub Copilot, Claude Code, and Gemini CLI.
|
||||
|
||||
Download the template for your preferred AI assistant:
|
||||
- spec-kit-template-copilot-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-claude-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
- spec-kit-template-gemini-${{ steps.get_tag.outputs.new_version }}.zip
|
||||
EOF
|
||||
|
||||
echo "Generated release notes:"
|
||||
cat release_notes.md
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
run: |
|
||||
# Remove 'v' prefix from version for release title
|
||||
VERSION_NO_V=${{ steps.get_tag.outputs.new_version }}
|
||||
VERSION_NO_V=${VERSION_NO_V#v}
|
||||
|
||||
gh release create ${{ steps.get_tag.outputs.new_version }} \
|
||||
spec-kit-template-copilot-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-claude-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
spec-kit-template-gemini-${{ steps.get_tag.outputs.new_version }}.zip \
|
||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||
--notes-file release_notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update version in pyproject.toml (for release artifacts only)
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
run: |
|
||||
# Update version in pyproject.toml (remove 'v' prefix for Python versioning)
|
||||
VERSION=${{ steps.get_tag.outputs.new_version }}
|
||||
PYTHON_VERSION=${VERSION#v}
|
||||
|
||||
if [ -f "pyproject.toml" ]; then
|
||||
sed -i "s/version = \".*\"/version = \"$PYTHON_VERSION\"/" pyproject.toml
|
||||
echo "Updated pyproject.toml version to $PYTHON_VERSION (for release artifacts only)"
|
||||
fi
|
||||
|
||||
# Note: No longer committing version changes back to main branch
|
||||
# The version is only updated in the release artifacts
|
||||
|
||||
205
.github/workflows/scripts/create-release-packages.sh
vendored
Normal file
205
.github/workflows/scripts/create-release-packages.sh
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# create-release-packages.sh (workflow-local)
|
||||
# Build Spec Kit template release archives for each supported AI assistant and script type.
|
||||
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
||||
# Version argument should include leading 'v'.
|
||||
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
||||
# AGENTS : space or comma separated subset of: claude gemini copilot qwen opencode (default: all)
|
||||
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
||||
# Examples:
|
||||
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
||||
# AGENTS="copilot,gemini" $0 v0.2.0
|
||||
# SCRIPTS=ps $0 v0.2.0
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "Usage: $0 <version-with-v-prefix>" >&2
|
||||
exit 1
|
||||
fi
|
||||
NEW_VERSION="$1"
|
||||
if [[ ! $NEW_VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Version must look like v0.0.0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building release packages for $NEW_VERSION"
|
||||
|
||||
rm -rf sdd-package-base* sdd-*-package-* spec-kit-template-*-${NEW_VERSION}.zip || true
|
||||
|
||||
rewrite_paths() {
|
||||
sed -E \
|
||||
-e 's@(/?)memory/@.specify/memory/@g' \
|
||||
-e 's@(/?)scripts/@.specify/scripts/@g' \
|
||||
-e 's@(/?)templates/@.specify/templates/@g'
|
||||
}
|
||||
|
||||
generate_commands() {
|
||||
local agent=$1 ext=$2 arg_format=$3 output_dir=$4 script_variant=$5
|
||||
mkdir -p "$output_dir"
|
||||
for template in templates/commands/*.md; do
|
||||
[[ -f "$template" ]] || continue
|
||||
local name description script_command body
|
||||
name=$(basename "$template" .md)
|
||||
|
||||
# Normalize line endings
|
||||
file_content=$(tr -d '\r' < "$template")
|
||||
|
||||
# Extract description and script command from YAML frontmatter
|
||||
description=$(printf '%s\n' "$file_content" | awk '/^description:/ {sub(/^description:[[:space:]]*/, ""); print; exit}')
|
||||
script_command=$(printf '%s\n' "$file_content" | awk -v sv="$script_variant" '/^[[:space:]]*'"$script_variant"':[[:space:]]*/ {sub(/^[[:space:]]*'"$script_variant"':[[:space:]]*/, ""); print; exit}')
|
||||
|
||||
if [[ -z $script_command ]]; then
|
||||
echo "Warning: no script command found for $script_variant in $template" >&2
|
||||
script_command="(Missing script command for $script_variant)"
|
||||
fi
|
||||
|
||||
# Replace {SCRIPT} placeholder with the script command
|
||||
body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g")
|
||||
|
||||
# Remove the scripts: section from frontmatter while preserving YAML structure
|
||||
body=$(printf '%s\n' "$body" | awk '
|
||||
/^---$/ { print; if (++dash_count == 1) in_frontmatter=1; else in_frontmatter=0; next }
|
||||
in_frontmatter && /^scripts:$/ { skip_scripts=1; next }
|
||||
in_frontmatter && /^[a-zA-Z].*:/ && skip_scripts { skip_scripts=0 }
|
||||
in_frontmatter && skip_scripts && /^[[:space:]]/ { next }
|
||||
{ print }
|
||||
')
|
||||
|
||||
# Apply other substitutions
|
||||
body=$(printf '%s\n' "$body" | sed "s/{ARGS}/$arg_format/g" | sed "s/__AGENT__/$agent/g" | rewrite_paths)
|
||||
|
||||
case $ext in
|
||||
toml)
|
||||
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/$name.$ext" ;;
|
||||
md)
|
||||
echo "$body" > "$output_dir/$name.$ext" ;;
|
||||
prompt.md)
|
||||
echo "$body" > "$output_dir/$name.$ext" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
build_variant() {
|
||||
local agent=$1 script=$2
|
||||
local base_dir="sdd-${agent}-package-${script}"
|
||||
echo "Building $agent ($script) package..."
|
||||
mkdir -p "$base_dir"
|
||||
|
||||
# Copy base structure but filter scripts by variant
|
||||
SPEC_DIR="$base_dir/.specify"
|
||||
mkdir -p "$SPEC_DIR"
|
||||
|
||||
[[ -d memory ]] && { cp -r memory "$SPEC_DIR/"; echo "Copied memory -> .specify"; }
|
||||
|
||||
# Only copy the relevant script variant directory
|
||||
if [[ -d scripts ]]; then
|
||||
mkdir -p "$SPEC_DIR/scripts"
|
||||
case $script in
|
||||
sh)
|
||||
[[ -d scripts/bash ]] && { cp -r scripts/bash "$SPEC_DIR/scripts/"; echo "Copied scripts/bash -> .specify/scripts"; }
|
||||
# Copy any script files that aren't in variant-specific directories
|
||||
find scripts -maxdepth 1 -type f -exec cp {} "$SPEC_DIR/scripts/" \; 2>/dev/null || true
|
||||
;;
|
||||
ps)
|
||||
[[ -d scripts/powershell ]] && { cp -r scripts/powershell "$SPEC_DIR/scripts/"; echo "Copied scripts/powershell -> .specify/scripts"; }
|
||||
# Copy any script files that aren't in variant-specific directories
|
||||
find scripts -maxdepth 1 -type f -exec cp {} "$SPEC_DIR/scripts/" \; 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
|
||||
# Inject variant into plan-template.md within .specify/templates if present
|
||||
local plan_tpl="$base_dir/.specify/templates/plan-template.md"
|
||||
if [[ -f "$plan_tpl" ]]; then
|
||||
plan_norm=$(tr -d '\r' < "$plan_tpl")
|
||||
# Extract script command from YAML frontmatter
|
||||
script_command=$(printf '%s\n' "$plan_norm" | awk -v sv="$script" '/^[[:space:]]*'"$script"':[[:space:]]*/ {sub(/^[[:space:]]*'"$script"':[[:space:]]*/, ""); print; exit}')
|
||||
if [[ -n $script_command ]]; then
|
||||
# Always prefix with .specify/ for plan usage
|
||||
script_command=".specify/$script_command"
|
||||
tmp_file=$(mktemp)
|
||||
# Replace {SCRIPT} placeholder with the script command and __AGENT__ with agent name
|
||||
substituted=$(sed "s|{SCRIPT}|${script_command}|g" "$plan_tpl" | tr -d '\r' | sed "s|__AGENT__|${agent}|g")
|
||||
# Strip YAML frontmatter from plan template output (keep body only)
|
||||
stripped=$(printf '%s\n' "$substituted" | awk 'BEGIN{fm=0;dash=0} /^---$/ {dash++; if(dash==1){fm=1; next} else if(dash==2){fm=0; next}} {if(!fm) print}')
|
||||
printf '%s\n' "$stripped" > "$plan_tpl"
|
||||
else
|
||||
echo "Warning: no plan-template script command found for $script in YAML frontmatter" >&2
|
||||
fi
|
||||
fi
|
||||
case $agent in
|
||||
claude)
|
||||
mkdir -p "$base_dir/.claude/commands"
|
||||
generate_commands claude md "\$ARGUMENTS" "$base_dir/.claude/commands" "$script" ;;
|
||||
gemini)
|
||||
mkdir -p "$base_dir/.gemini/commands"
|
||||
generate_commands gemini toml "{{args}}" "$base_dir/.gemini/commands" "$script"
|
||||
[[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md "$base_dir/GEMINI.md" ;;
|
||||
copilot)
|
||||
mkdir -p "$base_dir/.github/prompts"
|
||||
generate_commands copilot prompt.md "\$ARGUMENTS" "$base_dir/.github/prompts" "$script" ;;
|
||||
cursor)
|
||||
mkdir -p "$base_dir/.cursor/commands"
|
||||
generate_commands cursor md "\$ARGUMENTS" "$base_dir/.cursor/commands" "$script" ;;
|
||||
qwen)
|
||||
mkdir -p "$base_dir/.qwen/commands"
|
||||
generate_commands qwen md "\$ARGUMENTS" "$base_dir/.qwen/commands" "$script"
|
||||
[[ -f agent_templates/qwen/QWEN.md ]] && cp agent_templates/qwen/QWEN.md "$base_dir/QWEN.md" ;;
|
||||
opencode)
|
||||
mkdir -p "$base_dir/.opencode/command"
|
||||
generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$script" ;;
|
||||
esac
|
||||
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
||||
echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
||||
}
|
||||
|
||||
# Determine agent list
|
||||
ALL_AGENTS=(claude gemini copilot cursor qwen opencode)
|
||||
ALL_SCRIPTS=(sh ps)
|
||||
|
||||
norm_list() {
|
||||
# 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")}'
|
||||
}
|
||||
|
||||
validate_subset() {
|
||||
local type=$1; shift; local -n allowed=$1; shift; local items=($@)
|
||||
local ok=1
|
||||
for it in "${items[@]}"; do
|
||||
local found=0
|
||||
for a in "${allowed[@]}"; do [[ $it == $a ]] && { found=1; break; }; done
|
||||
if [[ $found -eq 0 ]]; then
|
||||
echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2
|
||||
ok=0
|
||||
fi
|
||||
done
|
||||
return $ok
|
||||
}
|
||||
|
||||
if [[ -n ${AGENTS:-} ]]; then
|
||||
AGENT_LIST=($(printf '%s' "$AGENTS" | norm_list))
|
||||
validate_subset agent ALL_AGENTS "${AGENT_LIST[@]}" || exit 1
|
||||
else
|
||||
AGENT_LIST=(${ALL_AGENTS[@]})
|
||||
fi
|
||||
|
||||
if [[ -n ${SCRIPTS:-} ]]; then
|
||||
SCRIPT_LIST=($(printf '%s' "$SCRIPTS" | norm_list))
|
||||
validate_subset script ALL_SCRIPTS "${SCRIPT_LIST[@]}" || exit 1
|
||||
else
|
||||
SCRIPT_LIST=(${ALL_SCRIPTS[@]})
|
||||
fi
|
||||
|
||||
echo "Agents: ${AGENT_LIST[*]}"
|
||||
echo "Scripts: ${SCRIPT_LIST[*]}"
|
||||
|
||||
for agent in "${AGENT_LIST[@]}"; do
|
||||
for script in "${SCRIPT_LIST[@]}"; do
|
||||
build_variant "$agent" "$script"
|
||||
done
|
||||
done
|
||||
|
||||
echo "Archives:"
|
||||
ls -1 spec-kit-template-*-${NEW_VERSION}.zip
|
||||
35
CHANGELOG.md
Normal file
35
CHANGELOG.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the Specify CLI will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.0.6] - 2025-09-17
|
||||
|
||||
### Added
|
||||
|
||||
- opencode support as additional AI assistant option
|
||||
|
||||
## [0.0.5] - 2025-09-17
|
||||
|
||||
### Added
|
||||
|
||||
- Qwen Code support as additional AI assistant option
|
||||
|
||||
## [0.0.4] - 2025-09-14
|
||||
|
||||
### Added
|
||||
|
||||
- SOCKS proxy support for corporate environments via `httpx[socks]` dependency
|
||||
|
||||
### Fixed
|
||||
|
||||
N/A
|
||||
|
||||
### Changed
|
||||
|
||||
N/A
|
||||
|
||||
@@ -11,10 +11,13 @@ These are one time installations required to be able to test your changes locall
|
||||
1. Install [Python 3.11+](https://www.python.org/downloads/)
|
||||
1. Install [uv](https://docs.astral.sh/uv/) for package management
|
||||
1. Install [Git](https://git-scm.com/downloads)
|
||||
1. Have an AI coding agent available: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
||||
1. Have an AI coding agent available: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Qwen Code](https://github.com/QwenLM/qwen-code). We're working on adding support for other agents as well.
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
>[!NOTE]
|
||||
>If your pull request introduces a large change that materially impacts the work of the CLI or the rest of the repository (e.g., you're introducing new templates, arguments, or otherwise major changes), make sure that it was **discussed and agreed upon** by the project maintainers. Pull requests with large changes that did not have a prior conversation and agreement will be closed.
|
||||
|
||||
1. Fork and clone the repository
|
||||
1. Configure and install the dependencies: `uv sync`
|
||||
1. Make sure the CLI works on your machine: `uv run specify --help`
|
||||
@@ -28,7 +31,7 @@ Here are a few things you can do that will increase the likelihood of your pull
|
||||
|
||||
- Follow the project's coding conventions.
|
||||
- Write tests for new functionality.
|
||||
- Update documentation (`README.md,` `spec-driven.md`) if your changes affect user-facing features.
|
||||
- Update documentation (`README.md`, `spec-driven.md`) if your changes affect user-facing features.
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
- Test your changes with the Spec-Driven Development workflow to ensure compatibility.
|
||||
|
||||
101
README.md
101
README.md
@@ -16,13 +16,19 @@
|
||||
|
||||
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
|
||||
- [⚡ Get started](#-get-started)
|
||||
- [📽️ Video Overview](#️-video-overview)
|
||||
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
||||
- [📚 Core philosophy](#-core-philosophy)
|
||||
- [🌟 Development phases](#-development-phases)
|
||||
- [🎯 Experimental goals](#-experimental-goals)
|
||||
- [🔧 Prerequisites](#-prerequisites)
|
||||
- [📖 Learn more](#-learn-more)
|
||||
- [Detailed process](#detailed-process)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [📋 Detailed process](#-detailed-process)
|
||||
- [🔍 Troubleshooting](#-troubleshooting)
|
||||
- [👥 Maintainers](#-maintainers)
|
||||
- [💬 Support](#-support)
|
||||
- [🙏 Acknowledgements](#-acknowledgements)
|
||||
- [📄 License](#-license)
|
||||
|
||||
## 🤔 What is Spec-Driven Development?
|
||||
|
||||
@@ -40,15 +46,15 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME
|
||||
|
||||
### 2. Create the spec
|
||||
|
||||
Use the `/specify` command to describe what you want to build. Focus on the **what** and **why**, not the tech stack.
|
||||
Use the **`/specify`** command to describe what you want to build. Focus on the **what** and **why**, not the tech stack.
|
||||
|
||||
```bash
|
||||
/specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums never other nested albums. Within each album, photos are previewed in a tile-like interface.
|
||||
/specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums are never in other nested albums. Within each album, photos are previewed in a tile-like interface.
|
||||
```
|
||||
|
||||
### 3. Create a technical implementation plan
|
||||
|
||||
Use the `/plan` command to provide your tech stack and architecture choices.
|
||||
Use the **`/plan`** command to provide your tech stack and architecture choices.
|
||||
|
||||
```bash
|
||||
/plan The application uses Vite with minimal number of libraries. Use vanilla HTML, CSS, and JavaScript as much as possible. Images are not uploaded anywhere and metadata is stored in a local SQLite database.
|
||||
@@ -56,10 +62,68 @@ Use the `/plan` command to provide your tech stack and architecture choices.
|
||||
|
||||
### 4. Break down and implement
|
||||
|
||||
Use `/tasks` to create an actionable task list, then ask your agent to implement the feature.
|
||||
Use **`/tasks`** to create an actionable task list, then ask your agent to implement the feature.
|
||||
|
||||
For detailed step-by-step instructions, see our [comprehensive guide](./spec-driven.md).
|
||||
|
||||
## 📽️ Video Overview
|
||||
|
||||
Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.com/watch?v=a9eR1xsfvHg&pp=0gcJCckJAYcqIYzv)!
|
||||
|
||||
[](https://www.youtube.com/watch?v=a9eR1xsfvHg&pp=0gcJCckJAYcqIYzv)
|
||||
|
||||
## 🔧 Specify CLI Reference
|
||||
|
||||
The `specify` command supports the following options:
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Description |
|
||||
|-------------|----------------------------------------------------------------|
|
||||
| `init` | Initialize a new Specify project from the latest template |
|
||||
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `opencode`, `cursor-agent`) |
|
||||
|
||||
### `specify init` Arguments & Options
|
||||
|
||||
| Argument/Option | Type | Description |
|
||||
|------------------------|----------|------------------------------------------------------------------------------|
|
||||
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`) |
|
||||
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `opencode`, or `cursor` |
|
||||
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
||||
| `--no-git` | Flag | Skip git repository initialization |
|
||||
| `--here` | Flag | Initialize project in the current directory instead of creating a new one |
|
||||
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
|
||||
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Basic project initialization
|
||||
specify init my-project
|
||||
|
||||
# Initialize with specific AI assistant
|
||||
specify init my-project --ai claude
|
||||
|
||||
# Initialize with Cursor support
|
||||
specify init my-project --ai cursor
|
||||
|
||||
# Initialize with PowerShell scripts (Windows/cross-platform)
|
||||
specify init my-project --ai copilot --script ps
|
||||
|
||||
# Initialize in current directory
|
||||
specify init --here --ai copilot
|
||||
|
||||
# Skip git initialization
|
||||
specify init my-project --ai gemini --no-git
|
||||
|
||||
# Enable debug output for troubleshooting
|
||||
specify init my-project --ai claude --debug
|
||||
|
||||
# Check system requirements
|
||||
specify check
|
||||
```
|
||||
|
||||
## 📚 Core philosophy
|
||||
|
||||
Spec-Driven Development is a structured process that emphasizes:
|
||||
@@ -101,12 +165,12 @@ Our research and experimentation focus on:
|
||||
|
||||
- Validate the concept of parallel implementation exploration
|
||||
- Provide robust iterative feature development workflows
|
||||
- Extend processes to handle upgrades and modernization tasks
|
||||
- Extend processes to handle upgrades and modernization tasks
|
||||
|
||||
## 🔧 Prerequisites
|
||||
|
||||
- **Linux/macOS** (or WSL2 on Windows)
|
||||
- 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/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Cursor](https://cursor.sh/), [Qwen CLI](https://github.com/QwenLM/qwen-code) or [opencode](https://opencode.ai/)
|
||||
- [uv](https://docs.astral.sh/uv/) for package management
|
||||
- [Python 3.11+](https://www.python.org/downloads/)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
@@ -114,11 +178,11 @@ Our research and experimentation focus on:
|
||||
## 📖 Learn more
|
||||
|
||||
- **[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>
|
||||
<summary>Click to expand the detailed step-by-step walkthrough</summary>
|
||||
@@ -143,11 +207,13 @@ You will be prompted to select the AI agent you are using. You can also proactiv
|
||||
specify init <project_name> --ai claude
|
||||
specify init <project_name> --ai gemini
|
||||
specify init <project_name> --ai copilot
|
||||
specify init <project_name> --ai qwen
|
||||
specify init <project_name> --ai opencode
|
||||
# Or in current directory:
|
||||
specify init --here --ai claude
|
||||
```
|
||||
|
||||
The CLI will check if you have Claude Code or Gemini CLI installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
|
||||
The CLI will check if you have Claude Code, Gemini CLI, Qwen CLI or opencode installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
|
||||
|
||||
```bash
|
||||
specify init <project_name> --ai claude --ignore-agent-tools
|
||||
@@ -210,7 +276,6 @@ At this stage, your project folder contents should resemble the following:
|
||||
│ └── 001-create-taskify
|
||||
│ └── spec.md
|
||||
└── templates
|
||||
├── CLAUDE-template.md
|
||||
├── plan-template.md
|
||||
├── spec-template.md
|
||||
└── tasks-template.md
|
||||
@@ -341,14 +406,14 @@ Once the implementation step is done, ask Claude Code to try to run the applicat
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Git Credential Manager on Linux
|
||||
|
||||
If you're having issues with Git authentication on Linux, you can install Git Credential Manager:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "Downloading Git Credential Manager v2.6.1..."
|
||||
wget https://github.com/git-ecosystem/git-credential-manager/releases/download/v2.6.1/gcm-linux_amd64.2.6.1.deb
|
||||
@@ -360,19 +425,19 @@ echo "Cleaning up..."
|
||||
rm gcm-linux_amd64.2.6.1.deb
|
||||
```
|
||||
|
||||
## Maintainers
|
||||
## 👥 Maintainers
|
||||
|
||||
- Den Delimarsky ([@localden](https://github.com/localden))
|
||||
- John Lam ([@jflam](https://github.com/jflam))
|
||||
|
||||
## Support
|
||||
## 💬 Support
|
||||
|
||||
For support, please open a [GitHub issue](https://github.com/github/spec-kit/issues/new). We welcome bug reports, feature requests, and questions about using Spec-Driven Development.
|
||||
|
||||
## Acknowledgements
|
||||
## 🙏 Acknowledgements
|
||||
|
||||
This project is heavily influenced by and based on the work and research of [John Lam](https://github.com/jflam).
|
||||
|
||||
## 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.
|
||||
|
||||
8
docs/.gitignore
vendored
Normal file
8
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# DocFX build output
|
||||
_site/
|
||||
obj/
|
||||
.docfx/
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.log
|
||||
33
docs/README.md
Normal file
33
docs/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Documentation
|
||||
|
||||
This folder contains the documentation source files for Spec Kit, built using [DocFX](https://dotnet.github.io/docfx/).
|
||||
|
||||
## Building Locally
|
||||
|
||||
To build the documentation locally:
|
||||
|
||||
1. Install DocFX:
|
||||
```bash
|
||||
dotnet tool install -g docfx
|
||||
```
|
||||
|
||||
2. Build the documentation:
|
||||
```bash
|
||||
cd docs
|
||||
docfx docfx.json --serve
|
||||
```
|
||||
|
||||
3. Open your browser to `http://localhost:8080` to view the documentation.
|
||||
|
||||
## Structure
|
||||
|
||||
- `docfx.json` - DocFX configuration file
|
||||
- `index.md` - Main documentation homepage
|
||||
- `toc.yml` - Table of contents configuration
|
||||
- `installation.md` - Installation guide
|
||||
- `quickstart.md` - Quick start guide
|
||||
- `_site/` - Generated documentation output (ignored by git)
|
||||
|
||||
## 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`.
|
||||
70
docs/docfx.json
Normal file
70
docs/docfx.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"build": {
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"*.md",
|
||||
"toc.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"../README.md",
|
||||
"../CONTRIBUTING.md",
|
||||
"../CODE_OF_CONDUCT.md",
|
||||
"../SECURITY.md",
|
||||
"../SUPPORT.md"
|
||||
],
|
||||
"dest": "."
|
||||
}
|
||||
],
|
||||
"resource": [
|
||||
{
|
||||
"files": [
|
||||
"images/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"../media/**"
|
||||
],
|
||||
"dest": "media"
|
||||
}
|
||||
],
|
||||
"overwrite": [
|
||||
{
|
||||
"files": [
|
||||
"apidoc/**.md"
|
||||
],
|
||||
"exclude": [
|
||||
"obj/**",
|
||||
"_site/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dest": "_site",
|
||||
"globalMetadataFiles": [],
|
||||
"fileMetadataFiles": [],
|
||||
"template": [
|
||||
"default",
|
||||
"modern"
|
||||
],
|
||||
"postProcessors": [],
|
||||
"markdownEngineName": "markdig",
|
||||
"noLangKeyword": false,
|
||||
"keepFileLink": false,
|
||||
"cleanupCacheHistory": false,
|
||||
"disableGitFeatures": false,
|
||||
"globalMetadata": {
|
||||
"_appTitle": "Spec Kit Documentation",
|
||||
"_appName": "Spec Kit",
|
||||
"_appFooter": "Spec Kit - A specification-driven development toolkit",
|
||||
"_enableSearch": true,
|
||||
"_disableContribution": false,
|
||||
"_gitContribute": {
|
||||
"repo": "https://github.com/github/spec-kit",
|
||||
"branch": "main"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
docs/index.md
Normal file
62
docs/index.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Spec Kit
|
||||
|
||||
*Build high-quality software faster.*
|
||||
|
||||
**An effort to allow organizations to focus on product scenarios rather than writing undifferentiated code with the help of Spec-Driven Development.**
|
||||
|
||||
## What is Spec-Driven Development?
|
||||
|
||||
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.
|
||||
|
||||
## Getting Started
|
||||
|
||||
- [Installation Guide](installation.md)
|
||||
- [Quick Start Guide](quickstart.md)
|
||||
- [Local Development](local-development.md)
|
||||
|
||||
## Core Philosophy
|
||||
|
||||
Spec-Driven Development is a structured process that emphasizes:
|
||||
|
||||
- **Intent-driven development** where specifications define the "_what_" before the "_how_"
|
||||
- **Rich specification creation** using guardrails and organizational principles
|
||||
- **Multi-step refinement** rather than one-shot code generation from prompts
|
||||
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
|
||||
|
||||
## Development Phases
|
||||
|
||||
| Phase | Focus | Key Activities |
|
||||
|-------|-------|----------------|
|
||||
| **0-to-1 Development** ("Greenfield") | Generate from scratch | <ul><li>Start with high-level requirements</li><li>Generate specifications</li><li>Plan implementation steps</li><li>Build production-ready applications</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> |
|
||||
|
||||
## Experimental Goals
|
||||
|
||||
Our research and experimentation focus on:
|
||||
|
||||
### Technology Independence
|
||||
- Create applications using diverse technology stacks
|
||||
- Validate the hypothesis that Spec-Driven Development is a process not tied to specific technologies, programming languages, or frameworks
|
||||
|
||||
### Enterprise Constraints
|
||||
- Demonstrate mission-critical application development
|
||||
- Incorporate organizational constraints (cloud providers, tech stacks, engineering practices)
|
||||
- Support enterprise design systems and compliance requirements
|
||||
|
||||
### User-Centric Development
|
||||
- Build applications for different user cohorts and preferences
|
||||
- Support various development approaches (from vibe-coding to AI-native development)
|
||||
|
||||
### Creative & Iterative Processes
|
||||
- Validate the concept of parallel implementation exploration
|
||||
- Provide robust iterative feature development workflows
|
||||
- Extend processes to handle upgrades and modernization tasks
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see our [Contributing Guide](CONTRIBUTING.md) for information on how to contribute to this project.
|
||||
|
||||
## Support
|
||||
|
||||
For support, please check our [Support Guide](SUPPORT.md) or open an issue on GitHub.
|
||||
86
docs/installation.md
Normal file
86
docs/installation.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Installation Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **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)
|
||||
- [uv](https://docs.astral.sh/uv/) for package management
|
||||
- [Python 3.11+](https://www.python.org/downloads/)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
## Installation
|
||||
|
||||
### Initialize a New Project
|
||||
|
||||
The easiest way to get started is to initialize a new project:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
||||
```
|
||||
|
||||
Or initialize in the current directory:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init --here
|
||||
```
|
||||
|
||||
### Specify AI Agent
|
||||
|
||||
You can proactively specify your AI agent during initialization:
|
||||
|
||||
```bash
|
||||
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 copilot
|
||||
```
|
||||
|
||||
### Specify Script Type (Shell vs PowerShell)
|
||||
|
||||
All automation scripts now have both Bash (`.sh`) and PowerShell (`.ps1`) variants.
|
||||
|
||||
Auto behavior:
|
||||
- Windows default: `ps`
|
||||
- Other OS default: `sh`
|
||||
- Interactive mode: you'll be prompted unless you pass `--script`
|
||||
|
||||
Force a specific script type:
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script sh
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script ps
|
||||
```
|
||||
|
||||
### Ignore Agent Tools Check
|
||||
|
||||
If you prefer to get the templates without checking for the right tools:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai claude --ignore-agent-tools
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
After initialization, you should see the following commands available in your AI agent:
|
||||
- `/specify` - Create specifications
|
||||
- `/plan` - Generate implementation plans
|
||||
- `/tasks` - Break down into actionable tasks
|
||||
|
||||
The `.specify/scripts` directory will contain both `.sh` and `.ps1` scripts.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Git Credential Manager on Linux
|
||||
|
||||
If you're having issues with Git authentication on Linux, you can install Git Credential Manager:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "Downloading Git Credential Manager v2.6.1..."
|
||||
wget https://github.com/git-ecosystem/git-credential-manager/releases/download/v2.6.1/gcm-linux_amd64.2.6.1.deb
|
||||
echo "Installing Git Credential Manager..."
|
||||
sudo dpkg -i gcm-linux_amd64.2.6.1.deb
|
||||
echo "Configuring Git to use GCM..."
|
||||
git config --global credential.helper manager
|
||||
echo "Cleaning up..."
|
||||
rm gcm-linux_amd64.2.6.1.deb
|
||||
```
|
||||
168
docs/local-development.md
Normal file
168
docs/local-development.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Local Development Guide
|
||||
|
||||
This guide shows how to iterate on the `specify` CLI locally without publishing a release or committing to `main` first.
|
||||
|
||||
> Scripts now have both Bash (`.sh`) and PowerShell (`.ps1`) variants. The CLI auto-selects based on OS unless you pass `--script sh|ps`.
|
||||
|
||||
## 1. Clone and Switch Branches
|
||||
|
||||
```bash
|
||||
git clone https://github.com/github/spec-kit.git
|
||||
cd spec-kit
|
||||
# Work on a feature branch
|
||||
git checkout -b your-feature-branch
|
||||
```
|
||||
|
||||
## 2. Run the CLI Directly (Fastest Feedback)
|
||||
|
||||
You can execute the CLI via the module entrypoint without installing anything:
|
||||
|
||||
```bash
|
||||
# From repo root
|
||||
python -m src.specify_cli --help
|
||||
python -m src.specify_cli init demo-project --ai claude --ignore-agent-tools --script sh
|
||||
```
|
||||
|
||||
If you prefer invoking the script file style (uses shebang):
|
||||
|
||||
```bash
|
||||
python src/specify_cli/__init__.py init demo-project --script ps
|
||||
```
|
||||
|
||||
## 3. Use Editable Install (Isolated Environment)
|
||||
|
||||
Create an isolated environment using `uv` so dependencies resolve exactly like end users get them:
|
||||
|
||||
```bash
|
||||
# Create & activate virtual env (uv auto-manages .venv)
|
||||
uv venv
|
||||
source .venv/bin/activate # or on Windows PowerShell: .venv\Scripts\Activate.ps1
|
||||
|
||||
# Install project in editable mode
|
||||
uv pip install -e .
|
||||
|
||||
# Now 'specify' entrypoint is available
|
||||
specify --help
|
||||
```
|
||||
|
||||
Re-running after code edits requires no reinstall because of editable mode.
|
||||
|
||||
## 4. Invoke with uvx Directly From Git (Current Branch)
|
||||
|
||||
`uvx` can run from a local path (or a Git ref) to simulate user flows:
|
||||
|
||||
```bash
|
||||
uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools --script sh
|
||||
```
|
||||
|
||||
You can also point uvx at a specific branch without merging:
|
||||
|
||||
```bash
|
||||
# Push your working branch first
|
||||
git push origin your-feature-branch
|
||||
uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specify init demo-branch-test --script ps
|
||||
```
|
||||
|
||||
### 4a. Absolute Path uvx (Run From Anywhere)
|
||||
|
||||
If you're in another directory, use an absolute path instead of `.`:
|
||||
|
||||
```bash
|
||||
uvx --from /mnt/c/GitHub/spec-kit specify --help
|
||||
uvx --from /mnt/c/GitHub/spec-kit specify init demo-anywhere --ai copilot --ignore-agent-tools --script sh
|
||||
```
|
||||
|
||||
Set an environment variable for convenience:
|
||||
```bash
|
||||
export SPEC_KIT_SRC=/mnt/c/GitHub/spec-kit
|
||||
uvx --from "$SPEC_KIT_SRC" specify init demo-env --ai copilot --ignore-agent-tools --script ps
|
||||
```
|
||||
|
||||
(Optional) Define a shell function:
|
||||
```bash
|
||||
specify-dev() { uvx --from /mnt/c/GitHub/spec-kit specify "$@"; }
|
||||
# Then
|
||||
specify-dev --help
|
||||
```
|
||||
|
||||
## 5. Testing Script Permission Logic
|
||||
|
||||
After running an `init`, check that shell scripts are executable on POSIX systems:
|
||||
|
||||
```bash
|
||||
ls -l scripts | grep .sh
|
||||
# Expect owner execute bit (e.g. -rwxr-xr-x)
|
||||
```
|
||||
On Windows you will instead use the `.ps1` scripts (no chmod needed).
|
||||
|
||||
## 6. Run Lint / Basic Checks (Add Your Own)
|
||||
|
||||
Currently no enforced lint config is bundled, but you can quickly sanity check importability:
|
||||
```bash
|
||||
python -c "import specify_cli; print('Import OK')"
|
||||
```
|
||||
|
||||
## 7. Build a Wheel Locally (Optional)
|
||||
|
||||
Validate packaging before publishing:
|
||||
|
||||
```bash
|
||||
uv build
|
||||
ls dist/
|
||||
```
|
||||
Install the built artifact into a fresh throwaway environment if needed.
|
||||
|
||||
## 8. Using a Temporary Workspace
|
||||
|
||||
When testing `init --here` in a dirty directory, create a temp workspace:
|
||||
|
||||
```bash
|
||||
mkdir /tmp/spec-test && cd /tmp/spec-test
|
||||
python -m src.specify_cli init --here --ai claude --ignore-agent-tools --script sh # if repo copied here
|
||||
```
|
||||
Or copy only the modified CLI portion if you want a lighter sandbox.
|
||||
|
||||
## 9. Debug Network / TLS Skips
|
||||
|
||||
If you need to bypass TLS validation while experimenting:
|
||||
|
||||
```bash
|
||||
specify check --skip-tls
|
||||
specify init demo --skip-tls --ai gemini --ignore-agent-tools --script ps
|
||||
```
|
||||
(Use only for local experimentation.)
|
||||
|
||||
## 10. Rapid Edit Loop Summary
|
||||
|
||||
| Action | Command |
|
||||
|--------|---------|
|
||||
| Run CLI directly | `python -m src.specify_cli --help` |
|
||||
| Editable install | `uv pip install -e .` then `specify ...` |
|
||||
| Local uvx run (repo root) | `uvx --from . specify ...` |
|
||||
| Local uvx run (abs path) | `uvx --from /mnt/c/GitHub/spec-kit specify ...` |
|
||||
| Git branch uvx | `uvx --from git+URL@branch specify ...` |
|
||||
| Build wheel | `uv build` |
|
||||
|
||||
## 11. Cleaning Up
|
||||
|
||||
Remove build artifacts / virtual env quickly:
|
||||
```bash
|
||||
rm -rf .venv dist build *.egg-info
|
||||
```
|
||||
|
||||
## 12. Common Issues
|
||||
|
||||
| Symptom | Fix |
|
||||
|---------|-----|
|
||||
| `ModuleNotFoundError: typer` | Run `uv pip install -e .` |
|
||||
| Scripts not executable (Linux) | Re-run init or `chmod +x scripts/*.sh` |
|
||||
| Git step skipped | You passed `--no-git` or Git not installed |
|
||||
| Wrong script type downloaded | Pass `--script sh` or `--script ps` explicitly |
|
||||
| TLS errors on corporate network | Try `--skip-tls` (not for production) |
|
||||
|
||||
## 13. Next Steps
|
||||
|
||||
- Update docs and run through Quick Start using your modified CLI
|
||||
- Open a PR when satisfied
|
||||
- (Optional) Tag a release once changes land in `main`
|
||||
|
||||
122
docs/quickstart.md
Normal file
122
docs/quickstart.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Quick Start Guide
|
||||
|
||||
This guide will help you get started with Spec-Driven Development using Spec Kit.
|
||||
|
||||
> NEW: All automation scripts now provide both Bash (`.sh`) and PowerShell (`.ps1`) variants. The `specify` CLI auto-selects based on OS unless you pass `--script sh|ps`.
|
||||
|
||||
## The 4-Step Process
|
||||
|
||||
### 1. Install Specify
|
||||
|
||||
Initialize your project depending on the coding agent you're using:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
||||
```
|
||||
|
||||
Pick script type explicitly (optional):
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME> --script ps # Force PowerShell
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME> --script sh # Force POSIX shell
|
||||
```
|
||||
|
||||
### 2. Create the Spec
|
||||
|
||||
Use the `/specify` command to describe what you want to build. Focus on the **what** and **why**, not the tech stack.
|
||||
|
||||
```bash
|
||||
/specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums are never in other nested albums. Within each album, photos are previewed in a tile-like interface.
|
||||
```
|
||||
|
||||
### 3. Create a Technical Implementation Plan
|
||||
|
||||
Use the `/plan` command to provide your tech stack and architecture choices.
|
||||
|
||||
```bash
|
||||
/plan The application uses Vite with minimal number of libraries. Use vanilla HTML, CSS, and JavaScript as much as possible. Images are not uploaded anywhere and metadata is stored in a local SQLite database.
|
||||
```
|
||||
|
||||
### 4. Break Down and Implement
|
||||
|
||||
Use `/tasks` to create an actionable task list, then ask your agent to implement the feature.
|
||||
|
||||
## Detailed Example: Building Taskify
|
||||
|
||||
Here's a complete example of building a team productivity platform:
|
||||
|
||||
### Step 1: Define Requirements with `/specify`
|
||||
|
||||
```text
|
||||
Develop Taskify, a team productivity platform. It should allow users to create projects, add team members,
|
||||
assign tasks, comment and move tasks between boards in Kanban style. In this initial phase for this feature,
|
||||
let's call it "Create Taskify," let's have multiple users but the users will be declared ahead of time, predefined.
|
||||
I want five users in two different categories, one product manager and four engineers. Let's create three
|
||||
different sample projects. Let's have the standard Kanban columns for the status of each task, such as "To Do,"
|
||||
"In Progress," "In Review," and "Done." There will be no login for this application as this is just the very
|
||||
first testing thing to ensure that our basic features are set up. For each task in the UI for a task card,
|
||||
you should be able to change the current status of the task between the different columns in the Kanban work board.
|
||||
You should be able to leave an unlimited number of comments for a particular card. You should be able to, from that task
|
||||
card, assign one of the valid users. When you first launch Taskify, it's going to give you a list of the five users to pick
|
||||
from. There will be no password required. When you click on a user, you go into the main view, which displays the list of
|
||||
projects. When you click on a project, you open the Kanban board for that project. You're going to see the columns.
|
||||
You'll be able to drag and drop cards back and forth between different columns. You will see any cards that are
|
||||
assigned to you, the currently logged in user, in a different color from all the other ones, so you can quickly
|
||||
see yours. You can edit any comments that you make, but you can't edit comments that other people made. You can
|
||||
delete any comments that you made, but you can't delete comments anybody else made.
|
||||
```
|
||||
|
||||
### Step 2: Refine the Specification
|
||||
|
||||
After the initial specification is created, clarify any missing requirements:
|
||||
|
||||
```text
|
||||
For each sample project or project that you create there should be a variable number of tasks between 5 and 15
|
||||
tasks for each one randomly distributed into different states of completion. Make sure that there's at least
|
||||
one task in each stage of completion.
|
||||
```
|
||||
|
||||
Also validate the specification checklist:
|
||||
|
||||
```text
|
||||
Read the review and acceptance checklist, and check off each item in the checklist if the feature spec meets the criteria. Leave it empty if it does not.
|
||||
```
|
||||
|
||||
### Step 3: Generate Technical Plan with `/plan`
|
||||
|
||||
Be specific about your tech stack and technical requirements:
|
||||
|
||||
```text
|
||||
We are going to generate this using .NET Aspire, using Postgres as the database. The frontend should use
|
||||
Blazor server with drag-and-drop task boards, real-time updates. There should be a REST API created with a projects API,
|
||||
tasks API, and a notifications API.
|
||||
```
|
||||
|
||||
### Step 4: Validate and Implement
|
||||
|
||||
Have your AI agent audit the implementation plan:
|
||||
|
||||
```text
|
||||
Now I want you to go and audit the implementation plan and the implementation detail files.
|
||||
Read through it with an eye on determining whether or not there is a sequence of tasks that you need
|
||||
to be doing that are obvious from reading this. Because I don't know if there's enough here.
|
||||
```
|
||||
|
||||
Finally, implement the solution:
|
||||
|
||||
```text
|
||||
implement specs/002-create-taskify/plan.md
|
||||
```
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **Be explicit** about what you're building and why
|
||||
- **Don't focus on tech stack** during specification phase
|
||||
- **Iterate and refine** your specifications before implementation
|
||||
- **Validate** the plan before coding begins
|
||||
- **Let the AI agent handle** the implementation details
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read the complete methodology for in-depth guidance
|
||||
- Check out more examples in the repository
|
||||
- Explore the source code on GitHub
|
||||
17
docs/toc.yml
Normal file
17
docs/toc.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Home page
|
||||
- name: Home
|
||||
href: index.md
|
||||
|
||||
# Getting started section
|
||||
- name: Getting Started
|
||||
items:
|
||||
- name: Installation
|
||||
href: installation.md
|
||||
- name: Quick Start
|
||||
href: quickstart.md
|
||||
|
||||
# Development workflows
|
||||
- name: Development
|
||||
items:
|
||||
- name: Local Development
|
||||
href: local-development.md
|
||||
BIN
media/spec-kit-video-header.jpg
Normal file
BIN
media/spec-kit-video-header.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -1,14 +1,15 @@
|
||||
[project]
|
||||
name = "specify-cli"
|
||||
version = "0.0.2"
|
||||
version = "0.0.6"
|
||||
description = "Setup tool for Specify spec-driven development projects"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"typer",
|
||||
"rich",
|
||||
"httpx",
|
||||
"httpx[socks]",
|
||||
"platformdirs",
|
||||
"readchar",
|
||||
"truststore>=0.10.4",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
@@ -19,4 +20,4 @@ requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/specify_cli"]
|
||||
packages = ["src/specify_cli"]
|
||||
|
||||
15
scripts/bash/check-task-prerequisites.sh
Normal file
15
scripts/bash/check-task-prerequisites.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
JSON_MODE=false
|
||||
for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; --help|-h) echo "Usage: $0 [--json]"; exit 0 ;; esac; done
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
eval $(get_feature_paths)
|
||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
||||
if [[ ! -d "$FEATURE_DIR" ]]; then echo "ERROR: Feature directory not found: $FEATURE_DIR"; echo "Run /specify first."; exit 1; fi
|
||||
if [[ ! -f "$IMPL_PLAN" ]]; then echo "ERROR: plan.md not found in $FEATURE_DIR"; echo "Run /plan first."; exit 1; fi
|
||||
if $JSON_MODE; then
|
||||
docs=(); [[ -f "$RESEARCH" ]] && docs+=("research.md"); [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md"); ([[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]) && docs+=("contracts/"); [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md");
|
||||
json_docs=$(printf '"%s",' "${docs[@]}"); json_docs="[${json_docs%,}]"; printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
|
||||
else
|
||||
echo "FEATURE_DIR:$FEATURE_DIR"; echo "AVAILABLE_DOCS:"; check_file "$RESEARCH" "research.md"; check_file "$DATA_MODEL" "data-model.md"; check_dir "$CONTRACTS_DIR" "contracts/"; check_file "$QUICKSTART" "quickstart.md"; fi
|
||||
37
scripts/bash/common.sh
Normal file
37
scripts/bash/common.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# (Moved to scripts/bash/) Common functions and variables for all scripts
|
||||
|
||||
get_repo_root() { git rev-parse --show-toplevel; }
|
||||
get_current_branch() { git rev-parse --abbrev-ref HEAD; }
|
||||
|
||||
check_feature_branch() {
|
||||
local branch="$1"
|
||||
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
||||
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
||||
echo "Feature branches should be named like: 001-feature-name" >&2
|
||||
return 1
|
||||
fi; return 0
|
||||
}
|
||||
|
||||
get_feature_dir() { echo "$1/specs/$2"; }
|
||||
|
||||
get_feature_paths() {
|
||||
local repo_root=$(get_repo_root)
|
||||
local current_branch=$(get_current_branch)
|
||||
local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
|
||||
cat <<EOF
|
||||
REPO_ROOT='$repo_root'
|
||||
CURRENT_BRANCH='$current_branch'
|
||||
FEATURE_DIR='$feature_dir'
|
||||
FEATURE_SPEC='$feature_dir/spec.md'
|
||||
IMPL_PLAN='$feature_dir/plan.md'
|
||||
TASKS='$feature_dir/tasks.md'
|
||||
RESEARCH='$feature_dir/research.md'
|
||||
DATA_MODEL='$feature_dir/data-model.md'
|
||||
QUICKSTART='$feature_dir/quickstart.md'
|
||||
CONTRACTS_DIR='$feature_dir/contracts'
|
||||
EOF
|
||||
}
|
||||
|
||||
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||
58
scripts/bash/create-new-feature.sh
Normal file
58
scripts/bash/create-new-feature.sh
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# (Moved to scripts/bash/) Create a new feature with branch, directory structure, and template
|
||||
set -e
|
||||
|
||||
JSON_MODE=false
|
||||
ARGS=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--json) JSON_MODE=true ;;
|
||||
--help|-h) echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
|
||||
*) ARGS+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
FEATURE_DESCRIPTION="${ARGS[*]}"
|
||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
echo "Usage: $0 [--json] <feature_description>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
SPECS_DIR="$REPO_ROOT/specs"
|
||||
mkdir -p "$SPECS_DIR"
|
||||
|
||||
HIGHEST=0
|
||||
if [ -d "$SPECS_DIR" ]; then
|
||||
for dir in "$SPECS_DIR"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
dirname=$(basename "$dir")
|
||||
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
|
||||
number=$((10#$number))
|
||||
if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi
|
||||
done
|
||||
fi
|
||||
|
||||
NEXT=$((HIGHEST + 1))
|
||||
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/-$//')
|
||||
WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
|
||||
BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
|
||||
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
||||
mkdir -p "$FEATURE_DIR"
|
||||
|
||||
TEMPLATE="$REPO_ROOT/templates/spec-template.md"
|
||||
SPEC_FILE="$FEATURE_DIR/spec.md"
|
||||
if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
|
||||
|
||||
if $JSON_MODE; then
|
||||
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
|
||||
else
|
||||
echo "BRANCH_NAME: $BRANCH_NAME"
|
||||
echo "SPEC_FILE: $SPEC_FILE"
|
||||
echo "FEATURE_NUM: $FEATURE_NUM"
|
||||
fi
|
||||
7
scripts/bash/get-feature-paths.sh
Normal file
7
scripts/bash/get-feature-paths.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
eval $(get_feature_paths)
|
||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
||||
echo "REPO_ROOT: $REPO_ROOT"; echo "BRANCH: $CURRENT_BRANCH"; echo "FEATURE_DIR: $FEATURE_DIR"; echo "FEATURE_SPEC: $FEATURE_SPEC"; echo "IMPL_PLAN: $IMPL_PLAN"; echo "TASKS: $TASKS"
|
||||
17
scripts/bash/setup-plan.sh
Normal file
17
scripts/bash/setup-plan.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
JSON_MODE=false
|
||||
for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; --help|-h) echo "Usage: $0 [--json]"; exit 0 ;; esac; done
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
eval $(get_feature_paths)
|
||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
||||
mkdir -p "$FEATURE_DIR"
|
||||
TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
|
||||
[[ -f "$TEMPLATE" ]] && cp "$TEMPLATE" "$IMPL_PLAN"
|
||||
if $JSON_MODE; then
|
||||
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
|
||||
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH"
|
||||
else
|
||||
echo "FEATURE_SPEC: $FEATURE_SPEC"; echo "IMPL_PLAN: $IMPL_PLAN"; echo "SPECS_DIR: $FEATURE_DIR"; echo "BRANCH: $CURRENT_BRANCH"
|
||||
fi
|
||||
66
scripts/bash/update-agent-context.sh
Normal file
66
scripts/bash/update-agent-context.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
|
||||
NEW_PLAN="$FEATURE_DIR/plan.md"
|
||||
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"; CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"; QWEN_FILE="$REPO_ROOT/QWEN.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md"
|
||||
AGENT_TYPE="$1"
|
||||
[ -f "$NEW_PLAN" ] || { echo "ERROR: No plan.md found at $NEW_PLAN"; exit 1; }
|
||||
echo "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||
NEW_LANG=$(grep "^**Language/Version**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Language\/Version**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
|
||||
NEW_FRAMEWORK=$(grep "^**Primary Dependencies**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Primary Dependencies**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
|
||||
NEW_DB=$(grep "^**Storage**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Storage**: //' | grep -v "N/A" | grep -v "NEEDS CLARIFICATION" || echo "")
|
||||
NEW_PROJECT_TYPE=$(grep "^**Project Type**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Project Type**: //' || echo "")
|
||||
update_agent_file() { local target_file="$1" agent_name="$2"; echo "Updating $agent_name context file: $target_file"; local temp_file=$(mktemp); if [ ! -f "$target_file" ]; then
|
||||
echo "Creating new $agent_name context file..."; if [ -f "$REPO_ROOT/.specify/templates/agent-file-template.md" ]; then cp "$REPO_ROOT/.specify/templates/agent-file-template.md" "$temp_file"; else echo "ERROR: Template not found"; return 1; fi;
|
||||
sed -i.bak "s/\[PROJECT NAME\]/$(basename $REPO_ROOT)/" "$temp_file"; sed -i.bak "s/\[DATE\]/$(date +%Y-%m-%d)/" "$temp_file"; sed -i.bak "s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/" "$temp_file";
|
||||
if [[ "$NEW_PROJECT_TYPE" == *"web"* ]]; then sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|backend/\nfrontend/\ntests/|" "$temp_file"; else sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|src/\ntests/|" "$temp_file"; fi;
|
||||
if [[ "$NEW_LANG" == *"Python"* ]]; then COMMANDS="cd src && pytest && ruff check ."; elif [[ "$NEW_LANG" == *"Rust"* ]]; then COMMANDS="cargo test && cargo clippy"; elif [[ "$NEW_LANG" == *"JavaScript"* ]] || [[ "$NEW_LANG" == *"TypeScript"* ]]; then COMMANDS="npm test && npm run lint"; else COMMANDS="# Add commands for $NEW_LANG"; fi; sed -i.bak "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$COMMANDS|" "$temp_file";
|
||||
sed -i.bak "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$NEW_LANG: Follow standard conventions|" "$temp_file"; sed -i.bak "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK|" "$temp_file"; rm "$temp_file.bak";
|
||||
else
|
||||
echo "Updating existing $agent_name context file..."; manual_start=$(grep -n "<!-- MANUAL ADDITIONS START -->" "$target_file" | cut -d: -f1); manual_end=$(grep -n "<!-- MANUAL ADDITIONS END -->" "$target_file" | cut -d: -f1); if [ -n "$manual_start" ] && [ -n "$manual_end" ]; then sed -n "${manual_start},${manual_end}p" "$target_file" > /tmp/manual_additions.txt; fi;
|
||||
python3 - "$target_file" <<'EOF'
|
||||
import re,sys,datetime
|
||||
target=sys.argv[1]
|
||||
with open(target) as f: content=f.read()
|
||||
NEW_LANG="'$NEW_LANG'";NEW_FRAMEWORK="'$NEW_FRAMEWORK'";CURRENT_BRANCH="'$CURRENT_BRANCH'";NEW_DB="'$NEW_DB'";NEW_PROJECT_TYPE="'$NEW_PROJECT_TYPE'"
|
||||
# Tech section
|
||||
m=re.search(r'## Active Technologies\n(.*?)\n\n',content, re.DOTALL)
|
||||
if m:
|
||||
existing=m.group(1)
|
||||
additions=[]
|
||||
if '$NEW_LANG' and '$NEW_LANG' not in existing: additions.append(f"- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)")
|
||||
if '$NEW_DB' and '$NEW_DB' not in existing and '$NEW_DB'!='N/A': additions.append(f"- $NEW_DB ($CURRENT_BRANCH)")
|
||||
if additions:
|
||||
new_block=existing+"\n"+"\n".join(additions)
|
||||
content=content.replace(m.group(0),f"## Active Technologies\n{new_block}\n\n")
|
||||
# Recent changes
|
||||
m2=re.search(r'## Recent Changes\n(.*?)(\n\n|$)',content, re.DOTALL)
|
||||
if m2:
|
||||
lines=[l for l in m2.group(1).strip().split('\n') if l]
|
||||
lines.insert(0,f"- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK")
|
||||
lines=lines[:3]
|
||||
content=re.sub(r'## Recent Changes\n.*?(\n\n|$)', '## Recent Changes\n'+"\n".join(lines)+'\n\n', content, flags=re.DOTALL)
|
||||
content=re.sub(r'Last updated: \d{4}-\d{2}-\d{2}', 'Last updated: '+datetime.datetime.now().strftime('%Y-%m-%d'), content)
|
||||
open(target+'.tmp','w').write(content)
|
||||
EOF
|
||||
mv "$target_file.tmp" "$target_file"; if [ -f /tmp/manual_additions.txt ]; then sed -i.bak '/<!-- MANUAL ADDITIONS START -->/,/<!-- MANUAL ADDITIONS END -->/d' "$target_file"; cat /tmp/manual_additions.txt >> "$target_file"; rm /tmp/manual_additions.txt "$target_file.bak"; fi;
|
||||
fi; mv "$temp_file" "$target_file" 2>/dev/null || true; echo "✅ $agent_name context file updated successfully"; }
|
||||
case "$AGENT_TYPE" in
|
||||
claude) update_agent_file "$CLAUDE_FILE" "Claude Code" ;;
|
||||
gemini) update_agent_file "$GEMINI_FILE" "Gemini CLI" ;;
|
||||
copilot) update_agent_file "$COPILOT_FILE" "GitHub Copilot" ;;
|
||||
cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;;
|
||||
qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;;
|
||||
opencode) update_agent_file "$AGENTS_FILE" "opencode" ;;
|
||||
"") [ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"; \
|
||||
[ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; \
|
||||
[ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; \
|
||||
[ -f "$CURSOR_FILE" ] && update_agent_file "$CURSOR_FILE" "Cursor IDE"; \
|
||||
[ -f "$QWEN_FILE" ] && update_agent_file "$QWEN_FILE" "Qwen Code"; \
|
||||
[ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "opencode"; \
|
||||
if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;;
|
||||
*) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode)"; exit 1 ;;
|
||||
esac
|
||||
echo; echo "Summary of changes:"; [ -n "$NEW_LANG" ] && echo "- Added language: $NEW_LANG"; [ -n "$NEW_FRAMEWORK" ] && echo "- Added framework: $NEW_FRAMEWORK"; [ -n "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ] && echo "- Added database: $NEW_DB"; echo; echo "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode]"
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Check that implementation plan exists and find optional design documents
|
||||
# Usage: ./check-task-prerequisites.sh [--json]
|
||||
|
||||
set -e
|
||||
|
||||
JSON_MODE=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--json) JSON_MODE=true ;;
|
||||
--help|-h) echo "Usage: $0 [--json]"; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
# Get all paths
|
||||
eval $(get_feature_paths)
|
||||
|
||||
# Check if on feature branch
|
||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
||||
|
||||
# Check if feature directory exists
|
||||
if [[ ! -d "$FEATURE_DIR" ]]; then
|
||||
echo "ERROR: Feature directory not found: $FEATURE_DIR"
|
||||
echo "Run /specify first to create the feature structure."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for implementation plan (required)
|
||||
if [[ ! -f "$IMPL_PLAN" ]]; then
|
||||
echo "ERROR: plan.md not found in $FEATURE_DIR"
|
||||
echo "Run /plan first to create the plan."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if $JSON_MODE; then
|
||||
# Build JSON array of available docs that actually exist
|
||||
docs=()
|
||||
[[ -f "$RESEARCH" ]] && docs+=("research.md")
|
||||
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
|
||||
([[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]) && docs+=("contracts/")
|
||||
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
|
||||
# join array into JSON
|
||||
json_docs=$(printf '"%s",' "${docs[@]}")
|
||||
json_docs="[${json_docs%,}]"
|
||||
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
|
||||
else
|
||||
# List available design documents (optional)
|
||||
echo "FEATURE_DIR:$FEATURE_DIR"
|
||||
echo "AVAILABLE_DOCS:"
|
||||
|
||||
# Use common check functions
|
||||
check_file "$RESEARCH" "research.md"
|
||||
check_file "$DATA_MODEL" "data-model.md"
|
||||
check_dir "$CONTRACTS_DIR" "contracts/"
|
||||
check_file "$QUICKSTART" "quickstart.md"
|
||||
fi
|
||||
|
||||
# Always succeed - task generation should work with whatever docs are available
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Common functions and variables for all scripts
|
||||
|
||||
# Get repository root
|
||||
get_repo_root() {
|
||||
git rev-parse --show-toplevel
|
||||
}
|
||||
|
||||
# Get current branch
|
||||
get_current_branch() {
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
}
|
||||
|
||||
# Check if current branch is a feature branch
|
||||
# Returns 0 if valid, 1 if not
|
||||
check_feature_branch() {
|
||||
local branch="$1"
|
||||
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
||||
echo "ERROR: Not on a feature branch. Current branch: $branch"
|
||||
echo "Feature branches should be named like: 001-feature-name"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Get feature directory path
|
||||
get_feature_dir() {
|
||||
local repo_root="$1"
|
||||
local branch="$2"
|
||||
echo "$repo_root/specs/$branch"
|
||||
}
|
||||
|
||||
# Get all standard paths for a feature
|
||||
# Usage: eval $(get_feature_paths)
|
||||
# Sets: REPO_ROOT, CURRENT_BRANCH, FEATURE_DIR, FEATURE_SPEC, IMPL_PLAN, TASKS
|
||||
get_feature_paths() {
|
||||
local repo_root=$(get_repo_root)
|
||||
local current_branch=$(get_current_branch)
|
||||
local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
|
||||
|
||||
echo "REPO_ROOT='$repo_root'"
|
||||
echo "CURRENT_BRANCH='$current_branch'"
|
||||
echo "FEATURE_DIR='$feature_dir'"
|
||||
echo "FEATURE_SPEC='$feature_dir/spec.md'"
|
||||
echo "IMPL_PLAN='$feature_dir/plan.md'"
|
||||
echo "TASKS='$feature_dir/tasks.md'"
|
||||
echo "RESEARCH='$feature_dir/research.md'"
|
||||
echo "DATA_MODEL='$feature_dir/data-model.md'"
|
||||
echo "QUICKSTART='$feature_dir/quickstart.md'"
|
||||
echo "CONTRACTS_DIR='$feature_dir/contracts'"
|
||||
}
|
||||
|
||||
# Check if a file exists and report
|
||||
check_file() {
|
||||
local file="$1"
|
||||
local description="$2"
|
||||
if [[ -f "$file" ]]; then
|
||||
echo " ✓ $description"
|
||||
return 0
|
||||
else
|
||||
echo " ✗ $description"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if a directory exists and has files
|
||||
check_dir() {
|
||||
local dir="$1"
|
||||
local description="$2"
|
||||
if [[ -d "$dir" ]] && [[ -n "$(ls -A "$dir" 2>/dev/null)" ]]; then
|
||||
echo " ✓ $description"
|
||||
return 0
|
||||
else
|
||||
echo " ✗ $description"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Create a new feature with branch, directory structure, and template
|
||||
# Usage: ./create-new-feature.sh "feature description"
|
||||
# ./create-new-feature.sh --json "feature description"
|
||||
|
||||
set -e
|
||||
|
||||
JSON_MODE=false
|
||||
|
||||
# Collect non-flag args
|
||||
ARGS=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--json)
|
||||
JSON_MODE=true
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
|
||||
*)
|
||||
ARGS+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
FEATURE_DESCRIPTION="${ARGS[*]}"
|
||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
echo "Usage: $0 [--json] <feature_description>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get repository root
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
SPECS_DIR="$REPO_ROOT/specs"
|
||||
|
||||
# Create specs directory if it doesn't exist
|
||||
mkdir -p "$SPECS_DIR"
|
||||
|
||||
# Find the highest numbered feature directory
|
||||
HIGHEST=0
|
||||
if [ -d "$SPECS_DIR" ]; then
|
||||
for dir in "$SPECS_DIR"/*; do
|
||||
if [ -d "$dir" ]; then
|
||||
dirname=$(basename "$dir")
|
||||
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
|
||||
number=$((10#$number))
|
||||
if [ "$number" -gt "$HIGHEST" ]; then
|
||||
HIGHEST=$number
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Generate next feature number with zero padding
|
||||
NEXT=$((HIGHEST + 1))
|
||||
FEATURE_NUM=$(printf "%03d" "$NEXT")
|
||||
|
||||
# Create branch name from description
|
||||
BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | \
|
||||
tr '[:upper:]' '[:lower:]' | \
|
||||
sed 's/[^a-z0-9]/-/g' | \
|
||||
sed 's/-\+/-/g' | \
|
||||
sed 's/^-//' | \
|
||||
sed 's/-$//')
|
||||
|
||||
# Extract 2-3 meaningful words
|
||||
WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
|
||||
|
||||
# Final branch name
|
||||
BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
|
||||
|
||||
# Create and switch to new branch
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
# Create feature directory
|
||||
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
||||
mkdir -p "$FEATURE_DIR"
|
||||
|
||||
# Copy template if it exists
|
||||
TEMPLATE="$REPO_ROOT/templates/spec-template.md"
|
||||
SPEC_FILE="$FEATURE_DIR/spec.md"
|
||||
|
||||
if [ -f "$TEMPLATE" ]; then
|
||||
cp "$TEMPLATE" "$SPEC_FILE"
|
||||
else
|
||||
echo "Warning: Template not found at $TEMPLATE" >&2
|
||||
touch "$SPEC_FILE"
|
||||
fi
|
||||
|
||||
if $JSON_MODE; then
|
||||
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' \
|
||||
"$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
|
||||
else
|
||||
# Output results for the LLM to use (legacy key: value format)
|
||||
echo "BRANCH_NAME: $BRANCH_NAME"
|
||||
echo "SPEC_FILE: $SPEC_FILE"
|
||||
echo "FEATURE_NUM: $FEATURE_NUM"
|
||||
fi
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Get paths for current feature branch without creating anything
|
||||
# Used by commands that need to find existing feature files
|
||||
|
||||
set -e
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
# Get all paths
|
||||
eval $(get_feature_paths)
|
||||
|
||||
# Check if on feature branch
|
||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
||||
|
||||
# Output paths (don't create anything)
|
||||
echo "REPO_ROOT: $REPO_ROOT"
|
||||
echo "BRANCH: $CURRENT_BRANCH"
|
||||
echo "FEATURE_DIR: $FEATURE_DIR"
|
||||
echo "FEATURE_SPEC: $FEATURE_SPEC"
|
||||
echo "IMPL_PLAN: $IMPL_PLAN"
|
||||
echo "TASKS: $TASKS"
|
||||
35
scripts/powershell/check-task-prerequisites.ps1
Normal file
35
scripts/powershell/check-task-prerequisites.ps1
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env pwsh
|
||||
[CmdletBinding()]
|
||||
param([switch]$Json)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. "$PSScriptRoot/common.ps1"
|
||||
|
||||
$paths = Get-FeaturePathsEnv
|
||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
||||
|
||||
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
|
||||
Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
|
||||
Write-Output "Run /specify first to create the feature structure."
|
||||
exit 1
|
||||
}
|
||||
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
|
||||
Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
|
||||
Write-Output "Run /plan first to create the plan."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Json) {
|
||||
$docs = @()
|
||||
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
|
||||
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
|
||||
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) { $docs += 'contracts/' }
|
||||
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
|
||||
[PSCustomObject]@{ FEATURE_DIR=$paths.FEATURE_DIR; AVAILABLE_DOCS=$docs } | ConvertTo-Json -Compress
|
||||
} else {
|
||||
Write-Output "FEATURE_DIR:$($paths.FEATURE_DIR)"
|
||||
Write-Output "AVAILABLE_DOCS:"
|
||||
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
|
||||
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
|
||||
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
|
||||
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
|
||||
}
|
||||
65
scripts/powershell/common.ps1
Normal file
65
scripts/powershell/common.ps1
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Common PowerShell functions analogous to common.sh (moved to powershell/)
|
||||
|
||||
function Get-RepoRoot {
|
||||
git rev-parse --show-toplevel
|
||||
}
|
||||
|
||||
function Get-CurrentBranch {
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
}
|
||||
|
||||
function Test-FeatureBranch {
|
||||
param([string]$Branch)
|
||||
if ($Branch -notmatch '^[0-9]{3}-') {
|
||||
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
||||
Write-Output "Feature branches should be named like: 001-feature-name"
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Get-FeatureDir {
|
||||
param([string]$RepoRoot, [string]$Branch)
|
||||
Join-Path $RepoRoot "specs/$Branch"
|
||||
}
|
||||
|
||||
function Get-FeaturePathsEnv {
|
||||
$repoRoot = Get-RepoRoot
|
||||
$currentBranch = Get-CurrentBranch
|
||||
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
|
||||
[PSCustomObject]@{
|
||||
REPO_ROOT = $repoRoot
|
||||
CURRENT_BRANCH = $currentBranch
|
||||
FEATURE_DIR = $featureDir
|
||||
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
|
||||
IMPL_PLAN = Join-Path $featureDir 'plan.md'
|
||||
TASKS = Join-Path $featureDir 'tasks.md'
|
||||
RESEARCH = Join-Path $featureDir 'research.md'
|
||||
DATA_MODEL = Join-Path $featureDir 'data-model.md'
|
||||
QUICKSTART = Join-Path $featureDir 'quickstart.md'
|
||||
CONTRACTS_DIR = Join-Path $featureDir 'contracts'
|
||||
}
|
||||
}
|
||||
|
||||
function Test-FileExists {
|
||||
param([string]$Path, [string]$Description)
|
||||
if (Test-Path -Path $Path -PathType Leaf) {
|
||||
Write-Output " ✓ $Description"
|
||||
return $true
|
||||
} else {
|
||||
Write-Output " ✗ $Description"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Test-DirHasFiles {
|
||||
param([string]$Path, [string]$Description)
|
||||
if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Select-Object -First 1)) {
|
||||
Write-Output " ✓ $Description"
|
||||
return $true
|
||||
} else {
|
||||
Write-Output " ✗ $Description"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
52
scripts/powershell/create-new-feature.ps1
Normal file
52
scripts/powershell/create-new-feature.ps1
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Create a new feature (moved to powershell/)
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Json,
|
||||
[Parameter(ValueFromRemainingArguments = $true)]
|
||||
[string[]]$FeatureDescription
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
||||
Write-Error "Usage: ./create-new-feature.ps1 [-Json] <feature description>"; exit 1
|
||||
}
|
||||
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel
|
||||
$specsDir = Join-Path $repoRoot 'specs'
|
||||
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
||||
|
||||
$highest = 0
|
||||
if (Test-Path $specsDir) {
|
||||
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
||||
if ($_.Name -match '^(\d{3})') {
|
||||
$num = [int]$matches[1]
|
||||
if ($num -gt $highest) { $highest = $num }
|
||||
}
|
||||
}
|
||||
}
|
||||
$next = $highest + 1
|
||||
$featureNum = ('{0:000}' -f $next)
|
||||
|
||||
$branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
|
||||
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
|
||||
$branchName = "$featureNum-$([string]::Join('-', $words))"
|
||||
|
||||
git checkout -b $branchName | Out-Null
|
||||
|
||||
$featureDir = Join-Path $specsDir $branchName
|
||||
New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
|
||||
|
||||
$template = Join-Path $repoRoot 'templates/spec-template.md'
|
||||
$specFile = Join-Path $featureDir 'spec.md'
|
||||
if (Test-Path $template) { Copy-Item $template $specFile -Force } else { New-Item -ItemType File -Path $specFile | Out-Null }
|
||||
|
||||
if ($Json) {
|
||||
$obj = [PSCustomObject]@{ BRANCH_NAME = $branchName; SPEC_FILE = $specFile; FEATURE_NUM = $featureNum }
|
||||
$obj | ConvertTo-Json -Compress
|
||||
} else {
|
||||
Write-Output "BRANCH_NAME: $branchName"
|
||||
Write-Output "SPEC_FILE: $specFile"
|
||||
Write-Output "FEATURE_NUM: $featureNum"
|
||||
}
|
||||
15
scripts/powershell/get-feature-paths.ps1
Normal file
15
scripts/powershell/get-feature-paths.ps1
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env pwsh
|
||||
param()
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
. "$PSScriptRoot/common.ps1"
|
||||
|
||||
$paths = Get-FeaturePathsEnv
|
||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
||||
|
||||
Write-Output "REPO_ROOT: $($paths.REPO_ROOT)"
|
||||
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
||||
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
|
||||
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
||||
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
||||
Write-Output "TASKS: $($paths.TASKS)"
|
||||
21
scripts/powershell/setup-plan.ps1
Normal file
21
scripts/powershell/setup-plan.ps1
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env pwsh
|
||||
[CmdletBinding()]
|
||||
param([switch]$Json)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. "$PSScriptRoot/common.ps1"
|
||||
|
||||
$paths = Get-FeaturePathsEnv
|
||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
||||
|
||||
New-Item -ItemType Directory -Path $paths.FEATURE_DIR -Force | Out-Null
|
||||
$template = Join-Path $paths.REPO_ROOT 'templates/plan-template.md'
|
||||
if (Test-Path $template) { Copy-Item $template $paths.IMPL_PLAN -Force }
|
||||
|
||||
if ($Json) {
|
||||
[PSCustomObject]@{ FEATURE_SPEC=$paths.FEATURE_SPEC; IMPL_PLAN=$paths.IMPL_PLAN; SPECS_DIR=$paths.FEATURE_DIR; BRANCH=$paths.CURRENT_BRANCH } | ConvertTo-Json -Compress
|
||||
} else {
|
||||
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
||||
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
||||
Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)"
|
||||
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
||||
}
|
||||
104
scripts/powershell/update-agent-context.ps1
Normal file
104
scripts/powershell/update-agent-context.ps1
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env pwsh
|
||||
[CmdletBinding()]
|
||||
param([string]$AgentType)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$repoRoot = git rev-parse --show-toplevel
|
||||
$currentBranch = git rev-parse --abbrev-ref HEAD
|
||||
$featureDir = Join-Path $repoRoot "specs/$currentBranch"
|
||||
$newPlan = Join-Path $featureDir 'plan.md'
|
||||
if (-not (Test-Path $newPlan)) { Write-Error "ERROR: No plan.md found at $newPlan"; exit 1 }
|
||||
|
||||
$claudeFile = Join-Path $repoRoot 'CLAUDE.md'
|
||||
$geminiFile = Join-Path $repoRoot 'GEMINI.md'
|
||||
$copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md'
|
||||
$cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc'
|
||||
$qwenFile = Join-Path $repoRoot 'QWEN.md'
|
||||
$agentsFile = Join-Path $repoRoot 'AGENTS.md'
|
||||
|
||||
Write-Output "=== Updating agent context files for feature $currentBranch ==="
|
||||
|
||||
function Get-PlanValue($pattern) {
|
||||
if (-not (Test-Path $newPlan)) { return '' }
|
||||
$line = Select-String -Path $newPlan -Pattern $pattern | Select-Object -First 1
|
||||
if ($line) { return ($line.Line -replace "^\*\*$pattern\*\*: ", '') }
|
||||
return ''
|
||||
}
|
||||
|
||||
$newLang = Get-PlanValue 'Language/Version'
|
||||
$newFramework = Get-PlanValue 'Primary Dependencies'
|
||||
$newTesting = Get-PlanValue 'Testing'
|
||||
$newDb = Get-PlanValue 'Storage'
|
||||
$newProjectType = Get-PlanValue 'Project Type'
|
||||
|
||||
function Initialize-AgentFile($targetFile, $agentName) {
|
||||
if (Test-Path $targetFile) { return }
|
||||
$template = Join-Path $repoRoot '.specify/templates/agent-file-template.md'
|
||||
if (-not (Test-Path $template)) { Write-Error "Template not found: $template"; return }
|
||||
$content = Get-Content $template -Raw
|
||||
$content = $content.Replace('[PROJECT NAME]', (Split-Path $repoRoot -Leaf))
|
||||
$content = $content.Replace('[DATE]', (Get-Date -Format 'yyyy-MM-dd'))
|
||||
$content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $newLang + $newFramework ($currentBranch)")
|
||||
if ($newProjectType -match 'web') { $structure = "backend/`nfrontend/`ntests/" } else { $structure = "src/`ntests/" }
|
||||
$content = $content.Replace('[ACTUAL STRUCTURE FROM PLANS]', $structure)
|
||||
if ($newLang -match 'Python') { $commands = 'cd src && pytest && ruff check .' }
|
||||
elseif ($newLang -match 'Rust') { $commands = 'cargo test && cargo clippy' }
|
||||
elseif ($newLang -match 'JavaScript|TypeScript') { $commands = 'npm test && npm run lint' }
|
||||
else { $commands = "# Add commands for $newLang" }
|
||||
$content = $content.Replace('[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]', $commands)
|
||||
$content = $content.Replace('[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]', "${newLang}: Follow standard conventions")
|
||||
$content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', "- ${currentBranch}: Added ${newLang} + ${newFramework}")
|
||||
$content | Set-Content $targetFile -Encoding UTF8
|
||||
}
|
||||
|
||||
function Update-AgentFile($targetFile, $agentName) {
|
||||
if (-not (Test-Path $targetFile)) { Initialize-AgentFile $targetFile $agentName; return }
|
||||
$content = Get-Content $targetFile -Raw
|
||||
if ($newLang -and ($content -notmatch [regex]::Escape($newLang))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newLang + $newFramework ($currentBranch)`n" }
|
||||
if ($newDb -and $newDb -ne 'N/A' -and ($content -notmatch [regex]::Escape($newDb))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($currentBranch)`n" }
|
||||
if ($content -match '## Recent Changes\n([\s\S]*?)(\n\n|$)') {
|
||||
$changesBlock = $matches[1].Trim().Split("`n")
|
||||
$changesBlock = ,"- ${currentBranch}: Added ${newLang} + ${newFramework}" + $changesBlock
|
||||
$changesBlock = $changesBlock | Where-Object { $_ } | Select-Object -First 3
|
||||
$joined = ($changesBlock -join "`n")
|
||||
$content = [regex]::Replace($content, '## Recent Changes\n([\s\S]*?)(\n\n|$)', "## Recent Changes`n$joined`n`n")
|
||||
}
|
||||
$content = [regex]::Replace($content, 'Last updated: \d{4}-\d{2}-\d{2}', "Last updated: $(Get-Date -Format 'yyyy-MM-dd')")
|
||||
$content | Set-Content $targetFile -Encoding UTF8
|
||||
Write-Output "✅ $agentName context file updated successfully"
|
||||
}
|
||||
|
||||
switch ($AgentType) {
|
||||
'claude' { Update-AgentFile $claudeFile 'Claude Code' }
|
||||
'gemini' { Update-AgentFile $geminiFile 'Gemini CLI' }
|
||||
'copilot' { Update-AgentFile $copilotFile 'GitHub Copilot' }
|
||||
'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' }
|
||||
'qwen' { Update-AgentFile $qwenFile 'Qwen Code' }
|
||||
'opencode' { Update-AgentFile $agentsFile 'opencode' }
|
||||
'' {
|
||||
foreach ($pair in @(
|
||||
@{file=$claudeFile; name='Claude Code'},
|
||||
@{file=$geminiFile; name='Gemini CLI'},
|
||||
@{file=$copilotFile; name='GitHub Copilot'},
|
||||
@{file=$cursorFile; name='Cursor IDE'},
|
||||
@{file=$qwenFile; name='Qwen Code'},
|
||||
@{file=$agentsFile; name='opencode'}
|
||||
)) {
|
||||
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
||||
}
|
||||
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile)) {
|
||||
Write-Output 'No agent context files found. Creating Claude Code context file by default.'
|
||||
Update-AgentFile $claudeFile 'Claude Code'
|
||||
}
|
||||
}
|
||||
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode or leave empty for all."; exit 1 }
|
||||
}
|
||||
|
||||
Write-Output ''
|
||||
Write-Output 'Summary of changes:'
|
||||
if ($newLang) { Write-Output "- Added language: $newLang" }
|
||||
if ($newFramework) { Write-Output "- Added framework: $newFramework" }
|
||||
if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" }
|
||||
|
||||
Write-Output ''
|
||||
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode]'
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Setup implementation plan structure for current branch
|
||||
# Returns paths needed for implementation plan generation
|
||||
# Usage: ./setup-plan.sh [--json]
|
||||
|
||||
set -e
|
||||
|
||||
JSON_MODE=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--json) JSON_MODE=true ;;
|
||||
--help|-h) echo "Usage: $0 [--json]"; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
# Get all paths
|
||||
eval $(get_feature_paths)
|
||||
|
||||
# Check if on feature branch
|
||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
||||
|
||||
# Create specs directory if it doesn't exist
|
||||
mkdir -p "$FEATURE_DIR"
|
||||
|
||||
# Copy plan template if it exists
|
||||
TEMPLATE="$REPO_ROOT/templates/plan-template.md"
|
||||
if [ -f "$TEMPLATE" ]; then
|
||||
cp "$TEMPLATE" "$IMPL_PLAN"
|
||||
fi
|
||||
|
||||
if $JSON_MODE; then
|
||||
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
|
||||
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH"
|
||||
else
|
||||
# Output all paths for LLM use
|
||||
echo "FEATURE_SPEC: $FEATURE_SPEC"
|
||||
echo "IMPL_PLAN: $IMPL_PLAN"
|
||||
echo "SPECS_DIR: $FEATURE_DIR"
|
||||
echo "BRANCH: $CURRENT_BRANCH"
|
||||
fi
|
||||
@@ -1,234 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Incrementally update agent context files based on new feature plan
|
||||
# Supports: CLAUDE.md, GEMINI.md, and .github/copilot-instructions.md
|
||||
# O(1) operation - only reads current context file and new plan.md
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
|
||||
NEW_PLAN="$FEATURE_DIR/plan.md"
|
||||
|
||||
# Determine which agent context files to update
|
||||
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
|
||||
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
|
||||
COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"
|
||||
|
||||
# Allow override via argument
|
||||
AGENT_TYPE="$1"
|
||||
|
||||
if [ ! -f "$NEW_PLAN" ]; then
|
||||
echo "ERROR: No plan.md found at $NEW_PLAN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||
|
||||
# Extract tech from new plan
|
||||
NEW_LANG=$(grep "^**Language/Version**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Language\/Version**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
|
||||
NEW_FRAMEWORK=$(grep "^**Primary Dependencies**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Primary Dependencies**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
|
||||
NEW_TESTING=$(grep "^**Testing**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Testing**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
|
||||
NEW_DB=$(grep "^**Storage**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Storage**: //' | grep -v "N/A" | grep -v "NEEDS CLARIFICATION" || echo "")
|
||||
NEW_PROJECT_TYPE=$(grep "^**Project Type**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Project Type**: //' || echo "")
|
||||
|
||||
# Function to update a single agent context file
|
||||
update_agent_file() {
|
||||
local target_file="$1"
|
||||
local agent_name="$2"
|
||||
|
||||
echo "Updating $agent_name context file: $target_file"
|
||||
|
||||
# Create temp file for new context
|
||||
local temp_file=$(mktemp)
|
||||
|
||||
# If file doesn't exist, create from template
|
||||
if [ ! -f "$target_file" ]; then
|
||||
echo "Creating new $agent_name context file..."
|
||||
|
||||
# Check if this is the SDD repo itself
|
||||
if [ -f "$REPO_ROOT/templates/agent-file-template.md" ]; then
|
||||
cp "$REPO_ROOT/templates/agent-file-template.md" "$temp_file"
|
||||
else
|
||||
echo "ERROR: Template not found at $REPO_ROOT/templates/agent-file-template.md"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Replace placeholders
|
||||
sed -i.bak "s/\[PROJECT NAME\]/$(basename $REPO_ROOT)/" "$temp_file"
|
||||
sed -i.bak "s/\[DATE\]/$(date +%Y-%m-%d)/" "$temp_file"
|
||||
sed -i.bak "s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/" "$temp_file"
|
||||
|
||||
# Add project structure based on type
|
||||
if [[ "$NEW_PROJECT_TYPE" == *"web"* ]]; then
|
||||
sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|backend/\nfrontend/\ntests/|" "$temp_file"
|
||||
else
|
||||
sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|src/\ntests/|" "$temp_file"
|
||||
fi
|
||||
|
||||
# Add minimal commands
|
||||
if [[ "$NEW_LANG" == *"Python"* ]]; then
|
||||
COMMANDS="cd src && pytest && ruff check ."
|
||||
elif [[ "$NEW_LANG" == *"Rust"* ]]; then
|
||||
COMMANDS="cargo test && cargo clippy"
|
||||
elif [[ "$NEW_LANG" == *"JavaScript"* ]] || [[ "$NEW_LANG" == *"TypeScript"* ]]; then
|
||||
COMMANDS="npm test && npm run lint"
|
||||
else
|
||||
COMMANDS="# Add commands for $NEW_LANG"
|
||||
fi
|
||||
sed -i.bak "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$COMMANDS|" "$temp_file"
|
||||
|
||||
# Add code style
|
||||
sed -i.bak "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$NEW_LANG: Follow standard conventions|" "$temp_file"
|
||||
|
||||
# Add recent changes
|
||||
sed -i.bak "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK|" "$temp_file"
|
||||
|
||||
rm "$temp_file.bak"
|
||||
else
|
||||
echo "Updating existing $agent_name context file..."
|
||||
|
||||
# Extract manual additions
|
||||
local manual_start=$(grep -n "<!-- MANUAL ADDITIONS START -->" "$target_file" | cut -d: -f1)
|
||||
local manual_end=$(grep -n "<!-- MANUAL ADDITIONS END -->" "$target_file" | cut -d: -f1)
|
||||
|
||||
if [ ! -z "$manual_start" ] && [ ! -z "$manual_end" ]; then
|
||||
sed -n "${manual_start},${manual_end}p" "$target_file" > /tmp/manual_additions.txt
|
||||
fi
|
||||
|
||||
# Parse existing file and create updated version
|
||||
python3 - << EOF
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Read existing file
|
||||
with open("$target_file", 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check if new tech already exists
|
||||
tech_section = re.search(r'## Active Technologies\n(.*?)\n\n', content, re.DOTALL)
|
||||
if tech_section:
|
||||
existing_tech = tech_section.group(1)
|
||||
|
||||
# Add new tech if not already present
|
||||
new_additions = []
|
||||
if "$NEW_LANG" and "$NEW_LANG" not in existing_tech:
|
||||
new_additions.append(f"- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)")
|
||||
if "$NEW_DB" and "$NEW_DB" not in existing_tech and "$NEW_DB" != "N/A":
|
||||
new_additions.append(f"- $NEW_DB ($CURRENT_BRANCH)")
|
||||
|
||||
if new_additions:
|
||||
updated_tech = existing_tech + "\n" + "\n".join(new_additions)
|
||||
content = content.replace(tech_section.group(0), f"## Active Technologies\n{updated_tech}\n\n")
|
||||
|
||||
# Update project structure if needed
|
||||
if "$NEW_PROJECT_TYPE" == "web" and "frontend/" not in content:
|
||||
struct_section = re.search(r'## Project Structure\n\`\`\`\n(.*?)\n\`\`\`', content, re.DOTALL)
|
||||
if struct_section:
|
||||
updated_struct = struct_section.group(1) + "\nfrontend/src/ # Web UI"
|
||||
content = re.sub(r'(## Project Structure\n\`\`\`\n).*?(\n\`\`\`)',
|
||||
f'\\1{updated_struct}\\2', content, flags=re.DOTALL)
|
||||
|
||||
# Add new commands if language is new
|
||||
if "$NEW_LANG" and f"# {NEW_LANG}" not in content:
|
||||
commands_section = re.search(r'## Commands\n\`\`\`bash\n(.*?)\n\`\`\`', content, re.DOTALL)
|
||||
if not commands_section:
|
||||
commands_section = re.search(r'## Commands\n(.*?)\n\n', content, re.DOTALL)
|
||||
|
||||
if commands_section:
|
||||
new_commands = commands_section.group(1)
|
||||
if "Python" in "$NEW_LANG":
|
||||
new_commands += "\ncd src && pytest && ruff check ."
|
||||
elif "Rust" in "$NEW_LANG":
|
||||
new_commands += "\ncargo test && cargo clippy"
|
||||
elif "JavaScript" in "$NEW_LANG" or "TypeScript" in "$NEW_LANG":
|
||||
new_commands += "\nnpm test && npm run lint"
|
||||
|
||||
if "```bash" in content:
|
||||
content = re.sub(r'(## Commands\n\`\`\`bash\n).*?(\n\`\`\`)',
|
||||
f'\\1{new_commands}\\2', content, flags=re.DOTALL)
|
||||
else:
|
||||
content = re.sub(r'(## Commands\n).*?(\n\n)',
|
||||
f'\\1{new_commands}\\2', content, flags=re.DOTALL)
|
||||
|
||||
# Update recent changes (keep only last 3)
|
||||
changes_section = re.search(r'## Recent Changes\n(.*?)(\n\n|$)', content, re.DOTALL)
|
||||
if changes_section:
|
||||
changes = changes_section.group(1).strip().split('\n')
|
||||
changes.insert(0, f"- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK")
|
||||
# Keep only last 3
|
||||
changes = changes[:3]
|
||||
content = re.sub(r'(## Recent Changes\n).*?(\n\n|$)',
|
||||
f'\\1{chr(10).join(changes)}\\2', content, flags=re.DOTALL)
|
||||
|
||||
# Update date
|
||||
content = re.sub(r'Last updated: \d{4}-\d{2}-\d{2}',
|
||||
f'Last updated: {datetime.now().strftime("%Y-%m-%d")}', content)
|
||||
|
||||
# Write to temp file
|
||||
with open("$temp_file", 'w') as f:
|
||||
f.write(content)
|
||||
EOF
|
||||
|
||||
# Restore manual additions if they exist
|
||||
if [ -f /tmp/manual_additions.txt ]; then
|
||||
# Remove old manual section from temp file
|
||||
sed -i.bak '/<!-- MANUAL ADDITIONS START -->/,/<!-- MANUAL ADDITIONS END -->/d' "$temp_file"
|
||||
# Append manual additions
|
||||
cat /tmp/manual_additions.txt >> "$temp_file"
|
||||
rm /tmp/manual_additions.txt "$temp_file.bak"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Move temp file to final location
|
||||
mv "$temp_file" "$target_file"
|
||||
echo "✅ $agent_name context file updated successfully"
|
||||
}
|
||||
|
||||
# Update files based on argument or detect existing files
|
||||
case "$AGENT_TYPE" in
|
||||
"claude")
|
||||
update_agent_file "$CLAUDE_FILE" "Claude Code"
|
||||
;;
|
||||
"gemini")
|
||||
update_agent_file "$GEMINI_FILE" "Gemini CLI"
|
||||
;;
|
||||
"copilot")
|
||||
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
|
||||
;;
|
||||
"")
|
||||
# Update all existing files
|
||||
[ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"
|
||||
[ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"
|
||||
[ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"
|
||||
|
||||
# If no files exist, create based on current directory or ask user
|
||||
if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ]; then
|
||||
echo "No agent context files found. Creating Claude Code context file by default."
|
||||
update_agent_file "$CLAUDE_FILE" "Claude Code"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unknown agent type '$AGENT_TYPE'. Use: claude, gemini, copilot, or leave empty for all."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
echo "Summary of changes:"
|
||||
if [ ! -z "$NEW_LANG" ]; then
|
||||
echo "- Added language: $NEW_LANG"
|
||||
fi
|
||||
if [ ! -z "$NEW_FRAMEWORK" ]; then
|
||||
echo "- Added framework: $NEW_FRAMEWORK"
|
||||
fi
|
||||
if [ ! -z "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ]; then
|
||||
echo "- Added database: $NEW_DB"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Usage: $0 [claude|gemini|copilot]"
|
||||
echo " - No argument: Update all existing agent context files"
|
||||
echo " - claude: Update only CLAUDE.md"
|
||||
echo " - gemini: Update only GEMINI.md"
|
||||
echo " - copilot: Update only .github/copilot-instructions.md"
|
||||
124
spec-driven.md
124
spec-driven.md
@@ -2,15 +2,15 @@
|
||||
|
||||
## The Power Inversion
|
||||
|
||||
For decades, code has been king. Specifications served code—they were the scaffolding we built and then discarded once the "real work" of coding began. We wrote PRDs to guide development, created design docs to inform implementation, drew diagrams to visualize architecture. But these were always subordinate to the code itself. Code was truth. Everything else was, at best, good intentions. Code was the source of truth, as it moved forward, and spec's rarely kept pace. As the asset (code) and the implementation are one, it's not easy to have a parallel implementation without trying to build from the code.
|
||||
For decades, code has been king. Specifications served code—they were the scaffolding we built and then discarded once the "real work" of coding began. We wrote PRDs to guide development, created design docs to inform implementation, drew diagrams to visualize architecture. But these were always subordinate to the code itself. Code was truth. Everything else was, at best, good intentions. Code was the source of truth, and as it moved forward, specs rarely kept pace. As the asset (code) and the implementation are one, it's not easy to have a parallel implementation without trying to build from the code.
|
||||
|
||||
Spec-Driven Development (SDD) inverts this power structure. Specifications don't serve code—code serves specifications. The (Product Requirements Document-Specification) PRD isn't a guide for implementation; it's the source that generates implementation. Technical plans aren't documents that inform coding; they're precise definitions that produce code. This isn't an incremental improvement to how we build software. It's a fundamental rethinking of what drives development.
|
||||
Spec-Driven Development (SDD) inverts this power structure. Specifications don't serve code—code serves specifications. The Product Requirements Document (PRD) isn't a guide for implementation; it's the source that generates implementation. Technical plans aren't documents that inform coding; they're precise definitions that produce code. This isn't an incremental improvement to how we build software. It's a fundamental rethinking of what drives development.
|
||||
|
||||
The gap between specification and implementation has plagued software development since its inception. We've tried to bridge it with better documentation, more detailed requirements, stricter processes. These approaches fail because they accept the gap as inevitable. They try to narrow it but never eliminate it. SDD eliminates the gap by making specifications or and their concrete implementation plans born from the specification executable. When specifications to implementation plans generate code, there is no gap—only transformation.
|
||||
The gap between specification and implementation has plagued software development since its inception. We've tried to bridge it with better documentation, more detailed requirements, stricter processes. These approaches fail because they accept the gap as inevitable. They try to narrow it but never eliminate it. SDD eliminates the gap by making specifications and their concrete implementation plans born from the specification executable. When specifications and implementation plans generate code, there is no gap—only transformation.
|
||||
|
||||
This transformation is now possible because AI can understand and implement complex specifications, and create detailed implementation plans. But raw AI generation without structure produces chaos. SDD provides that structure through specifications and subsequent implementation plans that are precise, complete, and unambiguous enough to generate working systems. The specification becomes the primary artifact. Code becomes its expression (as an implementation from the implementation plan) in a particular language and framework.
|
||||
|
||||
In this new world, maintaining software means evolving specifications. The intent of the development team is expressed in natural language ("**intent-driven development**"), design assets, core principles and other guidelines . The **lingua franca** of development moves to a higher-level, and code is the last-mile approach.
|
||||
In this new world, maintaining software means evolving specifications. The intent of the development team is expressed in natural language ("**intent-driven development**"), design assets, core principles and other guidelines. The **lingua franca** of development moves to a higher level, and code is the last-mile approach.
|
||||
|
||||
Debugging means fixing specifications and their implementation plans that generate incorrect code. Refactoring means restructuring for clarity. The entire development workflow reorganizes around specifications as the central source of truth, with implementation plans and code as the continuously regenerated output. Updating apps with new features or creating a new parallel implementation because we are creative beings, means revisiting the specification and creating new implementation plans. This process is therefore a 0 -> 1, (1', ..), 2, 3, N.
|
||||
|
||||
@@ -18,7 +18,7 @@ The development team focuses in on their creativity, experimentation, their crit
|
||||
|
||||
## The SDD Workflow in Practice
|
||||
|
||||
The workflow begins with an idea—often vague and incomplete. Through iterative dialogue with AI, this idea becomes a comprehensive PRD. The AI asks clarifying questions, identifies edge cases, and helps define precise acceptance criteria. What might take days of meetings and documentation in traditional development happens in hours of focused specification work. This transforms the traditional SDLC—requirements and design become continuous activities rather than discrete phases. This is supportive of a **team process**, that's team reviewed-specifications are expressed and versioned, created in branches, and merged.
|
||||
The workflow begins with an idea—often vague and incomplete. Through iterative dialogue with AI, this idea becomes a comprehensive PRD. The AI asks clarifying questions, identifies edge cases, and helps define precise acceptance criteria. What might take days of meetings and documentation in traditional development happens in hours of focused specification work. This transforms the traditional SDLC—requirements and design become continuous activities rather than discrete phases. This is supportive of a **team process**, where team-reviewed specifications are expressed and versioned, created in branches, and merged.
|
||||
|
||||
When a product manager updates acceptance criteria, implementation plans automatically flag affected technical decisions. When an architect discovers a better pattern, the PRD updates to reflect new possibilities.
|
||||
|
||||
@@ -34,13 +34,13 @@ The feedback loop extends beyond initial development. Production metrics and inc
|
||||
|
||||
Three trends make SDD not just possible but necessary:
|
||||
|
||||
First, AI capabilities have reached a threshold where natural language specifications can reliably generate working code. This isn't about replacing developers—it's about amplifying their effectiveness by automating the mechanical translation from specification to implementation. It can amplify exploration and creativity, it can support "start-over" easily, it supports addition subtraction and critical thinking.
|
||||
First, AI capabilities have reached a threshold where natural language specifications can reliably generate working code. This isn't about replacing developers—it's about amplifying their effectiveness by automating the mechanical translation from specification to implementation. It can amplify exploration and creativity, support "start-over" easily, and support addition, subtraction, and critical thinking.
|
||||
|
||||
Second, software complexity continues to grow exponentially. Modern systems integrate dozens of services, frameworks, and dependencies. Keeping all these pieces aligned with original intent through manual processes becomes increasingly difficult. SDD provides systematic alignment through specification-driven generation. Frameworks may evolve to provide AI-first support, not human-first support, or architect around reusable components.
|
||||
|
||||
Third, the pace of change accelerates. Requirements change far more rapidly today than ever before. Pivoting is no longer exceptional—it's expected. Modern product development demands rapid iteration based on user feedback, market conditions, and competitive pressures. Traditional development treats these changes as disruptions. Each pivot requires manually propagating changes through documentation, design, and code. The result is either slow, careful updates that limit velocity, or fast, reckless changes that accumulate technical debt.
|
||||
|
||||
SDD can support what-if/simulation experiments, "If we need to re-implement or change the application to promote a business need to sell more T-shirts, how would we implement and experiment for that?".
|
||||
SDD can support what-if/simulation experiments: "If we need to re-implement or change the application to promote a business need to sell more T-shirts, how would we implement and experiment for that?"
|
||||
|
||||
SDD transforms requirement changes from obstacles into normal workflow. When specifications drive implementation, pivots become systematic regenerations rather than manual rewrites. Change a core requirement in the PRD, and affected implementation plans update automatically. Modify a user story, and corresponding API endpoints regenerate. This isn't just about initial development—it's about maintaining engineering velocity through inevitable changes.
|
||||
|
||||
@@ -70,11 +70,11 @@ Today, practicing SDD requires assembling existing tools and maintaining discipl
|
||||
|
||||
The key is treating specifications as the source of truth, with code as the generated output that serves the specification rather than the other way around.
|
||||
|
||||
## Streamlining SDD with Claude Commands
|
||||
## Streamlining SDD with Commands
|
||||
|
||||
The SDD methodology is significantly enhanced through two powerful Claude commands that automate the specification and planning workflow:
|
||||
The SDD methodology is significantly enhanced through three powerful commands that automate the specification → planning → tasking workflow:
|
||||
|
||||
### The `new_feature` Command
|
||||
### The `/specify` Command
|
||||
|
||||
This command transforms a simple feature description (the user-prompt) into a complete, structured specification with automatic repository management:
|
||||
|
||||
@@ -83,7 +83,7 @@ This command transforms a simple feature description (the user-prompt) into a co
|
||||
3. **Template-Based Generation**: Copies and customizes the feature specification template with your requirements
|
||||
4. **Directory Structure**: Creates the proper `specs/[branch-name]/` structure for all related documents
|
||||
|
||||
### The `generate_plan` Command
|
||||
### The `/plan` Command
|
||||
|
||||
Once a feature specification exists, this command creates a comprehensive implementation plan:
|
||||
|
||||
@@ -91,14 +91,24 @@ Once a feature specification exists, this command creates a comprehensive implem
|
||||
2. **Constitutional Compliance**: Ensures alignment with project constitution and architectural principles
|
||||
3. **Technical Translation**: Converts business requirements into technical architecture and implementation details
|
||||
4. **Detailed Documentation**: Generates supporting documents for data models, API contracts, and test scenarios
|
||||
5. **Manual Testing Plans**: Creates step-by-step validation procedures for each user story
|
||||
5. **Quickstart Validation**: Produces a quickstart guide capturing key validation scenarios
|
||||
|
||||
### The `/tasks` Command
|
||||
|
||||
After a plan is created, this command analyzes the plan and related design documents to generate an executable task list:
|
||||
|
||||
1. **Inputs**: Reads `plan.md` (required) and, if present, `data-model.md`, `contracts/`, and `research.md`
|
||||
2. **Task Derivation**: Converts contracts, entities, and scenarios into specific tasks
|
||||
3. **Parallelization**: Marks independent tasks `[P]` and outlines safe parallel groups
|
||||
4. **Output**: Writes `tasks.md` in the feature directory, ready for execution by a Task agent
|
||||
|
||||
### Example: Building a Chat Feature
|
||||
|
||||
Here's how these commands transform the traditional development workflow:
|
||||
|
||||
**Traditional Approach:**
|
||||
```
|
||||
|
||||
```text
|
||||
1. Write a PRD in a document (2-3 hours)
|
||||
2. Create design documents (2-3 hours)
|
||||
3. Set up project structure manually (30 minutes)
|
||||
@@ -108,30 +118,33 @@ Total: ~12 hours of documentation work
|
||||
```
|
||||
|
||||
**SDD with Commands Approach:**
|
||||
|
||||
```bash
|
||||
# Step 1: Create the feature specification (5 minutes)
|
||||
/new_feature Real-time chat system with message history and user presence
|
||||
/specify Real-time chat system with message history and user presence
|
||||
|
||||
# This automatically:
|
||||
# - Creates branch "003-chat-system"
|
||||
# - Generates specs/003-chat-system/feature-spec.md
|
||||
# - Generates specs/003-chat-system/spec.md
|
||||
# - Populates it with structured requirements
|
||||
|
||||
# Step 2: Generate implementation plan (10 minutes)
|
||||
/generate_plan WebSocket for real-time messaging, PostgreSQL for history, Redis for presence
|
||||
# Step 2: Generate implementation plan (5 minutes)
|
||||
/plan WebSocket for real-time messaging, PostgreSQL for history, Redis for presence
|
||||
|
||||
# Step 3: Generate executable tasks (5 minutes)
|
||||
/tasks
|
||||
|
||||
# This automatically creates:
|
||||
# - specs/003-chat-system/implementation-plan.md
|
||||
# - specs/003-chat-system/implementation-details/
|
||||
# - 00-research.md (WebSocket library comparisons)
|
||||
# - 02-data-model.md (Message and User schemas)
|
||||
# - 03-api-contracts.md (WebSocket events, REST endpoints)
|
||||
# - 06-contract-tests.md (Message flow scenarios)
|
||||
# - 08-inter-library-tests.md (Database-WebSocket integration)
|
||||
# - specs/003-chat-system/manual-testing.md
|
||||
# - specs/003-chat-system/plan.md
|
||||
# - specs/003-chat-system/research.md (WebSocket library comparisons)
|
||||
# - specs/003-chat-system/data-model.md (Message and User schemas)
|
||||
# - specs/003-chat-system/contracts/ (WebSocket events, REST endpoints)
|
||||
# - specs/003-chat-system/quickstart.md (Key validation scenarios)
|
||||
# - specs/003-chat-system/tasks.md (Task list derived from the plan)
|
||||
```
|
||||
|
||||
In 15 minutes, you have:
|
||||
|
||||
- A complete feature specification with user stories and acceptance criteria
|
||||
- A detailed implementation plan with technology choices and rationale
|
||||
- API contracts and data models ready for code generation
|
||||
@@ -156,7 +169,8 @@ The true power of these commands lies not just in automation, but in how the tem
|
||||
#### 1. **Preventing Premature Implementation Details**
|
||||
|
||||
The feature specification template explicitly instructs:
|
||||
```
|
||||
|
||||
```text
|
||||
- ✅ Focus on WHAT users need and WHY
|
||||
- ❌ Avoid HOW to implement (no tech stack, APIs, code structure)
|
||||
```
|
||||
@@ -166,9 +180,10 @@ This constraint forces the LLM to maintain proper abstraction levels. When an LL
|
||||
#### 2. **Forcing Explicit Uncertainty Markers**
|
||||
|
||||
Both templates mandate the use of `[NEEDS CLARIFICATION]` markers:
|
||||
```
|
||||
|
||||
```text
|
||||
When creating this spec from a user prompt:
|
||||
1. **Mark all ambiguities**: Use [NEEDS CLARIFICATION: specific question]
|
||||
1. **Mark all ambiguities**: Use [NEEDS CLARIFICATION: specific question]
|
||||
2. **Don't guess**: If the prompt doesn't specify something, mark it
|
||||
```
|
||||
|
||||
@@ -177,10 +192,11 @@ This prevents the common LLM behavior of making plausible but potentially incorr
|
||||
#### 3. **Structured Thinking Through Checklists**
|
||||
|
||||
The templates include comprehensive checklists that act as "unit tests" for the specification:
|
||||
```
|
||||
|
||||
```markdown
|
||||
### Requirement Completeness
|
||||
- [ ] No [NEEDS CLARIFICATION] markers remain
|
||||
- [ ] Requirements are testable and unambiguous
|
||||
- [ ] Requirements are testable and unambiguous
|
||||
- [ ] Success criteria are measurable
|
||||
```
|
||||
|
||||
@@ -189,7 +205,8 @@ These checklists force the LLM to self-review its output systematically, catchin
|
||||
#### 4. **Constitutional Compliance Through Gates**
|
||||
|
||||
The implementation plan template enforces architectural principles through phase gates:
|
||||
```
|
||||
|
||||
```markdown
|
||||
### Phase -1: Pre-Implementation Gates
|
||||
#### Simplicity Gate (Article VII)
|
||||
- [ ] Using ≤3 projects?
|
||||
@@ -204,9 +221,10 @@ These gates prevent over-engineering by making the LLM explicitly justify any co
|
||||
#### 5. **Hierarchical Detail Management**
|
||||
|
||||
The templates enforce proper information architecture:
|
||||
```
|
||||
**IMPORTANT**: This implementation plan should remain high-level and readable.
|
||||
Any code samples, detailed algorithms, or extensive technical specifications
|
||||
|
||||
```text
|
||||
**IMPORTANT**: This implementation plan should remain high-level and readable.
|
||||
Any code samples, detailed algorithms, or extensive technical specifications
|
||||
must be placed in the appropriate `implementation-details/` file
|
||||
```
|
||||
|
||||
@@ -215,7 +233,8 @@ This prevents the common problem of specifications becoming unreadable code dump
|
||||
#### 6. **Test-First Thinking**
|
||||
|
||||
The implementation template enforces test-first development:
|
||||
```
|
||||
|
||||
```text
|
||||
### File Creation Order
|
||||
1. Create `contracts/` with API specifications
|
||||
2. Create test files in order: contract → integration → e2e → unit
|
||||
@@ -227,7 +246,8 @@ This ordering constraint ensures the LLM thinks about testability and contracts
|
||||
#### 7. **Preventing Speculative Features**
|
||||
|
||||
Templates explicitly discourage speculation:
|
||||
```
|
||||
|
||||
```text
|
||||
- [ ] No speculative or "might need" features
|
||||
- [ ] All phases have clear prerequisites and deliverables
|
||||
```
|
||||
@@ -237,6 +257,7 @@ This stops the LLM from adding "nice to have" features that complicate implement
|
||||
### The Compound Effect
|
||||
|
||||
These constraints work together to produce specifications that are:
|
||||
|
||||
- **Complete**: Checklists ensure nothing is forgotten
|
||||
- **Unambiguous**: Forced clarification markers highlight uncertainties
|
||||
- **Testable**: Test-first thinking baked into the process
|
||||
@@ -247,25 +268,29 @@ The templates transform the LLM from a creative writer into a disciplined specif
|
||||
|
||||
## The Constitutional Foundation: Enforcing Architectural Discipline
|
||||
|
||||
At the heart of SDD lies a constitution—a set of immutable principles that govern how specifications become code. The constitution (`base/memory/constitution.md`) acts as the architectural DNA of the system, ensuring that every generated implementation maintains consistency, simplicity, and quality.
|
||||
At the heart of SDD lies a constitution—a set of immutable principles that govern how specifications become code. The constitution (`memory/constitution.md`) acts as the architectural DNA of the system, ensuring that every generated implementation maintains consistency, simplicity, and quality.
|
||||
|
||||
### The Nine Articles of Development
|
||||
|
||||
The constitution defines nine articles that shape every aspect of the development process:
|
||||
|
||||
#### Article I: Library-First Principle
|
||||
|
||||
Every feature must begin as a standalone library—no exceptions. This forces modular design from the start:
|
||||
```
|
||||
Every feature in Specify MUST begin its existence as a standalone library.
|
||||
No feature shall be implemented directly within application code without
|
||||
|
||||
```text
|
||||
Every feature in Specify MUST begin its existence as a standalone library.
|
||||
No feature shall be implemented directly within application code without
|
||||
first being abstracted into a reusable library component.
|
||||
```
|
||||
|
||||
This principle ensures that specifications generate modular, reusable code rather than monolithic applications. When the LLM generates an implementation plan, it must structure features as libraries with clear boundaries and minimal dependencies.
|
||||
|
||||
#### Article II: CLI Interface Mandate
|
||||
|
||||
Every library must expose its functionality through a command-line interface:
|
||||
```
|
||||
|
||||
```text
|
||||
All CLI interfaces MUST:
|
||||
- Accept text as input (via stdin, arguments, or files)
|
||||
- Produce text as output (via stdout)
|
||||
@@ -275,8 +300,10 @@ All CLI interfaces MUST:
|
||||
This enforces observability and testability. The LLM cannot hide functionality inside opaque classes—everything must be accessible and verifiable through text-based interfaces.
|
||||
|
||||
#### Article III: Test-First Imperative
|
||||
|
||||
The most transformative article—no code before tests:
|
||||
```
|
||||
|
||||
```text
|
||||
This is NON-NEGOTIABLE: All implementation MUST follow strict Test-Driven Development.
|
||||
No implementation code shall be written before:
|
||||
1. Unit tests are written
|
||||
@@ -287,8 +314,10 @@ No implementation code shall be written before:
|
||||
This completely inverts traditional AI code generation. Instead of generating code and hoping it works, the LLM must first generate comprehensive tests that define behavior, get them approved, and only then generate implementation.
|
||||
|
||||
#### Articles VII & VIII: Simplicity and Anti-Abstraction
|
||||
|
||||
These paired articles combat over-engineering:
|
||||
```
|
||||
|
||||
```text
|
||||
Section 7.3: Minimal Project Structure
|
||||
- Maximum 3 projects for initial implementation
|
||||
- Additional projects require documented justification
|
||||
@@ -300,8 +329,10 @@ Section 8.1: Framework Trust
|
||||
When an LLM might naturally create elaborate abstractions, these articles force it to justify every layer of complexity. The implementation plan template's "Phase -1 Gates" directly enforce these principles.
|
||||
|
||||
#### Article IX: Integration-First Testing
|
||||
|
||||
Prioritizes real-world testing over isolated unit tests:
|
||||
```
|
||||
|
||||
```text
|
||||
Tests MUST use realistic environments:
|
||||
- Prefer real databases over mocks
|
||||
- Use actual service instances over stubs
|
||||
@@ -343,7 +374,8 @@ The constitution's power lies in its immutability. While implementation details
|
||||
### Constitutional Evolution
|
||||
|
||||
While principles are immutable, their application can evolve:
|
||||
```
|
||||
|
||||
```text
|
||||
Section 4.2: Amendment Process
|
||||
Modifications to this constitution require:
|
||||
- Explicit documentation of the rationale for change
|
||||
@@ -368,4 +400,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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -30,7 +30,7 @@ import tempfile
|
||||
import shutil
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import typer
|
||||
import httpx
|
||||
@@ -46,13 +46,26 @@ from typer.core import TyperGroup
|
||||
|
||||
# For cross-platform keyboard input
|
||||
import readchar
|
||||
import ssl
|
||||
import truststore
|
||||
|
||||
ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
client = httpx.Client(verify=ssl_context)
|
||||
|
||||
# Constants
|
||||
AI_CHOICES = {
|
||||
"copilot": "GitHub Copilot",
|
||||
"claude": "Claude Code",
|
||||
"gemini": "Gemini CLI"
|
||||
"gemini": "Gemini CLI",
|
||||
"cursor": "Cursor",
|
||||
"qwen": "Qwen Code",
|
||||
"opencode": "opencode"
|
||||
}
|
||||
# Add script type choices
|
||||
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
||||
|
||||
# Claude CLI local installation path after migrate-installer
|
||||
CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
|
||||
|
||||
# ASCII Art Banner
|
||||
BANNER = """
|
||||
@@ -330,8 +343,28 @@ def run_command(cmd: list[str], check_return: bool = True, capture: bool = False
|
||||
return None
|
||||
|
||||
|
||||
def check_tool_for_tracker(tool: str, install_hint: str, tracker: StepTracker) -> bool:
|
||||
"""Check if a tool is installed and update tracker."""
|
||||
if shutil.which(tool):
|
||||
tracker.complete(tool, "available")
|
||||
return True
|
||||
else:
|
||||
tracker.error(tool, f"not found - {install_hint}")
|
||||
return False
|
||||
|
||||
|
||||
def check_tool(tool: str, install_hint: str) -> bool:
|
||||
"""Check if a tool is installed."""
|
||||
|
||||
# Special handling for Claude CLI after `claude migrate-installer`
|
||||
# See: https://github.com/github/spec-kit/issues/123
|
||||
# The migrate-installer command REMOVES the original executable from PATH
|
||||
# and creates an alias at ~/.claude/local/claude instead
|
||||
# This path should be prioritized over other claude executables in PATH
|
||||
if tool == "claude":
|
||||
if CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file():
|
||||
return True
|
||||
|
||||
if shutil.which(tool):
|
||||
return True
|
||||
else:
|
||||
@@ -385,39 +418,44 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
|
||||
os.chdir(original_cwd)
|
||||
|
||||
|
||||
def download_template_from_github(ai_assistant: str, download_dir: Path, *, verbose: bool = True, show_progress: bool = True):
|
||||
"""Download the latest template release from GitHub using HTTP requests.
|
||||
Returns (zip_path, metadata_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) -> Tuple[Path, dict]:
|
||||
repo_owner = "github"
|
||||
repo_name = "spec-kit"
|
||||
if client is None:
|
||||
client = httpx.Client(verify=ssl_context)
|
||||
|
||||
if verbose:
|
||||
console.print("[cyan]Fetching latest release information...[/cyan]")
|
||||
api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest"
|
||||
|
||||
try:
|
||||
response = httpx.get(api_url, timeout=30, follow_redirects=True)
|
||||
response.raise_for_status()
|
||||
release_data = response.json()
|
||||
except httpx.RequestError as e:
|
||||
if verbose:
|
||||
console.print(f"[red]Error fetching release information:[/red] {e}")
|
||||
response = client.get(api_url, timeout=30, follow_redirects=True)
|
||||
status = response.status_code
|
||||
if status != 200:
|
||||
msg = f"GitHub API returned {status} for {api_url}"
|
||||
if debug:
|
||||
msg += f"\nResponse headers: {response.headers}\nBody (truncated 500): {response.text[:500]}"
|
||||
raise RuntimeError(msg)
|
||||
try:
|
||||
release_data = response.json()
|
||||
except ValueError as je:
|
||||
raise RuntimeError(f"Failed to parse release JSON: {je}\nRaw (truncated 400): {response.text[:400]}")
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error fetching release information[/red]")
|
||||
console.print(Panel(str(e), title="Fetch Error", border_style="red"))
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Find the template asset for the specified AI assistant
|
||||
pattern = f"spec-kit-template-{ai_assistant}"
|
||||
pattern = f"spec-kit-template-{ai_assistant}-{script_type}"
|
||||
matching_assets = [
|
||||
asset for asset in release_data.get("assets", [])
|
||||
if pattern in asset["name"] and asset["name"].endswith(".zip")
|
||||
]
|
||||
|
||||
if not matching_assets:
|
||||
if verbose:
|
||||
console.print(f"[red]Error:[/red] No template found for AI assistant '{ai_assistant}'")
|
||||
console.print(f"[yellow]Available assets:[/yellow]")
|
||||
for asset in release_data.get("assets", []):
|
||||
console.print(f" - {asset['name']}")
|
||||
console.print(f"[red]No matching release asset found[/red] for pattern: [bold]{pattern}[/bold]")
|
||||
asset_names = [a.get('name','?') for a in release_data.get('assets', [])]
|
||||
console.print(Panel("\n".join(asset_names) or "(no assets)", title="Available Assets", border_style="yellow"))
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Use the first matching asset
|
||||
@@ -437,18 +475,17 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
||||
console.print(f"[cyan]Downloading template...[/cyan]")
|
||||
|
||||
try:
|
||||
with httpx.stream("GET", download_url, timeout=30, follow_redirects=True) as response:
|
||||
response.raise_for_status()
|
||||
with client.stream("GET", download_url, timeout=60, follow_redirects=True) as response:
|
||||
if response.status_code != 200:
|
||||
body_sample = response.text[:400]
|
||||
raise RuntimeError(f"Download failed with {response.status_code}\nHeaders: {response.headers}\nBody (truncated): {body_sample}")
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
|
||||
with open(zip_path, 'wb') as f:
|
||||
if total_size == 0:
|
||||
# No content-length header, download without progress
|
||||
for chunk in response.iter_bytes(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
else:
|
||||
if show_progress:
|
||||
# Show progress bar
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
@@ -462,15 +499,14 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
||||
downloaded += len(chunk)
|
||||
progress.update(task, completed=downloaded)
|
||||
else:
|
||||
# Silent download loop
|
||||
for chunk in response.iter_bytes(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
except httpx.RequestError as e:
|
||||
if verbose:
|
||||
console.print(f"[red]Error downloading template:[/red] {e}")
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error downloading template[/red]")
|
||||
detail = str(e)
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
console.print(Panel(detail, title="Download Error", border_style="red"))
|
||||
raise typer.Exit(1)
|
||||
if verbose:
|
||||
console.print(f"Downloaded: {filename}")
|
||||
@@ -483,7 +519,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
||||
return zip_path, metadata
|
||||
|
||||
|
||||
def download_and_extract_template(project_path: Path, ai_assistant: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None) -> Path:
|
||||
def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False) -> Path:
|
||||
"""Download the latest release and extract it to create a new project.
|
||||
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
|
||||
"""
|
||||
@@ -496,13 +532,16 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
|
||||
zip_path, meta = download_template_from_github(
|
||||
ai_assistant,
|
||||
current_dir,
|
||||
script_type=script_type,
|
||||
verbose=verbose and tracker is None,
|
||||
show_progress=(tracker is None)
|
||||
show_progress=(tracker is None),
|
||||
client=client,
|
||||
debug=debug
|
||||
)
|
||||
if tracker:
|
||||
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
|
||||
tracker.add("download", "Download template")
|
||||
tracker.complete("download", meta['filename']) # already downloaded inside helper
|
||||
tracker.complete("download", meta['filename'])
|
||||
except Exception as e:
|
||||
if tracker:
|
||||
tracker.error("fetch", str(e))
|
||||
@@ -614,6 +653,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
|
||||
else:
|
||||
if verbose:
|
||||
console.print(f"[red]Error extracting template:[/red] {e}")
|
||||
if debug:
|
||||
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():
|
||||
shutil.rmtree(project_path)
|
||||
@@ -635,20 +676,68 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
|
||||
return project_path
|
||||
|
||||
|
||||
def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None:
|
||||
"""Ensure POSIX .sh scripts under .specify/scripts (recursively) have execute bits (no-op on Windows)."""
|
||||
if os.name == "nt":
|
||||
return # Windows: skip silently
|
||||
scripts_root = project_path / ".specify" / "scripts"
|
||||
if not scripts_root.is_dir():
|
||||
return
|
||||
failures: list[str] = []
|
||||
updated = 0
|
||||
for script in scripts_root.rglob("*.sh"):
|
||||
try:
|
||||
if script.is_symlink() or not script.is_file():
|
||||
continue
|
||||
try:
|
||||
with script.open("rb") as f:
|
||||
if f.read(2) != b"#!":
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
st = script.stat(); mode = st.st_mode
|
||||
if mode & 0o111:
|
||||
continue
|
||||
new_mode = mode
|
||||
if mode & 0o400: new_mode |= 0o100
|
||||
if mode & 0o040: new_mode |= 0o010
|
||||
if mode & 0o004: new_mode |= 0o001
|
||||
if not (new_mode & 0o100):
|
||||
new_mode |= 0o100
|
||||
os.chmod(script, new_mode)
|
||||
updated += 1
|
||||
except Exception as e:
|
||||
failures.append(f"{script.relative_to(scripts_root)}: {e}")
|
||||
if tracker:
|
||||
detail = f"{updated} updated" + (f", {len(failures)} failed" if failures else "")
|
||||
tracker.add("chmod", "Set script permissions recursively")
|
||||
(tracker.error if failures else tracker.complete)("chmod", detail)
|
||||
else:
|
||||
if updated:
|
||||
console.print(f"[cyan]Updated execute permissions on {updated} script(s) recursively[/cyan]")
|
||||
if failures:
|
||||
console.print("[yellow]Some scripts could not be updated:[/yellow]")
|
||||
for f in failures:
|
||||
console.print(f" - {f}")
|
||||
|
||||
|
||||
@app.command()
|
||||
def init(
|
||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
||||
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, or copilot"),
|
||||
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen or opencode"),
|
||||
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
|
||||
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
|
||||
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
|
||||
here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
|
||||
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
|
||||
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
|
||||
):
|
||||
"""
|
||||
Initialize a new Specify project from the latest template.
|
||||
|
||||
This command will:
|
||||
1. Check that required tools are installed (git is optional)
|
||||
2. Let you choose your AI assistant (Claude Code, Gemini CLI, or GitHub Copilot)
|
||||
2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code or opencode)
|
||||
3. Download the appropriate template from GitHub
|
||||
4. Extract the template to a new project directory or current directory
|
||||
5. Initialize a fresh git repository (if not --no-git and no existing repo)
|
||||
@@ -659,6 +748,9 @@ def init(
|
||||
specify init my-project --ai claude
|
||||
specify init my-project --ai gemini
|
||||
specify init my-project --ai copilot --no-git
|
||||
specify init my-project --ai cursor
|
||||
specify init my-project --ai qwen
|
||||
specify init my-project --ai opencode
|
||||
specify init --ignore-agent-tools my-project
|
||||
specify init --here --ai claude
|
||||
specify init --here
|
||||
@@ -706,10 +798,11 @@ def init(
|
||||
))
|
||||
|
||||
# Check git only if we might need it (not --no-git)
|
||||
git_available = True
|
||||
# Only set to True if the user wants it and the tool is available
|
||||
should_init_git = False
|
||||
if not no_git:
|
||||
git_available = check_tool("git", "https://git-scm.com/downloads")
|
||||
if not git_available:
|
||||
should_init_git = check_tool("git", "https://git-scm.com/downloads")
|
||||
if not should_init_git:
|
||||
console.print("[yellow]Git not found - will skip repository initialization[/yellow]")
|
||||
|
||||
# AI assistant selection
|
||||
@@ -737,13 +830,39 @@ def init(
|
||||
if not check_tool("gemini", "Install from: https://github.com/google-gemini/gemini-cli"):
|
||||
console.print("[red]Error:[/red] Gemini CLI is required for Gemini projects")
|
||||
agent_tool_missing = True
|
||||
# GitHub Copilot check is not needed as it's typically available in supported IDEs
|
||||
|
||||
elif selected_ai == "qwen":
|
||||
if not check_tool("qwen", "Install from: https://github.com/QwenLM/qwen-code"):
|
||||
console.print("[red]Error:[/red] Qwen CLI is required for Qwen Code projects")
|
||||
agent_tool_missing = True
|
||||
elif selected_ai == "opencode":
|
||||
if not check_tool("opencode", "Install from: https://opencode.ai"):
|
||||
console.print("[red]Error:[/red] opencode CLI is required for opencode projects")
|
||||
agent_tool_missing = True
|
||||
# GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs
|
||||
|
||||
if agent_tool_missing:
|
||||
console.print("\n[red]Required AI tool is missing![/red]")
|
||||
console.print("[yellow]Tip:[/yellow] Use --ignore-agent-tools to skip this check")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Determine script type (explicit, interactive, or OS default)
|
||||
if script_type:
|
||||
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())}")
|
||||
raise typer.Exit(1)
|
||||
selected_script = script_type
|
||||
else:
|
||||
# Auto-detect default
|
||||
default_script = "ps" if os.name == "nt" else "sh"
|
||||
# Provide interactive selection similar to AI if stdin is a TTY
|
||||
if sys.stdin.isatty():
|
||||
selected_script = select_with_arrows(SCRIPT_TYPE_CHOICES, "Choose script type (or press Enter)", default_script)
|
||||
else:
|
||||
selected_script = default_script
|
||||
|
||||
console.print(f"[cyan]Selected AI assistant:[/cyan] {selected_ai}")
|
||||
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")
|
||||
@@ -754,12 +873,15 @@ def init(
|
||||
tracker.complete("precheck", "ok")
|
||||
tracker.add("ai-select", "Select AI assistant")
|
||||
tracker.complete("ai-select", f"{selected_ai}")
|
||||
tracker.add("script-select", "Select script type")
|
||||
tracker.complete("script-select", selected_script)
|
||||
for key, label in [
|
||||
("fetch", "Fetch latest release"),
|
||||
("download", "Download template"),
|
||||
("extract", "Extract template"),
|
||||
("zip-list", "Archive contents"),
|
||||
("extracted-summary", "Extraction summary"),
|
||||
("chmod", "Ensure scripts executable"),
|
||||
("cleanup", "Cleanup"),
|
||||
("git", "Initialize git repository"),
|
||||
("final", "Finalize")
|
||||
@@ -770,14 +892,22 @@ def init(
|
||||
with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live:
|
||||
tracker.attach_refresh(lambda: live.update(tracker.render()))
|
||||
try:
|
||||
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker)
|
||||
# Create a httpx client with verify based on skip_tls
|
||||
verify = not skip_tls
|
||||
local_ssl_context = ssl_context if verify else False
|
||||
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)
|
||||
|
||||
# Ensure scripts are executable (POSIX)
|
||||
ensure_executable_scripts(project_path, tracker=tracker)
|
||||
|
||||
# Git step
|
||||
if not no_git:
|
||||
tracker.start("git")
|
||||
if is_git_repo(project_path):
|
||||
tracker.complete("git", "existing repo detected")
|
||||
elif git_available:
|
||||
elif should_init_git:
|
||||
if init_git_repo(project_path, quiet=True):
|
||||
tracker.complete("git", "initialized")
|
||||
else:
|
||||
@@ -790,6 +920,16 @@ def init(
|
||||
tracker.complete("final", "project ready")
|
||||
except Exception as e:
|
||||
tracker.error("final", str(e))
|
||||
console.print(Panel(f"Initialization failed: {e}", title="Failure", border_style="red"))
|
||||
if debug:
|
||||
_env_pairs = [
|
||||
("Python", sys.version.split()[0]),
|
||||
("Platform", sys.platform),
|
||||
("CWD", str(Path.cwd())),
|
||||
]
|
||||
_label_width = max(len(k) for k, _ in _env_pairs)
|
||||
env_lines = [f"{k.ljust(_label_width)} → [bright_black]{v}[/bright_black]" for k, v in _env_pairs]
|
||||
console.print(Panel("\n".join(env_lines), title="Debug Environment", border_style="magenta"))
|
||||
if not here and project_path.exists():
|
||||
shutil.rmtree(project_path)
|
||||
raise typer.Exit(1)
|
||||
@@ -820,10 +960,23 @@ def init(
|
||||
steps_lines.append(f"{step_num}. Use / commands with Gemini CLI")
|
||||
steps_lines.append(" - Run gemini /specify to create specifications")
|
||||
steps_lines.append(" - Run gemini /plan to create implementation plans")
|
||||
steps_lines.append(" - Run gemini /tasks to generate tasks")
|
||||
steps_lines.append(" - See GEMINI.md for all available commands")
|
||||
elif selected_ai == "copilot":
|
||||
steps_lines.append(f"{step_num}. Open in Visual Studio Code and use [bold cyan]/specify[/], [bold cyan]/plan[/], [bold cyan]/tasks[/] commands with GitHub Copilot")
|
||||
elif selected_ai == "qwen":
|
||||
steps_lines.append(f"{step_num}. Use / commands with Qwen CLI")
|
||||
steps_lines.append(" - Run qwen /specify to create specifications")
|
||||
steps_lines.append(" - Run qwen /plan to create implementation plans")
|
||||
steps_lines.append(" - Run qwen /tasks to generate tasks")
|
||||
steps_lines.append(" - See QWEN.md for all available commands")
|
||||
elif selected_ai == "opencode":
|
||||
steps_lines.append(f"{step_num}. Use / commands with opencode")
|
||||
steps_lines.append(" - Use /specify to create specifications")
|
||||
steps_lines.append(" - Use /plan to create implementation plans")
|
||||
steps_lines.append(" - Use /tasks to generate tasks")
|
||||
|
||||
# Removed script variant step (scripts are transparent to users)
|
||||
step_num += 1
|
||||
steps_lines.append(f"{step_num}. Update [bold magenta]CONSTITUTION.md[/bold magenta] with your project's non-negotiable principles")
|
||||
|
||||
@@ -838,29 +991,43 @@ def init(
|
||||
def check():
|
||||
"""Check that all required tools are installed."""
|
||||
show_banner()
|
||||
console.print("[bold]Checking Specify requirements...[/bold]\n")
|
||||
console.print("[bold]Checking for installed tools...[/bold]\n")
|
||||
|
||||
# Create tracker for checking tools
|
||||
tracker = StepTracker("Check Available Tools")
|
||||
|
||||
# Check if we have internet connectivity by trying to reach GitHub API
|
||||
console.print("[cyan]Checking internet connectivity...[/cyan]")
|
||||
try:
|
||||
response = httpx.get("https://api.github.com", timeout=5, follow_redirects=True)
|
||||
console.print("[green]✓[/green] Internet connection available")
|
||||
except httpx.RequestError:
|
||||
console.print("[red]✗[/red] No internet connection - required for downloading templates")
|
||||
console.print("[yellow]Please check your internet connection[/yellow]")
|
||||
# Add all tools we want to check
|
||||
tracker.add("git", "Git version control")
|
||||
tracker.add("claude", "Claude Code CLI")
|
||||
tracker.add("gemini", "Gemini CLI")
|
||||
tracker.add("qwen", "Qwen Code CLI")
|
||||
tracker.add("code", "VS Code (for GitHub Copilot)")
|
||||
tracker.add("cursor-agent", "Cursor IDE agent (optional)")
|
||||
tracker.add("opencode", "opencode")
|
||||
|
||||
console.print("\n[cyan]Optional tools:[/cyan]")
|
||||
git_ok = check_tool("git", "https://git-scm.com/downloads")
|
||||
# Check each tool
|
||||
git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker)
|
||||
claude_ok = check_tool_for_tracker("claude", "https://docs.anthropic.com/en/docs/claude-code/setup", tracker)
|
||||
gemini_ok = check_tool_for_tracker("gemini", "https://github.com/google-gemini/gemini-cli", tracker)
|
||||
qwen_ok = check_tool_for_tracker("qwen", "https://github.com/QwenLM/qwen-code", tracker)
|
||||
# Check for VS Code (code or code-insiders)
|
||||
code_ok = check_tool_for_tracker("code", "https://code.visualstudio.com/", tracker)
|
||||
if not code_ok:
|
||||
code_ok = check_tool_for_tracker("code-insiders", "https://code.visualstudio.com/insiders/", tracker)
|
||||
cursor_ok = check_tool_for_tracker("cursor-agent", "https://cursor.sh/", tracker)
|
||||
opencode_ok = check_tool_for_tracker("opencode", "https://opencode.ai/", tracker)
|
||||
|
||||
console.print("\n[cyan]Optional AI tools:[/cyan]")
|
||||
claude_ok = check_tool("claude", "Install from: https://docs.anthropic.com/en/docs/claude-code/setup")
|
||||
gemini_ok = check_tool("gemini", "Install from: https://github.com/google-gemini/gemini-cli")
|
||||
# Render the final tree
|
||||
console.print(tracker.render())
|
||||
|
||||
console.print("\n[green]✓ Specify CLI is ready to use![/green]")
|
||||
# Summary
|
||||
console.print("\n[bold green]Specify CLI is ready to use![/bold green]")
|
||||
|
||||
# Recommendations
|
||||
if not git_ok:
|
||||
console.print("[yellow]Consider installing git for repository management[/yellow]")
|
||||
if not (claude_ok or gemini_ok):
|
||||
console.print("[yellow]Consider installing an AI assistant for the best experience[/yellow]")
|
||||
console.print("[dim]Tip: Install git for repository management[/dim]")
|
||||
if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or opencode_ok):
|
||||
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
---
|
||||
name: plan
|
||||
description: "Plan how to implement the specified feature. This is the second step in the Spec-Driven Development lifecycle."
|
||||
description: Execute the implementation planning workflow using the plan template to generate design artifacts.
|
||||
scripts:
|
||||
sh: scripts/bash/setup-plan.sh --json
|
||||
ps: scripts/powershell/setup-plan.ps1 -Json
|
||||
---
|
||||
|
||||
Plan how to implement the specified feature.
|
||||
|
||||
This is the second step in the Spec-Driven Development lifecycle.
|
||||
|
||||
Given the implementation details provided as an argument, do this:
|
||||
|
||||
1. Run `scripts/setup-plan.sh --json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute.
|
||||
1. Run `{SCRIPT}` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute.
|
||||
2. Read and analyze the feature specification to understand:
|
||||
- The feature requirements and user stories
|
||||
- Functional and non-functional requirements
|
||||
@@ -21,7 +19,7 @@ Given the implementation details provided as an argument, do this:
|
||||
4. Execute the implementation plan template:
|
||||
- Load `/templates/plan-template.md` (already copied to IMPL_PLAN path)
|
||||
- Set Input path to FEATURE_SPEC
|
||||
- Run the Execution Flow (main) function steps 1-10
|
||||
- Run the Execution Flow (main) function steps 1-9
|
||||
- The template is self-contained and executable
|
||||
- Follow error handling and gate checks as specified
|
||||
- Let the template guide artifact generation in $SPECS_DIR:
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
---
|
||||
name: specify
|
||||
description: "Start a new feature by creating a specification and feature branch. This is the first step in the Spec-Driven Development lifecycle."
|
||||
description: Create or update the feature specification from a natural language feature description.
|
||||
scripts:
|
||||
sh: scripts/bash/create-new-feature.sh --json "{ARGS}"
|
||||
ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}"
|
||||
---
|
||||
|
||||
Start a new feature by creating a specification and feature branch.
|
||||
|
||||
This is the first step in the Spec-Driven Development lifecycle.
|
||||
|
||||
Given the feature description provided as an argument, do this:
|
||||
|
||||
1. Run the script `scripts/create-new-feature.sh --json "{ARGS}"` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute.
|
||||
1. Run the script `{SCRIPT}` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute.
|
||||
2. Load `templates/spec-template.md` to understand required sections.
|
||||
3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
|
||||
4. Report completion with branch name, spec file path, and readiness for the next phase.
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
---
|
||||
name: tasks
|
||||
description: "Break down the plan into executable tasks. This is the third step in the Spec-Driven Development lifecycle."
|
||||
description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
|
||||
scripts:
|
||||
sh: scripts/bash/check-task-prerequisites.sh --json
|
||||
ps: scripts/powershell/check-task-prerequisites.ps1 -Json
|
||||
---
|
||||
|
||||
Break down the plan into executable tasks.
|
||||
|
||||
This is the third step in the Spec-Driven Development lifecycle.
|
||||
|
||||
Given the context provided as an argument, do this:
|
||||
|
||||
1. Run `scripts/check-task-prerequisites.sh --json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
|
||||
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
|
||||
2. Load and analyze available design documents:
|
||||
- Always read plan.md for tech stack and libraries
|
||||
- IF EXISTS: Read data-model.md for entities
|
||||
- IF EXISTS: Read contracts/ for API endpoints
|
||||
- IF EXISTS: Read contracts/ for API endpoints
|
||||
- IF EXISTS: Read research.md for technical decisions
|
||||
- IF EXISTS: Read quickstart.md for test scenarios
|
||||
|
||||
|
||||
Note: Not all projects have all documents. For example:
|
||||
- CLI tools might not have contracts/
|
||||
- Simple libraries might not need data-model.md
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
---
|
||||
description: "Implementation plan template for feature development"
|
||||
scripts:
|
||||
sh: scripts/bash/update-agent-context.sh __AGENT__
|
||||
ps: scripts/powershell/update-agent-context.ps1 -AgentType __AGENT__
|
||||
---
|
||||
|
||||
# Implementation Plan: [FEATURE]
|
||||
|
||||
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||
@@ -10,18 +17,19 @@
|
||||
2. Fill Technical Context (scan for NEEDS CLARIFICATION)
|
||||
→ Detect Project Type from context (web=frontend+backend, mobile=app+api)
|
||||
→ Set Structure Decision based on project type
|
||||
3. Evaluate Constitution Check section below
|
||||
3. Fill the Constitution Check section based on the content of the constitution document.
|
||||
4. Evaluate Constitution Check section below
|
||||
→ If violations exist: Document in Complexity Tracking
|
||||
→ If no justification possible: ERROR "Simplify approach first"
|
||||
→ Update Progress Tracking: Initial Constitution Check
|
||||
4. Execute Phase 0 → research.md
|
||||
5. Execute Phase 0 → research.md
|
||||
→ If NEEDS CLARIFICATION remain: ERROR "Resolve unknowns"
|
||||
5. Execute Phase 1 → contracts, data-model.md, quickstart.md, agent-specific template file (e.g., `CLAUDE.md` for Claude Code, `.github/copilot-instructions.md` for GitHub Copilot, or `GEMINI.md` for Gemini CLI).
|
||||
6. Re-evaluate Constitution Check section
|
||||
6. Execute Phase 1 → contracts, data-model.md, quickstart.md, agent-specific template file (e.g., `CLAUDE.md` for Claude Code, `.github/copilot-instructions.md` for GitHub Copilot, `GEMINI.md` for Gemini CLI, `QWEN.md` for Qwen Code or `AGENTS.md` for opencode).
|
||||
7. Re-evaluate Constitution Check section
|
||||
→ If new violations: Refactor design, return to Phase 1
|
||||
→ Update Progress Tracking: Post-Design Constitution Check
|
||||
7. Plan Phase 2 → Describe task generation approach (DO NOT create tasks.md)
|
||||
8. STOP - Ready for /tasks command
|
||||
8. Plan Phase 2 → Describe task generation approach (DO NOT create tasks.md)
|
||||
9. STOP - Ready for /tasks command
|
||||
```
|
||||
|
||||
**IMPORTANT**: The /plan command STOPS at step 7. Phases 2-4 are executed by other commands:
|
||||
@@ -45,35 +53,7 @@
|
||||
## Constitution Check
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
**Simplicity**:
|
||||
- Projects: [#] (max 3 - e.g., api, cli, tests)
|
||||
- Using framework directly? (no wrapper classes)
|
||||
- Single data model? (no DTOs unless serialization differs)
|
||||
- Avoiding patterns? (no Repository/UoW without proven need)
|
||||
|
||||
**Architecture**:
|
||||
- EVERY feature as library? (no direct app code)
|
||||
- Libraries listed: [name + purpose for each]
|
||||
- CLI per library: [commands with --help/--version/--format]
|
||||
- Library docs: llms.txt format planned?
|
||||
|
||||
**Testing (NON-NEGOTIABLE)**:
|
||||
- RED-GREEN-Refactor cycle enforced? (test MUST fail first)
|
||||
- Git commits show tests before implementation?
|
||||
- Order: Contract→Integration→E2E→Unit strictly followed?
|
||||
- Real dependencies used? (actual DBs, not mocks)
|
||||
- Integration tests for: new libraries, contract changes, shared schemas?
|
||||
- FORBIDDEN: Implementation before test, skipping RED phase
|
||||
|
||||
**Observability**:
|
||||
- Structured logging included?
|
||||
- Frontend logs → backend? (unified stream)
|
||||
- Error context sufficient?
|
||||
|
||||
**Versioning**:
|
||||
- Version number assigned? (MAJOR.MINOR.BUILD)
|
||||
- BUILD increments on every change?
|
||||
- Breaking changes handled? (parallel tests, migration plan)
|
||||
[Gates determined based on constitution file]
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -171,7 +151,7 @@ ios/ or android/
|
||||
- Quickstart test = story validation steps
|
||||
|
||||
5. **Update agent file incrementally** (O(1) operation):
|
||||
- Run `/scripts/update-agent-context.sh [claude|gemini|copilot]` for your AI assistant
|
||||
- Run `{SCRIPT}` for your AI assistant
|
||||
- If exists: Add only NEW tech from current plan
|
||||
- Preserve manual additions between markers
|
||||
- Update recent changes (keep last 3)
|
||||
@@ -184,7 +164,7 @@ ios/ or android/
|
||||
*This section describes what the /tasks command will do - DO NOT execute during /plan*
|
||||
|
||||
**Task Generation Strategy**:
|
||||
- Load `/templates/tasks-template.md` as base
|
||||
- Load `.specify/templates/tasks-template.md` as base
|
||||
- Generate tasks from Phase 1 design docs (contracts, data model, quickstart)
|
||||
- Each contract → contract test task [P]
|
||||
- Each entity → model creation task [P]
|
||||
@@ -234,4 +214,4 @@ ios/ or android/
|
||||
- [ ] Complexity deviations documented
|
||||
|
||||
---
|
||||
*Based on Constitution v2.1.1 - See `/memory/constitution.md`*
|
||||
*Based on Constitution v2.1.1 - See `/memory/constitution.md`*
|
||||
|
||||
Reference in New Issue
Block a user