From 584175351ae2f3ceb04b5766e7de1cfcdc4f6c1a Mon Sep 17 00:00:00 2001 From: Zhiqiang ZHOU Date: Tue, 9 Sep 2025 13:39:00 -0700 Subject: [PATCH 01/13] fix: support Claude CLI installed via migrate-installer After running `claude migrate-installer`, the Claude executable is moved from PATH to ~/.claude/local/claude. This change updates check_tool() to prioritize checking this local path before falling back to PATH lookup. Fixes: #123 Co-Authored-By: Claude --- src/specify_cli/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index aa3cf3c..7991a64 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -332,6 +332,17 @@ def run_command(cmd: list[str], check_return: bool = True, capture: bool = False def check_tool(tool: str, install_hint: str) -> bool: """Check if a tool is installed.""" + + # Special handling for Claude CLI after `claude migrate-installer` + # See: https://github.com/github/spec-kit/issues/123 + # The migrate-installer command REMOVES the original executable from PATH + # and creates an alias at ~/.claude/local/claude instead + # This path should be prioritized over other claude executables in PATH + if tool == "claude": + claude_local_path = Path.home() / ".claude" / "local" / "claude" + if claude_local_path.exists() and claude_local_path.is_file(): + return True + if shutil.which(tool): return True else: From 24ba30444eb6ae4cce663b4c00d3fefee523c68e Mon Sep 17 00:00:00 2001 From: Zhiqiang ZHOU Date: Tue, 9 Sep 2025 13:40:54 -0700 Subject: [PATCH 02/13] refactor: extract Claude local path to constant for maintainability Extract the hardcoded Claude CLI local installation path to a constant CLAUDE_LOCAL_PATH to improve maintainability and make it easier to update if the installation path changes in the future. Co-Authored-By: Claude --- src/specify_cli/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 7991a64..0fc0681 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -54,6 +54,9 @@ AI_CHOICES = { "gemini": "Gemini CLI" } +# Claude CLI local installation path after migrate-installer +CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude" + # ASCII Art Banner BANNER = """ ███████╗██████╗ ███████╗ ██████╗██╗███████╗██╗ ██╗ @@ -339,8 +342,7 @@ def check_tool(tool: str, install_hint: str) -> bool: # and creates an alias at ~/.claude/local/claude instead # This path should be prioritized over other claude executables in PATH if tool == "claude": - claude_local_path = Path.home() / ".claude" / "local" / "claude" - if claude_local_path.exists() and claude_local_path.is_file(): + if CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file(): return True if shutil.which(tool): From f89361cd3dc326ca8f40e36607dcc7134f50a3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:06:04 -0700 Subject: [PATCH 03/13] Local dev guide and script updates --- docs/index.md | 1 + docs/local-development.md | 131 ++++++++++++++++++++++++++++++++++++ docs/toc.yml | 23 ++++--- src/specify_cli/__init__.py | 65 ++++++++++++++++++ 4 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 docs/local-development.md diff --git a/docs/index.md b/docs/index.md index fcc1835..4729164 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,6 +12,7 @@ Spec-Driven Development **flips the script** on traditional software development - [Installation Guide](installation.md) - [Quick Start Guide](quickstart.md) + - [Local Development](local-development.md) ## Core Philosophy diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 0000000..90bf7d3 --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,131 @@ +# Local Development Guide + +This guide shows how to iterate on the `specify` CLI locally without publishing a release or committing to `main` first. + +## 1. Clone and Switch Branches + +```bash +git clone https://github.com/github/spec-kit.git +cd spec-kit +# Work on a feature branch +git checkout -b your-feature-branch +``` + +## 2. Run the CLI Directly (Fastest Feedback) + +You can execute the CLI via the module entrypoint without installing anything: + +```bash +# From repo root +python -m src.specify_cli --help +python -m src.specify_cli init demo-project --ai claude --ignore-agent-tools +``` + +If you prefer invoking the script file style (uses shebang): + +```bash +python src/specify_cli/__init__.py init demo-project +``` + +## 3. Use Editable Install (Isolated Environment) + +Create an isolated environment using `uv` so dependencies resolve exactly like end users get them: + +```bash +# Create & activate virtual env (uv auto-manages .venv) +uv venv +source .venv/bin/activate # or on Windows: .venv\\Scripts\\activate + +# Install project in editable mode +uv pip install -e . + +# Now 'specify' entrypoint is available +specify --help +``` + +Re-running after code edits requires no reinstall because of editable mode. + +## 4. Invoke with uvx Directly From Git (Current Branch) + +`uvx` can run from a local path (or a Git ref) to simulate user flows: + +```bash +uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools +``` + +You can also point uvx at a specific branch without merging: + +```bash +# Push your working branch first +git push origin your-feature-branch +uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specify init demo-branch-test +``` + +## 5. Testing Script Permission Logic +After running an `init`, check that shell scripts are executable on POSIX systems: +```bash +ls -l scripts | grep .sh +# Expect owner execute bit (e.g. -rwxr-xr-x) +``` +On Windows this step is a no-op. + +## 6. Run Lint / Basic Checks (Add Your Own) +Currently no enforced lint config is bundled, but you can quickly sanity check importability: +```bash +python -c "import specify_cli; print('Import OK')" +``` + +## 7. Build a Wheel Locally (Optional) +Validate packaging before publishing: +```bash +uv build +ls dist/ +``` +Install the built artifact into a fresh throwaway environment if needed. + +## 8. Using a Temporary Workspace +When testing `init --here` in a dirty directory, create a temp workspace: +```bash +mkdir /tmp/spec-test && cd /tmp/spec-test +python -m src.specify_cli init --here --ai claude --ignore-agent-tools # if repo copied here +``` +Or copy only the modified CLI portion if you want a lighter sandbox. + +## 9. Debug Network / TLS Skips +If you need to bypass TLS validation while experimenting: +```bash +specify check --skip-tls +specify init demo --skip-tls --ai gemini --ignore-agent-tools +``` +(Use only for local experimentation.) + +## 10. Rapid Edit Loop Summary +| Action | Command | +|--------|---------| +| Run CLI directly | `python -m src.specify_cli --help` | +| Editable install | `uv pip install -e .` then `specify ...` | +| Local uvx run | `uvx --from . specify ...` | +| Git branch uvx | `uvx --from git+URL@branch specify ...` | +| Build wheel | `uv build` | + +## 11. Cleaning Up +Remove build artifacts / virtual env quickly: +```bash +rm -rf .venv dist build *.egg-info +``` + +## 12. Common Issues +| Symptom | Fix | +|---------|-----| +| `ModuleNotFoundError: typer` | Run `uv pip install -e .` | +| Scripts not executable (Linux) | Re-run init (logic adds bits) or `chmod +x scripts/*.sh` | +| Git step skipped | You passed `--no-git` or Git not installed | +| TLS errors on corporate network | Try `--skip-tls` (not for production) | + +## 13. Next Steps +- Update docs and run through Quick Start using your modified CLI +- Open a PR when satisfied +- (Optional) Tag a release once changes land in `main` + +--- +Feel free to expand this guide with additional local workflows (debugging, profiling, test automation) as the project matures. diff --git a/docs/toc.yml b/docs/toc.yml index c12e149..ecabd18 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,10 +1,17 @@ +# Home page - name: Home href: index.md -- name: Installation - href: installation.md -- name: Quick Start - href: quickstart.md -- name: Contributing - href: CONTRIBUTING.md -- name: Support - href: SUPPORT.md + +# Getting started section +- name: Getting Started + items: + - name: Installation + href: installation.md + - name: Quick Start + href: quickstart.md + +# Development workflows +- name: Development + items: + - name: Local Development + href: local-development.md diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 704d354..1e1004c 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -635,6 +635,67 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr return project_path +def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None: + """Ensure POSIX .sh scripts in the project scripts directory have execute bits (no-op on Windows).""" + if os.name == "nt": + return # Windows: skip silently + scripts_dir = project_path / "scripts" + if not scripts_dir.is_dir(): + return + failures: list[str] = [] + updated = 0 + for script in scripts_dir.glob("*.sh"): + try: + # Skip symlinks + if script.is_symlink(): + continue + # Must be a regular file + if not script.is_file(): + continue + # Quick shebang check + try: + with script.open("rb") as f: + first_two = f.read(2) + if first_two != b"#!": + continue + except Exception: + continue + st = script.stat() + mode = st.st_mode + # If already any execute bit set, skip + if mode & 0o111: + continue + # Only add execute bits that correspond to existing read bits + new_mode = mode + if mode & 0o400: # owner read + new_mode |= 0o100 + if mode & 0o040: # group read + new_mode |= 0o010 + if mode & 0o004: # other read + new_mode |= 0o001 + # Fallback: ensure at least owner execute + if not (new_mode & 0o100): + new_mode |= 0o100 + os.chmod(script, new_mode) + updated += 1 + except Exception as e: + failures.append(f"{script.name}: {e}") + if tracker: + detail = f"{updated} updated" + (f", {len(failures)} failed" if failures else "") + tracker.add("chmod", "Set script permissions") + if failures: + tracker.error("chmod", detail) + else: + tracker.complete("chmod", detail) + else: + if updated: + console.print(f"[cyan]Updated execute permissions on {updated} script(s)[/cyan]") + if failures: + console.print("[yellow]Some scripts could not be updated:[/yellow]") + for f in failures: + console.print(f" - {f}") + + @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), @@ -761,6 +822,7 @@ def init( ("extract", "Extract template"), ("zip-list", "Archive contents"), ("extracted-summary", "Extraction summary"), + ("chmod", "Ensure scripts executable"), ("cleanup", "Cleanup"), ("git", "Initialize git repository"), ("final", "Finalize") @@ -778,6 +840,9 @@ def init( download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client) + # Ensure scripts are executable (POSIX) + ensure_executable_scripts(project_path, tracker=tracker) + # Git step if not no_git: tracker.start("git") From 15917c2094341060f54512b7e5e580675b12eb74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:07:43 -0700 Subject: [PATCH 04/13] Update local-development.md --- docs/local-development.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/local-development.md b/docs/local-development.md index 90bf7d3..2a12ac1 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -62,7 +62,9 @@ uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specif ``` ## 5. Testing Script Permission Logic + After running an `init`, check that shell scripts are executable on POSIX systems: + ```bash ls -l scripts | grep .sh # Expect owner execute bit (e.g. -rwxr-xr-x) @@ -70,13 +72,16 @@ ls -l scripts | grep .sh On Windows this step is a no-op. ## 6. Run Lint / Basic Checks (Add Your Own) + Currently no enforced lint config is bundled, but you can quickly sanity check importability: ```bash python -c "import specify_cli; print('Import OK')" ``` ## 7. Build a Wheel Locally (Optional) + Validate packaging before publishing: + ```bash uv build ls dist/ @@ -84,7 +89,9 @@ ls dist/ Install the built artifact into a fresh throwaway environment if needed. ## 8. Using a Temporary Workspace + When testing `init --here` in a dirty directory, create a temp workspace: + ```bash mkdir /tmp/spec-test && cd /tmp/spec-test python -m src.specify_cli init --here --ai claude --ignore-agent-tools # if repo copied here @@ -92,7 +99,9 @@ python -m src.specify_cli init --here --ai claude --ignore-agent-tools # if rep Or copy only the modified CLI portion if you want a lighter sandbox. ## 9. Debug Network / TLS Skips + If you need to bypass TLS validation while experimenting: + ```bash specify check --skip-tls specify init demo --skip-tls --ai gemini --ignore-agent-tools @@ -100,6 +109,7 @@ specify init demo --skip-tls --ai gemini --ignore-agent-tools (Use only for local experimentation.) ## 10. Rapid Edit Loop Summary + | Action | Command | |--------|---------| | Run CLI directly | `python -m src.specify_cli --help` | @@ -109,12 +119,14 @@ specify init demo --skip-tls --ai gemini --ignore-agent-tools | Build wheel | `uv build` | ## 11. Cleaning Up + Remove build artifacts / virtual env quickly: ```bash rm -rf .venv dist build *.egg-info ``` ## 12. Common Issues + | Symptom | Fix | |---------|-----| | `ModuleNotFoundError: typer` | Run `uv pip install -e .` | @@ -123,9 +135,8 @@ rm -rf .venv dist build *.egg-info | TLS errors on corporate network | Try `--skip-tls` (not for production) | ## 13. Next Steps + - Update docs and run through Quick Start using your modified CLI - Open a PR when satisfied - (Optional) Tag a release once changes land in `main` ---- -Feel free to expand this guide with additional local workflows (debugging, profiling, test automation) as the project matures. From b31ca199629989c2623e5c114f218372403f305d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:46:21 -0700 Subject: [PATCH 05/13] Update how Copilot prompts are created --- .github/workflows/release.yml | 23 ++++++----------------- docs/local-development.md | 25 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c1ebfbc..efa13f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -96,27 +96,16 @@ jobs: name=$(basename "$template" .md) description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r') content=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template" | sed "s/{ARGS}/$arg_format/g") - case $ext in "toml") - { - echo "description = \"$description\"" - echo "" - echo "prompt = \"\"\"" - echo "$content" - echo "\"\"\"" - } > "$output_dir/$name.$ext" - ;; + { echo "description = \"$description\""; echo ""; echo "prompt = \"\"\""; echo "$content"; echo "\"\"\""; } > "$output_dir/$name.$ext" ;; "md") - echo "$content" > "$output_dir/$name.$ext" - ;; + echo "$content" > "$output_dir/$name.$ext" ;; "prompt.md") - { - echo "# $(echo "$description" | sed 's/\. .*//')" - echo "" - echo "$content" - } > "$output_dir/$name.$ext" - ;; + # Preserve original front matter + body with argument substitution for Copilot + # Simply copy the entire template, replacing {ARGS} + # This keeps YAML so tooling can parse metadata + sed "s/{ARGS}/$arg_format/g" "$template" > "$output_dir/$name.$ext" ;; esac fi done diff --git a/docs/local-development.md b/docs/local-development.md index 2a12ac1..20a3d9e 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -61,6 +61,28 @@ git push origin your-feature-branch uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specify init demo-branch-test ``` +### 4a. Absolute Path uvx (Run From Anywhere) + +If you're in another directory, use an absolute path instead of `.`: + +```bash +uvx --from /mnt/c/GitHub/spec-kit specify --help +uvx --from /mnt/c/GitHub/spec-kit specify init demo-anywhere --ai copilot --ignore-agent-tools +``` + +Set an environment variable for convenience: +```bash +export SPEC_KIT_SRC=/mnt/c/GitHub/spec-kit +uvx --from "$SPEC_KIT_SRC" specify init demo-env --ai copilot --ignore-agent-tools +``` + +(Optional) Define a shell function: +```bash +specify-dev() { uvx --from /mnt/c/GitHub/spec-kit specify "$@"; } +# Then +specify-dev --help +``` + ## 5. Testing Script Permission Logic After running an `init`, check that shell scripts are executable on POSIX systems: @@ -114,7 +136,8 @@ specify init demo --skip-tls --ai gemini --ignore-agent-tools |--------|---------| | Run CLI directly | `python -m src.specify_cli --help` | | Editable install | `uv pip install -e .` then `specify ...` | -| Local uvx run | `uvx --from . specify ...` | +| Local uvx run (repo root) | `uvx --from . specify ...` | +| Local uvx run (abs path) | `uvx --from /mnt/c/GitHub/spec-kit specify ...` | | Git branch uvx | `uvx --from git+URL@branch specify ...` | | Build wheel | `uv build` | From ee6b83c1dd94fec0b48b4203853549ce860ede9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:59:36 -0700 Subject: [PATCH 06/13] Consolidate script creation --- .github/workflows/manual-release.yml | 191 --------------------------- .github/workflows/release.yml | 90 +------------ scripts/create-release-packages.sh | 115 ++++++++++++++++ 3 files changed, 117 insertions(+), 279 deletions(-) delete mode 100644 .github/workflows/manual-release.yml create mode 100644 scripts/create-release-packages.sh diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml deleted file mode 100644 index 0251517..0000000 --- a/.github/workflows/manual-release.yml +++ /dev/null @@ -1,191 +0,0 @@ -name: Manual Release - -on: - workflow_dispatch: - inputs: - version_bump: - description: 'Version bump type' - required: true - default: 'patch' - type: choice - options: - - patch - - minor - - major - -jobs: - manual_release: - runs-on: ubuntu-latest - - permissions: - contents: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Calculate new version - id: version - run: | - # Get the latest tag, or use v0.0.0 if no tags exist - LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") - echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT - - # Extract version number - VERSION=$(echo $LATEST_TAG | sed 's/v//') - IFS='.' read -ra VERSION_PARTS <<< "$VERSION" - MAJOR=${VERSION_PARTS[0]:-0} - MINOR=${VERSION_PARTS[1]:-0} - PATCH=${VERSION_PARTS[2]:-0} - - # Increment based on input - case "${{ github.event.inputs.version_bump }}" in - "major") - MAJOR=$((MAJOR + 1)) - MINOR=0 - PATCH=0 - ;; - "minor") - MINOR=$((MINOR + 1)) - PATCH=0 - ;; - "patch") - PATCH=$((PATCH + 1)) - ;; - esac - - NEW_VERSION="v$MAJOR.$MINOR.$PATCH" - echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT - echo "New version will be: $NEW_VERSION (was $LATEST_TAG)" - - - name: Create release package - run: | - # Create base package directory structure - mkdir -p sdd-package-base - - # Copy common folders to base - echo "Packaging SDD common components..." - - if [ -d "memory" ]; then - cp -r memory sdd-package-base/ - echo "✓ Copied memory folder ($(find memory -type f | wc -l) files)" - else - echo "⚠️ memory folder not found" - fi - - if [ -d "scripts" ]; then - cp -r scripts sdd-package-base/ - echo "✓ Copied scripts folder ($(find scripts -type f | wc -l) files)" - else - echo "⚠️ scripts folder not found" - fi - - # Create Claude Code package - echo "Creating Claude Code package..." - mkdir -p sdd-claude-package - cp -r sdd-package-base/* sdd-claude-package/ - if [ -d "agent_templates/claude" ]; then - cp -r agent_templates/claude sdd-claude-package/.claude - echo "✓ Added Claude Code commands ($(find agent_templates/claude -type f | wc -l) files)" - else - echo "⚠️ agent_templates/claude folder not found" - fi - - # Create Gemini CLI package - echo "Creating Gemini CLI package..." - mkdir -p sdd-gemini-package - cp -r sdd-package-base/* sdd-gemini-package/ - if [ -d "agent_templates/gemini" ]; then - cp -r agent_templates/gemini sdd-gemini-package/.gemini - # Move GEMINI.md to root for easier access - if [ -f "sdd-gemini-package/.gemini/GEMINI.md" ]; then - mv sdd-gemini-package/.gemini/GEMINI.md sdd-gemini-package/GEMINI.md - echo "✓ Moved GEMINI.md to root of Gemini package" - fi - # Remove empty .gemini folder if it only contained GEMINI.md - if [ -d "sdd-gemini-package/.gemini" ] && [ -z "$(find sdd-gemini-package/.gemini -type f)" ]; then - rm -rf sdd-gemini-package/.gemini - echo "✓ Removed empty .gemini folder" - fi - echo "✓ Added Gemini CLI commands ($(find agent_templates/gemini -type f | wc -l) files)" - else - echo "⚠️ agent_templates/gemini folder not found" - fi - - # Create GitHub Copilot package - echo "Creating GitHub Copilot package..." - mkdir -p sdd-copilot-package - cp -r sdd-package-base/* sdd-copilot-package/ - if [ -d "agent_templates/copilot" ]; then - mkdir -p sdd-copilot-package/.github - cp -r agent_templates/copilot/* sdd-copilot-package/.github/ - echo "✓ Added Copilot instructions to .github ($(find agent_templates/copilot -type f | wc -l) files)" - else - echo "⚠️ agent_templates/copilot folder not found" - fi - - # Create archive files for each package - echo "Creating archive files..." - cd sdd-claude-package && zip -r ../spec-kit-template-claude-${{ steps.version.outputs.new_version }}.zip . && cd .. - - cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${{ steps.version.outputs.new_version }}.zip . && cd .. - - cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${{ steps.version.outputs.new_version }}.zip . && cd .. - - echo "" - echo "📦 Packages created:" - echo "Claude: $(ls -lh spec-kit-template-claude-*.zip | awk '{print $5}')" - echo "Gemini: $(ls -lh spec-kit-template-gemini-*.zip | awk '{print $5}')" - echo "Copilot: $(ls -lh spec-kit-template-copilot-*.zip | awk '{print $5}')" - echo "Copilot: $(ls -lh sdd-template-copilot-*.zip | awk '{print $5}')" - - - name: Generate detailed release notes - run: | - LAST_TAG=${{ steps.version.outputs.latest_tag }} - - # Get commit range - if [ "$LAST_TAG" = "v0.0.0" ]; then - COMMIT_RANGE="HEAD~10..HEAD" - COMMITS=$(git log --oneline --pretty=format:"- %s" $COMMIT_RANGE 2>/dev/null || echo "- Initial release") - else - COMMIT_RANGE="$LAST_TAG..HEAD" - COMMITS=$(git log --oneline --pretty=format:"- %s" $COMMIT_RANGE 2>/dev/null || echo "- No changes since last release") - fi - - # Count files in each directory - CLAUDE_COUNT=$(find agent_templates/claude -type f 2>/dev/null | wc -l || echo "0") - GEMINI_COUNT=$(find agent_templates/gemini -type f 2>/dev/null | wc -l || echo "0") - COPILOT_COUNT=$(find agent_templates/copilot -type f 2>/dev/null | wc -l || echo "0") - MEMORY_COUNT=$(find memory -type f 2>/dev/null | wc -l || echo "0") - SCRIPTS_COUNT=$(find scripts -type f 2>/dev/null | wc -l || echo "0") - - cat > release_notes.md << EOF - Template release ${{ steps.version.outputs.new_version }} - - Updated specification-driven development templates for GitHub Copilot, Claude Code, and Gemini CLI. - - Download the template for your preferred AI assistant: - - spec-kit-template-copilot-${{ steps.version.outputs.new_version }}.zip - - spec-kit-template-claude-${{ steps.version.outputs.new_version }}.zip - - spec-kit-template-gemini-${{ steps.version.outputs.new_version }}.zip - - Changes since $LAST_TAG: - $COMMITS - EOF - - - name: Create GitHub Release - run: | - # Remove 'v' prefix from version for release title - VERSION_NO_V=${{ steps.version.outputs.new_version }} - VERSION_NO_V=${VERSION_NO_V#v} - - gh release create ${{ steps.version.outputs.new_version }} \ - spec-kit-template-copilot-${{ steps.version.outputs.new_version }}.zip \ - spec-kit-template-claude-${{ steps.version.outputs.new_version }}.zip \ - spec-kit-template-gemini-${{ steps.version.outputs.new_version }}.zip \ - --title "Spec Kit Templates - $VERSION_NO_V" \ - --notes-file release_notes.md - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efa13f3..b2897d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,94 +61,8 @@ jobs: - name: Create release package if: steps.check_release.outputs.exists == 'false' run: | - # Create base package directory structure - mkdir -p sdd-package-base - - # Copy common folders to base - if [ -d "memory" ]; then - cp -r memory sdd-package-base/ - echo "Copied memory folder" - fi - - if [ -d "scripts" ]; then - cp -r scripts sdd-package-base/ - echo "Copied scripts folder" - fi - - if [ -d "templates" ]; then - mkdir -p sdd-package-base/templates - # Copy templates folder but exclude the commands directory - find templates -type f -not -path "templates/commands/*" -exec cp --parents {} sdd-package-base/ \; - echo "Copied templates folder (excluding commands directory)" - fi - - # Generate command files for each agent from source templates - generate_commands() { - local agent=$1 - local ext=$2 - local arg_format=$3 - local output_dir=$4 - - mkdir -p "$output_dir" - - for template in templates/commands/*.md; do - if [[ -f "$template" ]]; then - name=$(basename "$template" .md) - description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r') - content=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template" | sed "s/{ARGS}/$arg_format/g") - case $ext in - "toml") - { echo "description = \"$description\""; echo ""; echo "prompt = \"\"\""; echo "$content"; echo "\"\"\""; } > "$output_dir/$name.$ext" ;; - "md") - echo "$content" > "$output_dir/$name.$ext" ;; - "prompt.md") - # Preserve original front matter + body with argument substitution for Copilot - # Simply copy the entire template, replacing {ARGS} - # This keeps YAML so tooling can parse metadata - sed "s/{ARGS}/$arg_format/g" "$template" > "$output_dir/$name.$ext" ;; - esac - fi - done - } - - # Create Claude Code package - mkdir -p sdd-claude-package - cp -r sdd-package-base/* sdd-claude-package/ - mkdir -p sdd-claude-package/.claude/commands - generate_commands "claude" "md" "\$ARGUMENTS" "sdd-claude-package/.claude/commands" - echo "Created Claude Code package" - - # Create Gemini CLI package - mkdir -p sdd-gemini-package - cp -r sdd-package-base/* sdd-gemini-package/ - mkdir -p sdd-gemini-package/.gemini/commands - generate_commands "gemini" "toml" "{{args}}" "sdd-gemini-package/.gemini/commands" - if [ -f "agent_templates/gemini/GEMINI.md" ]; then - cp agent_templates/gemini/GEMINI.md sdd-gemini-package/GEMINI.md - fi - echo "Created Gemini CLI package" - - # Create GitHub Copilot package - mkdir -p sdd-copilot-package - cp -r sdd-package-base/* sdd-copilot-package/ - mkdir -p sdd-copilot-package/.github/prompts - generate_commands "copilot" "prompt.md" "\$ARGUMENTS" "sdd-copilot-package/.github/prompts" - echo "Created GitHub Copilot package" - - # Create archive files for each package - cd sdd-claude-package && zip -r ../spec-kit-template-claude-${{ steps.get_tag.outputs.new_version }}.zip . && cd .. - - cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${{ steps.get_tag.outputs.new_version }}.zip . && cd .. - - cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${{ steps.get_tag.outputs.new_version }}.zip . && cd .. - - # List contents for verification - echo "Claude package contents:" - unzip -l spec-kit-template-claude-${{ steps.get_tag.outputs.new_version }}.zip | head -10 - echo "Gemini package contents:" - unzip -l spec-kit-template-gemini-${{ steps.get_tag.outputs.new_version }}.zip | head -10 - echo "Copilot package contents:" - unzip -l spec-kit-template-copilot-${{ steps.get_tag.outputs.new_version }}.zip | head -10 + chmod +x scripts/create-release-packages.sh + ./scripts/create-release-packages.sh ${{ steps.get_tag.outputs.new_version }} - name: Generate release notes if: steps.check_release.outputs.exists == 'false' diff --git a/scripts/create-release-packages.sh b/scripts/create-release-packages.sh new file mode 100644 index 0000000..9f1380f --- /dev/null +++ b/scripts/create-release-packages.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +set -euo pipefail + +# create-release-packages.sh +# Build Spec Kit template release archives for each supported AI assistant. +# Usage: ./scripts/create-release-packages.sh +# should include the leading 'v' (e.g. v0.0.4) + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +NEW_VERSION="$1" +if [[ ! $NEW_VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Version must look like v0.0.0" >&2 + exit 1 +fi + +echo "Building release packages for $NEW_VERSION" + +# Clean any previous build dirs +rm -rf sdd-package-base sdd-claude-package sdd-gemini-package sdd-copilot-package \ + spec-kit-template-claude-${NEW_VERSION}.zip \ + spec-kit-template-gemini-${NEW_VERSION}.zip \ + spec-kit-template-copilot-${NEW_VERSION}.zip || true + +mkdir -p sdd-package-base + +# Copy common folders to base +if [[ -d memory ]]; then + cp -r memory sdd-package-base/ + echo "Copied memory folder" +fi + +if [[ -d scripts ]]; then + # Exclude this script itself from being copied + rsync -a --exclude 'create-release-packages.sh' scripts/ sdd-package-base/scripts/ + echo "Copied scripts folder (excluding create-release-packages.sh)" +fi + +if [[ -d templates ]]; then + mkdir -p sdd-package-base/templates + # Copy all template files excluding commands (processed separately per assistant) + find templates -type f -not -path "templates/commands/*" -exec cp --parents {} sdd-package-base/ \; + echo "Copied templates folder (excluding commands directory)" +fi + +# Function to generate assistant command files/prompts +# Args: agent ext arg_format output_dir +generate_commands() { + local agent=$1 + local ext=$2 + local arg_format=$3 + local output_dir=$4 + mkdir -p "$output_dir" + for template in templates/commands/*.md; do + [[ -f "$template" ]] || continue + local name + name=$(basename "$template" .md) + local description + description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r') + local content + content=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template" | sed "s/{ARGS}/$arg_format/g") + case $ext in + "toml") + { + echo "description = \"$description\""; echo ""; echo "prompt = \"\"\""; echo "$content"; echo "\"\"\""; + } > "$output_dir/$name.$ext" + ;; + "md") + echo "$content" > "$output_dir/$name.$ext" + ;; + "prompt.md") + # Preserve front matter exactly, just substitute {ARGS} + sed "s/{ARGS}/$arg_format/g" "$template" > "$output_dir/$name.$ext" + ;; + esac + done +} + +# Claude package +mkdir -p sdd-claude-package +cp -r sdd-package-base/* sdd-claude-package/ +mkdir -p sdd-claude-package/.claude/commands +generate_commands "claude" "md" "\$ARGUMENTS" "sdd-claude-package/.claude/commands" +echo "Created Claude Code package" + +# Gemini package +mkdir -p sdd-gemini-package +cp -r sdd-package-base/* sdd-gemini-package/ +mkdir -p sdd-gemini-package/.gemini/commands +generate_commands "gemini" "toml" "{{args}}" "sdd-gemini-package/.gemini/commands" +if [[ -f agent_templates/gemini/GEMINI.md ]]; then + cp agent_templates/gemini/GEMINI.md sdd-gemini-package/GEMINI.md +fi +echo "Created Gemini CLI package" + +# Copilot package +mkdir -p sdd-copilot-package +cp -r sdd-package-base/* sdd-copilot-package/ +mkdir -p sdd-copilot-package/.github/prompts +generate_commands "copilot" "prompt.md" "\$ARGUMENTS" "sdd-copilot-package/.github/prompts" +echo "Created GitHub Copilot package" + +# Archives +( cd sdd-claude-package && zip -r ../spec-kit-template-claude-${NEW_VERSION}.zip . ) +( cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${NEW_VERSION}.zip . ) +( cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${NEW_VERSION}.zip . ) + +echo "Package archives created:" +ls -1 spec-kit-template-*-${NEW_VERSION}.zip + +# Basic verification snippet +unzip -l spec-kit-template-copilot-${NEW_VERSION}.zip | head -10 || true From 4b66f216e91c348d4c1ab22da98d9197a0953837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:41:45 -0700 Subject: [PATCH 07/13] Update release file --- .github/workflows/release.yml | 4 +- .../scripts/create-release-packages.sh | 89 ++++++++++++++ scripts/create-release-packages.sh | 115 ------------------ 3 files changed, 91 insertions(+), 117 deletions(-) create mode 100644 .github/workflows/scripts/create-release-packages.sh delete mode 100644 scripts/create-release-packages.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2897d4..39b7174 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,8 +61,8 @@ jobs: - name: Create release package if: steps.check_release.outputs.exists == 'false' run: | - chmod +x scripts/create-release-packages.sh - ./scripts/create-release-packages.sh ${{ steps.get_tag.outputs.new_version }} + chmod +x .github/workflows/scripts/create-release-packages.sh + .github/workflows/scripts/create-release-packages.sh ${{ steps.get_tag.outputs.new_version }} - name: Generate release notes if: steps.check_release.outputs.exists == 'false' diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh new file mode 100644 index 0000000..d4ad988 --- /dev/null +++ b/.github/workflows/scripts/create-release-packages.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -euo pipefail + +# create-release-packages.sh (workflow-local) +# Build Spec Kit template release archives for each supported AI assistant. +# Usage: .github/workflows/scripts/create-release-packages.sh +# Version argument should include leading 'v'. + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi +NEW_VERSION="$1" +if [[ ! $NEW_VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Version must look like v0.0.0" >&2 + exit 1 +fi + +echo "Building release packages for $NEW_VERSION" + +rm -rf sdd-package-base sdd-claude-package sdd-gemini-package sdd-copilot-package \ + spec-kit-template-claude-${NEW_VERSION}.zip \ + spec-kit-template-gemini-${NEW_VERSION}.zip \ + spec-kit-template-copilot-${NEW_VERSION}.zip || true + +mkdir -p sdd-package-base +SPEC_DIR="sdd-package-base/.specify" +mkdir -p "$SPEC_DIR" + +[[ -d memory ]] && { cp -r memory "$SPEC_DIR/"; echo "Copied memory -> .specify"; } +[[ -d scripts ]] && { mkdir -p "$SPEC_DIR/scripts"; rsync -a scripts/ "$SPEC_DIR/scripts/" --exclude 'create-release-packages.sh'; echo "Copied scripts -> .specify/scripts"; } +[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -exec cp --parents {} "$SPEC_DIR"/ \;; echo "Copied templates -> .specify/templates"; } + +rewrite_paths() { + sed -E \ + -e 's@(/?)memory/@.specify/memory/@g' \ + -e 's@(/?)scripts/@.specify/scripts/@g' \ + -e 's@(/?)templates/@.specify/templates/@g' +} + +generate_commands() { + local agent=$1 ext=$2 arg_format=$3 output_dir=$4 + mkdir -p "$output_dir" + for template in templates/commands/*.md; do + [[ -f "$template" ]] || continue + local name description body + name=$(basename "$template" .md) + description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r') + body=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template" | sed "s/{ARGS}/$arg_format/g" | rewrite_paths) + case $ext in + toml) + { echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/$name.$ext" ;; + md) + echo "$body" > "$output_dir/$name.$ext" ;; + prompt.md) + sed "s/{ARGS}/$arg_format/g" "$template" | rewrite_paths > "$output_dir/$name.$ext" ;; + esac + done +} + +mkdir -p sdd-claude-package +cp -r sdd-package-base/* sdd-claude-package/ +mkdir -p sdd-claude-package/.claude/commands +generate_commands claude md "\$ARGUMENTS" sdd-claude-package/.claude/commands + +echo "Created Claude package" + +mkdir -p sdd-gemini-package +cp -r sdd-package-base/* sdd-gemini-package/ +mkdir -p sdd-gemini-package/.gemini/commands +generate_commands gemini toml "{{args}}" sdd-gemini-package/.gemini/commands +[[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md sdd-gemini-package/GEMINI.md + +echo "Created Gemini package" + +mkdir -p sdd-copilot-package +cp -r sdd-package-base/* sdd-copilot-package/ +mkdir -p sdd-copilot-package/.github/prompts +generate_commands copilot prompt.md "\$ARGUMENTS" sdd-copilot-package/.github/prompts + +echo "Created Copilot package" + +( cd sdd-claude-package && zip -r ../spec-kit-template-claude-${NEW_VERSION}.zip . ) +( cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${NEW_VERSION}.zip . ) +( cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${NEW_VERSION}.zip . ) + +echo "Archives:" +ls -1 spec-kit-template-*-${NEW_VERSION}.zip +unzip -l spec-kit-template-copilot-${NEW_VERSION}.zip | head -10 || true diff --git a/scripts/create-release-packages.sh b/scripts/create-release-packages.sh deleted file mode 100644 index 9f1380f..0000000 --- a/scripts/create-release-packages.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# create-release-packages.sh -# Build Spec Kit template release archives for each supported AI assistant. -# Usage: ./scripts/create-release-packages.sh -# should include the leading 'v' (e.g. v0.0.4) - -if [[ $# -ne 1 ]]; then - echo "Usage: $0 " >&2 - exit 1 -fi - -NEW_VERSION="$1" -if [[ ! $NEW_VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Version must look like v0.0.0" >&2 - exit 1 -fi - -echo "Building release packages for $NEW_VERSION" - -# Clean any previous build dirs -rm -rf sdd-package-base sdd-claude-package sdd-gemini-package sdd-copilot-package \ - spec-kit-template-claude-${NEW_VERSION}.zip \ - spec-kit-template-gemini-${NEW_VERSION}.zip \ - spec-kit-template-copilot-${NEW_VERSION}.zip || true - -mkdir -p sdd-package-base - -# Copy common folders to base -if [[ -d memory ]]; then - cp -r memory sdd-package-base/ - echo "Copied memory folder" -fi - -if [[ -d scripts ]]; then - # Exclude this script itself from being copied - rsync -a --exclude 'create-release-packages.sh' scripts/ sdd-package-base/scripts/ - echo "Copied scripts folder (excluding create-release-packages.sh)" -fi - -if [[ -d templates ]]; then - mkdir -p sdd-package-base/templates - # Copy all template files excluding commands (processed separately per assistant) - find templates -type f -not -path "templates/commands/*" -exec cp --parents {} sdd-package-base/ \; - echo "Copied templates folder (excluding commands directory)" -fi - -# Function to generate assistant command files/prompts -# Args: agent ext arg_format output_dir -generate_commands() { - local agent=$1 - local ext=$2 - local arg_format=$3 - local output_dir=$4 - mkdir -p "$output_dir" - for template in templates/commands/*.md; do - [[ -f "$template" ]] || continue - local name - name=$(basename "$template" .md) - local description - description=$(awk '/^description:/ {gsub(/^description: *"?/, ""); gsub(/"$/, ""); print; exit}' "$template" | tr -d '\r') - local content - content=$(awk '/^---$/{if(++count==2) start=1; next} start' "$template" | sed "s/{ARGS}/$arg_format/g") - case $ext in - "toml") - { - echo "description = \"$description\""; echo ""; echo "prompt = \"\"\""; echo "$content"; echo "\"\"\""; - } > "$output_dir/$name.$ext" - ;; - "md") - echo "$content" > "$output_dir/$name.$ext" - ;; - "prompt.md") - # Preserve front matter exactly, just substitute {ARGS} - sed "s/{ARGS}/$arg_format/g" "$template" > "$output_dir/$name.$ext" - ;; - esac - done -} - -# Claude package -mkdir -p sdd-claude-package -cp -r sdd-package-base/* sdd-claude-package/ -mkdir -p sdd-claude-package/.claude/commands -generate_commands "claude" "md" "\$ARGUMENTS" "sdd-claude-package/.claude/commands" -echo "Created Claude Code package" - -# Gemini package -mkdir -p sdd-gemini-package -cp -r sdd-package-base/* sdd-gemini-package/ -mkdir -p sdd-gemini-package/.gemini/commands -generate_commands "gemini" "toml" "{{args}}" "sdd-gemini-package/.gemini/commands" -if [[ -f agent_templates/gemini/GEMINI.md ]]; then - cp agent_templates/gemini/GEMINI.md sdd-gemini-package/GEMINI.md -fi -echo "Created Gemini CLI package" - -# Copilot package -mkdir -p sdd-copilot-package -cp -r sdd-package-base/* sdd-copilot-package/ -mkdir -p sdd-copilot-package/.github/prompts -generate_commands "copilot" "prompt.md" "\$ARGUMENTS" "sdd-copilot-package/.github/prompts" -echo "Created GitHub Copilot package" - -# Archives -( cd sdd-claude-package && zip -r ../spec-kit-template-claude-${NEW_VERSION}.zip . ) -( cd sdd-gemini-package && zip -r ../spec-kit-template-gemini-${NEW_VERSION}.zip . ) -( cd sdd-copilot-package && zip -r ../spec-kit-template-copilot-${NEW_VERSION}.zip . ) - -echo "Package archives created:" -ls -1 spec-kit-template-*-${NEW_VERSION}.zip - -# Basic verification snippet -unzip -l spec-kit-template-copilot-${NEW_VERSION}.zip | head -10 || true From b1858498d402e2248fd1110a6f484b24bec3fa96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:43:44 -0700 Subject: [PATCH 08/13] Update create-release-packages.sh --- .github/workflows/scripts/create-release-packages.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index d4ad988..5331236 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -28,7 +28,7 @@ SPEC_DIR="sdd-package-base/.specify" mkdir -p "$SPEC_DIR" [[ -d memory ]] && { cp -r memory "$SPEC_DIR/"; echo "Copied memory -> .specify"; } -[[ -d scripts ]] && { mkdir -p "$SPEC_DIR/scripts"; rsync -a scripts/ "$SPEC_DIR/scripts/" --exclude 'create-release-packages.sh'; echo "Copied scripts -> .specify/scripts"; } +[[ -d scripts ]] && { cp -r scripts "$SPEC_DIR/"; echo "Copied scripts -> .specify/scripts"; } [[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -exec cp --parents {} "$SPEC_DIR"/ \;; echo "Copied templates -> .specify/templates"; } rewrite_paths() { From 020fd27352c31bfaaf8189210d617d58b2822685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:48:07 -0700 Subject: [PATCH 09/13] Update create-release-packages.sh --- .../workflows/scripts/create-release-packages.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index 5331236..682222b 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -58,26 +58,29 @@ generate_commands() { done } +# Create Claude package +echo "Building Claude package..." mkdir -p sdd-claude-package -cp -r sdd-package-base/* sdd-claude-package/ +cp -r sdd-package-base/. sdd-claude-package/ mkdir -p sdd-claude-package/.claude/commands generate_commands claude md "\$ARGUMENTS" sdd-claude-package/.claude/commands - echo "Created Claude package" +# Create Gemini package +echo "Building Gemini package..." mkdir -p sdd-gemini-package -cp -r sdd-package-base/* sdd-gemini-package/ +cp -r sdd-package-base/. sdd-gemini-package/ mkdir -p sdd-gemini-package/.gemini/commands generate_commands gemini toml "{{args}}" sdd-gemini-package/.gemini/commands [[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md sdd-gemini-package/GEMINI.md - echo "Created Gemini package" +# Create Copilot package +echo "Building Copilot package..." mkdir -p sdd-copilot-package -cp -r sdd-package-base/* sdd-copilot-package/ +cp -r sdd-package-base/. sdd-copilot-package/ mkdir -p sdd-copilot-package/.github/prompts generate_commands copilot prompt.md "\$ARGUMENTS" sdd-copilot-package/.github/prompts - echo "Created Copilot package" ( cd sdd-claude-package && zip -r ../spec-kit-template-claude-${NEW_VERSION}.zip . ) From 20f6c9dede83b2aabbd93cbb0137fcf59e18f968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:51:47 -0700 Subject: [PATCH 10/13] Update release.yml --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39b7174..bdb071a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: - 'memory/**' - 'scripts/**' - 'templates/**' + - '.github/workflows/**' workflow_dispatch: jobs: From 38ad8b0bac385d0958a633a8ad805acaa4ef7873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:54:49 -0700 Subject: [PATCH 11/13] Update __init__.py --- src/specify_cli/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 1e1004c..250cad5 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -636,10 +636,10 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None: - """Ensure POSIX .sh scripts in the project scripts directory have execute bits (no-op on Windows).""" + """Ensure POSIX .sh scripts in the project .specify/scripts directory have execute bits (no-op on Windows).""" if os.name == "nt": return # Windows: skip silently - scripts_dir = project_path / "scripts" + scripts_dir = project_path / ".specify" / "scripts" if not scripts_dir.is_dir(): return failures: list[str] = [] From 1ae6b55c870635bac044fd72b1ae0d45749dd501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Fri, 12 Sep 2025 00:15:25 -0700 Subject: [PATCH 12/13] Update CLI reference --- README.md | 42 +++++++++++++++++++++++++++- src/specify_cli/__init__.py | 55 +++++++++++++++++++++---------------- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index ff768c5..242ff89 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,47 @@ Use `/tasks` to create an actionable task list, then ask your agent to implement For detailed step-by-step instructions, see our [comprehensive guide](./spec-driven.md). +## 🔧 Specify CLI Reference + +The `specify` command supports the following options: + +### Commands + +| Command | Description | +|-------------|----------------------------------------------------------------| +| `init` | Initialize a new Specify project from the latest template | +| `check` | Check for installed tools (`git`, `claude`, `gemini`) | + +### `specify init` Arguments & Options + +| Argument/Option | Type | Description | +|------------------------|----------|------------------------------------------------------------------------------| +| `` | Argument | Name for your new project directory (optional if using `--here`) | +| `--ai` | Option | AI assistant to use: `claude`, `gemini`, or `copilot` | +| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code | +| `--no-git` | Flag | Skip git repository initialization | +| `--here` | Flag | Initialize project in the current directory instead of creating a new one | +| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) | + +### Examples + +```bash +# Basic project initialization +specify init my-project + +# Initialize with specific AI assistant +specify init my-project --ai claude + +# Initialize in current directory +specify init --here --ai copilot + +# Skip git initialization +specify init my-project --ai gemini --no-git + +# Check system requirements +specify check +``` + ## 📚 Core philosophy Spec-Driven Development is a structured process that emphasizes: @@ -214,7 +255,6 @@ At this stage, your project folder contents should resemble the following: │ └── 001-create-taskify │ └── spec.md └── templates - ├── CLAUDE-template.md ├── plan-template.md ├── spec-template.md └── tasks-template.md diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 250cad5..8c08d61 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -335,6 +335,16 @@ def run_command(cmd: list[str], check_return: bool = True, capture: bool = False return None +def check_tool_for_tracker(tool: str, install_hint: str, tracker: StepTracker) -> bool: + """Check if a tool is installed and update tracker.""" + if shutil.which(tool): + tracker.complete(tool, "available") + return True + else: + tracker.error(tool, f"not found - {install_hint}") + return False + + def check_tool(tool: str, install_hint: str) -> bool: """Check if a tool is installed.""" if shutil.which(tool): @@ -906,37 +916,36 @@ def init( # Removed farewell line per user request -# Add skip_tls option to check @app.command() -def check(skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)")): +def check(): """Check that all required tools are installed.""" show_banner() - console.print("[bold]Checking Specify requirements...[/bold]\n") + console.print("[bold]Checking for installed tools...[/bold]\n") - # Check if we have internet connectivity by trying to reach GitHub API - console.print("[cyan]Checking internet connectivity...[/cyan]") - verify = not skip_tls - local_ssl_context = ssl_context if verify else False - local_client = httpx.Client(verify=local_ssl_context) - try: - response = local_client.get("https://api.github.com", timeout=5, follow_redirects=True) - console.print("[green]✓[/green] Internet connection available") - except httpx.RequestError: - console.print("[red]✗[/red] No internet connection - required for downloading templates") - console.print("[yellow]Please check your internet connection[/yellow]") - - console.print("\n[cyan]Optional tools:[/cyan]") - git_ok = check_tool("git", "https://git-scm.com/downloads") + # Create tracker for checking tools + tracker = StepTracker("Check Available Tools") - console.print("\n[cyan]Optional AI tools:[/cyan]") - claude_ok = check_tool("claude", "Install from: https://docs.anthropic.com/en/docs/claude-code/setup") - gemini_ok = check_tool("gemini", "Install from: https://github.com/google-gemini/gemini-cli") + # Add all tools we want to check + tracker.add("git", "Git version control") + tracker.add("claude", "Claude Code CLI") + tracker.add("gemini", "Gemini CLI") - console.print("\n[green]✓ Specify CLI is ready to use![/green]") + # Check each tool + git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker) + claude_ok = check_tool_for_tracker("claude", "https://docs.anthropic.com/en/docs/claude-code/setup", tracker) + gemini_ok = check_tool_for_tracker("gemini", "https://github.com/google-gemini/gemini-cli", tracker) + + # Render the final tree + console.print(tracker.render()) + + # Summary + console.print("\n[bold green]Specify CLI is ready to use![/bold green]") + + # Recommendations if not git_ok: - console.print("[yellow]Consider installing git for repository management[/yellow]") + console.print("[dim]Tip: Install git for repository management[/dim]") if not (claude_ok or gemini_ok): - console.print("[yellow]Consider installing an AI assistant for the best experience[/yellow]") + console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") def main(): From 57024454bf29c06f61b274615c08e6bc9a14291f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=8C=BA?= <53200638+localden@users.noreply.github.com> Date: Fri, 12 Sep 2025 00:17:31 -0700 Subject: [PATCH 13/13] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 242ff89..a456a16 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development) - [⚡ Get started](#-get-started) +- [🔧 Specify CLI Reference](#-specify-cli-reference) - [📚 Core philosophy](#-core-philosophy) - [🌟 Development phases](#-development-phases) - [🎯 Experimental goals](#-experimental-goals)