Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad746ce35d | ||
|
|
14ac43ba41 | ||
|
|
0857f83de8 | ||
|
|
18236f27d6 | ||
|
|
974347c758 | ||
|
|
e9aed2da44 | ||
|
|
ecf1757672 | ||
|
|
ceba130e52 | ||
|
|
494cdede53 | ||
|
|
bc896086f1 | ||
|
|
dceb903804 | ||
|
|
39b33ebd22 | ||
|
|
ef05d4846a | ||
|
|
026aa69aad | ||
|
|
ebf53e10b3 | ||
|
|
713af3c314 | ||
|
|
33652bf143 | ||
|
|
cef4e8f495 | ||
|
|
86aaf2daed | ||
|
|
c65b0fbb62 | ||
|
|
385d17c83c | ||
|
|
1a84b4b23c | ||
|
|
b03bba37ce | ||
|
|
2d89075106 | ||
|
|
a810b1bd1a | ||
|
|
5243137f25 | ||
|
|
8c4f348ac1 | ||
|
|
0e49e79610 | ||
|
|
93e41567d9 | ||
|
|
da60d35bc1 | ||
|
|
84b61bcd20 | ||
|
|
0672bfc6aa | ||
|
|
d682f5a164 | ||
|
|
895bcbef00 | ||
|
|
90f06521a2 | ||
|
|
d92d6f57db | ||
|
|
f9c9cd3b61 | ||
|
|
f4b16080da | ||
|
|
7c2fd502c8 | ||
|
|
5eccac5524 | ||
|
|
ee9f83929a | ||
|
|
3bdb1d9f3f | ||
|
|
0e5f7cee9a | ||
|
|
4cc15bab98 | ||
|
|
8d529599f1 | ||
|
|
406521c664 | ||
|
|
1a71b03195 | ||
|
|
f04e01d4a2 | ||
|
|
2d242b4732 | ||
|
|
84ec4611c4 | ||
|
|
2c1e1688e8 | ||
|
|
3f67cf2f5f | ||
|
|
505b956bfd | ||
|
|
0bebcf93b3 | ||
|
|
826c3a6102 | ||
|
|
e83e1cd8e3 | ||
|
|
aa08257d98 | ||
|
|
64171ec062 | ||
|
|
7c0f0a4627 |
108
.github/workflows/release.yml
vendored
108
.github/workflows/release.yml
vendored
@@ -25,33 +25,13 @@ jobs:
|
|||||||
- 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
|
chmod +x .github/workflows/scripts/get-next-version.sh
|
||||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
.github/workflows/scripts/get-next-version.sh
|
||||||
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Extract version number and increment
|
|
||||||
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
|
||||||
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
|
||||||
MAJOR=${VERSION_PARTS[0]:-0}
|
|
||||||
MINOR=${VERSION_PARTS[1]:-0}
|
|
||||||
PATCH=${VERSION_PARTS[2]:-0}
|
|
||||||
|
|
||||||
# Increment patch version
|
|
||||||
PATCH=$((PATCH + 1))
|
|
||||||
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
|
||||||
|
|
||||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "New version will be: $NEW_VERSION"
|
|
||||||
- name: Check if release already exists
|
- name: Check if release already exists
|
||||||
id: check_release
|
id: check_release
|
||||||
run: |
|
run: |
|
||||||
if gh release view ${{ steps.get_tag.outputs.new_version }} >/dev/null 2>&1; then
|
chmod +x .github/workflows/scripts/check-release-exists.sh
|
||||||
echo "exists=true" >> $GITHUB_OUTPUT
|
.github/workflows/scripts/check-release-exists.sh ${{ steps.get_tag.outputs.new_version }}
|
||||||
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:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create release package variants
|
- name: Create release package variants
|
||||||
@@ -63,85 +43,17 @@ jobs:
|
|||||||
if: steps.check_release.outputs.exists == 'false'
|
if: steps.check_release.outputs.exists == 'false'
|
||||||
id: release_notes
|
id: release_notes
|
||||||
run: |
|
run: |
|
||||||
# Get commits since last tag
|
chmod +x .github/workflows/scripts/generate-release-notes.sh
|
||||||
LAST_TAG=${{ steps.get_tag.outputs.latest_tag }}
|
.github/workflows/scripts/generate-release-notes.sh ${{ steps.get_tag.outputs.new_version }} ${{ steps.get_tag.outputs.latest_tag }}
|
||||||
if [ "$LAST_TAG" = "v0.0.0" ]; then
|
|
||||||
# Check how many commits we have and use that as the limit
|
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
|
||||||
if [ "$COMMIT_COUNT" -gt 10 ]; then
|
|
||||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~10..HEAD)
|
|
||||||
else
|
|
||||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~$COMMIT_COUNT..HEAD 2>/dev/null || git log --oneline --pretty=format:"- %s")
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
COMMITS=$(git log --oneline --pretty=format:"- %s" $LAST_TAG..HEAD)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create release notes
|
|
||||||
cat > release_notes.md << EOF
|
|
||||||
Template release ${{ steps.get_tag.outputs.new_version }}
|
|
||||||
|
|
||||||
Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, opencode, Windsurf, and Codex.
|
|
||||||
|
|
||||||
Now includes per-script variants for POSIX shell (sh) and PowerShell (ps).
|
|
||||||
|
|
||||||
Download the template for your preferred AI assistant + script type:
|
|
||||||
- spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-codex-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-codex-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "Generated release notes:"
|
|
||||||
cat release_notes.md
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
if: steps.check_release.outputs.exists == 'false'
|
if: steps.check_release.outputs.exists == 'false'
|
||||||
run: |
|
run: |
|
||||||
# Remove 'v' prefix from version for release title
|
chmod +x .github/workflows/scripts/create-github-release.sh
|
||||||
VERSION_NO_V=${{ steps.get_tag.outputs.new_version }}
|
.github/workflows/scripts/create-github-release.sh ${{ steps.get_tag.outputs.new_version }}
|
||||||
VERSION_NO_V=${VERSION_NO_V#v}
|
|
||||||
|
|
||||||
gh release create ${{ steps.get_tag.outputs.new_version }} \
|
|
||||||
spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-codex-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-codex-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
|
||||||
--notes-file release_notes.md
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Update version in pyproject.toml (for release artifacts only)
|
- name: Update version in pyproject.toml (for release artifacts only)
|
||||||
if: steps.check_release.outputs.exists == 'false'
|
if: steps.check_release.outputs.exists == 'false'
|
||||||
run: |
|
run: |
|
||||||
# Update version in pyproject.toml (remove 'v' prefix for Python versioning)
|
chmod +x .github/workflows/scripts/update-version.sh
|
||||||
VERSION=${{ steps.get_tag.outputs.new_version }}
|
.github/workflows/scripts/update-version.sh ${{ 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
|
|
||||||
|
|||||||
21
.github/workflows/scripts/check-release-exists.sh
vendored
Normal file
21
.github/workflows/scripts/check-release-exists.sh
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# check-release-exists.sh
|
||||||
|
# Check if a GitHub release already exists for the given version
|
||||||
|
# Usage: check-release-exists.sh <version>
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "Usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
if gh release view "$VERSION" >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Release $VERSION already exists, skipping..."
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Release $VERSION does not exist, proceeding..."
|
||||||
|
fi
|
||||||
40
.github/workflows/scripts/create-github-release.sh
vendored
Normal file
40
.github/workflows/scripts/create-github-release.sh
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# create-github-release.sh
|
||||||
|
# Create a GitHub release with all template zip files
|
||||||
|
# Usage: create-github-release.sh <version>
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "Usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
# Remove 'v' prefix from version for release title
|
||||||
|
VERSION_NO_V=${VERSION#v}
|
||||||
|
|
||||||
|
gh release create "$VERSION" \
|
||||||
|
.genreleases/spec-kit-template-copilot-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-copilot-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-claude-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-claude-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-gemini-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-gemini-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-cursor-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-cursor-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-opencode-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-opencode-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-qwen-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-qwen-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-windsurf-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-windsurf-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-codex-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-codex-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-kilocode-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-kilocode-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-auggie-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-auggie-ps-"$VERSION".zip \
|
||||||
|
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||||
|
--notes-file release_notes.md
|
||||||
@@ -25,7 +25,10 @@ fi
|
|||||||
|
|
||||||
echo "Building release packages for $NEW_VERSION"
|
echo "Building release packages for $NEW_VERSION"
|
||||||
|
|
||||||
rm -rf sdd-package-base* sdd-*-package-* spec-kit-template-*-"${NEW_VERSION}".zip || true
|
# Create and use .genreleases directory for all build artifacts
|
||||||
|
GENRELEASES_DIR=".genreleases"
|
||||||
|
mkdir -p "$GENRELEASES_DIR"
|
||||||
|
rm -rf "$GENRELEASES_DIR"/* || true
|
||||||
|
|
||||||
rewrite_paths() {
|
rewrite_paths() {
|
||||||
sed -E \
|
sed -E \
|
||||||
@@ -82,7 +85,7 @@ generate_commands() {
|
|||||||
|
|
||||||
build_variant() {
|
build_variant() {
|
||||||
local agent=$1 script=$2
|
local agent=$1 script=$2
|
||||||
local base_dir="sdd-${agent}-package-${script}"
|
local base_dir="$GENRELEASES_DIR/sdd-${agent}-package-${script}"
|
||||||
echo "Building $agent ($script) package..."
|
echo "Building $agent ($script) package..."
|
||||||
mkdir -p "$base_dir"
|
mkdir -p "$base_dir"
|
||||||
|
|
||||||
@@ -158,15 +161,21 @@ build_variant() {
|
|||||||
mkdir -p "$base_dir/.windsurf/workflows"
|
mkdir -p "$base_dir/.windsurf/workflows"
|
||||||
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
||||||
codex)
|
codex)
|
||||||
mkdir -p "$base_dir/.codex/commands"
|
mkdir -p "$base_dir/.codex/prompts"
|
||||||
generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/commands" "$script" ;;
|
generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/prompts" "$script" ;;
|
||||||
|
kilocode)
|
||||||
|
mkdir -p "$base_dir/.kilocode/workflows"
|
||||||
|
generate_commands kilocode md "\$ARGUMENTS" "$base_dir/.kilocode/workflows" "$script" ;;
|
||||||
|
auggie)
|
||||||
|
mkdir -p "$base_dir/.augment/commands"
|
||||||
|
generate_commands auggie md "\$ARGUMENTS" "$base_dir/.augment/commands" "$script" ;;
|
||||||
esac
|
esac
|
||||||
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
||||||
echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
echo "Created $GENRELEASES_DIR/spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine agent list
|
# Determine agent list
|
||||||
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf codex)
|
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf codex kilocode auggie)
|
||||||
ALL_SCRIPTS=(sh ps)
|
ALL_SCRIPTS=(sh ps)
|
||||||
|
|
||||||
|
|
||||||
@@ -212,5 +221,5 @@ for agent in "${AGENT_LIST[@]}"; do
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Archives:"
|
echo "Archives in $GENRELEASES_DIR:"
|
||||||
ls -1 spec-kit-template-*-"${NEW_VERSION}".zip
|
ls -1 "$GENRELEASES_DIR"/spec-kit-template-*-"${NEW_VERSION}".zip
|
||||||
|
|||||||
36
.github/workflows/scripts/generate-release-notes.sh
vendored
Normal file
36
.github/workflows/scripts/generate-release-notes.sh
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# generate-release-notes.sh
|
||||||
|
# Generate release notes from git history
|
||||||
|
# Usage: generate-release-notes.sh <new_version> <last_tag>
|
||||||
|
|
||||||
|
if [[ $# -ne 2 ]]; then
|
||||||
|
echo "Usage: $0 <new_version> <last_tag>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEW_VERSION="$1"
|
||||||
|
LAST_TAG="$2"
|
||||||
|
|
||||||
|
# Get commits since last 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
|
||||||
|
This is the latest set of releases that you can use with your agent of choice. We recommend using the Specify CLI to scaffold your projects, however you can download these independently and manage them yourself.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Generated release notes:"
|
||||||
|
cat release_notes.md
|
||||||
24
.github/workflows/scripts/get-next-version.sh
vendored
Normal file
24
.github/workflows/scripts/get-next-version.sh
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# get-next-version.sh
|
||||||
|
# Calculate the next version based on the latest git tag and output GitHub Actions variables
|
||||||
|
# Usage: get-next-version.sh
|
||||||
|
|
||||||
|
# Get the latest tag, or use v0.0.0 if no tags exist
|
||||||
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
|
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Extract version number and increment
|
||||||
|
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
||||||
|
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
||||||
|
MAJOR=${VERSION_PARTS[0]:-0}
|
||||||
|
MINOR=${VERSION_PARTS[1]:-0}
|
||||||
|
PATCH=${VERSION_PARTS[2]:-0}
|
||||||
|
|
||||||
|
# Increment patch version
|
||||||
|
PATCH=$((PATCH + 1))
|
||||||
|
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
||||||
|
|
||||||
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "New version will be: $NEW_VERSION"
|
||||||
23
.github/workflows/scripts/update-version.sh
vendored
Normal file
23
.github/workflows/scripts/update-version.sh
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# update-version.sh
|
||||||
|
# Update version in pyproject.toml (for release artifacts only)
|
||||||
|
# Usage: update-version.sh <version>
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "Usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
# Remove 'v' prefix for Python versioning
|
||||||
|
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)"
|
||||||
|
else
|
||||||
|
echo "Warning: pyproject.toml not found, skipping version update"
|
||||||
|
fi
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -38,3 +38,8 @@ env/
|
|||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
*.lock
|
*.lock
|
||||||
|
|
||||||
|
# Spec Kit-specific files
|
||||||
|
.genreleases/
|
||||||
|
*.zip
|
||||||
|
sdd-*/
|
||||||
44
AGENTS.md
44
AGENTS.md
@@ -59,6 +59,23 @@ AI_CHOICES = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Also update the `agent_folder_map` in the same file to include the new agent's folder for the security notice:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agent_folder_map = {
|
||||||
|
"claude": ".claude/",
|
||||||
|
"gemini": ".gemini/",
|
||||||
|
"cursor": ".cursor/",
|
||||||
|
"qwen": ".qwen/",
|
||||||
|
"opencode": ".opencode/",
|
||||||
|
"codex": ".codex/",
|
||||||
|
"windsurf": ".windsurf/", # Add new agent folder here
|
||||||
|
"kilocode": ".kilocode/",
|
||||||
|
"auggie": ".auggie/",
|
||||||
|
"copilot": ".github/"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### 2. Update CLI Help Text
|
#### 2. Update CLI Help Text
|
||||||
|
|
||||||
Update all help text and examples to include the new agent:
|
Update all help text and examples to include the new agent:
|
||||||
@@ -67,7 +84,16 @@ Update all help text and examples to include the new agent:
|
|||||||
- Function docstrings and examples
|
- Function docstrings and examples
|
||||||
- Error messages with agent lists
|
- Error messages with agent lists
|
||||||
|
|
||||||
#### 3. Update Release Package Script
|
#### 3. Update README Documentation
|
||||||
|
|
||||||
|
Update the **Supported AI Agents** section in `README.md` to include the new agent:
|
||||||
|
|
||||||
|
- Add the new agent to the table with appropriate support level (Full/Partial)
|
||||||
|
- Include the agent's official website link
|
||||||
|
- Add any relevant notes about the agent's implementation
|
||||||
|
- Ensure the table formatting remains aligned and consistent
|
||||||
|
|
||||||
|
#### 4. Update Release Package Script
|
||||||
|
|
||||||
Modify `.github/workflows/scripts/create-release-packages.sh`:
|
Modify `.github/workflows/scripts/create-release-packages.sh`:
|
||||||
|
|
||||||
@@ -86,7 +112,19 @@ case $agent in
|
|||||||
esac
|
esac
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. Update Agent Context Scripts
|
#### 4. Update GitHub Release Script
|
||||||
|
|
||||||
|
Modify `.github/workflows/scripts/create-github-release.sh` to include the new agent's packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh release create "$VERSION" \
|
||||||
|
# ... existing packages ...
|
||||||
|
.genreleases/spec-kit-template-windsurf-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-windsurf-ps-"$VERSION".zip \
|
||||||
|
# Add new agent packages here
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Update Agent Context Scripts
|
||||||
|
|
||||||
##### Bash script (`scripts/bash/update-agent-context.sh`):
|
##### Bash script (`scripts/bash/update-agent-context.sh`):
|
||||||
|
|
||||||
@@ -132,7 +170,7 @@ switch ($AgentType) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5. Update CLI Tool Checks (Optional)
|
#### 6. Update CLI Tool Checks (Optional)
|
||||||
|
|
||||||
For agents that require CLI tools, add checks in the `check()` command and agent validation:
|
For agents that require CLI tools, add checks in the `check()` command and agent validation:
|
||||||
|
|
||||||
|
|||||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -5,6 +5,31 @@ All notable changes to the Specify CLI will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.0.14] - 2025-09-21
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Error messages are now shown consistently.
|
||||||
|
|
||||||
|
## [0.0.13] - 2025-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for Kilo Code. Thank you [@shahrukhkhan489](https://github.com/shahrukhkhan489) with [#394](https://github.com/github/spec-kit/pull/394).
|
||||||
|
- Support for Auggie CLI. Thank you [@hungthai1401](https://github.com/hungthai1401) with [#137](https://github.com/github/spec-kit/pull/137).
|
||||||
|
- Agent folder security notice displayed after project provisioning completion, warning users that some agents may store credentials or auth tokens in their agent folders and recommending adding relevant folders to `.gitignore` to prevent accidental credential leakage.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Warning displayed to ensure that folks are aware that they might need to add their agent folder to `.gitignore`.
|
||||||
|
- Cleaned up the `check` command output.
|
||||||
|
|
||||||
|
## [0.0.12] - 2025-09-21
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added additional context for OpenAI Codex users - they need to set an additional environment variable, as described in [#417](https://github.com/github/spec-kit/issues/417).
|
||||||
|
|
||||||
## [0.0.11] - 2025-09-20
|
## [0.0.11] - 2025-09-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ These are one time installations required to be able to test your changes locall
|
|||||||
1. Install [Python 3.11+](https://www.python.org/downloads/)
|
1. Install [Python 3.11+](https://www.python.org/downloads/)
|
||||||
1. Install [uv](https://docs.astral.sh/uv/) for package management
|
1. Install [uv](https://docs.astral.sh/uv/) for package management
|
||||||
1. Install [Git](https://git-scm.com/downloads)
|
1. Install [Git](https://git-scm.com/downloads)
|
||||||
1. Have an AI coding agent available: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Qwen Code](https://github.com/QwenLM/qwen-code). We're working on adding support for other agents as well.
|
1. Have an [AI coding agent available](README.md#-supported-ai-agents)
|
||||||
|
|
||||||
## Submitting a pull request
|
## Submitting a pull request
|
||||||
|
|
||||||
@@ -45,6 +45,33 @@ When working on spec-kit:
|
|||||||
3. Test script functionality in the `scripts/` directory
|
3. Test script functionality in the `scripts/` directory
|
||||||
4. Ensure memory files (`memory/constitution.md`) are updated if major process changes are made
|
4. Ensure memory files (`memory/constitution.md`) are updated if major process changes are made
|
||||||
|
|
||||||
|
## AI contributions in Spec Kit
|
||||||
|
|
||||||
|
We welcome and encourage the use of AI tools to help improve Spec Kit! Many valuable contributions have been enhanced with AI assistance for code generation, issue detection, and feature definition.
|
||||||
|
|
||||||
|
### What we're looking for
|
||||||
|
|
||||||
|
When submitting AI-assisted contributions, please ensure they include:
|
||||||
|
|
||||||
|
- **Human understanding and testing** - You've personally tested the changes and understand what they do
|
||||||
|
- **Clear rationale** - You can explain why the change is needed and how it fits within Spec Kit's goals
|
||||||
|
- **Concrete evidence** - Include test cases, scenarios, or examples that demonstrate the improvement
|
||||||
|
- **Your own analysis** - Share your thoughts on the end-to-end developer experience
|
||||||
|
|
||||||
|
### What we'll close
|
||||||
|
|
||||||
|
We reserve the right to close contributions that appear to be:
|
||||||
|
|
||||||
|
- Untested changes submitted without verification
|
||||||
|
- Generic suggestions that don't address specific Spec Kit needs
|
||||||
|
- Bulk submissions that show no human review or understanding
|
||||||
|
|
||||||
|
### Guidelines for success
|
||||||
|
|
||||||
|
The key is demonstrating that you understand and have validated your proposed changes. If a maintainer can easily tell that a contribution was generated entirely by AI without human input or testing, it likely needs more work before submission.
|
||||||
|
|
||||||
|
Contributors who consistently submit low-effort AI-generated changes may be restricted from further contributions at the maintainers' discretion.
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [Spec-Driven Development Methodology](./spec-driven.md)
|
- [Spec-Driven Development Methodology](./spec-driven.md)
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -17,6 +17,7 @@
|
|||||||
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
|
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
|
||||||
- [⚡ Get started](#-get-started)
|
- [⚡ Get started](#-get-started)
|
||||||
- [📽️ Video Overview](#️-video-overview)
|
- [📽️ Video Overview](#️-video-overview)
|
||||||
|
- [🤖 Supported AI Agents](#-supported-ai-agents)
|
||||||
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
||||||
- [📚 Core philosophy](#-core-philosophy)
|
- [📚 Core philosophy](#-core-philosophy)
|
||||||
- [🌟 Development phases](#-development-phases)
|
- [🌟 Development phases](#-development-phases)
|
||||||
@@ -92,6 +93,21 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
|
|||||||
|
|
||||||
[](https://www.youtube.com/watch?v=a9eR1xsfvHg&pp=0gcJCckJAYcqIYzv)
|
[](https://www.youtube.com/watch?v=a9eR1xsfvHg&pp=0gcJCckJAYcqIYzv)
|
||||||
|
|
||||||
|
## 🤖 Supported AI Agents
|
||||||
|
|
||||||
|
| Agent | Support | Notes |
|
||||||
|
|-----------------------------------------------------------|---------|---------------------------------------------------|
|
||||||
|
| [Claude Code](https://www.anthropic.com/claude-code) | ✅ | |
|
||||||
|
| [GitHub Copilot](https://code.visualstudio.com/) | ✅ | |
|
||||||
|
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | ✅ | |
|
||||||
|
| [Cursor](https://cursor.sh/) | ✅ | |
|
||||||
|
| [Qwen Code](https://github.com/QwenLM/qwen-code) | ✅ | |
|
||||||
|
| [opencode](https://opencode.ai/) | ✅ | |
|
||||||
|
| [Windsurf](https://windsurf.com/) | ✅ | |
|
||||||
|
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | |
|
||||||
|
| [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | |
|
||||||
|
| [Codex CLI](https://github.com/openai/codex) | ⚠️ | Codex [does not support](https://github.com/openai/codex/issues/2890) custom arguments for slash commands. |
|
||||||
|
|
||||||
## 🔧 Specify CLI Reference
|
## 🔧 Specify CLI Reference
|
||||||
|
|
||||||
The `specify` command supports the following options:
|
The `specify` command supports the following options:
|
||||||
@@ -101,14 +117,14 @@ The `specify` command supports the following options:
|
|||||||
| Command | Description |
|
| Command | Description |
|
||||||
|-------------|----------------------------------------------------------------|
|
|-------------|----------------------------------------------------------------|
|
||||||
| `init` | Initialize a new Specify project from the latest template |
|
| `init` | Initialize a new Specify project from the latest template |
|
||||||
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`) |
|
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`) |
|
||||||
|
|
||||||
### `specify init` Arguments & Options
|
### `specify init` Arguments & Options
|
||||||
|
|
||||||
| Argument/Option | Type | Description |
|
| Argument/Option | Type | Description |
|
||||||
|------------------------|----------|------------------------------------------------------------------------------|
|
|------------------------|----------|------------------------------------------------------------------------------|
|
||||||
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`) |
|
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`) |
|
||||||
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, `qwen`, `opencode`, or `windsurf` |
|
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, or `auggie` |
|
||||||
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||||
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
||||||
| `--no-git` | Flag | Skip git repository initialization |
|
| `--no-git` | Flag | Skip git repository initialization |
|
||||||
@@ -163,6 +179,12 @@ After running `specify init`, your AI coding agent will have access to these sla
|
|||||||
| `/tasks` | Generate actionable task lists for implementation |
|
| `/tasks` | Generate actionable task lists for implementation |
|
||||||
| `/implement` | Execute all tasks to build the feature according to the plan |
|
| `/implement` | Execute all tasks to build the feature according to the plan |
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|------------------|------------------------------------------------------------------------------------------------|
|
||||||
|
| `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.<br/>**Must be set in the context of the agent you're working with prior to using `/plan` or follow-up commands. |
|
||||||
|
|
||||||
## 📚 Core philosophy
|
## 📚 Core philosophy
|
||||||
|
|
||||||
Spec-Driven Development is a structured process that emphasizes:
|
Spec-Driven Development is a structured process that emphasizes:
|
||||||
@@ -214,6 +236,8 @@ Our research and experimentation focus on:
|
|||||||
- [Python 3.11+](https://www.python.org/downloads/)
|
- [Python 3.11+](https://www.python.org/downloads/)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
|
||||||
|
If you encounter issues with an agent, please open an issue so we can refine the integration.
|
||||||
|
|
||||||
## 📖 Learn more
|
## 📖 Learn more
|
||||||
|
|
||||||
- **[Complete Spec-Driven Development Methodology](./spec-driven.md)** - Deep dive into the full process
|
- **[Complete Spec-Driven Development Methodology](./spec-driven.md)** - Deep dive into the full process
|
||||||
@@ -319,10 +343,9 @@ At this stage, your project folder contents should resemble the following:
|
|||||||
│ ├── constitution.md
|
│ ├── constitution.md
|
||||||
│ └── constitution_update_checklist.md
|
│ └── constitution_update_checklist.md
|
||||||
├── scripts
|
├── scripts
|
||||||
│ ├── check-task-prerequisites.sh
|
│ ├── check-prerequisites.sh
|
||||||
│ ├── common.sh
|
│ ├── common.sh
|
||||||
│ ├── create-new-feature.sh
|
│ ├── create-new-feature.sh
|
||||||
│ ├── get-feature-paths.sh
|
|
||||||
│ ├── setup-plan.sh
|
│ ├── setup-plan.sh
|
||||||
│ └── update-claude-md.sh
|
│ └── update-claude-md.sh
|
||||||
├── specs
|
├── specs
|
||||||
@@ -371,10 +394,9 @@ The output of this step will include a number of implementation detail documents
|
|||||||
│ ├── constitution.md
|
│ ├── constitution.md
|
||||||
│ └── constitution_update_checklist.md
|
│ └── constitution_update_checklist.md
|
||||||
├── scripts
|
├── scripts
|
||||||
│ ├── check-task-prerequisites.sh
|
│ ├── check-prerequisites.sh
|
||||||
│ ├── common.sh
|
│ ├── common.sh
|
||||||
│ ├── create-new-feature.sh
|
│ ├── create-new-feature.sh
|
||||||
│ ├── get-feature-paths.sh
|
|
||||||
│ ├── setup-plan.sh
|
│ ├── setup-plan.sh
|
||||||
│ └── update-claude-md.sh
|
│ └── update-claude-md.sh
|
||||||
├── specs
|
├── specs
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.0.10"
|
version = "0.0.14"
|
||||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/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 [[ ! -f "$TASKS" ]]; then echo "ERROR: tasks.md not found in $FEATURE_DIR"; echo "Run /tasks 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"); [[ -f "$TASKS" ]] && docs+=("tasks.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"; check_file "$TASKS" "tasks.md"; fi
|
|
||||||
160
scripts/bash/check-prerequisites.sh
Normal file
160
scripts/bash/check-prerequisites.sh
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Consolidated prerequisite checking script
|
||||||
|
#
|
||||||
|
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
# It replaces the functionality previously spread across multiple scripts.
|
||||||
|
#
|
||||||
|
# Usage: ./check-prerequisites.sh [OPTIONS]
|
||||||
|
#
|
||||||
|
# OPTIONS:
|
||||||
|
# --json Output in JSON format
|
||||||
|
# --require-tasks Require tasks.md to exist (for implementation phase)
|
||||||
|
# --include-tasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
# --paths-only Only output path variables (no validation)
|
||||||
|
# --help, -h Show help message
|
||||||
|
#
|
||||||
|
# OUTPUTS:
|
||||||
|
# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
|
||||||
|
# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
|
||||||
|
# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
JSON_MODE=false
|
||||||
|
REQUIRE_TASKS=false
|
||||||
|
INCLUDE_TASKS=false
|
||||||
|
PATHS_ONLY=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--json)
|
||||||
|
JSON_MODE=true
|
||||||
|
;;
|
||||||
|
--require-tasks)
|
||||||
|
REQUIRE_TASKS=true
|
||||||
|
;;
|
||||||
|
--include-tasks)
|
||||||
|
INCLUDE_TASKS=true
|
||||||
|
;;
|
||||||
|
--paths-only)
|
||||||
|
PATHS_ONLY=true
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
cat << 'EOF'
|
||||||
|
Usage: check-prerequisites.sh [OPTIONS]
|
||||||
|
|
||||||
|
Consolidated prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--json Output in JSON format
|
||||||
|
--require-tasks Require tasks.md to exist (for implementation phase)
|
||||||
|
--include-tasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
--paths-only Only output path variables (no prerequisite validation)
|
||||||
|
--help, -h Show this help message
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
# Check task prerequisites (plan.md required)
|
||||||
|
./check-prerequisites.sh --json
|
||||||
|
|
||||||
|
# Check implementation prerequisites (plan.md + tasks.md required)
|
||||||
|
./check-prerequisites.sh --json --require-tasks --include-tasks
|
||||||
|
|
||||||
|
# Get feature paths only (no validation)
|
||||||
|
./check-prerequisites.sh --paths-only
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Source common functions
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Get feature paths and validate branch
|
||||||
|
eval $(get_feature_paths)
|
||||||
|
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||||
|
|
||||||
|
# If paths-only mode, output paths and exit
|
||||||
|
if $PATHS_ONLY; then
|
||||||
|
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"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate required directories and files
|
||||||
|
if [[ ! -d "$FEATURE_DIR" ]]; then
|
||||||
|
echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
|
||||||
|
echo "Run /specify first to create the feature structure." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$IMPL_PLAN" ]]; then
|
||||||
|
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /plan first to create the implementation plan." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for tasks.md if required
|
||||||
|
if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
|
||||||
|
echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /tasks first to create the task list." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build list of available documents
|
||||||
|
docs=()
|
||||||
|
|
||||||
|
# Always check these optional docs
|
||||||
|
[[ -f "$RESEARCH" ]] && docs+=("research.md")
|
||||||
|
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
|
||||||
|
|
||||||
|
# Check contracts directory (only if it exists and has files)
|
||||||
|
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
|
||||||
|
docs+=("contracts/")
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
|
||||||
|
|
||||||
|
# Include tasks.md if requested and it exists
|
||||||
|
if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
|
||||||
|
docs+=("tasks.md")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if $JSON_MODE; then
|
||||||
|
# Build JSON array of documents
|
||||||
|
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||||
|
json_docs="[]"
|
||||||
|
else
|
||||||
|
json_docs=$(printf '"%s",' "${docs[@]}")
|
||||||
|
json_docs="[${json_docs%,}]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
|
||||||
|
else
|
||||||
|
# Text output
|
||||||
|
echo "FEATURE_DIR:$FEATURE_DIR"
|
||||||
|
echo "AVAILABLE_DOCS:"
|
||||||
|
|
||||||
|
# Show status of each potential document
|
||||||
|
check_file "$RESEARCH" "research.md"
|
||||||
|
check_file "$DATA_MODEL" "data-model.md"
|
||||||
|
check_dir "$CONTRACTS_DIR" "contracts/"
|
||||||
|
check_file "$QUICKSTART" "quickstart.md"
|
||||||
|
|
||||||
|
if $INCLUDE_TASKS; then
|
||||||
|
check_file "$TASKS" "tasks.md"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,16 +1,84 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# (Moved to scripts/bash/) Common functions and variables for all scripts
|
# Common functions and variables for all scripts
|
||||||
|
|
||||||
get_repo_root() { git rev-parse --show-toplevel; }
|
# Get repository root, with fallback for non-git repositories
|
||||||
get_current_branch() { git rev-parse --abbrev-ref HEAD; }
|
get_repo_root() {
|
||||||
|
if git rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
else
|
||||||
|
# Fall back to script location for non-git repos
|
||||||
|
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
(cd "$script_dir/../../.." && pwd)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get current branch, with fallback for non-git repositories
|
||||||
|
get_current_branch() {
|
||||||
|
# First check if SPECIFY_FEATURE environment variable is set
|
||||||
|
if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
|
||||||
|
echo "$SPECIFY_FEATURE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Then check git if available
|
||||||
|
if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
|
||||||
|
git rev-parse --abbrev-ref HEAD
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For non-git repos, try to find the latest feature directory
|
||||||
|
local repo_root=$(get_repo_root)
|
||||||
|
local specs_dir="$repo_root/specs"
|
||||||
|
|
||||||
|
if [[ -d "$specs_dir" ]]; then
|
||||||
|
local latest_feature=""
|
||||||
|
local highest=0
|
||||||
|
|
||||||
|
for dir in "$specs_dir"/*; do
|
||||||
|
if [[ -d "$dir" ]]; then
|
||||||
|
local dirname=$(basename "$dir")
|
||||||
|
if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
|
||||||
|
local number=${BASH_REMATCH[1]}
|
||||||
|
number=$((10#$number))
|
||||||
|
if [[ "$number" -gt "$highest" ]]; then
|
||||||
|
highest=$number
|
||||||
|
latest_feature=$dirname
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$latest_feature" ]]; then
|
||||||
|
echo "$latest_feature"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "main" # Final fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if we have git available
|
||||||
|
has_git() {
|
||||||
|
git rev-parse --show-toplevel >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
check_feature_branch() {
|
check_feature_branch() {
|
||||||
local branch="$1"
|
local branch="$1"
|
||||||
|
local has_git_repo="$2"
|
||||||
|
|
||||||
|
# For non-git repos, we can't enforce branch naming but still provide output
|
||||||
|
if [[ "$has_git_repo" != "true" ]]; then
|
||||||
|
echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
||||||
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
||||||
echo "Feature branches should be named like: 001-feature-name" >&2
|
echo "Feature branches should be named like: 001-feature-name" >&2
|
||||||
return 1
|
return 1
|
||||||
fi; return 0
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
get_feature_dir() { echo "$1/specs/$2"; }
|
get_feature_dir() { echo "$1/specs/$2"; }
|
||||||
@@ -18,10 +86,18 @@ get_feature_dir() { echo "$1/specs/$2"; }
|
|||||||
get_feature_paths() {
|
get_feature_paths() {
|
||||||
local repo_root=$(get_repo_root)
|
local repo_root=$(get_repo_root)
|
||||||
local current_branch=$(get_current_branch)
|
local current_branch=$(get_current_branch)
|
||||||
|
local has_git_repo="false"
|
||||||
|
|
||||||
|
if has_git; then
|
||||||
|
has_git_repo="true"
|
||||||
|
fi
|
||||||
|
|
||||||
local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
|
local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
REPO_ROOT='$repo_root'
|
REPO_ROOT='$repo_root'
|
||||||
CURRENT_BRANCH='$current_branch'
|
CURRENT_BRANCH='$current_branch'
|
||||||
|
HAS_GIT='$has_git_repo'
|
||||||
FEATURE_DIR='$feature_dir'
|
FEATURE_DIR='$feature_dir'
|
||||||
FEATURE_SPEC='$feature_dir/spec.md'
|
FEATURE_SPEC='$feature_dir/spec.md'
|
||||||
IMPL_PLAN='$feature_dir/plan.md'
|
IMPL_PLAN='$feature_dir/plan.md'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# (Moved to scripts/bash/) Create a new feature with branch, directory structure, and template
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
JSON_MODE=false
|
JSON_MODE=false
|
||||||
@@ -18,16 +18,33 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Function to find the repository root by searching for existing project markers
|
||||||
|
find_repo_root() {
|
||||||
|
local dir="$1"
|
||||||
|
while [ "$dir" != "/" ]; do
|
||||||
|
if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then
|
||||||
|
echo "$dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
dir="$(dirname "$dir")"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Resolve repository root. Prefer git information when available, but fall back
|
# Resolve repository root. Prefer git information when available, but fall back
|
||||||
# to the script location so the workflow still functions in repositories that
|
# to searching for repository markers so the workflow still functions in repositories that
|
||||||
# were initialised with --no-git.
|
# were initialised with --no-git.
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
FALLBACK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
||||||
if git rev-parse --show-toplevel >/dev/null 2>&1; then
|
if git rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
HAS_GIT=true
|
HAS_GIT=true
|
||||||
else
|
else
|
||||||
REPO_ROOT="$FALLBACK_ROOT"
|
REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")"
|
||||||
|
if [ -z "$REPO_ROOT" ]; then
|
||||||
|
echo "Error: Could not determine repository root. Please run this script from within the repository." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
HAS_GIT=false
|
HAS_GIT=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -67,10 +84,14 @@ TEMPLATE="$REPO_ROOT/templates/spec-template.md"
|
|||||||
SPEC_FILE="$FEATURE_DIR/spec.md"
|
SPEC_FILE="$FEATURE_DIR/spec.md"
|
||||||
if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
|
if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
|
||||||
|
|
||||||
|
# Set the SPECIFY_FEATURE environment variable for the current session
|
||||||
|
export SPECIFY_FEATURE="$BRANCH_NAME"
|
||||||
|
|
||||||
if $JSON_MODE; then
|
if $JSON_MODE; then
|
||||||
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
|
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
|
||||||
else
|
else
|
||||||
echo "BRANCH_NAME: $BRANCH_NAME"
|
echo "BRANCH_NAME: $BRANCH_NAME"
|
||||||
echo "SPEC_FILE: $SPEC_FILE"
|
echo "SPEC_FILE: $SPEC_FILE"
|
||||||
echo "FEATURE_NUM: $FEATURE_NUM"
|
echo "FEATURE_NUM: $FEATURE_NUM"
|
||||||
|
echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@@ -1,17 +1,60 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
JSON_MODE=false
|
JSON_MODE=false
|
||||||
for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; --help|-h) echo "Usage: $0 [--json]"; exit 0 ;; esac; done
|
ARGS=()
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--json)
|
||||||
|
JSON_MODE=true
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--json]"
|
||||||
|
echo " --json Output results in JSON format"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ARGS+=("$arg")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Get script directory and load common functions
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "$SCRIPT_DIR/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Get all paths and variables from common functions
|
||||||
eval $(get_feature_paths)
|
eval $(get_feature_paths)
|
||||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
|
||||||
|
# Check if we're on a proper feature branch (only for git repos)
|
||||||
|
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||||
|
|
||||||
|
# Ensure the feature directory exists
|
||||||
mkdir -p "$FEATURE_DIR"
|
mkdir -p "$FEATURE_DIR"
|
||||||
|
|
||||||
|
# Copy plan template if it exists
|
||||||
TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
|
TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
|
||||||
[[ -f "$TEMPLATE" ]] && cp "$TEMPLATE" "$IMPL_PLAN"
|
if [[ -f "$TEMPLATE" ]]; then
|
||||||
if $JSON_MODE; then
|
cp "$TEMPLATE" "$IMPL_PLAN"
|
||||||
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
|
echo "Copied plan template to $IMPL_PLAN"
|
||||||
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH"
|
|
||||||
else
|
else
|
||||||
echo "FEATURE_SPEC: $FEATURE_SPEC"; echo "IMPL_PLAN: $IMPL_PLAN"; echo "SPECS_DIR: $FEATURE_DIR"; echo "BRANCH: $CURRENT_BRANCH"
|
echo "Warning: Plan template not found at $TEMPLATE"
|
||||||
|
# Create a basic plan file if template doesn't exist
|
||||||
|
touch "$IMPL_PLAN"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if $JSON_MODE; then
|
||||||
|
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \
|
||||||
|
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT"
|
||||||
|
else
|
||||||
|
echo "FEATURE_SPEC: $FEATURE_SPEC"
|
||||||
|
echo "IMPL_PLAN: $IMPL_PLAN"
|
||||||
|
echo "SPECS_DIR: $FEATURE_DIR"
|
||||||
|
echo "BRANCH: $CURRENT_BRANCH"
|
||||||
|
echo "HAS_GIT: $HAS_GIT"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -48,10 +48,14 @@ set -o pipefail
|
|||||||
# Configuration and Global Variables
|
# Configuration and Global Variables
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|
||||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
# Get script directory and load common functions
|
||||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
NEW_PLAN="$FEATURE_DIR/plan.md"
|
|
||||||
|
# Get all paths and variables from common functions
|
||||||
|
eval $(get_feature_paths)
|
||||||
|
|
||||||
|
NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code
|
||||||
AGENT_TYPE="${1:-}"
|
AGENT_TYPE="${1:-}"
|
||||||
|
|
||||||
# Agent-specific file paths
|
# Agent-specific file paths
|
||||||
@@ -62,6 +66,8 @@ CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
|
|||||||
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
||||||
AGENTS_FILE="$REPO_ROOT/AGENTS.md"
|
AGENTS_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
||||||
|
KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md"
|
||||||
|
AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
|
||||||
|
|
||||||
# Template file
|
# Template file
|
||||||
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
|
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
|
||||||
@@ -108,22 +114,24 @@ trap cleanup EXIT INT TERM
|
|||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|
||||||
validate_environment() {
|
validate_environment() {
|
||||||
# Check if we're in a git repository
|
# Check if we have a current branch/feature (git or non-git)
|
||||||
if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
|
|
||||||
log_error "Not in a git repository"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if we have a current branch
|
|
||||||
if [[ -z "$CURRENT_BRANCH" ]]; then
|
if [[ -z "$CURRENT_BRANCH" ]]; then
|
||||||
log_error "Unable to determine current git branch"
|
log_error "Unable to determine current feature"
|
||||||
|
if [[ "$HAS_GIT" == "true" ]]; then
|
||||||
|
log_info "Make sure you're on a feature branch"
|
||||||
|
else
|
||||||
|
log_info "Set SPECIFY_FEATURE environment variable or create a feature first"
|
||||||
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if plan.md exists
|
# Check if plan.md exists
|
||||||
if [[ ! -f "$NEW_PLAN" ]]; then
|
if [[ ! -f "$NEW_PLAN" ]]; then
|
||||||
log_error "No plan.md found at $NEW_PLAN"
|
log_error "No plan.md found at $NEW_PLAN"
|
||||||
log_info "Make sure you're on a feature branch with a corresponding spec directory"
|
log_info "Make sure you're working on a feature with a corresponding spec directory"
|
||||||
|
if [[ "$HAS_GIT" != "true" ]]; then
|
||||||
|
log_info "Use: export SPECIFY_FEATURE=your-feature-name or create a new feature first"
|
||||||
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -142,9 +150,10 @@ extract_plan_field() {
|
|||||||
local field_pattern="$1"
|
local field_pattern="$1"
|
||||||
local plan_file="$2"
|
local plan_file="$2"
|
||||||
|
|
||||||
grep "^**${field_pattern}**: " "$plan_file" 2>/dev/null | \
|
grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \
|
||||||
head -1 | \
|
head -1 | \
|
||||||
sed "s/^**${field_pattern}**: //" | \
|
sed "s|^\*\*${field_pattern}\*\*: ||" | \
|
||||||
|
sed 's/^[ \t]*//;s/[ \t]*$//' | \
|
||||||
grep -v "NEEDS CLARIFICATION" | \
|
grep -v "NEEDS CLARIFICATION" | \
|
||||||
grep -v "^N/A$" || echo ""
|
grep -v "^N/A$" || echo ""
|
||||||
}
|
}
|
||||||
@@ -188,6 +197,31 @@ parse_plan_data() {
|
|||||||
log_info "Found project type: $NEW_PROJECT_TYPE"
|
log_info "Found project type: $NEW_PROJECT_TYPE"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format_technology_stack() {
|
||||||
|
local lang="$1"
|
||||||
|
local framework="$2"
|
||||||
|
local parts=()
|
||||||
|
|
||||||
|
# Add non-empty parts
|
||||||
|
[[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang")
|
||||||
|
[[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework")
|
||||||
|
|
||||||
|
# Join with proper formatting
|
||||||
|
if [[ ${#parts[@]} -eq 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
elif [[ ${#parts[@]} -eq 1 ]]; then
|
||||||
|
echo "${parts[0]}"
|
||||||
|
else
|
||||||
|
# Join multiple parts with " + "
|
||||||
|
local result="${parts[0]}"
|
||||||
|
for ((i=1; i<${#parts[@]}; i++)); do
|
||||||
|
result="$result + ${parts[i]}"
|
||||||
|
done
|
||||||
|
echo "$result"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Template and Content Generation Functions
|
# Template and Content Generation Functions
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
@@ -196,12 +230,9 @@ get_project_structure() {
|
|||||||
local project_type="$1"
|
local project_type="$1"
|
||||||
|
|
||||||
if [[ "$project_type" == *"web"* ]]; then
|
if [[ "$project_type" == *"web"* ]]; then
|
||||||
echo "backend/
|
echo "backend/\\nfrontend/\\ntests/"
|
||||||
frontend/
|
|
||||||
tests/"
|
|
||||||
else
|
else
|
||||||
echo "src/
|
echo "src/\\ntests/"
|
||||||
tests/"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,169 +293,65 @@ create_new_agent_file() {
|
|||||||
local language_conventions
|
local language_conventions
|
||||||
language_conventions=$(get_language_conventions "$NEW_LANG")
|
language_conventions=$(get_language_conventions "$NEW_LANG")
|
||||||
|
|
||||||
# Perform substitutions with error checking
|
# Perform substitutions with error checking using safer approach
|
||||||
|
# Escape special characters for sed by using a different delimiter or escaping
|
||||||
|
local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g')
|
||||||
|
local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g')
|
||||||
|
local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g')
|
||||||
|
|
||||||
|
# Build technology stack and recent change strings conditionally
|
||||||
|
local tech_stack
|
||||||
|
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
|
||||||
|
tech_stack="- $escaped_lang + $escaped_framework ($escaped_branch)"
|
||||||
|
elif [[ -n "$escaped_lang" ]]; then
|
||||||
|
tech_stack="- $escaped_lang ($escaped_branch)"
|
||||||
|
elif [[ -n "$escaped_framework" ]]; then
|
||||||
|
tech_stack="- $escaped_framework ($escaped_branch)"
|
||||||
|
else
|
||||||
|
tech_stack="- ($escaped_branch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local recent_change
|
||||||
|
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
|
||||||
|
recent_change="- $escaped_branch: Added $escaped_lang + $escaped_framework"
|
||||||
|
elif [[ -n "$escaped_lang" ]]; then
|
||||||
|
recent_change="- $escaped_branch: Added $escaped_lang"
|
||||||
|
elif [[ -n "$escaped_framework" ]]; then
|
||||||
|
recent_change="- $escaped_branch: Added $escaped_framework"
|
||||||
|
else
|
||||||
|
recent_change="- $escaped_branch: Added"
|
||||||
|
fi
|
||||||
|
|
||||||
local substitutions=(
|
local substitutions=(
|
||||||
"s/\[PROJECT NAME\]/$project_name/"
|
"s|\[PROJECT NAME\]|$project_name|"
|
||||||
"s/\[DATE\]/$current_date/"
|
"s|\[DATE\]|$current_date|"
|
||||||
"s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/"
|
"s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|"
|
||||||
"s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|"
|
"s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g"
|
||||||
"s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|"
|
"s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|"
|
||||||
"s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|"
|
"s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|"
|
||||||
"s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK|"
|
"s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|"
|
||||||
)
|
)
|
||||||
|
|
||||||
for substitution in "${substitutions[@]}"; do
|
for substitution in "${substitutions[@]}"; do
|
||||||
if ! sed -i.bak "$substitution" "$temp_file"; then
|
if ! sed -i.bak -e "$substitution" "$temp_file"; then
|
||||||
log_error "Failed to perform substitution: $substitution"
|
log_error "Failed to perform substitution: $substitution"
|
||||||
rm -f "$temp_file" "$temp_file.bak"
|
rm -f "$temp_file" "$temp_file.bak"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Convert \n sequences to actual newlines
|
||||||
|
newline=$(printf '\n')
|
||||||
|
sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file"
|
||||||
|
|
||||||
# Clean up backup files
|
# Clean up backup files
|
||||||
rm -f "$temp_file.bak"
|
rm -f "$temp_file.bak" "$temp_file.bak2"
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
update_active_technologies() {
|
|
||||||
local target_file="$1"
|
|
||||||
local temp_file="$2"
|
|
||||||
|
|
||||||
# Find the Active Technologies section and add new entries
|
|
||||||
local tech_section_start
|
|
||||||
tech_section_start=$(grep -n "## Active Technologies" "$target_file" | cut -d: -f1)
|
|
||||||
|
|
||||||
if [[ -z "$tech_section_start" ]]; then
|
|
||||||
return 0 # No Active Technologies section found
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find the end of the Active Technologies section (next ## heading or empty line)
|
|
||||||
local tech_section_end
|
|
||||||
tech_section_end=$(tail -n +$((tech_section_start + 1)) "$target_file" | grep -n "^## \|^$" | head -1 | cut -d: -f1)
|
|
||||||
|
|
||||||
if [[ -n "$tech_section_end" ]]; then
|
|
||||||
tech_section_end=$((tech_section_start + tech_section_end))
|
|
||||||
else
|
|
||||||
tech_section_end=$(wc -l < "$target_file")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract existing technologies section
|
|
||||||
local existing_tech
|
|
||||||
existing_tech=$(sed -n "${tech_section_start},${tech_section_end}p" "$target_file")
|
|
||||||
|
|
||||||
# Build list of new additions
|
|
||||||
local additions=()
|
|
||||||
if [[ -n "$NEW_LANG" ]] && ! echo "$existing_tech" | grep -q "$NEW_LANG"; then
|
|
||||||
additions+=("- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && ! echo "$existing_tech" | grep -q "$NEW_DB"; then
|
|
||||||
additions+=("- $NEW_DB ($CURRENT_BRANCH)")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we have additions, update the section
|
|
||||||
if [[ ${#additions[@]} -gt 0 ]]; then
|
|
||||||
{
|
|
||||||
# Copy everything before the Active Technologies section
|
|
||||||
head -n $((tech_section_start)) "$target_file"
|
|
||||||
|
|
||||||
# Copy existing tech section content
|
|
||||||
sed -n "$((tech_section_start + 1)),$((tech_section_end - 1))p" "$target_file"
|
|
||||||
|
|
||||||
# Add new technologies
|
|
||||||
printf '%s\n' "${additions[@]}"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Copy everything after the Active Technologies section
|
|
||||||
tail -n +$((tech_section_end + 1)) "$target_file"
|
|
||||||
} > "$temp_file"
|
|
||||||
else
|
|
||||||
cp "$target_file" "$temp_file"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
update_recent_changes() {
|
|
||||||
local temp_file="$1"
|
|
||||||
local temp_file2="$2"
|
|
||||||
|
|
||||||
# Find Recent Changes section
|
|
||||||
local changes_section_start
|
|
||||||
changes_section_start=$(grep -n "## Recent Changes" "$temp_file" | cut -d: -f1)
|
|
||||||
|
|
||||||
if [[ -z "$changes_section_start" ]]; then
|
|
||||||
return 0 # No Recent Changes section found
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find the end of the Recent Changes section
|
|
||||||
local changes_section_end
|
|
||||||
changes_section_end=$(tail -n +$((changes_section_start + 1)) "$temp_file" | grep -n "^## \|^$" | head -1 | cut -d: -f1)
|
|
||||||
|
|
||||||
if [[ -n "$changes_section_end" ]]; then
|
|
||||||
changes_section_end=$((changes_section_start + changes_section_end))
|
|
||||||
else
|
|
||||||
changes_section_end=$(wc -l < "$temp_file")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract existing changes, keep only non-empty lines, and limit to 2 (so we can add 1 new one)
|
|
||||||
local existing_changes=()
|
|
||||||
while IFS= read -r line; do
|
|
||||||
if [[ -n "$line" ]] && [[ "$line" == "- "* ]]; then
|
|
||||||
existing_changes+=("$line")
|
|
||||||
fi
|
|
||||||
done < <(sed -n "$((changes_section_start + 1)),$((changes_section_end - 1))p" "$temp_file")
|
|
||||||
|
|
||||||
# Keep only the first 2 existing changes
|
|
||||||
existing_changes=("${existing_changes[@]:0:2}")
|
|
||||||
|
|
||||||
# Create updated Recent Changes section
|
|
||||||
{
|
|
||||||
# Copy everything before Recent Changes
|
|
||||||
head -n "$changes_section_start" "$temp_file"
|
|
||||||
|
|
||||||
# Add new change at the top
|
|
||||||
echo "- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK"
|
|
||||||
|
|
||||||
# Add existing changes (up to 2)
|
|
||||||
printf '%s\n' "${existing_changes[@]}"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Copy everything after Recent Changes section
|
|
||||||
tail -n +$((changes_section_end + 1)) "$temp_file"
|
|
||||||
} > "$temp_file2"
|
|
||||||
}
|
|
||||||
|
|
||||||
update_last_updated() {
|
|
||||||
local temp_file="$1"
|
|
||||||
local current_date="$2"
|
|
||||||
|
|
||||||
# Update the "Last updated" timestamp
|
|
||||||
sed -i.bak "s/Last updated: [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/Last updated: $current_date/" "$temp_file"
|
|
||||||
rm -f "$temp_file.bak"
|
|
||||||
}
|
|
||||||
|
|
||||||
preserve_manual_additions() {
|
|
||||||
local target_file="$1"
|
|
||||||
local temp_file="$2"
|
|
||||||
|
|
||||||
# Check if there are manual additions to preserve
|
|
||||||
local manual_start manual_end
|
|
||||||
manual_start=$(grep -n "<!-- MANUAL ADDITIONS START -->" "$target_file" 2>/dev/null | cut -d: -f1 || echo "")
|
|
||||||
manual_end=$(grep -n "<!-- MANUAL ADDITIONS END -->" "$target_file" 2>/dev/null | cut -d: -f1 || echo "")
|
|
||||||
|
|
||||||
if [[ -n "$manual_start" ]] && [[ -n "$manual_end" ]]; then
|
|
||||||
# Extract manual additions
|
|
||||||
local manual_file="/tmp/manual_additions_$$"
|
|
||||||
sed -n "${manual_start},${manual_end}p" "$target_file" > "$manual_file"
|
|
||||||
|
|
||||||
# Remove any existing manual additions from temp file
|
|
||||||
sed -i.bak '/<!-- MANUAL ADDITIONS START -->/,/<!-- MANUAL ADDITIONS END -->/d' "$temp_file"
|
|
||||||
rm -f "$temp_file.bak"
|
|
||||||
|
|
||||||
# Append preserved manual additions
|
|
||||||
cat "$manual_file" >> "$temp_file"
|
|
||||||
rm -f "$manual_file"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
update_existing_agent_file() {
|
update_existing_agent_file() {
|
||||||
local target_file="$1"
|
local target_file="$1"
|
||||||
@@ -432,26 +359,110 @@ update_existing_agent_file() {
|
|||||||
|
|
||||||
log_info "Updating existing agent context file..."
|
log_info "Updating existing agent context file..."
|
||||||
|
|
||||||
local temp_file1="/tmp/agent_update_1_$$"
|
# Use a single temporary file for atomic update
|
||||||
local temp_file2="/tmp/agent_update_2_$$"
|
local temp_file
|
||||||
|
temp_file=$(mktemp) || {
|
||||||
|
log_error "Failed to create temporary file"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Step 1: Update Active Technologies section
|
# Process the file in one pass
|
||||||
update_active_technologies "$target_file" "$temp_file1"
|
local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK")
|
||||||
|
local new_tech_entries=()
|
||||||
|
local new_change_entry=""
|
||||||
|
|
||||||
# Step 2: Update Recent Changes section
|
# Prepare new technology entries
|
||||||
update_recent_changes "$temp_file1" "$temp_file2"
|
if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then
|
||||||
|
new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)")
|
||||||
|
fi
|
||||||
|
|
||||||
# Step 3: Update timestamp
|
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then
|
||||||
update_last_updated "$temp_file2" "$current_date"
|
new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)")
|
||||||
|
fi
|
||||||
|
|
||||||
# Step 4: Preserve manual additions
|
# Prepare new change entry
|
||||||
preserve_manual_additions "$target_file" "$temp_file2"
|
if [[ -n "$tech_stack" ]]; then
|
||||||
|
new_change_entry="- $CURRENT_BRANCH: Added $tech_stack"
|
||||||
|
elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then
|
||||||
|
new_change_entry="- $CURRENT_BRANCH: Added $NEW_DB"
|
||||||
|
fi
|
||||||
|
|
||||||
# Move the final result to target
|
# Process file line by line
|
||||||
mv "$temp_file2" "$target_file"
|
local in_tech_section=false
|
||||||
|
local in_changes_section=false
|
||||||
|
local tech_entries_added=false
|
||||||
|
local changes_entries_added=false
|
||||||
|
local existing_changes_count=0
|
||||||
|
|
||||||
# Cleanup
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
rm -f "$temp_file1"
|
# Handle Active Technologies section
|
||||||
|
if [[ "$line" == "## Active Technologies" ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
in_tech_section=true
|
||||||
|
continue
|
||||||
|
elif [[ $in_tech_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
|
||||||
|
# Add new tech entries before closing the section
|
||||||
|
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||||
|
tech_entries_added=true
|
||||||
|
fi
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
in_tech_section=false
|
||||||
|
continue
|
||||||
|
elif [[ $in_tech_section == true ]] && [[ -z "$line" ]]; then
|
||||||
|
# Add new tech entries before empty line in tech section
|
||||||
|
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||||
|
tech_entries_added=true
|
||||||
|
fi
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle Recent Changes section
|
||||||
|
if [[ "$line" == "## Recent Changes" ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
# Add new change entry right after the heading
|
||||||
|
if [[ -n "$new_change_entry" ]]; then
|
||||||
|
echo "$new_change_entry" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
in_changes_section=true
|
||||||
|
changes_entries_added=true
|
||||||
|
continue
|
||||||
|
elif [[ $in_changes_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
in_changes_section=false
|
||||||
|
continue
|
||||||
|
elif [[ $in_changes_section == true ]] && [[ "$line" == "- "* ]]; then
|
||||||
|
# Keep only first 2 existing changes
|
||||||
|
if [[ $existing_changes_count -lt 2 ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
((existing_changes_count++))
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update timestamp
|
||||||
|
if [[ "$line" =~ \*\*Last\ updated\*\*:.*[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then
|
||||||
|
echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file"
|
||||||
|
else
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
done < "$target_file"
|
||||||
|
|
||||||
|
# Post-loop check: if we're still in the Active Technologies section and haven't added new entries
|
||||||
|
if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Move temp file to target atomically
|
||||||
|
if ! mv "$temp_file" "$target_file"; then
|
||||||
|
log_error "Failed to update target file"
|
||||||
|
rm -f "$temp_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Main Agent File Update Function
|
# Main Agent File Update Function
|
||||||
@@ -559,9 +570,15 @@ update_specific_agent() {
|
|||||||
windsurf)
|
windsurf)
|
||||||
update_agent_file "$WINDSURF_FILE" "Windsurf"
|
update_agent_file "$WINDSURF_FILE" "Windsurf"
|
||||||
;;
|
;;
|
||||||
|
kilocode)
|
||||||
|
update_agent_file "$KILOCODE_FILE" "Kilo Code"
|
||||||
|
;;
|
||||||
|
auggie)
|
||||||
|
update_agent_file "$AUGGIE_FILE" "Auggie CLI"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown agent type '$agent_type'"
|
log_error "Unknown agent type '$agent_type'"
|
||||||
log_error "Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf"
|
log_error "Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -606,6 +623,16 @@ update_all_existing_agents() {
|
|||||||
found_agent=true
|
found_agent=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$KILOCODE_FILE" ]]; then
|
||||||
|
update_agent_file "$KILOCODE_FILE" "Kilo Code"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$AUGGIE_FILE" ]]; then
|
||||||
|
update_agent_file "$AUGGIE_FILE" "Auggie CLI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
# If no agent files exist, create a default Claude file
|
# If no agent files exist, create a default Claude file
|
||||||
if [[ "$found_agent" == false ]]; then
|
if [[ "$found_agent" == false ]]; then
|
||||||
log_info "No existing agent files found, creating default Claude file..."
|
log_info "No existing agent files found, creating default Claude file..."
|
||||||
@@ -629,7 +656,7 @@ print_summary() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
log_info "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf]"
|
log_info "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie]"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/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 (-not (Test-Path $paths.TASKS -PathType Leaf)) {
|
|
||||||
Write-Output "ERROR: tasks.md not found in $($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "Run /tasks first to create the task list."
|
|
||||||
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' }
|
|
||||||
if (Test-Path $paths.TASKS) { $docs += 'tasks.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
|
|
||||||
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
|
|
||||||
}
|
|
||||||
137
scripts/powershell/check-prerequisites.ps1
Normal file
137
scripts/powershell/check-prerequisites.ps1
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
|
||||||
|
# Consolidated prerequisite checking script (PowerShell)
|
||||||
|
#
|
||||||
|
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
# It replaces the functionality previously spread across multiple scripts.
|
||||||
|
#
|
||||||
|
# Usage: ./check-prerequisites.ps1 [OPTIONS]
|
||||||
|
#
|
||||||
|
# OPTIONS:
|
||||||
|
# -Json Output in JSON format
|
||||||
|
# -RequireTasks Require tasks.md to exist (for implementation phase)
|
||||||
|
# -IncludeTasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
# -PathsOnly Only output path variables (no validation)
|
||||||
|
# -Help, -h Show help message
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$Json,
|
||||||
|
[switch]$RequireTasks,
|
||||||
|
[switch]$IncludeTasks,
|
||||||
|
[switch]$PathsOnly,
|
||||||
|
[switch]$Help
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
if ($Help) {
|
||||||
|
Write-Output @"
|
||||||
|
Usage: check-prerequisites.ps1 [OPTIONS]
|
||||||
|
|
||||||
|
Consolidated prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-Json Output in JSON format
|
||||||
|
-RequireTasks Require tasks.md to exist (for implementation phase)
|
||||||
|
-IncludeTasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
-PathsOnly Only output path variables (no prerequisite validation)
|
||||||
|
-Help, -h Show this help message
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
# Check task prerequisites (plan.md required)
|
||||||
|
.\check-prerequisites.ps1 -Json
|
||||||
|
|
||||||
|
# Check implementation prerequisites (plan.md + tasks.md required)
|
||||||
|
.\check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
|
||||||
|
|
||||||
|
# Get feature paths only (no validation)
|
||||||
|
.\check-prerequisites.ps1 -PathsOnly
|
||||||
|
|
||||||
|
"@
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Source common functions
|
||||||
|
. "$PSScriptRoot/common.ps1"
|
||||||
|
|
||||||
|
# Get feature paths and validate branch
|
||||||
|
$paths = Get-FeaturePathsEnv
|
||||||
|
|
||||||
|
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$paths.HAS_GIT)) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# If paths-only mode, output paths and exit
|
||||||
|
if ($PathsOnly) {
|
||||||
|
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)"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate required directories and files
|
||||||
|
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 implementation plan."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for tasks.md if required
|
||||||
|
if ($RequireTasks -and -not (Test-Path $paths.TASKS -PathType Leaf)) {
|
||||||
|
Write-Output "ERROR: tasks.md not found in $($paths.FEATURE_DIR)"
|
||||||
|
Write-Output "Run /tasks first to create the task list."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build list of available documents
|
||||||
|
$docs = @()
|
||||||
|
|
||||||
|
# Always check these optional docs
|
||||||
|
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
|
||||||
|
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
|
||||||
|
|
||||||
|
# Check contracts directory (only if it exists and has files)
|
||||||
|
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' }
|
||||||
|
|
||||||
|
# Include tasks.md if requested and it exists
|
||||||
|
if ($IncludeTasks -and (Test-Path $paths.TASKS)) {
|
||||||
|
$docs += 'tasks.md'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if ($Json) {
|
||||||
|
# JSON output
|
||||||
|
[PSCustomObject]@{
|
||||||
|
FEATURE_DIR = $paths.FEATURE_DIR
|
||||||
|
AVAILABLE_DOCS = $docs
|
||||||
|
} | ConvertTo-Json -Compress
|
||||||
|
} else {
|
||||||
|
# Text output
|
||||||
|
Write-Output "FEATURE_DIR:$($paths.FEATURE_DIR)"
|
||||||
|
Write-Output "AVAILABLE_DOCS:"
|
||||||
|
|
||||||
|
# Show status of each potential document
|
||||||
|
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
|
||||||
|
|
||||||
|
if ($IncludeTasks) {
|
||||||
|
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/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
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,84 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
# Common PowerShell functions analogous to common.sh (moved to powershell/)
|
# Common PowerShell functions analogous to common.sh
|
||||||
|
|
||||||
function Get-RepoRoot {
|
function Get-RepoRoot {
|
||||||
git rev-parse --show-toplevel
|
try {
|
||||||
|
$result = git rev-parse --show-toplevel 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Git command failed
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fall back to script location for non-git repos
|
||||||
|
return (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-CurrentBranch {
|
function Get-CurrentBranch {
|
||||||
git rev-parse --abbrev-ref HEAD
|
# First check if SPECIFY_FEATURE environment variable is set
|
||||||
|
if ($env:SPECIFY_FEATURE) {
|
||||||
|
return $env:SPECIFY_FEATURE
|
||||||
|
}
|
||||||
|
|
||||||
|
# Then check git if available
|
||||||
|
try {
|
||||||
|
$result = git rev-parse --abbrev-ref HEAD 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Git command failed
|
||||||
|
}
|
||||||
|
|
||||||
|
# For non-git repos, try to find the latest feature directory
|
||||||
|
$repoRoot = Get-RepoRoot
|
||||||
|
$specsDir = Join-Path $repoRoot "specs"
|
||||||
|
|
||||||
|
if (Test-Path $specsDir) {
|
||||||
|
$latestFeature = ""
|
||||||
|
$highest = 0
|
||||||
|
|
||||||
|
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
||||||
|
if ($_.Name -match '^(\d{3})-') {
|
||||||
|
$num = [int]$matches[1]
|
||||||
|
if ($num -gt $highest) {
|
||||||
|
$highest = $num
|
||||||
|
$latestFeature = $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($latestFeature) {
|
||||||
|
return $latestFeature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Final fallback
|
||||||
|
return "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-HasGit {
|
||||||
|
try {
|
||||||
|
git rev-parse --show-toplevel 2>$null | Out-Null
|
||||||
|
return ($LASTEXITCODE -eq 0)
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Test-FeatureBranch {
|
function Test-FeatureBranch {
|
||||||
param([string]$Branch)
|
param(
|
||||||
|
[string]$Branch,
|
||||||
|
[bool]$HasGit = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
# For non-git repos, we can't enforce branch naming but still provide output
|
||||||
|
if (-not $HasGit) {
|
||||||
|
Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
if ($Branch -notmatch '^[0-9]{3}-') {
|
if ($Branch -notmatch '^[0-9]{3}-') {
|
||||||
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
||||||
Write-Output "Feature branches should be named like: 001-feature-name"
|
Write-Output "Feature branches should be named like: 001-feature-name"
|
||||||
@@ -27,10 +95,13 @@ function Get-FeatureDir {
|
|||||||
function Get-FeaturePathsEnv {
|
function Get-FeaturePathsEnv {
|
||||||
$repoRoot = Get-RepoRoot
|
$repoRoot = Get-RepoRoot
|
||||||
$currentBranch = Get-CurrentBranch
|
$currentBranch = Get-CurrentBranch
|
||||||
|
$hasGit = Test-HasGit
|
||||||
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
|
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
|
||||||
|
|
||||||
[PSCustomObject]@{
|
[PSCustomObject]@{
|
||||||
REPO_ROOT = $repoRoot
|
REPO_ROOT = $repoRoot
|
||||||
CURRENT_BRANCH = $currentBranch
|
CURRENT_BRANCH = $currentBranch
|
||||||
|
HAS_GIT = $hasGit
|
||||||
FEATURE_DIR = $featureDir
|
FEATURE_DIR = $featureDir
|
||||||
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
|
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
|
||||||
IMPL_PLAN = Join-Path $featureDir 'plan.md'
|
IMPL_PLAN = Join-Path $featureDir 'plan.md'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
# Create a new feature (moved to powershell/)
|
# Create a new feature
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[switch]$Json,
|
[switch]$Json,
|
||||||
@@ -9,11 +9,54 @@ param(
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
||||||
Write-Error "Usage: ./create-new-feature.ps1 [-Json] <feature description>"; exit 1
|
Write-Error "Usage: ./create-new-feature.ps1 [-Json] <feature description>"
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
||||||
|
|
||||||
$repoRoot = git rev-parse --show-toplevel
|
# Resolve repository root. Prefer git information when available, but fall back
|
||||||
|
# to searching for repository markers so the workflow still functions in repositories that
|
||||||
|
# were initialised with --no-git.
|
||||||
|
function Find-RepositoryRoot {
|
||||||
|
param(
|
||||||
|
[string]$StartDir,
|
||||||
|
[string[]]$Markers = @('.git', '.specify')
|
||||||
|
)
|
||||||
|
$current = Resolve-Path $StartDir
|
||||||
|
while ($true) {
|
||||||
|
foreach ($marker in $Markers) {
|
||||||
|
if (Test-Path (Join-Path $current $marker)) {
|
||||||
|
return $current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$parent = Split-Path $current -Parent
|
||||||
|
if ($parent -eq $current) {
|
||||||
|
# Reached filesystem root without finding markers
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$current = $parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot)
|
||||||
|
if (-not $fallbackRoot) {
|
||||||
|
Write-Error "Error: Could not determine repository root. Please run this script from within the repository."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
$hasGit = $true
|
||||||
|
} else {
|
||||||
|
throw "Git not available"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$repoRoot = $fallbackRoot
|
||||||
|
$hasGit = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Location $repoRoot
|
||||||
|
|
||||||
$specsDir = Join-Path $repoRoot 'specs'
|
$specsDir = Join-Path $repoRoot 'specs'
|
||||||
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
||||||
|
|
||||||
@@ -33,20 +76,42 @@ $branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}',
|
|||||||
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
|
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
|
||||||
$branchName = "$featureNum-$([string]::Join('-', $words))"
|
$branchName = "$featureNum-$([string]::Join('-', $words))"
|
||||||
|
|
||||||
|
if ($hasGit) {
|
||||||
|
try {
|
||||||
git checkout -b $branchName | Out-Null
|
git checkout -b $branchName | Out-Null
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Failed to create git branch: $branchName"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName"
|
||||||
|
}
|
||||||
|
|
||||||
$featureDir = Join-Path $specsDir $branchName
|
$featureDir = Join-Path $specsDir $branchName
|
||||||
New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
|
||||||
|
|
||||||
$template = Join-Path $repoRoot 'templates/spec-template.md'
|
$template = Join-Path $repoRoot 'templates/spec-template.md'
|
||||||
$specFile = Join-Path $featureDir 'spec.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 (Test-Path $template) {
|
||||||
|
Copy-Item $template $specFile -Force
|
||||||
|
} else {
|
||||||
|
New-Item -ItemType File -Path $specFile | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the SPECIFY_FEATURE environment variable for the current session
|
||||||
|
$env:SPECIFY_FEATURE = $branchName
|
||||||
|
|
||||||
if ($Json) {
|
if ($Json) {
|
||||||
$obj = [PSCustomObject]@{ BRANCH_NAME = $branchName; SPEC_FILE = $specFile; FEATURE_NUM = $featureNum }
|
$obj = [PSCustomObject]@{
|
||||||
|
BRANCH_NAME = $branchName
|
||||||
|
SPEC_FILE = $specFile
|
||||||
|
FEATURE_NUM = $featureNum
|
||||||
|
HAS_GIT = $hasGit
|
||||||
|
}
|
||||||
$obj | ConvertTo-Json -Compress
|
$obj | ConvertTo-Json -Compress
|
||||||
} else {
|
} else {
|
||||||
Write-Output "BRANCH_NAME: $branchName"
|
Write-Output "BRANCH_NAME: $branchName"
|
||||||
Write-Output "SPEC_FILE: $specFile"
|
Write-Output "SPEC_FILE: $specFile"
|
||||||
Write-Output "FEATURE_NUM: $featureNum"
|
Write-Output "FEATURE_NUM: $featureNum"
|
||||||
|
Write-Output "HAS_GIT: $hasGit"
|
||||||
|
Write-Output "SPECIFY_FEATURE environment variable set to: $branchName"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/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)"
|
|
||||||
@@ -1,21 +1,61 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
|
# Setup implementation plan for a feature
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param([switch]$Json)
|
param(
|
||||||
|
[switch]$Json,
|
||||||
|
[switch]$Help
|
||||||
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
if ($Help) {
|
||||||
|
Write-Output "Usage: ./setup-plan.ps1 [-Json] [-Help]"
|
||||||
|
Write-Output " -Json Output results in JSON format"
|
||||||
|
Write-Output " -Help Show this help message"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load common functions
|
||||||
. "$PSScriptRoot/common.ps1"
|
. "$PSScriptRoot/common.ps1"
|
||||||
|
|
||||||
|
# Get all paths and variables from common functions
|
||||||
$paths = Get-FeaturePathsEnv
|
$paths = Get-FeaturePathsEnv
|
||||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
|
||||||
|
|
||||||
|
# Check if we're on a proper feature branch (only for git repos)
|
||||||
|
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure the feature directory exists
|
||||||
New-Item -ItemType Directory -Path $paths.FEATURE_DIR -Force | Out-Null
|
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 }
|
|
||||||
|
|
||||||
|
# Copy plan template if it exists, otherwise note it or create empty file
|
||||||
|
$template = Join-Path $paths.REPO_ROOT '.specify/templates/plan-template.md'
|
||||||
|
if (Test-Path $template) {
|
||||||
|
Copy-Item $template $paths.IMPL_PLAN -Force
|
||||||
|
Write-Output "Copied plan template to $($paths.IMPL_PLAN)"
|
||||||
|
} else {
|
||||||
|
Write-Warning "Plan template not found at $template"
|
||||||
|
# Create a basic plan file if template doesn't exist
|
||||||
|
New-Item -ItemType File -Path $paths.IMPL_PLAN -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output results
|
||||||
if ($Json) {
|
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
|
$result = [PSCustomObject]@{
|
||||||
|
FEATURE_SPEC = $paths.FEATURE_SPEC
|
||||||
|
IMPL_PLAN = $paths.IMPL_PLAN
|
||||||
|
SPECS_DIR = $paths.FEATURE_DIR
|
||||||
|
BRANCH = $paths.CURRENT_BRANCH
|
||||||
|
HAS_GIT = $paths.HAS_GIT
|
||||||
|
}
|
||||||
|
$result | ConvertTo-Json -Compress
|
||||||
} else {
|
} else {
|
||||||
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
||||||
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
||||||
Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)"
|
Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)"
|
||||||
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
||||||
|
Write-Output "HAS_GIT: $($paths.HAS_GIT)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,109 +1,427 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
[CmdletBinding()]
|
<#!
|
||||||
param([string]$AgentType)
|
.SYNOPSIS
|
||||||
|
Update agent context files with information from plan.md (PowerShell version)
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Mirrors the behavior of scripts/bash/update-agent-context.sh:
|
||||||
|
1. Environment Validation
|
||||||
|
2. Plan Data Extraction
|
||||||
|
3. Agent File Management (create from template or update existing)
|
||||||
|
4. Content Generation (technology stack, recent changes, timestamp)
|
||||||
|
5. Multi-Agent Support (claude, gemini, copilot, cursor, qwen, opencode, codex, windsurf)
|
||||||
|
|
||||||
|
.PARAMETER AgentType
|
||||||
|
Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist).
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./update-agent-context.ps1 -AgentType claude
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./update-agent-context.ps1 # Updates all existing agent files
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Relies on common helper functions in common.ps1
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(Position=0)]
|
||||||
|
[ValidateSet('claude','gemini','copilot','cursor','qwen','opencode','codex','windsurf','kilocode','auggie')]
|
||||||
|
[string]$AgentType
|
||||||
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
$repoRoot = git rev-parse --show-toplevel
|
# Import common helpers
|
||||||
$currentBranch = git rev-parse --abbrev-ref HEAD
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
$featureDir = Join-Path $repoRoot "specs/$currentBranch"
|
. (Join-Path $ScriptDir 'common.ps1')
|
||||||
$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'
|
# Acquire environment paths
|
||||||
$geminiFile = Join-Path $repoRoot 'GEMINI.md'
|
$envData = Get-FeaturePathsEnv
|
||||||
$copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md'
|
$REPO_ROOT = $envData.REPO_ROOT
|
||||||
$cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc'
|
$CURRENT_BRANCH = $envData.CURRENT_BRANCH
|
||||||
$qwenFile = Join-Path $repoRoot 'QWEN.md'
|
$HAS_GIT = $envData.HAS_GIT
|
||||||
$agentsFile = Join-Path $repoRoot 'AGENTS.md'
|
$IMPL_PLAN = $envData.IMPL_PLAN
|
||||||
$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md'
|
$NEW_PLAN = $IMPL_PLAN
|
||||||
|
|
||||||
Write-Output "=== Updating agent context files for feature $currentBranch ==="
|
# Agent file paths
|
||||||
|
$CLAUDE_FILE = Join-Path $REPO_ROOT 'CLAUDE.md'
|
||||||
|
$GEMINI_FILE = Join-Path $REPO_ROOT 'GEMINI.md'
|
||||||
|
$COPILOT_FILE = Join-Path $REPO_ROOT '.github/copilot-instructions.md'
|
||||||
|
$CURSOR_FILE = Join-Path $REPO_ROOT '.cursor/rules/specify-rules.mdc'
|
||||||
|
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
|
||||||
|
$AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
|
$WINDSURF_FILE = Join-Path $REPO_ROOT '.windsurf/rules/specify-rules.md'
|
||||||
|
$KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md'
|
||||||
|
$AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md'
|
||||||
|
|
||||||
function Get-PlanValue($pattern) {
|
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
||||||
if (-not (Test-Path $newPlan)) { return '' }
|
|
||||||
$line = Select-String -Path $newPlan -Pattern $pattern | Select-Object -First 1
|
# Parsed plan data placeholders
|
||||||
if ($line) { return ($line.Line -replace "^\*\*$pattern\*\*: ", '') }
|
$script:NEW_LANG = ''
|
||||||
return ''
|
$script:NEW_FRAMEWORK = ''
|
||||||
|
$script:NEW_DB = ''
|
||||||
|
$script:NEW_PROJECT_TYPE = ''
|
||||||
|
|
||||||
|
function Write-Info {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
Write-Host "INFO: $Message"
|
||||||
}
|
}
|
||||||
|
|
||||||
$newLang = Get-PlanValue 'Language/Version'
|
function Write-Success {
|
||||||
$newFramework = Get-PlanValue 'Primary Dependencies'
|
param(
|
||||||
$newTesting = Get-PlanValue 'Testing'
|
[Parameter(Mandatory=$true)]
|
||||||
$newDb = Get-PlanValue 'Storage'
|
[string]$Message
|
||||||
$newProjectType = Get-PlanValue 'Project Type'
|
)
|
||||||
|
Write-Host "$([char]0x2713) $Message"
|
||||||
function Initialize-AgentFile($targetFile, $agentName) {
|
|
||||||
if (Test-Path $targetFile) { return }
|
|
||||||
$template = Join-Path $repoRoot '.specify/templates/agent-file-template.md'
|
|
||||||
if (-not (Test-Path $template)) { Write-Error "Template not found: $template"; return }
|
|
||||||
$content = Get-Content $template -Raw
|
|
||||||
$content = $content.Replace('[PROJECT NAME]', (Split-Path $repoRoot -Leaf))
|
|
||||||
$content = $content.Replace('[DATE]', (Get-Date -Format 'yyyy-MM-dd'))
|
|
||||||
$content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $newLang + $newFramework ($currentBranch)")
|
|
||||||
if ($newProjectType -match 'web') { $structure = "backend/`nfrontend/`ntests/" } else { $structure = "src/`ntests/" }
|
|
||||||
$content = $content.Replace('[ACTUAL STRUCTURE FROM PLANS]', $structure)
|
|
||||||
if ($newLang -match 'Python') { $commands = 'cd src && pytest && ruff check .' }
|
|
||||||
elseif ($newLang -match 'Rust') { $commands = 'cargo test && cargo clippy' }
|
|
||||||
elseif ($newLang -match 'JavaScript|TypeScript') { $commands = 'npm test && npm run lint' }
|
|
||||||
else { $commands = "# Add commands for $newLang" }
|
|
||||||
$content = $content.Replace('[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]', $commands)
|
|
||||||
$content = $content.Replace('[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]', "${newLang}: Follow standard conventions")
|
|
||||||
$content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', "- ${currentBranch}: Added ${newLang} + ${newFramework}")
|
|
||||||
$content | Set-Content $targetFile -Encoding UTF8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Update-AgentFile($targetFile, $agentName) {
|
function Write-WarningMsg {
|
||||||
if (-not (Test-Path $targetFile)) { Initialize-AgentFile $targetFile $agentName; return }
|
param(
|
||||||
$content = Get-Content $targetFile -Raw
|
[Parameter(Mandatory=$true)]
|
||||||
if ($newLang -and ($content -notmatch [regex]::Escape($newLang))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newLang + $newFramework ($currentBranch)`n" }
|
[string]$Message
|
||||||
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|$)') {
|
Write-Warning $Message
|
||||||
$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) {
|
function Write-Err {
|
||||||
'claude' { Update-AgentFile $claudeFile 'Claude Code' }
|
param(
|
||||||
'gemini' { Update-AgentFile $geminiFile 'Gemini CLI' }
|
[Parameter(Mandatory=$true)]
|
||||||
'copilot' { Update-AgentFile $copilotFile 'GitHub Copilot' }
|
[string]$Message
|
||||||
'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' }
|
)
|
||||||
'qwen' { Update-AgentFile $qwenFile 'Qwen Code' }
|
Write-Host "ERROR: $Message" -ForegroundColor Red
|
||||||
'opencode' { Update-AgentFile $agentsFile 'opencode' }
|
|
||||||
'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' }
|
|
||||||
'codex' { Update-AgentFile $agentsFile 'Codex CLI' }
|
|
||||||
'' {
|
|
||||||
foreach ($pair in @(
|
|
||||||
@{file=$claudeFile; name='Claude Code'},
|
|
||||||
@{file=$geminiFile; name='Gemini CLI'},
|
|
||||||
@{file=$copilotFile; name='GitHub Copilot'},
|
|
||||||
@{file=$cursorFile; name='Cursor IDE'},
|
|
||||||
@{file=$qwenFile; name='Qwen Code'},
|
|
||||||
@{file=$agentsFile; name='opencode'},
|
|
||||||
@{file=$windsurfFile; name='Windsurf'},
|
|
||||||
@{file=$agentsFile; name='Codex CLI'}
|
|
||||||
)) {
|
|
||||||
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
|
||||||
}
|
|
||||||
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile) -and -not (Test-Path $windsurfFile)) {
|
|
||||||
Write-Output 'No agent context files found. Creating Claude Code context file by default.'
|
|
||||||
Update-AgentFile $claudeFile 'Claude Code'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode, windsurf, codex or leave empty for all."; exit 1 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Output ''
|
function Validate-Environment {
|
||||||
Write-Output 'Summary of changes:'
|
if (-not $CURRENT_BRANCH) {
|
||||||
if ($newLang) { Write-Output "- Added language: $newLang" }
|
Write-Err 'Unable to determine current feature'
|
||||||
if ($newFramework) { Write-Output "- Added framework: $newFramework" }
|
if ($HAS_GIT) { Write-Info "Make sure you're on a feature branch" } else { Write-Info 'Set SPECIFY_FEATURE environment variable or create a feature first' }
|
||||||
if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" }
|
exit 1
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $NEW_PLAN)) {
|
||||||
|
Write-Err "No plan.md found at $NEW_PLAN"
|
||||||
|
Write-Info 'Ensure you are working on a feature with a corresponding spec directory'
|
||||||
|
if (-not $HAS_GIT) { Write-Info 'Use: $env:SPECIFY_FEATURE=your-feature-name or create a new feature first' }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $TEMPLATE_FILE)) {
|
||||||
|
Write-Err "Template file not found at $TEMPLATE_FILE"
|
||||||
|
Write-Info 'Run specify init to scaffold .specify/templates, or add agent-file-template.md there.'
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Write-Output ''
|
function Extract-PlanField {
|
||||||
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode|windsurf|codex]'
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$FieldPattern,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$PlanFile
|
||||||
|
)
|
||||||
|
if (-not (Test-Path $PlanFile)) { return '' }
|
||||||
|
# Lines like **Language/Version**: Python 3.12
|
||||||
|
$regex = "^\*\*$([Regex]::Escape($FieldPattern))\*\*: (.+)$"
|
||||||
|
Get-Content -LiteralPath $PlanFile | ForEach-Object {
|
||||||
|
if ($_ -match $regex) {
|
||||||
|
$val = $Matches[1].Trim()
|
||||||
|
if ($val -notin @('NEEDS CLARIFICATION','N/A')) { return $val }
|
||||||
|
}
|
||||||
|
} | Select-Object -First 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function Parse-PlanData {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$PlanFile
|
||||||
|
)
|
||||||
|
if (-not (Test-Path $PlanFile)) { Write-Err "Plan file not found: $PlanFile"; return $false }
|
||||||
|
Write-Info "Parsing plan data from $PlanFile"
|
||||||
|
$script:NEW_LANG = Extract-PlanField -FieldPattern 'Language/Version' -PlanFile $PlanFile
|
||||||
|
$script:NEW_FRAMEWORK = Extract-PlanField -FieldPattern 'Primary Dependencies' -PlanFile $PlanFile
|
||||||
|
$script:NEW_DB = Extract-PlanField -FieldPattern 'Storage' -PlanFile $PlanFile
|
||||||
|
$script:NEW_PROJECT_TYPE = Extract-PlanField -FieldPattern 'Project Type' -PlanFile $PlanFile
|
||||||
|
|
||||||
|
if ($NEW_LANG) { Write-Info "Found language: $NEW_LANG" } else { Write-WarningMsg 'No language information found in plan' }
|
||||||
|
if ($NEW_FRAMEWORK) { Write-Info "Found framework: $NEW_FRAMEWORK" }
|
||||||
|
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Info "Found database: $NEW_DB" }
|
||||||
|
if ($NEW_PROJECT_TYPE) { Write-Info "Found project type: $NEW_PROJECT_TYPE" }
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Format-TechnologyStack {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Lang,
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Framework
|
||||||
|
)
|
||||||
|
$parts = @()
|
||||||
|
if ($Lang -and $Lang -ne 'NEEDS CLARIFICATION') { $parts += $Lang }
|
||||||
|
if ($Framework -and $Framework -notin @('NEEDS CLARIFICATION','N/A')) { $parts += $Framework }
|
||||||
|
if (-not $parts) { return '' }
|
||||||
|
return ($parts -join ' + ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ProjectStructure {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$ProjectType
|
||||||
|
)
|
||||||
|
if ($ProjectType -match 'web') { return "backend/`nfrontend/`ntests/" } else { return "src/`ntests/" }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-CommandsForLanguage {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Lang
|
||||||
|
)
|
||||||
|
switch -Regex ($Lang) {
|
||||||
|
'Python' { return "cd src; pytest; ruff check ." }
|
||||||
|
'Rust' { return "cargo test; cargo clippy" }
|
||||||
|
'JavaScript|TypeScript' { return "npm test; npm run lint" }
|
||||||
|
default { return "# Add commands for $Lang" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-LanguageConventions {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Lang
|
||||||
|
)
|
||||||
|
if ($Lang) { "${Lang}: Follow standard conventions" } else { 'General: Follow standard conventions' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-AgentFile {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$TargetFile,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$ProjectName,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[datetime]$Date
|
||||||
|
)
|
||||||
|
if (-not (Test-Path $TEMPLATE_FILE)) { Write-Err "Template not found at $TEMPLATE_FILE"; return $false }
|
||||||
|
$temp = New-TemporaryFile
|
||||||
|
Copy-Item -LiteralPath $TEMPLATE_FILE -Destination $temp -Force
|
||||||
|
|
||||||
|
$projectStructure = Get-ProjectStructure -ProjectType $NEW_PROJECT_TYPE
|
||||||
|
$commands = Get-CommandsForLanguage -Lang $NEW_LANG
|
||||||
|
$languageConventions = Get-LanguageConventions -Lang $NEW_LANG
|
||||||
|
|
||||||
|
$escaped_lang = $NEW_LANG
|
||||||
|
$escaped_framework = $NEW_FRAMEWORK
|
||||||
|
$escaped_branch = $CURRENT_BRANCH
|
||||||
|
|
||||||
|
$content = Get-Content -LiteralPath $temp -Raw
|
||||||
|
$content = $content -replace '\[PROJECT NAME\]',$ProjectName
|
||||||
|
$content = $content -replace '\[DATE\]',$Date.ToString('yyyy-MM-dd')
|
||||||
|
|
||||||
|
# Build the technology stack string safely
|
||||||
|
$techStackForTemplate = ""
|
||||||
|
if ($escaped_lang -and $escaped_framework) {
|
||||||
|
$techStackForTemplate = "- $escaped_lang + $escaped_framework ($escaped_branch)"
|
||||||
|
} elseif ($escaped_lang) {
|
||||||
|
$techStackForTemplate = "- $escaped_lang ($escaped_branch)"
|
||||||
|
} elseif ($escaped_framework) {
|
||||||
|
$techStackForTemplate = "- $escaped_framework ($escaped_branch)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $content -replace '\[EXTRACTED FROM ALL PLAN.MD FILES\]',$techStackForTemplate
|
||||||
|
# For project structure we manually embed (keep newlines)
|
||||||
|
$escapedStructure = [Regex]::Escape($projectStructure)
|
||||||
|
$content = $content -replace '\[ACTUAL STRUCTURE FROM PLANS\]',$escapedStructure
|
||||||
|
# Replace escaped newlines placeholder after all replacements
|
||||||
|
$content = $content -replace '\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]',$commands
|
||||||
|
$content = $content -replace '\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]',$languageConventions
|
||||||
|
|
||||||
|
# Build the recent changes string safely
|
||||||
|
$recentChangesForTemplate = ""
|
||||||
|
if ($escaped_lang -and $escaped_framework) {
|
||||||
|
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang} + ${escaped_framework}"
|
||||||
|
} elseif ($escaped_lang) {
|
||||||
|
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang}"
|
||||||
|
} elseif ($escaped_framework) {
|
||||||
|
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_framework}"
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $content -replace '\[LAST 3 FEATURES AND WHAT THEY ADDED\]',$recentChangesForTemplate
|
||||||
|
# Convert literal \n sequences introduced by Escape to real newlines
|
||||||
|
$content = $content -replace '\\n',[Environment]::NewLine
|
||||||
|
|
||||||
|
$parent = Split-Path -Parent $TargetFile
|
||||||
|
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
|
||||||
|
Set-Content -LiteralPath $TargetFile -Value $content -NoNewline
|
||||||
|
Remove-Item $temp -Force
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-ExistingAgentFile {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$TargetFile,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[datetime]$Date
|
||||||
|
)
|
||||||
|
if (-not (Test-Path $TargetFile)) { return (New-AgentFile -TargetFile $TargetFile -ProjectName (Split-Path $REPO_ROOT -Leaf) -Date $Date) }
|
||||||
|
|
||||||
|
$techStack = Format-TechnologyStack -Lang $NEW_LANG -Framework $NEW_FRAMEWORK
|
||||||
|
$newTechEntries = @()
|
||||||
|
if ($techStack) {
|
||||||
|
$escapedTechStack = [Regex]::Escape($techStack)
|
||||||
|
if (-not (Select-String -Pattern $escapedTechStack -Path $TargetFile -Quiet)) {
|
||||||
|
$newTechEntries += "- $techStack ($CURRENT_BRANCH)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) {
|
||||||
|
$escapedDB = [Regex]::Escape($NEW_DB)
|
||||||
|
if (-not (Select-String -Pattern $escapedDB -Path $TargetFile -Quiet)) {
|
||||||
|
$newTechEntries += "- $NEW_DB ($CURRENT_BRANCH)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$newChangeEntry = ''
|
||||||
|
if ($techStack) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${techStack}" }
|
||||||
|
elseif ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${NEW_DB}" }
|
||||||
|
|
||||||
|
$lines = Get-Content -LiteralPath $TargetFile
|
||||||
|
$output = New-Object System.Collections.Generic.List[string]
|
||||||
|
$inTech = $false; $inChanges = $false; $techAdded = $false; $changeAdded = $false; $existingChanges = 0
|
||||||
|
|
||||||
|
for ($i=0; $i -lt $lines.Count; $i++) {
|
||||||
|
$line = $lines[$i]
|
||||||
|
if ($line -eq '## Active Technologies') {
|
||||||
|
$output.Add($line)
|
||||||
|
$inTech = $true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($inTech -and $line -match '^##\s') {
|
||||||
|
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
|
||||||
|
$output.Add($line); $inTech = $false; continue
|
||||||
|
}
|
||||||
|
if ($inTech -and [string]::IsNullOrWhiteSpace($line)) {
|
||||||
|
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
|
||||||
|
$output.Add($line); continue
|
||||||
|
}
|
||||||
|
if ($line -eq '## Recent Changes') {
|
||||||
|
$output.Add($line)
|
||||||
|
if ($newChangeEntry) { $output.Add($newChangeEntry); $changeAdded = $true }
|
||||||
|
$inChanges = $true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($inChanges -and $line -match '^##\s') { $output.Add($line); $inChanges = $false; continue }
|
||||||
|
if ($inChanges -and $line -match '^- ') {
|
||||||
|
if ($existingChanges -lt 2) { $output.Add($line); $existingChanges++ }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($line -match '\*\*Last updated\*\*: .*\d{4}-\d{2}-\d{2}') {
|
||||||
|
$output.Add(($line -replace '\d{4}-\d{2}-\d{2}',$Date.ToString('yyyy-MM-dd')))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$output.Add($line)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Post-loop check: if we're still in the Active Technologies section and haven't added new entries
|
||||||
|
if ($inTech -and -not $techAdded -and $newTechEntries.Count -gt 0) {
|
||||||
|
$newTechEntries | ForEach-Object { $output.Add($_) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -LiteralPath $TargetFile -Value ($output -join [Environment]::NewLine)
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-AgentFile {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$TargetFile,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$AgentName
|
||||||
|
)
|
||||||
|
if (-not $TargetFile -or -not $AgentName) { Write-Err 'Update-AgentFile requires TargetFile and AgentName'; return $false }
|
||||||
|
Write-Info "Updating $AgentName context file: $TargetFile"
|
||||||
|
$projectName = Split-Path $REPO_ROOT -Leaf
|
||||||
|
$date = Get-Date
|
||||||
|
|
||||||
|
$dir = Split-Path -Parent $TargetFile
|
||||||
|
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
|
||||||
|
|
||||||
|
if (-not (Test-Path $TargetFile)) {
|
||||||
|
if (New-AgentFile -TargetFile $TargetFile -ProjectName $projectName -Date $date) { Write-Success "Created new $AgentName context file" } else { Write-Err 'Failed to create new agent file'; return $false }
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (Update-ExistingAgentFile -TargetFile $TargetFile -Date $date) { Write-Success "Updated existing $AgentName context file" } else { Write-Err 'Failed to update agent file'; return $false }
|
||||||
|
} catch {
|
||||||
|
Write-Err "Cannot access or update existing file: $TargetFile. $_"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-SpecificAgent {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$Type
|
||||||
|
)
|
||||||
|
switch ($Type) {
|
||||||
|
'claude' { Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code' }
|
||||||
|
'gemini' { Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI' }
|
||||||
|
'copilot' { Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot' }
|
||||||
|
'cursor' { Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE' }
|
||||||
|
'qwen' { Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code' }
|
||||||
|
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
|
||||||
|
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
|
||||||
|
'windsurf' { Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf' }
|
||||||
|
'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' }
|
||||||
|
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
|
||||||
|
default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie'; return $false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-AllExistingAgents {
|
||||||
|
$found = $false
|
||||||
|
$ok = $true
|
||||||
|
if (Test-Path $CLAUDE_FILE) { if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $GEMINI_FILE) { if (-not (Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $COPILOT_FILE) { if (-not (Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $CURSOR_FILE) { if (-not (Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $QWEN_FILE) { if (-not (Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $AGENTS_FILE) { if (-not (Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex/opencode')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $WINDSURF_FILE) { if (-not (Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true }
|
||||||
|
if (-not $found) {
|
||||||
|
Write-Info 'No existing agent files found, creating default Claude file...'
|
||||||
|
if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
|
||||||
|
}
|
||||||
|
return $ok
|
||||||
|
}
|
||||||
|
|
||||||
|
function Print-Summary {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Info 'Summary of changes:'
|
||||||
|
if ($NEW_LANG) { Write-Host " - Added language: $NEW_LANG" }
|
||||||
|
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
||||||
|
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
||||||
|
Write-Host ''
|
||||||
|
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie]'
|
||||||
|
}
|
||||||
|
|
||||||
|
function Main {
|
||||||
|
Validate-Environment
|
||||||
|
Write-Info "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||||
|
if (-not (Parse-PlanData -PlanFile $NEW_PLAN)) { Write-Err 'Failed to parse plan data'; exit 1 }
|
||||||
|
$success = $true
|
||||||
|
if ($AgentType) {
|
||||||
|
Write-Info "Updating specific agent: $AgentType"
|
||||||
|
if (-not (Update-SpecificAgent -Type $AgentType)) { $success = $false }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Info 'No agent specified, updating all existing agent files...'
|
||||||
|
if (-not (Update-AllExistingAgents)) { $success = $false }
|
||||||
|
}
|
||||||
|
Print-Summary
|
||||||
|
if ($success) { Write-Success 'Agent context update completed successfully'; exit 0 } else { Write-Err 'Agent context update completed with errors'; exit 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
Main
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import sys
|
|||||||
import zipfile
|
import zipfile
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
|
import shlex
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
@@ -71,6 +72,8 @@ AI_CHOICES = {
|
|||||||
"opencode": "opencode",
|
"opencode": "opencode",
|
||||||
"codex": "Codex CLI",
|
"codex": "Codex CLI",
|
||||||
"windsurf": "Windsurf",
|
"windsurf": "Windsurf",
|
||||||
|
"kilocode": "Kilo Code",
|
||||||
|
"auggie": "Auggie CLI",
|
||||||
}
|
}
|
||||||
# Add script type choices
|
# Add script type choices
|
||||||
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
||||||
@@ -354,13 +357,13 @@ def run_command(cmd: list[str], check_return: bool = True, capture: bool = False
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_tool_for_tracker(tool: str, install_hint: str, tracker: StepTracker) -> bool:
|
def check_tool_for_tracker(tool: str, tracker: StepTracker) -> bool:
|
||||||
"""Check if a tool is installed and update tracker."""
|
"""Check if a tool is installed and update tracker."""
|
||||||
if shutil.which(tool):
|
if shutil.which(tool):
|
||||||
tracker.complete(tool, "available")
|
tracker.complete(tool, "available")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
tracker.error(tool, f"not found - {install_hint}")
|
tracker.error(tool, "not found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -379,8 +382,6 @@ def check_tool(tool: str, install_hint: str) -> bool:
|
|||||||
if shutil.which(tool):
|
if shutil.which(tool):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
console.print(f"[yellow]⚠️ {tool} not found[/yellow]")
|
|
||||||
console.print(f" Install with: [cyan]{install_hint}[/cyan]")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -746,7 +747,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
|
|||||||
@app.command()
|
@app.command()
|
||||||
def init(
|
def init(
|
||||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
||||||
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, codex, or windsurf"),
|
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, codex, windsurf, kilocode, or auggie"),
|
||||||
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
|
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
|
||||||
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
|
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
|
||||||
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
|
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
|
||||||
@@ -760,7 +761,7 @@ def init(
|
|||||||
|
|
||||||
This command will:
|
This command will:
|
||||||
1. Check that required tools are installed (git is optional)
|
1. Check that required tools are installed (git is optional)
|
||||||
2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, or Windsurf)
|
2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, or Auggie CLI)
|
||||||
3. Download the appropriate template from GitHub
|
3. Download the appropriate template from GitHub
|
||||||
4. Extract the template to a new project directory or current directory
|
4. Extract the template to a new project directory or current directory
|
||||||
5. Initialize a fresh git repository (if not --no-git and no existing repo)
|
5. Initialize a fresh git repository (if not --no-git and no existing repo)
|
||||||
@@ -776,6 +777,7 @@ def init(
|
|||||||
specify init my-project --ai opencode
|
specify init my-project --ai opencode
|
||||||
specify init my-project --ai codex
|
specify init my-project --ai codex
|
||||||
specify init my-project --ai windsurf
|
specify init my-project --ai windsurf
|
||||||
|
specify init my-project --ai auggie
|
||||||
specify init --ignore-agent-tools my-project
|
specify init --ignore-agent-tools my-project
|
||||||
specify init --here --ai claude
|
specify init --here --ai claude
|
||||||
specify init --here --ai codex
|
specify init --here --ai codex
|
||||||
@@ -813,7 +815,15 @@ def init(
|
|||||||
project_path = Path(project_name).resolve()
|
project_path = Path(project_name).resolve()
|
||||||
# Check if project directory already exists
|
# Check if project directory already exists
|
||||||
if project_path.exists():
|
if project_path.exists():
|
||||||
console.print(f"[red]Error:[/red] Directory '{project_name}' already exists")
|
error_panel = Panel(
|
||||||
|
f"Directory '[cyan]{project_name}[/cyan]' already exists\n"
|
||||||
|
"Please choose a different project name or remove the existing directory.",
|
||||||
|
title="[red]Directory Conflict[/red]",
|
||||||
|
border_style="red",
|
||||||
|
padding=(1, 2)
|
||||||
|
)
|
||||||
|
console.print()
|
||||||
|
console.print(error_panel)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
# Create formatted setup info with column alignment
|
# Create formatted setup info with column alignment
|
||||||
@@ -857,31 +867,45 @@ def init(
|
|||||||
# Check agent tools unless ignored
|
# Check agent tools unless ignored
|
||||||
if not ignore_agent_tools:
|
if not ignore_agent_tools:
|
||||||
agent_tool_missing = False
|
agent_tool_missing = False
|
||||||
|
install_url = ""
|
||||||
if selected_ai == "claude":
|
if selected_ai == "claude":
|
||||||
if not check_tool("claude", "Install from: https://docs.anthropic.com/en/docs/claude-code/setup"):
|
if not check_tool("claude", "https://docs.anthropic.com/en/docs/claude-code/setup"):
|
||||||
console.print("[red]Error:[/red] Claude CLI is required for Claude Code projects")
|
install_url = "https://docs.anthropic.com/en/docs/claude-code/setup"
|
||||||
agent_tool_missing = True
|
agent_tool_missing = True
|
||||||
elif selected_ai == "gemini":
|
elif selected_ai == "gemini":
|
||||||
if not check_tool("gemini", "Install from: https://github.com/google-gemini/gemini-cli"):
|
if not check_tool("gemini", "https://github.com/google-gemini/gemini-cli"):
|
||||||
console.print("[red]Error:[/red] Gemini CLI is required for Gemini projects")
|
install_url = "https://github.com/google-gemini/gemini-cli"
|
||||||
agent_tool_missing = True
|
agent_tool_missing = True
|
||||||
elif selected_ai == "qwen":
|
elif selected_ai == "qwen":
|
||||||
if not check_tool("qwen", "Install from: https://github.com/QwenLM/qwen-code"):
|
if not check_tool("qwen", "https://github.com/QwenLM/qwen-code"):
|
||||||
console.print("[red]Error:[/red] Qwen CLI is required for Qwen Code projects")
|
install_url = "https://github.com/QwenLM/qwen-code"
|
||||||
agent_tool_missing = True
|
agent_tool_missing = True
|
||||||
elif selected_ai == "opencode":
|
elif selected_ai == "opencode":
|
||||||
if not check_tool("opencode", "Install from: https://opencode.ai"):
|
if not check_tool("opencode", "https://opencode.ai"):
|
||||||
console.print("[red]Error:[/red] opencode CLI is required for opencode projects")
|
install_url = "https://opencode.ai"
|
||||||
agent_tool_missing = True
|
agent_tool_missing = True
|
||||||
elif selected_ai == "codex":
|
elif selected_ai == "codex":
|
||||||
if not check_tool("codex", "Install from: https://github.com/openai/codex"):
|
if not check_tool("codex", "https://github.com/openai/codex"):
|
||||||
console.print("[red]Error:[/red] Codex CLI is required for Codex projects")
|
install_url = "https://github.com/openai/codex"
|
||||||
|
agent_tool_missing = True
|
||||||
|
elif selected_ai == "auggie":
|
||||||
|
if not check_tool("auggie", "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"):
|
||||||
|
install_url = "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"
|
||||||
agent_tool_missing = True
|
agent_tool_missing = True
|
||||||
# GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs
|
# GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs
|
||||||
|
|
||||||
if agent_tool_missing:
|
if agent_tool_missing:
|
||||||
console.print("\n[red]Required AI tool is missing![/red]")
|
error_panel = Panel(
|
||||||
console.print("[yellow]Tip:[/yellow] Use --ignore-agent-tools to skip this check")
|
f"[cyan]{selected_ai}[/cyan] not found\n"
|
||||||
|
f"Install with: [cyan]{install_url}[/cyan]\n"
|
||||||
|
f"{AI_CHOICES[selected_ai]} is required to continue with this project type.\n\n"
|
||||||
|
"Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check",
|
||||||
|
title="[red]Agent Detection Error[/red]",
|
||||||
|
border_style="red",
|
||||||
|
padding=(1, 2)
|
||||||
|
)
|
||||||
|
console.print()
|
||||||
|
console.print(error_panel)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
# Determine script type (explicit, interactive, or OS default)
|
# Determine script type (explicit, interactive, or OS default)
|
||||||
@@ -980,15 +1004,53 @@ def init(
|
|||||||
console.print(tracker.render())
|
console.print(tracker.render())
|
||||||
console.print("\n[bold green]Project ready.[/bold green]")
|
console.print("\n[bold green]Project ready.[/bold green]")
|
||||||
|
|
||||||
|
# Agent folder security notice
|
||||||
|
agent_folder_map = {
|
||||||
|
"claude": ".claude/",
|
||||||
|
"gemini": ".gemini/",
|
||||||
|
"cursor": ".cursor/",
|
||||||
|
"qwen": ".qwen/",
|
||||||
|
"opencode": ".opencode/",
|
||||||
|
"codex": ".codex/",
|
||||||
|
"windsurf": ".windsurf/",
|
||||||
|
"kilocode": ".kilocode/",
|
||||||
|
"auggie": ".augment/",
|
||||||
|
"copilot": ".github/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if selected_ai in agent_folder_map:
|
||||||
|
agent_folder = agent_folder_map[selected_ai]
|
||||||
|
security_notice = Panel(
|
||||||
|
f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
|
||||||
|
f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
|
||||||
|
title="[yellow]Agent Folder Security[/yellow]",
|
||||||
|
border_style="yellow",
|
||||||
|
padding=(1, 2)
|
||||||
|
)
|
||||||
|
console.print()
|
||||||
|
console.print(security_notice)
|
||||||
|
|
||||||
# Boxed "Next steps" section
|
# Boxed "Next steps" section
|
||||||
steps_lines = []
|
steps_lines = []
|
||||||
if not here:
|
if not here:
|
||||||
steps_lines.append(f"1. [bold green]cd {project_name}[/bold green]")
|
steps_lines.append(f"1. Go to the project folder: [cyan]cd {project_name}[/cyan]")
|
||||||
step_num = 2
|
step_num = 2
|
||||||
else:
|
else:
|
||||||
steps_lines.append("1. You're already in the project directory!")
|
steps_lines.append("1. You're already in the project directory!")
|
||||||
step_num = 2
|
step_num = 2
|
||||||
|
|
||||||
|
# Add Codex-specific setup step if needed
|
||||||
|
if selected_ai == "codex":
|
||||||
|
codex_path = project_path / ".codex"
|
||||||
|
quoted_path = shlex.quote(str(codex_path))
|
||||||
|
if os.name == "nt": # Windows
|
||||||
|
cmd = f"setx CODEX_HOME {quoted_path}"
|
||||||
|
else: # Unix-like systems
|
||||||
|
cmd = f"export CODEX_HOME={quoted_path}"
|
||||||
|
|
||||||
|
steps_lines.append(f"{step_num}. Set [cyan]CODEX_HOME[/cyan] environment variable before running Codex: [cyan]{cmd}[/cyan]")
|
||||||
|
step_num += 1
|
||||||
|
|
||||||
steps_lines.append(f"{step_num}. Start using slash commands with your AI agent:")
|
steps_lines.append(f"{step_num}. Start using slash commands with your AI agent:")
|
||||||
steps_lines.append(" 2.1 [cyan]/constitution[/] - Establish project principles")
|
steps_lines.append(" 2.1 [cyan]/constitution[/] - Establish project principles")
|
||||||
steps_lines.append(" 2.2 [cyan]/specify[/] - Create specifications")
|
steps_lines.append(" 2.2 [cyan]/specify[/] - Create specifications")
|
||||||
@@ -996,10 +1058,21 @@ def init(
|
|||||||
steps_lines.append(" 2.4 [cyan]/tasks[/] - Generate actionable tasks")
|
steps_lines.append(" 2.4 [cyan]/tasks[/] - Generate actionable tasks")
|
||||||
steps_lines.append(" 2.5 [cyan]/implement[/] - Execute implementation")
|
steps_lines.append(" 2.5 [cyan]/implement[/] - Execute implementation")
|
||||||
|
|
||||||
steps_panel = Panel("\n".join(steps_lines), title="Next steps", border_style="cyan", padding=(1,2))
|
steps_panel = Panel("\n".join(steps_lines), title="Next Steps", border_style="cyan", padding=(1,2))
|
||||||
console.print()
|
console.print()
|
||||||
console.print(steps_panel)
|
console.print(steps_panel)
|
||||||
|
|
||||||
|
if selected_ai == "codex":
|
||||||
|
warning_text = """[bold yellow]Important Note:[/bold yellow]
|
||||||
|
|
||||||
|
Custom prompts do not yet support arguments in Codex. You may need to manually specify additional project instructions directly in prompt files located in [cyan].codex/prompts/[/cyan].
|
||||||
|
|
||||||
|
For more information, see: [cyan]https://github.com/openai/codex/issues/2890[/cyan]"""
|
||||||
|
|
||||||
|
warning_panel = Panel(warning_text, title="Slash Commands in Codex", border_style="yellow", padding=(1,2))
|
||||||
|
console.print()
|
||||||
|
console.print(warning_panel)
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def check():
|
def check():
|
||||||
"""Check that all required tools are installed."""
|
"""Check that all required tools are installed."""
|
||||||
@@ -1012,23 +1085,27 @@ def check():
|
|||||||
tracker.add("claude", "Claude Code CLI")
|
tracker.add("claude", "Claude Code CLI")
|
||||||
tracker.add("gemini", "Gemini CLI")
|
tracker.add("gemini", "Gemini CLI")
|
||||||
tracker.add("qwen", "Qwen Code CLI")
|
tracker.add("qwen", "Qwen Code CLI")
|
||||||
tracker.add("code", "VS Code (for GitHub Copilot)")
|
tracker.add("code", "Visual Studio Code")
|
||||||
tracker.add("cursor-agent", "Cursor IDE agent (optional)")
|
tracker.add("code-insiders", "Visual Studio Code Insiders")
|
||||||
tracker.add("windsurf", "Windsurf IDE (optional)")
|
tracker.add("cursor-agent", "Cursor IDE agent")
|
||||||
|
tracker.add("windsurf", "Windsurf IDE")
|
||||||
|
tracker.add("kilocode", "Kilo Code IDE")
|
||||||
tracker.add("opencode", "opencode")
|
tracker.add("opencode", "opencode")
|
||||||
tracker.add("codex", "Codex CLI")
|
tracker.add("codex", "Codex CLI")
|
||||||
|
tracker.add("auggie", "Auggie CLI")
|
||||||
|
|
||||||
git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker)
|
git_ok = check_tool_for_tracker("git", tracker)
|
||||||
claude_ok = check_tool_for_tracker("claude", "https://docs.anthropic.com/en/docs/claude-code/setup", tracker)
|
claude_ok = check_tool_for_tracker("claude", tracker)
|
||||||
gemini_ok = check_tool_for_tracker("gemini", "https://github.com/google-gemini/gemini-cli", tracker)
|
gemini_ok = check_tool_for_tracker("gemini", tracker)
|
||||||
qwen_ok = check_tool_for_tracker("qwen", "https://github.com/QwenLM/qwen-code", tracker)
|
qwen_ok = check_tool_for_tracker("qwen", tracker)
|
||||||
code_ok = check_tool_for_tracker("code", "https://code.visualstudio.com/", tracker)
|
code_ok = check_tool_for_tracker("code", tracker)
|
||||||
if not code_ok:
|
code_insiders_ok = check_tool_for_tracker("code-insiders", tracker)
|
||||||
code_ok = check_tool_for_tracker("code-insiders", "https://code.visualstudio.com/insiders/", tracker)
|
cursor_ok = check_tool_for_tracker("cursor-agent", tracker)
|
||||||
cursor_ok = check_tool_for_tracker("cursor-agent", "https://cursor.sh/", tracker)
|
windsurf_ok = check_tool_for_tracker("windsurf", tracker)
|
||||||
windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", tracker)
|
kilocode_ok = check_tool_for_tracker("kilocode", tracker)
|
||||||
opencode_ok = check_tool_for_tracker("opencode", "https://opencode.ai/", tracker)
|
opencode_ok = check_tool_for_tracker("opencode", tracker)
|
||||||
codex_ok = check_tool_for_tracker("codex", "https://github.com/openai/codex", tracker)
|
codex_ok = check_tool_for_tracker("codex", tracker)
|
||||||
|
auggie_ok = check_tool_for_tracker("auggie", tracker)
|
||||||
|
|
||||||
console.print(tracker.render())
|
console.print(tracker.render())
|
||||||
|
|
||||||
@@ -1036,7 +1113,7 @@ def check():
|
|||||||
|
|
||||||
if not git_ok:
|
if not git_ok:
|
||||||
console.print("[dim]Tip: Install git for repository management[/dim]")
|
console.print("[dim]Tip: Install git for repository management[/dim]")
|
||||||
if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or opencode_ok or codex_ok):
|
if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok):
|
||||||
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
---
|
---
|
||||||
description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync.
|
description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync.
|
||||||
# (No scripts section: constitution edits are manual authoring assisted by the agent)
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
|
||||||
|
|
||||||
|
User input:
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
You are updating the project constitution at `/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts.
|
You are updating the project constitution at `/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts.
|
||||||
|
|
||||||
Follow this execution flow:
|
Follow this execution flow:
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
---
|
---
|
||||||
description: Execute the implementation plan by processing and executing all tasks defined in tasks.md
|
description: Execute the implementation plan by processing and executing all tasks defined in tasks.md
|
||||||
scripts:
|
scripts:
|
||||||
sh: scripts/bash/check-implementation-prerequisites.sh --json
|
sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
|
||||||
ps: scripts/powershell/check-implementation-prerequisites.ps1 -Json
|
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
|
||||||
---
|
---
|
||||||
|
|
||||||
Given the current feature context, do this:
|
The user input can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
|
||||||
|
|
||||||
|
User input:
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
|
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ scripts:
|
|||||||
ps: scripts/powershell/setup-plan.ps1 -Json
|
ps: scripts/powershell/setup-plan.ps1 -Json
|
||||||
---
|
---
|
||||||
|
|
||||||
|
The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
|
||||||
|
|
||||||
|
User input:
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
Given the implementation details provided as an argument, do this:
|
Given the implementation details provided as an argument, do this:
|
||||||
|
|
||||||
1. Run `{SCRIPT}` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute.
|
1. Run `{SCRIPT}` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute.
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ scripts:
|
|||||||
ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}"
|
ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
|
||||||
|
|
||||||
|
User input:
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided an empty command.
|
The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided an empty command.
|
||||||
|
|
||||||
Given that feature description, do this:
|
Given that feature description, do this:
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
---
|
---
|
||||||
description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
|
description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
|
||||||
scripts:
|
scripts:
|
||||||
sh: scripts/bash/check-task-prerequisites.sh --json
|
sh: scripts/bash/check-prerequisites.sh --json
|
||||||
ps: scripts/powershell/check-task-prerequisites.ps1 -Json
|
ps: scripts/powershell/check-prerequisites.ps1 -Json
|
||||||
---
|
---
|
||||||
|
|
||||||
Given the context provided as an argument, do this:
|
The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
|
||||||
|
|
||||||
|
User input:
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
|
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
|
||||||
2. Load and analyze available design documents:
|
2. Load and analyze available design documents:
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ 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 `{SCRIPT}` for your AI assistant
|
- Run `{SCRIPT}`
|
||||||
|
**IMPORTANT**: Execute it exactly as specified above. Do not add or remove any arguments.
|
||||||
- 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