Refactor with platform-specific constraints
This commit is contained in:
215
.github/workflows/release.yml
vendored
215
.github/workflows/release.yml
vendored
@@ -13,118 +13,115 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Get latest tag
|
||||||
- name: Get latest tag
|
id: get_tag
|
||||||
id: get_tag
|
run: |
|
||||||
run: |
|
# Get the latest tag, or use v0.0.0 if no tags exist
|
||||||
# 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")
|
||||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||||
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
|
||||||
|
# Extract version number and increment
|
||||||
# Extract version number and increment
|
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
||||||
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
||||||
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
MAJOR=${VERSION_PARTS[0]:-0}
|
||||||
MAJOR=${VERSION_PARTS[0]:-0}
|
MINOR=${VERSION_PARTS[1]:-0}
|
||||||
MINOR=${VERSION_PARTS[1]:-0}
|
PATCH=${VERSION_PARTS[2]:-0}
|
||||||
PATCH=${VERSION_PARTS[2]:-0}
|
|
||||||
|
# Increment patch version
|
||||||
# Increment patch version
|
PATCH=$((PATCH + 1))
|
||||||
PATCH=$((PATCH + 1))
|
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
||||||
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
|
||||||
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
echo "New version will be: $NEW_VERSION"
|
||||||
echo "New version will be: $NEW_VERSION"
|
- name: Check if release already exists
|
||||||
|
id: check_release
|
||||||
- name: Check if release already exists
|
run: |
|
||||||
id: check_release
|
if gh release view ${{ steps.get_tag.outputs.new_version }} >/dev/null 2>&1; then
|
||||||
run: |
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
if gh release view ${{ steps.get_tag.outputs.new_version }} >/dev/null 2>&1; then
|
echo "Release ${{ steps.get_tag.outputs.new_version }} already exists, skipping..."
|
||||||
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: |
|
|
||||||
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
|
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, and Gemini CLI.
|
||||||
|
|
||||||
|
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
|
||||||
|
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 \
|
||||||
|
--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
|
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
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# create-release-packages.sh (workflow-local)
|
# create-release-packages.sh (workflow-local)
|
||||||
# Build Spec Kit template release archives for each supported AI assistant.
|
# Build Spec Kit template release archives for each supported AI assistant and script type.
|
||||||
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
||||||
# Version argument should include leading 'v'.
|
# Version argument should include leading 'v'.
|
||||||
|
|
||||||
@@ -18,10 +18,7 @@ fi
|
|||||||
|
|
||||||
echo "Building release packages for $NEW_VERSION"
|
echo "Building release packages for $NEW_VERSION"
|
||||||
|
|
||||||
rm -rf sdd-package-base sdd-claude-package sdd-gemini-package sdd-copilot-package \
|
rm -rf sdd-package-base* sdd-*-package-* spec-kit-template-*-${NEW_VERSION}.zip || true
|
||||||
spec-kit-template-claude-${NEW_VERSION}.zip \
|
|
||||||
spec-kit-template-gemini-${NEW_VERSION}.zip \
|
|
||||||
spec-kit-template-copilot-${NEW_VERSION}.zip || true
|
|
||||||
|
|
||||||
mkdir -p sdd-package-base
|
mkdir -p sdd-package-base
|
||||||
SPEC_DIR="sdd-package-base/.specify"
|
SPEC_DIR="sdd-package-base/.specify"
|
||||||
@@ -29,7 +26,7 @@ mkdir -p "$SPEC_DIR"
|
|||||||
|
|
||||||
[[ -d memory ]] && { cp -r memory "$SPEC_DIR/"; echo "Copied memory -> .specify"; }
|
[[ -d memory ]] && { cp -r memory "$SPEC_DIR/"; echo "Copied memory -> .specify"; }
|
||||||
[[ -d scripts ]] && { cp -r scripts "$SPEC_DIR/"; echo "Copied scripts -> .specify/scripts"; }
|
[[ -d scripts ]] && { cp -r scripts "$SPEC_DIR/"; echo "Copied scripts -> .specify/scripts"; }
|
||||||
[[ -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"; }
|
[[ -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"; }
|
||||||
|
|
||||||
rewrite_paths() {
|
rewrite_paths() {
|
||||||
sed -E \
|
sed -E \
|
||||||
@@ -39,54 +36,76 @@ rewrite_paths() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generate_commands() {
|
generate_commands() {
|
||||||
local agent=$1 ext=$2 arg_format=$3 output_dir=$4
|
local agent=$1 ext=$2 arg_format=$3 output_dir=$4 script_variant=$5
|
||||||
mkdir -p "$output_dir"
|
mkdir -p "$output_dir"
|
||||||
for template in templates/commands/*.md; do
|
for template in templates/commands/*.md; do
|
||||||
[[ -f "$template" ]] || continue
|
[[ -f "$template" ]] || continue
|
||||||
local name description body
|
local name description raw_body variant_line injected body
|
||||||
name=$(basename "$template" .md)
|
name=$(basename "$template" .md)
|
||||||
description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r')
|
description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r')
|
||||||
body=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template" | sed "s/{ARGS}/$arg_format/g" | rewrite_paths)
|
raw_body=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template")
|
||||||
|
# Find single-line variant comment matching the variant: <!-- VARIANT:sh ... --> or <!-- VARIANT:ps ... -->
|
||||||
|
variant_line=$(printf '%s\n' "$raw_body" | awk -v sv="$script_variant" '/<!--[[:space:]]+VARIANT:'sv'/ {match($0, /VARIANT:'"sv"'[[:space:]]+(.*)-->/, m); if (m[1]!="") {print m[1]; exit}}')
|
||||||
|
if [[ -z $variant_line ]]; then
|
||||||
|
echo "Warning: no variant line found for $script_variant in $template" >&2
|
||||||
|
variant_line="(Missing variant command for $script_variant)"
|
||||||
|
fi
|
||||||
|
# Replace the token VARIANT-INJECT with the selected variant line
|
||||||
|
injected=$(printf '%s\n' "$raw_body" | sed "s/VARIANT-INJECT/${variant_line//\//\/}/")
|
||||||
|
# Remove all single-line variant comments
|
||||||
|
injected=$(printf '%s\n' "$injected" | sed '/<!--[[:space:]]*VARIANT:sh/d' | sed '/<!--[[:space:]]*VARIANT:ps/d')
|
||||||
|
# Apply arg substitution and path rewrite
|
||||||
|
body=$(printf '%s\n' "$injected" | sed "s/{ARGS}/$arg_format/g" | sed "s/__AGENT__/$agent/g" | rewrite_paths)
|
||||||
case $ext in
|
case $ext in
|
||||||
toml)
|
toml)
|
||||||
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/$name.$ext" ;;
|
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/$name.$ext" ;;
|
||||||
md)
|
md)
|
||||||
echo "$body" > "$output_dir/$name.$ext" ;;
|
echo "$body" > "$output_dir/$name.$ext" ;;
|
||||||
prompt.md)
|
prompt.md)
|
||||||
sed "s/{ARGS}/$arg_format/g" "$template" | rewrite_paths > "$output_dir/$name.$ext" ;;
|
echo "$body" > "$output_dir/$name.$ext" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create Claude package
|
build_variant() {
|
||||||
echo "Building Claude package..."
|
local agent=$1 script=$2
|
||||||
mkdir -p sdd-claude-package
|
local base_dir="sdd-${agent}-package-${script}"
|
||||||
cp -r sdd-package-base/. sdd-claude-package/
|
echo "Building $agent ($script) package..."
|
||||||
mkdir -p sdd-claude-package/.claude/commands
|
mkdir -p "$base_dir"
|
||||||
generate_commands claude md "\$ARGUMENTS" sdd-claude-package/.claude/commands
|
cp -r sdd-package-base/. "$base_dir"/
|
||||||
echo "Created Claude package"
|
# 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
|
||||||
|
variant_line=$(awk -v sv="$script" '/<!--[[:space:]]*VARIANT:'"$script"'/ {match($0, /VARIANT:'"$script"'[[:space:]]+(.*)-->/, m); if(m[1]!=""){print m[1]; exit}}' "$plan_tpl")
|
||||||
|
if [[ -n $variant_line ]]; then
|
||||||
|
tmp_file=$(mktemp)
|
||||||
|
sed "s/VARIANT-INJECT/${variant_line//\//\/}/" "$plan_tpl" | sed "/__AGENT__/s//${agent}/g" | sed '/<!--[[:space:]]*VARIANT:sh/d' | sed '/<!--[[:space:]]*VARIANT:ps/d' > "$tmp_file" && mv "$tmp_file" "$plan_tpl"
|
||||||
|
else
|
||||||
|
echo "Warning: no plan-template variant for $script" >&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" ;;
|
||||||
|
esac
|
||||||
|
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
||||||
|
echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
||||||
|
}
|
||||||
|
|
||||||
# Create Gemini package
|
# Build for each agent+script variant
|
||||||
echo "Building Gemini package..."
|
for agent in claude gemini copilot; do
|
||||||
mkdir -p sdd-gemini-package
|
for script in sh ps; do
|
||||||
cp -r sdd-package-base/. sdd-gemini-package/
|
build_variant "$agent" "$script"
|
||||||
mkdir -p sdd-gemini-package/.gemini/commands
|
done
|
||||||
generate_commands gemini toml "{{args}}" sdd-gemini-package/.gemini/commands
|
done
|
||||||
[[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md sdd-gemini-package/GEMINI.md
|
|
||||||
echo "Created Gemini package"
|
|
||||||
|
|
||||||
# Create Copilot package
|
|
||||||
echo "Building 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 Copilot package"
|
|
||||||
|
|
||||||
( cd sdd-claude-package && zip -r ../spec-kit-template-claude-${NEW_VERSION}.zip . )
|
|
||||||
( cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${NEW_VERSION}.zip . )
|
|
||||||
( cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${NEW_VERSION}.zip . )
|
|
||||||
|
|
||||||
echo "Archives:"
|
echo "Archives:"
|
||||||
ls -1 spec-kit-template-*-${NEW_VERSION}.zip
|
ls -1 spec-kit-template-*-${NEW_VERSION}.zip
|
||||||
unzip -l spec-kit-template-copilot-${NEW_VERSION}.zip | head -10 || true
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Linux/macOS** (or WSL2 on Windows)
|
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
||||||
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
||||||
- [uv](https://docs.astral.sh/uv/) for package management
|
- [uv](https://docs.astral.sh/uv/) for package management
|
||||||
- [Python 3.11+](https://www.python.org/downloads/)
|
- [Python 3.11+](https://www.python.org/downloads/)
|
||||||
@@ -34,6 +34,21 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <project_name
|
|||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai copilot
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai copilot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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
|
### Ignore Agent Tools Check
|
||||||
|
|
||||||
If you prefer to get the templates without checking for the right tools:
|
If you prefer to get the templates without checking for the right tools:
|
||||||
@@ -49,6 +64,8 @@ After initialization, you should see the following commands available in your AI
|
|||||||
- `/plan` - Generate implementation plans
|
- `/plan` - Generate implementation plans
|
||||||
- `/tasks` - Break down into actionable tasks
|
- `/tasks` - Break down into actionable tasks
|
||||||
|
|
||||||
|
The `.specify/scripts` directory will contain both `.sh` and `.ps1` scripts.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Git Credential Manager on Linux
|
### Git Credential Manager on Linux
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
This guide shows how to iterate on the `specify` CLI locally without publishing a release or committing to `main` first.
|
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
|
## 1. Clone and Switch Branches
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -18,13 +20,13 @@ You can execute the CLI via the module entrypoint without installing anything:
|
|||||||
```bash
|
```bash
|
||||||
# From repo root
|
# From repo root
|
||||||
python -m src.specify_cli --help
|
python -m src.specify_cli --help
|
||||||
python -m src.specify_cli init demo-project --ai claude --ignore-agent-tools
|
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):
|
If you prefer invoking the script file style (uses shebang):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python src/specify_cli/__init__.py init demo-project
|
python src/specify_cli/__init__.py init demo-project --script ps
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. Use Editable Install (Isolated Environment)
|
## 3. Use Editable Install (Isolated Environment)
|
||||||
@@ -34,7 +36,7 @@ Create an isolated environment using `uv` so dependencies resolve exactly like e
|
|||||||
```bash
|
```bash
|
||||||
# Create & activate virtual env (uv auto-manages .venv)
|
# Create & activate virtual env (uv auto-manages .venv)
|
||||||
uv venv
|
uv venv
|
||||||
source .venv/bin/activate # or on Windows: .venv\\Scripts\\activate
|
source .venv/bin/activate # or on Windows PowerShell: .venv\Scripts\Activate.ps1
|
||||||
|
|
||||||
# Install project in editable mode
|
# Install project in editable mode
|
||||||
uv pip install -e .
|
uv pip install -e .
|
||||||
@@ -50,7 +52,7 @@ Re-running after code edits requires no reinstall because of editable mode.
|
|||||||
`uvx` can run from a local path (or a Git ref) to simulate user flows:
|
`uvx` can run from a local path (or a Git ref) to simulate user flows:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools
|
uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools --script sh
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also point uvx at a specific branch without merging:
|
You can also point uvx at a specific branch without merging:
|
||||||
@@ -58,7 +60,7 @@ You can also point uvx at a specific branch without merging:
|
|||||||
```bash
|
```bash
|
||||||
# Push your working branch first
|
# Push your working branch first
|
||||||
git push origin your-feature-branch
|
git push origin your-feature-branch
|
||||||
uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specify init demo-branch-test
|
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)
|
### 4a. Absolute Path uvx (Run From Anywhere)
|
||||||
@@ -67,13 +69,13 @@ If you're in another directory, use an absolute path instead of `.`:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from /mnt/c/GitHub/spec-kit specify --help
|
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
|
uvx --from /mnt/c/GitHub/spec-kit specify init demo-anywhere --ai copilot --ignore-agent-tools --script sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Set an environment variable for convenience:
|
Set an environment variable for convenience:
|
||||||
```bash
|
```bash
|
||||||
export SPEC_KIT_SRC=/mnt/c/GitHub/spec-kit
|
export SPEC_KIT_SRC=/mnt/c/GitHub/spec-kit
|
||||||
uvx --from "$SPEC_KIT_SRC" specify init demo-env --ai copilot --ignore-agent-tools
|
uvx --from "$SPEC_KIT_SRC" specify init demo-env --ai copilot --ignore-agent-tools --script ps
|
||||||
```
|
```
|
||||||
|
|
||||||
(Optional) Define a shell function:
|
(Optional) Define a shell function:
|
||||||
@@ -91,7 +93,7 @@ After running an `init`, check that shell scripts are executable on POSIX system
|
|||||||
ls -l scripts | grep .sh
|
ls -l scripts | grep .sh
|
||||||
# Expect owner execute bit (e.g. -rwxr-xr-x)
|
# Expect owner execute bit (e.g. -rwxr-xr-x)
|
||||||
```
|
```
|
||||||
On Windows this step is a no-op.
|
On Windows you will instead use the `.ps1` scripts (no chmod needed).
|
||||||
|
|
||||||
## 6. Run Lint / Basic Checks (Add Your Own)
|
## 6. Run Lint / Basic Checks (Add Your Own)
|
||||||
|
|
||||||
@@ -116,7 +118,7 @@ When testing `init --here` in a dirty directory, create a temp workspace:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir /tmp/spec-test && cd /tmp/spec-test
|
mkdir /tmp/spec-test && cd /tmp/spec-test
|
||||||
python -m src.specify_cli init --here --ai claude --ignore-agent-tools # if repo copied here
|
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.
|
Or copy only the modified CLI portion if you want a lighter sandbox.
|
||||||
|
|
||||||
@@ -126,7 +128,7 @@ If you need to bypass TLS validation while experimenting:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
specify check --skip-tls
|
specify check --skip-tls
|
||||||
specify init demo --skip-tls --ai gemini --ignore-agent-tools
|
specify init demo --skip-tls --ai gemini --ignore-agent-tools --script ps
|
||||||
```
|
```
|
||||||
(Use only for local experimentation.)
|
(Use only for local experimentation.)
|
||||||
|
|
||||||
@@ -153,8 +155,9 @@ rm -rf .venv dist build *.egg-info
|
|||||||
| Symptom | Fix |
|
| Symptom | Fix |
|
||||||
|---------|-----|
|
|---------|-----|
|
||||||
| `ModuleNotFoundError: typer` | Run `uv pip install -e .` |
|
| `ModuleNotFoundError: typer` | Run `uv pip install -e .` |
|
||||||
| Scripts not executable (Linux) | Re-run init (logic adds bits) or `chmod +x scripts/*.sh` |
|
| Scripts not executable (Linux) | Re-run init or `chmod +x scripts/*.sh` |
|
||||||
| Git step skipped | You passed `--no-git` or Git not installed |
|
| 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) |
|
| TLS errors on corporate network | Try `--skip-tls` (not for production) |
|
||||||
|
|
||||||
## 13. Next Steps
|
## 13. Next Steps
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
This guide will help you get started with Spec-Driven Development using Spec Kit.
|
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
|
## The 4-Step Process
|
||||||
|
|
||||||
### 1. Install Specify
|
### 1. Install Specify
|
||||||
@@ -12,6 +14,12 @@ Initialize your project depending on the coding agent you're using:
|
|||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
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
|
### 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.
|
||||||
|
|||||||
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/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
|
||||||
57
scripts/bash/update-agent-context.sh
Normal file
57
scripts/bash/update-agent-context.sh
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/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"
|
||||||
|
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/templates/agent-file-template.md" ]; then cp "$REPO_ROOT/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" ;;
|
||||||
|
"") [ -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 [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;;
|
||||||
|
*) echo "ERROR: Unknown agent type '$AGENT_TYPE'"; 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]"
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#!/usr/bin/env 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 @@
|
|||||||
#!/usr/bin/env 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 @@
|
|||||||
#!/usr/bin/env 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 @@
|
|||||||
#!/usr/bin/env 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)"
|
||||||
|
}
|
||||||
91
scripts/powershell/update-agent-context.ps1
Normal file
91
scripts/powershell/update-agent-context.ps1
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/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'
|
||||||
|
|
||||||
|
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 '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' }
|
||||||
|
'' {
|
||||||
|
foreach ($pair in @(@{file=$claudeFile; name='Claude Code'}, @{file=$geminiFile; name='Gemini CLI'}, @{file=$copilotFile; name='GitHub Copilot'})) {
|
||||||
|
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)) {
|
||||||
|
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, 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]'
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/usr/bin/env 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 @@
|
|||||||
#!/usr/bin/env 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"
|
|
||||||
@@ -58,6 +58,8 @@ AI_CHOICES = {
|
|||||||
"claude": "Claude Code",
|
"claude": "Claude Code",
|
||||||
"gemini": "Gemini CLI"
|
"gemini": "Gemini CLI"
|
||||||
}
|
}
|
||||||
|
# Add script type choices
|
||||||
|
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
||||||
|
|
||||||
# ASCII Art Banner
|
# ASCII Art Banner
|
||||||
BANNER = """
|
BANNER = """
|
||||||
@@ -400,7 +402,7 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
|
|||||||
os.chdir(original_cwd)
|
os.chdir(original_cwd)
|
||||||
|
|
||||||
|
|
||||||
def download_template_from_github(ai_assistant: str, download_dir: Path, *, verbose: bool = True, show_progress: bool = True, client: httpx.Client = None):
|
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):
|
||||||
repo_owner = "github"
|
repo_owner = "github"
|
||||||
repo_name = "spec-kit"
|
repo_name = "spec-kit"
|
||||||
if client is None:
|
if client is None:
|
||||||
@@ -420,7 +422,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
# Find the template asset for the specified AI assistant
|
# 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 = [
|
matching_assets = [
|
||||||
asset for asset in release_data.get("assets", [])
|
asset for asset in release_data.get("assets", [])
|
||||||
if pattern in asset["name"] and asset["name"].endswith(".zip")
|
if pattern in asset["name"] and asset["name"].endswith(".zip")
|
||||||
@@ -492,7 +494,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, verb
|
|||||||
return zip_path, metadata
|
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, client: httpx.Client = 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) -> Path:
|
||||||
"""Download the latest release and extract it to create a new project.
|
"""Download the latest release and extract it to create a new project.
|
||||||
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
|
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
|
||||||
"""
|
"""
|
||||||
@@ -505,6 +507,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
|
|||||||
zip_path, meta = download_template_from_github(
|
zip_path, meta = download_template_from_github(
|
||||||
ai_assistant,
|
ai_assistant,
|
||||||
current_dir,
|
current_dir,
|
||||||
|
script_type=script_type,
|
||||||
verbose=verbose and tracker is None,
|
verbose=verbose and tracker is None,
|
||||||
show_progress=(tracker is None),
|
show_progress=(tracker is None),
|
||||||
client=client
|
client=client
|
||||||
@@ -646,60 +649,44 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
|
|||||||
|
|
||||||
|
|
||||||
def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None:
|
def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None:
|
||||||
"""Ensure POSIX .sh scripts in the project .specify/scripts directory have execute bits (no-op on Windows)."""
|
"""Ensure POSIX .sh scripts under .specify/scripts (recursively) have execute bits (no-op on Windows)."""
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
return # Windows: skip silently
|
return # Windows: skip silently
|
||||||
scripts_dir = project_path / ".specify" / "scripts"
|
scripts_root = project_path / ".specify" / "scripts"
|
||||||
if not scripts_dir.is_dir():
|
if not scripts_root.is_dir():
|
||||||
return
|
return
|
||||||
failures: list[str] = []
|
failures: list[str] = []
|
||||||
updated = 0
|
updated = 0
|
||||||
for script in scripts_dir.glob("*.sh"):
|
for script in scripts_root.rglob("*.sh"):
|
||||||
try:
|
try:
|
||||||
# Skip symlinks
|
if script.is_symlink() or not script.is_file():
|
||||||
if script.is_symlink():
|
|
||||||
continue
|
continue
|
||||||
# Must be a regular file
|
|
||||||
if not script.is_file():
|
|
||||||
continue
|
|
||||||
# Quick shebang check
|
|
||||||
try:
|
try:
|
||||||
with script.open("rb") as f:
|
with script.open("rb") as f:
|
||||||
first_two = f.read(2)
|
if f.read(2) != b"#!":
|
||||||
if first_two != b"#!":
|
continue
|
||||||
continue
|
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
st = script.stat()
|
st = script.stat(); mode = st.st_mode
|
||||||
mode = st.st_mode
|
|
||||||
# If already any execute bit set, skip
|
|
||||||
if mode & 0o111:
|
if mode & 0o111:
|
||||||
continue
|
continue
|
||||||
# Only add execute bits that correspond to existing read bits
|
|
||||||
new_mode = mode
|
new_mode = mode
|
||||||
if mode & 0o400: # owner read
|
if mode & 0o400: new_mode |= 0o100
|
||||||
new_mode |= 0o100
|
if mode & 0o040: new_mode |= 0o010
|
||||||
if mode & 0o040: # group read
|
if mode & 0o004: new_mode |= 0o001
|
||||||
new_mode |= 0o010
|
|
||||||
if mode & 0o004: # other read
|
|
||||||
new_mode |= 0o001
|
|
||||||
# Fallback: ensure at least owner execute
|
|
||||||
if not (new_mode & 0o100):
|
if not (new_mode & 0o100):
|
||||||
new_mode |= 0o100
|
new_mode |= 0o100
|
||||||
os.chmod(script, new_mode)
|
os.chmod(script, new_mode)
|
||||||
updated += 1
|
updated += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
failures.append(f"{script.name}: {e}")
|
failures.append(f"{script.relative_to(scripts_root)}: {e}")
|
||||||
if tracker:
|
if tracker:
|
||||||
detail = f"{updated} updated" + (f", {len(failures)} failed" if failures else "")
|
detail = f"{updated} updated" + (f", {len(failures)} failed" if failures else "")
|
||||||
tracker.add("chmod", "Set script permissions")
|
tracker.add("chmod", "Set script permissions recursively")
|
||||||
if failures:
|
(tracker.error if failures else tracker.complete)("chmod", detail)
|
||||||
tracker.error("chmod", detail)
|
|
||||||
else:
|
|
||||||
tracker.complete("chmod", detail)
|
|
||||||
else:
|
else:
|
||||||
if updated:
|
if updated:
|
||||||
console.print(f"[cyan]Updated execute permissions on {updated} script(s)[/cyan]")
|
console.print(f"[cyan]Updated execute permissions on {updated} script(s) recursively[/cyan]")
|
||||||
if failures:
|
if failures:
|
||||||
console.print("[yellow]Some scripts could not be updated:[/yellow]")
|
console.print("[yellow]Some scripts could not be updated:[/yellow]")
|
||||||
for f in failures:
|
for f in failures:
|
||||||
@@ -710,6 +697,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
|
|||||||
def init(
|
def init(
|
||||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
||||||
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, or copilot"),
|
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, or copilot"),
|
||||||
|
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
|
||||||
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
|
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
|
||||||
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
|
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
|
||||||
here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
|
here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
|
||||||
@@ -816,6 +804,24 @@ def init(
|
|||||||
console.print("[yellow]Tip:[/yellow] Use --ignore-agent-tools to skip this check")
|
console.print("[yellow]Tip:[/yellow] Use --ignore-agent-tools to skip this check")
|
||||||
raise typer.Exit(1)
|
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
|
# Download and set up project
|
||||||
# New tree-based progress (no emojis); include earlier substeps
|
# New tree-based progress (no emojis); include earlier substeps
|
||||||
tracker = StepTracker("Initialize Specify Project")
|
tracker = StepTracker("Initialize Specify Project")
|
||||||
@@ -826,6 +832,8 @@ def init(
|
|||||||
tracker.complete("precheck", "ok")
|
tracker.complete("precheck", "ok")
|
||||||
tracker.add("ai-select", "Select AI assistant")
|
tracker.add("ai-select", "Select AI assistant")
|
||||||
tracker.complete("ai-select", f"{selected_ai}")
|
tracker.complete("ai-select", f"{selected_ai}")
|
||||||
|
tracker.add("script-select", "Select script type")
|
||||||
|
tracker.complete("script-select", selected_script)
|
||||||
for key, label in [
|
for key, label in [
|
||||||
("fetch", "Fetch latest release"),
|
("fetch", "Fetch latest release"),
|
||||||
("download", "Download template"),
|
("download", "Download template"),
|
||||||
@@ -848,7 +856,7 @@ def init(
|
|||||||
local_ssl_context = ssl_context if verify else False
|
local_ssl_context = ssl_context if verify else False
|
||||||
local_client = httpx.Client(verify=local_ssl_context)
|
local_client = httpx.Client(verify=local_ssl_context)
|
||||||
|
|
||||||
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client)
|
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client)
|
||||||
|
|
||||||
# Ensure scripts are executable (POSIX)
|
# Ensure scripts are executable (POSIX)
|
||||||
ensure_executable_scripts(project_path, tracker=tracker)
|
ensure_executable_scripts(project_path, tracker=tracker)
|
||||||
@@ -906,6 +914,7 @@ def init(
|
|||||||
elif selected_ai == "copilot":
|
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")
|
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")
|
||||||
|
|
||||||
|
# Removed script variant step (scripts are transparent to users)
|
||||||
step_num += 1
|
step_num += 1
|
||||||
steps_lines.append(f"{step_num}. Update [bold magenta]CONSTITUTION.md[/bold magenta] with your project's non-negotiable principles")
|
steps_lines.append(f"{step_num}. Update [bold magenta]CONSTITUTION.md[/bold magenta] with your project's non-negotiable principles")
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
---
|
<!-- VARIANT:sh 1. Run `scripts/bash/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. -->
|
||||||
name: plan
|
<!-- VARIANT:ps 1. Run `scripts/powershell/setup-plan.ps1 -Json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute. -->
|
||||||
description: "Plan how to implement the specified feature. This is the second step in the Spec-Driven Development lifecycle."
|
|
||||||
---
|
|
||||||
|
|
||||||
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:
|
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. VARIANT-INJECT
|
||||||
2. Read and analyze the feature specification to understand:
|
2. Read and analyze the feature specification to understand:
|
||||||
- The feature requirements and user stories
|
- The feature requirements and user stories
|
||||||
- Functional and non-functional requirements
|
- Functional and non-functional requirements
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
---
|
<!-- VARIANT:sh 1. Run the script `scripts/bash/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. -->
|
||||||
name: specify
|
<!-- VARIANT:ps 1. Run the script `scripts/powershell/create-new-feature.ps1 -Json "{ARGS}"` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute. -->
|
||||||
description: "Start a new feature by creating a specification and feature branch. This is the first step in the Spec-Driven Development lifecycle."
|
|
||||||
---
|
|
||||||
|
|
||||||
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:
|
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. VARIANT-INJECT
|
||||||
2. Load `templates/spec-template.md` to understand required sections.
|
2. Load `templates/spec-template.md` to understand required sections.
|
||||||
3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
|
3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
|
||||||
4. Report completion with branch name, spec file path, and readiness for the next phase.
|
4. Report completion with branch name, spec file path, and readiness for the next phase.
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
---
|
<!-- VARIANT:sh 1. Run `scripts/bash/check-task-prerequisites.sh --json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. -->
|
||||||
name: tasks
|
<!-- VARIANT:ps 1. Run `scripts/powershell/check-task-prerequisites.ps1 -Json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. -->
|
||||||
description: "Break down the plan into executable tasks. This is the third step in the Spec-Driven Development lifecycle."
|
|
||||||
---
|
|
||||||
|
|
||||||
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:
|
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. VARIANT-INJECT
|
||||||
2. Load and analyze available design documents:
|
2. Load and analyze available design documents:
|
||||||
- Always read plan.md for tech stack and libraries
|
- Always read plan.md for tech stack and libraries
|
||||||
- IF EXISTS: Read data-model.md for entities
|
- IF EXISTS: Read data-model.md for entities
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Implementation Plan: [FEATURE]
|
# Implementation Plan: [FEATURE]
|
||||||
|
|
||||||
|
<!-- VARIANT:sh - Run `/scripts/bash/update-agent-context.sh __AGENT__` for your AI assistant -->
|
||||||
|
<!-- VARIANT:ps - Run `/scripts/powershell/update-agent-context.ps1 -AgentType __AGENT__` for your AI assistant -->
|
||||||
|
|
||||||
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||||
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
||||||
|
|
||||||
@@ -171,7 +174,7 @@ ios/ or android/
|
|||||||
- Quickstart test = story validation steps
|
- Quickstart test = story validation steps
|
||||||
|
|
||||||
5. **Update agent file incrementally** (O(1) operation):
|
5. **Update agent file incrementally** (O(1) operation):
|
||||||
- Run `/scripts/update-agent-context.sh [claude|gemini|copilot]` for your AI assistant
|
VARIANT-INJECT
|
||||||
- If exists: Add only NEW tech from current plan
|
- If exists: Add only NEW tech from current plan
|
||||||
- Preserve manual additions between markers
|
- Preserve manual additions between markers
|
||||||
- Update recent changes (keep last 3)
|
- Update recent changes (keep last 3)
|
||||||
|
|||||||
Reference in New Issue
Block a user