From 7c0f0a4627e0ea6a3caaf72c2ea93389f4957a1e 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: Sat, 20 Sep 2025 11:19:47 -0700 Subject: [PATCH 01/35] Consolidate scripts --- README.md | 6 +- .../check-implementation-prerequisites.sh | 16 -- scripts/bash/check-prerequisites.sh | 170 ++++++++++++++++++ scripts/bash/check-task-prerequisites.sh | 15 -- scripts/bash/get-feature-paths.sh | 7 - .../check-implementation-prerequisites.ps1 | 42 ----- scripts/powershell/check-prerequisites.ps1 | 146 +++++++++++++++ .../powershell/check-task-prerequisites.ps1 | 35 ---- scripts/powershell/get-feature-paths.ps1 | 15 -- templates/commands/implement.md | 4 +- templates/commands/tasks.md | 4 +- 11 files changed, 322 insertions(+), 138 deletions(-) delete mode 100644 scripts/bash/check-implementation-prerequisites.sh create mode 100644 scripts/bash/check-prerequisites.sh delete mode 100644 scripts/bash/check-task-prerequisites.sh delete mode 100644 scripts/bash/get-feature-paths.sh delete mode 100644 scripts/powershell/check-implementation-prerequisites.ps1 create mode 100644 scripts/powershell/check-prerequisites.ps1 delete mode 100644 scripts/powershell/check-task-prerequisites.ps1 delete mode 100644 scripts/powershell/get-feature-paths.ps1 diff --git a/README.md b/README.md index b2dbca540..272458097 100644 --- a/README.md +++ b/README.md @@ -319,10 +319,9 @@ At this stage, your project folder contents should resemble the following: │ ├── constitution.md │ └── constitution_update_checklist.md ├── scripts -│ ├── check-task-prerequisites.sh +│ ├── check-prerequisites.sh │ ├── common.sh │ ├── create-new-feature.sh -│ ├── get-feature-paths.sh │ ├── setup-plan.sh │ └── update-claude-md.sh ├── specs @@ -371,10 +370,9 @@ The output of this step will include a number of implementation detail documents │ ├── constitution.md │ └── constitution_update_checklist.md ├── scripts -│ ├── check-task-prerequisites.sh +│ ├── check-prerequisites.sh │ ├── common.sh │ ├── create-new-feature.sh -│ ├── get-feature-paths.sh │ ├── setup-plan.sh │ └── update-claude-md.sh ├── specs diff --git a/scripts/bash/check-implementation-prerequisites.sh b/scripts/bash/check-implementation-prerequisites.sh deleted file mode 100644 index c660ffa78..000000000 --- a/scripts/bash/check-implementation-prerequisites.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/bash/check-prerequisites.sh b/scripts/bash/check-prerequisites.sh new file mode 100644 index 000000000..edb3891ca --- /dev/null +++ b/scripts/bash/check-prerequisites.sh @@ -0,0 +1,170 @@ +#!/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 + +COMPATIBILITY: + # For task prerequisites (plan.md required) + ./check-prerequisites.sh --json + + # For implementation prerequisites (plan.md + tasks.md required) + ./check-prerequisites.sh --json --require-tasks --include-tasks + + # For 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" || 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 \ No newline at end of file diff --git a/scripts/bash/check-task-prerequisites.sh b/scripts/bash/check-task-prerequisites.sh deleted file mode 100644 index e578f8646..000000000 --- a/scripts/bash/check-task-prerequisites.sh +++ /dev/null @@ -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 diff --git a/scripts/bash/get-feature-paths.sh b/scripts/bash/get-feature-paths.sh deleted file mode 100644 index 016727dbd..000000000 --- a/scripts/bash/get-feature-paths.sh +++ /dev/null @@ -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" diff --git a/scripts/powershell/check-implementation-prerequisites.ps1 b/scripts/powershell/check-implementation-prerequisites.ps1 deleted file mode 100644 index 312a3b1f4..000000000 --- a/scripts/powershell/check-implementation-prerequisites.ps1 +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/scripts/powershell/check-prerequisites.ps1 b/scripts/powershell/check-prerequisites.ps1 new file mode 100644 index 000000000..8bec816a6 --- /dev/null +++ b/scripts/powershell/check-prerequisites.ps1 @@ -0,0 +1,146 @@ +#!/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 + +COMPATIBILITY: + # For task prerequisites (plan.md required) + .\check-prerequisites.ps1 -Json + + # For implementation prerequisites (plan.md + tasks.md required) + .\check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks + + # For 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)) { + 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 + } +} \ No newline at end of file diff --git a/scripts/powershell/check-task-prerequisites.ps1 b/scripts/powershell/check-task-prerequisites.ps1 deleted file mode 100644 index 3be870f31..000000000 --- a/scripts/powershell/check-task-prerequisites.ps1 +++ /dev/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 -} diff --git a/scripts/powershell/get-feature-paths.ps1 b/scripts/powershell/get-feature-paths.ps1 deleted file mode 100644 index fc0958579..000000000 --- a/scripts/powershell/get-feature-paths.ps1 +++ /dev/null @@ -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)" diff --git a/templates/commands/implement.md b/templates/commands/implement.md index a14bd0d4b..e017d8ea6 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -1,8 +1,8 @@ --- description: Execute the implementation plan by processing and executing all tasks defined in tasks.md scripts: - sh: scripts/bash/check-implementation-prerequisites.sh --json - ps: scripts/powershell/check-implementation-prerequisites.ps1 -Json + sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks + ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks --- Given the current feature context, do this: diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 29b4cd25e..3e2b226e5 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -1,8 +1,8 @@ --- description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts. scripts: - sh: scripts/bash/check-task-prerequisites.sh --json - ps: scripts/powershell/check-task-prerequisites.ps1 -Json + sh: scripts/bash/check-prerequisites.sh --json + ps: scripts/powershell/check-prerequisites.ps1 -Json --- Given the context provided as an argument, do this: From 64171ec062f8a6e5d3ea6c80c0946f8d5272aa85 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: Sat, 20 Sep 2025 11:38:04 -0700 Subject: [PATCH 02/35] Use proper line endings --- .gitignore | 4 ++++ scripts/bash/create-new-feature.sh | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 21c7cd017..47df405b7 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,7 @@ env/ .env .env.local *.lock + +# Spec Kit-specific files +*.zip +sdd-*/ \ No newline at end of file diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 575e714c7..321636ab8 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# (Moved to scripts/bash/) Create a new feature with branch, directory structure, and template + set -e JSON_MODE=false From aa08257d9876babffdb3fc39a33b0083c03faecb 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: Sat, 20 Sep 2025 11:52:29 -0700 Subject: [PATCH 03/35] Cleanup the setup for generated packages --- .github/workflows/release.yml | 32 +++++++++---------- .../scripts/create-release-packages.sh | 13 +++++--- .gitignore | 1 + templates/commands/constitution.md | 1 - 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 675ee3b67..b31bbaa69 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,22 +114,22 @@ jobs: 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 \ + .genreleases/spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/spec-kit-template-codex-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + .genreleases/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: diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index 05b5cce3f..6b66eae9c 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -25,7 +25,10 @@ fi 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() { sed -E \ @@ -82,7 +85,7 @@ generate_commands() { build_variant() { 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..." mkdir -p "$base_dir" @@ -162,7 +165,7 @@ build_variant() { generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/commands" "$script" ;; esac ( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . ) - echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" + echo "Created $GENRELEASES_DIR/spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" } # Determine agent list @@ -212,5 +215,5 @@ for agent in "${AGENT_LIST[@]}"; do done done -echo "Archives:" -ls -1 spec-kit-template-*-"${NEW_VERSION}".zip +echo "Archives in $GENRELEASES_DIR:" +ls -1 "$GENRELEASES_DIR"/spec-kit-template-*-"${NEW_VERSION}".zip diff --git a/.gitignore b/.gitignore index 47df405b7..42a1fbbfa 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,6 @@ env/ *.lock # Spec Kit-specific files +.genreleases/ *.zip sdd-*/ \ No newline at end of file diff --git a/templates/commands/constitution.md b/templates/commands/constitution.md index e1b15cd42..583ff24cb 100644 --- a/templates/commands/constitution.md +++ b/templates/commands/constitution.md @@ -1,6 +1,5 @@ --- 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) --- 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. From e83e1cd8e34b69e1925a803c5dae68bbf28ba724 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: Sat, 20 Sep 2025 11:57:01 -0700 Subject: [PATCH 04/35] Update script delegation from GitHub Action --- .github/workflows/release.yml | 108 ++---------------- .../workflows/scripts/check-release-exists.sh | 21 ++++ .../scripts/create-github-release.sh | 36 ++++++ .../scripts/generate-release-notes.sh | 57 +++++++++ .github/workflows/scripts/get-next-version.sh | 24 ++++ .github/workflows/scripts/update-version.sh | 23 ++++ 6 files changed, 171 insertions(+), 98 deletions(-) create mode 100644 .github/workflows/scripts/check-release-exists.sh create mode 100644 .github/workflows/scripts/create-github-release.sh create mode 100644 .github/workflows/scripts/generate-release-notes.sh create mode 100644 .github/workflows/scripts/get-next-version.sh create mode 100644 .github/workflows/scripts/update-version.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b31bbaa69..0bede837f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,33 +25,13 @@ jobs: - name: Get latest tag id: get_tag run: | - # Get the latest tag, or use v0.0.0 if no tags exist - LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") - echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT - - # Extract version number and increment - VERSION=$(echo $LATEST_TAG | sed 's/v//') - IFS='.' read -ra VERSION_PARTS <<< "$VERSION" - MAJOR=${VERSION_PARTS[0]:-0} - MINOR=${VERSION_PARTS[1]:-0} - PATCH=${VERSION_PARTS[2]:-0} - - # Increment patch version - PATCH=$((PATCH + 1)) - NEW_VERSION="v$MAJOR.$MINOR.$PATCH" - - echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT - echo "New version will be: $NEW_VERSION" + chmod +x .github/workflows/scripts/get-next-version.sh + .github/workflows/scripts/get-next-version.sh - name: Check if release already exists id: check_release run: | - if gh release view ${{ steps.get_tag.outputs.new_version }} >/dev/null 2>&1; then - echo "exists=true" >> $GITHUB_OUTPUT - echo "Release ${{ steps.get_tag.outputs.new_version }} already exists, skipping..." - else - echo "exists=false" >> $GITHUB_OUTPUT - echo "Release ${{ steps.get_tag.outputs.new_version }} does not exist, proceeding..." - fi + chmod +x .github/workflows/scripts/check-release-exists.sh + .github/workflows/scripts/check-release-exists.sh ${{ steps.get_tag.outputs.new_version }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create release package variants @@ -63,85 +43,17 @@ jobs: if: steps.check_release.outputs.exists == 'false' id: release_notes run: | - # Get commits since last tag - LAST_TAG=${{ steps.get_tag.outputs.latest_tag }} - if [ "$LAST_TAG" = "v0.0.0" ]; then - # Check how many commits we have and use that as the limit - COMMIT_COUNT=$(git rev-list --count HEAD) - if [ "$COMMIT_COUNT" -gt 10 ]; then - COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~10..HEAD) - else - COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~$COMMIT_COUNT..HEAD 2>/dev/null || git log --oneline --pretty=format:"- %s") - fi - else - COMMITS=$(git log --oneline --pretty=format:"- %s" $LAST_TAG..HEAD) - fi - - # Create release notes - cat > release_notes.md << EOF - Template release ${{ steps.get_tag.outputs.new_version }} - - Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, 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 + chmod +x .github/workflows/scripts/generate-release-notes.sh + .github/workflows/scripts/generate-release-notes.sh ${{ steps.get_tag.outputs.new_version }} ${{ steps.get_tag.outputs.latest_tag }} - name: Create GitHub Release if: steps.check_release.outputs.exists == 'false' run: | - # Remove 'v' prefix from version for release title - VERSION_NO_V=${{ steps.get_tag.outputs.new_version }} - VERSION_NO_V=${VERSION_NO_V#v} - - gh release create ${{ steps.get_tag.outputs.new_version }} \ - .genreleases/spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-codex-sh-${{ steps.get_tag.outputs.new_version }}.zip \ - .genreleases/spec-kit-template-codex-ps-${{ steps.get_tag.outputs.new_version }}.zip \ - --title "Spec Kit Templates - $VERSION_NO_V" \ - --notes-file release_notes.md + chmod +x .github/workflows/scripts/create-github-release.sh + .github/workflows/scripts/create-github-release.sh ${{ steps.get_tag.outputs.new_version }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update version in pyproject.toml (for release artifacts only) if: steps.check_release.outputs.exists == 'false' run: | - # Update version in pyproject.toml (remove 'v' prefix for Python versioning) - VERSION=${{ steps.get_tag.outputs.new_version }} - PYTHON_VERSION=${VERSION#v} - - if [ -f "pyproject.toml" ]; then - sed -i "s/version = \".*\"/version = \"$PYTHON_VERSION\"/" pyproject.toml - echo "Updated pyproject.toml version to $PYTHON_VERSION (for release artifacts only)" - fi + chmod +x .github/workflows/scripts/update-version.sh + .github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }} diff --git a/.github/workflows/scripts/check-release-exists.sh b/.github/workflows/scripts/check-release-exists.sh new file mode 100644 index 000000000..161bf208c --- /dev/null +++ b/.github/workflows/scripts/check-release-exists.sh @@ -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 + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&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 \ No newline at end of file diff --git a/.github/workflows/scripts/create-github-release.sh b/.github/workflows/scripts/create-github-release.sh new file mode 100644 index 000000000..86ce3f73a --- /dev/null +++ b/.github/workflows/scripts/create-github-release.sh @@ -0,0 +1,36 @@ +#!/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 + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&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 \ + --title "Spec Kit Templates - $VERSION_NO_V" \ + --notes-file release_notes.md \ No newline at end of file diff --git a/.github/workflows/scripts/generate-release-notes.sh b/.github/workflows/scripts/generate-release-notes.sh new file mode 100644 index 000000000..f65992d45 --- /dev/null +++ b/.github/workflows/scripts/generate-release-notes.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +# generate-release-notes.sh +# Generate release notes from git history +# Usage: generate-release-notes.sh + +if [[ $# -ne 2 ]]; then + echo "Usage: $0 " >&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 +Template release $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-$NEW_VERSION.zip +- spec-kit-template-copilot-ps-$NEW_VERSION.zip +- spec-kit-template-claude-sh-$NEW_VERSION.zip +- spec-kit-template-claude-ps-$NEW_VERSION.zip +- spec-kit-template-gemini-sh-$NEW_VERSION.zip +- spec-kit-template-gemini-ps-$NEW_VERSION.zip +- spec-kit-template-cursor-sh-$NEW_VERSION.zip +- spec-kit-template-cursor-ps-$NEW_VERSION.zip +- spec-kit-template-opencode-sh-$NEW_VERSION.zip +- spec-kit-template-opencode-ps-$NEW_VERSION.zip +- spec-kit-template-qwen-sh-$NEW_VERSION.zip +- spec-kit-template-qwen-ps-$NEW_VERSION.zip +- spec-kit-template-windsurf-sh-$NEW_VERSION.zip +- spec-kit-template-windsurf-ps-$NEW_VERSION.zip +- spec-kit-template-codex-sh-$NEW_VERSION.zip +- spec-kit-template-codex-ps-$NEW_VERSION.zip +EOF + +echo "Generated release notes:" +cat release_notes.md \ No newline at end of file diff --git a/.github/workflows/scripts/get-next-version.sh b/.github/workflows/scripts/get-next-version.sh new file mode 100644 index 000000000..2be0b6cf8 --- /dev/null +++ b/.github/workflows/scripts/get-next-version.sh @@ -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" \ No newline at end of file diff --git a/.github/workflows/scripts/update-version.sh b/.github/workflows/scripts/update-version.sh new file mode 100644 index 000000000..b0dc0e672 --- /dev/null +++ b/.github/workflows/scripts/update-version.sh @@ -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 + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&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 \ No newline at end of file From 826c3a610227b85edba7775a47ee6eca7216d28b Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 12:01:05 -0700 Subject: [PATCH 05/35] Update scripts/powershell/check-prerequisites.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/check-prerequisites.ps1 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scripts/powershell/check-prerequisites.ps1 b/scripts/powershell/check-prerequisites.ps1 index 8bec816a6..94582f77c 100644 --- a/scripts/powershell/check-prerequisites.ps1 +++ b/scripts/powershell/check-prerequisites.ps1 @@ -48,16 +48,6 @@ EXAMPLES: # Get feature paths only (no validation) .\check-prerequisites.ps1 -PathsOnly - -COMPATIBILITY: - # For task prerequisites (plan.md required) - .\check-prerequisites.ps1 -Json - - # For implementation prerequisites (plan.md + tasks.md required) - .\check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks - - # For feature paths only (no validation) - .\check-prerequisites.ps1 -PathsOnly "@ exit 0 From 0bebcf93b35858141c77bcffcfb2ec73ca177e3f Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 12:01:26 -0700 Subject: [PATCH 06/35] Update scripts/bash/check-prerequisites.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/check-prerequisites.sh | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scripts/bash/check-prerequisites.sh b/scripts/bash/check-prerequisites.sh index edb3891ca..9dba90afc 100644 --- a/scripts/bash/check-prerequisites.sh +++ b/scripts/bash/check-prerequisites.sh @@ -64,16 +64,6 @@ EXAMPLES: # Get feature paths only (no validation) ./check-prerequisites.sh --paths-only -COMPATIBILITY: - # For task prerequisites (plan.md required) - ./check-prerequisites.sh --json - - # For implementation prerequisites (plan.md + tasks.md required) - ./check-prerequisites.sh --json --require-tasks --include-tasks - - # For feature paths only (no validation) - ./check-prerequisites.sh --paths-only - EOF exit 0 ;; From 505b956bfdeeccb4abb2bf5c343b5973c6d411f4 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: Sat, 20 Sep 2025 12:14:42 -0700 Subject: [PATCH 07/35] Script cleanup --- scripts/bash/common.sh | 48 ++++++++++++++-- scripts/bash/setup-plan.sh | 57 ++++++++++++++++--- scripts/powershell/common.ps1 | 67 +++++++++++++++++++---- scripts/powershell/create-new-feature.ps1 | 50 +++++++++++++++-- scripts/powershell/setup-plan.ps1 | 50 +++++++++++++++-- 5 files changed, 238 insertions(+), 34 deletions(-) diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index 582d940de..92b00a85d 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -1,16 +1,48 @@ #!/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_current_branch() { git rev-parse --abbrev-ref HEAD; } +# Get repository root, with fallback for non-git repositories +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() { + if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then + git rev-parse --abbrev-ref HEAD + else + echo "main" # Default branch name for non-git repos + fi +} + +# Check if we have git available +has_git() { + git rev-parse --show-toplevel >/dev/null 2>&1 +} check_feature_branch() { 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 echo "ERROR: Not on a feature branch. Current branch: $branch" >&2 echo "Feature branches should be named like: 001-feature-name" >&2 return 1 - fi; return 0 + fi + + return 0 } get_feature_dir() { echo "$1/specs/$2"; } @@ -18,10 +50,18 @@ get_feature_dir() { echo "$1/specs/$2"; } get_feature_paths() { local repo_root=$(get_repo_root) 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") + cat <$null + if ($LASTEXITCODE -eq 0) { + return $result + } + } catch { + # Git command failed + } + + # Fall back to script location for non-git repos + $scriptDir = Split-Path -Parent $PSScriptRoot + return (Resolve-Path (Join-Path $scriptDir "..")).Path } function Get-CurrentBranch { - git rev-parse --abbrev-ref HEAD + try { + $result = git rev-parse --abbrev-ref HEAD 2>$null + if ($LASTEXITCODE -eq 0) { + return $result + } + } catch { + # Git command failed + } + + # Default branch name for non-git repos + 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 { - 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}-') { Write-Output "ERROR: Not on a feature branch. Current branch: $Branch" Write-Output "Feature branches should be named like: 001-feature-name" @@ -27,17 +67,20 @@ function Get-FeatureDir { function Get-FeaturePathsEnv { $repoRoot = Get-RepoRoot $currentBranch = Get-CurrentBranch + $hasGit = Test-HasGit $featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch + [PSCustomObject]@{ - REPO_ROOT = $repoRoot + REPO_ROOT = $repoRoot CURRENT_BRANCH = $currentBranch - FEATURE_DIR = $featureDir - FEATURE_SPEC = Join-Path $featureDir 'spec.md' - IMPL_PLAN = Join-Path $featureDir 'plan.md' - TASKS = Join-Path $featureDir 'tasks.md' - RESEARCH = Join-Path $featureDir 'research.md' - DATA_MODEL = Join-Path $featureDir 'data-model.md' - QUICKSTART = Join-Path $featureDir 'quickstart.md' + HAS_GIT = $hasGit + FEATURE_DIR = $featureDir + FEATURE_SPEC = Join-Path $featureDir 'spec.md' + IMPL_PLAN = Join-Path $featureDir 'plan.md' + TASKS = Join-Path $featureDir 'tasks.md' + RESEARCH = Join-Path $featureDir 'research.md' + DATA_MODEL = Join-Path $featureDir 'data-model.md' + QUICKSTART = Join-Path $featureDir 'quickstart.md' CONTRACTS_DIR = Join-Path $featureDir 'contracts' } } diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index b99f08898..7e1a98c02 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -1,5 +1,5 @@ #!/usr/bin/env pwsh -# Create a new feature (moved to powershell/) +# Create a new feature [CmdletBinding()] param( [switch]$Json, @@ -9,11 +9,31 @@ param( $ErrorActionPreference = 'Stop' if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) { - Write-Error "Usage: ./create-new-feature.ps1 [-Json] "; exit 1 + Write-Error "Usage: ./create-new-feature.ps1 [-Json] " + exit 1 } $featureDesc = ($FeatureDescription -join ' ').Trim() -$repoRoot = git rev-parse --show-toplevel +# Resolve repository root. Prefer git information when available, but fall back +# to the script location so the workflow still functions in repositories that +# were initialised with --no-git. +$scriptDir = Split-Path -Parent $PSScriptRoot +$fallbackRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path + +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' New-Item -ItemType Directory -Path $specsDir -Force | Out-Null @@ -33,20 +53,38 @@ $branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', $words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3 $branchName = "$featureNum-$([string]::Join('-', $words))" -git checkout -b $branchName | Out-Null +if ($hasGit) { + try { + 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 New-Item -ItemType Directory -Path $featureDir -Force | Out-Null $template = Join-Path $repoRoot 'templates/spec-template.md' $specFile = Join-Path $featureDir 'spec.md' -if (Test-Path $template) { Copy-Item $template $specFile -Force } else { New-Item -ItemType File -Path $specFile | Out-Null } +if (Test-Path $template) { + Copy-Item $template $specFile -Force +} else { + New-Item -ItemType File -Path $specFile | Out-Null +} if ($Json) { - $obj = [PSCustomObject]@{ BRANCH_NAME = $branchName; SPEC_FILE = $specFile; FEATURE_NUM = $featureNum } + $obj = [PSCustomObject]@{ + BRANCH_NAME = $branchName + SPEC_FILE = $specFile + FEATURE_NUM = $featureNum + HAS_GIT = $hasGit + } $obj | ConvertTo-Json -Compress } else { Write-Output "BRANCH_NAME: $branchName" Write-Output "SPEC_FILE: $specFile" Write-Output "FEATURE_NUM: $featureNum" + Write-Output "HAS_GIT: $hasGit" } diff --git a/scripts/powershell/setup-plan.ps1 b/scripts/powershell/setup-plan.ps1 index b0264405b..d0ed582fa 100644 --- a/scripts/powershell/setup-plan.ps1 +++ b/scripts/powershell/setup-plan.ps1 @@ -1,21 +1,61 @@ #!/usr/bin/env pwsh +# Setup implementation plan for a feature + [CmdletBinding()] -param([switch]$Json) +param( + [switch]$Json, + [switch]$Help +) + $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" +# Get all paths and variables from common functions $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 -$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) { - [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 { Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)" Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)" Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)" Write-Output "BRANCH: $($paths.CURRENT_BRANCH)" + Write-Output "HAS_GIT: $($paths.HAS_GIT)" } From 3f67cf2f5fef285824b5d1d015d2d094960aa434 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: Sat, 20 Sep 2025 12:26:57 -0700 Subject: [PATCH 08/35] Fix script path --- scripts/powershell/common.ps1 | 3 +-- scripts/powershell/create-new-feature.ps1 | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/powershell/common.ps1 b/scripts/powershell/common.ps1 index fe7be7b8b..a8d475002 100644 --- a/scripts/powershell/common.ps1 +++ b/scripts/powershell/common.ps1 @@ -12,8 +12,7 @@ function Get-RepoRoot { } # Fall back to script location for non-git repos - $scriptDir = Split-Path -Parent $PSScriptRoot - return (Resolve-Path (Join-Path $scriptDir "..")).Path + return (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path } function Get-CurrentBranch { diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 7e1a98c02..997c6e8f6 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -17,8 +17,7 @@ $featureDesc = ($FeatureDescription -join ' ').Trim() # Resolve repository root. Prefer git information when available, but fall back # to the script location so the workflow still functions in repositories that # were initialised with --no-git. -$scriptDir = Split-Path -Parent $PSScriptRoot -$fallbackRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path +$fallbackRoot = (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path try { $repoRoot = git rev-parse --show-toplevel 2>$null From 2c1e1688e855eeb4ace76a064d550212fde6e21a Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 13:56:06 -0700 Subject: [PATCH 09/35] Update scripts/bash/check-prerequisites.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/check-prerequisites.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bash/check-prerequisites.sh b/scripts/bash/check-prerequisites.sh index 9dba90afc..d8e79301b 100644 --- a/scripts/bash/check-prerequisites.sh +++ b/scripts/bash/check-prerequisites.sh @@ -80,7 +80,7 @@ source "$SCRIPT_DIR/common.sh" # Get feature paths and validate branch eval $(get_feature_paths) -check_feature_branch "$CURRENT_BRANCH" || exit 1 +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 # If paths-only mode, output paths and exit if $PATHS_ONLY; then From 84ec4611c4dabe4cbf16fac8df68d0cecdb82525 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 13:56:20 -0700 Subject: [PATCH 10/35] Update scripts/powershell/check-prerequisites.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/check-prerequisites.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/powershell/check-prerequisites.ps1 b/scripts/powershell/check-prerequisites.ps1 index 94582f77c..3ce11634f 100644 --- a/scripts/powershell/check-prerequisites.ps1 +++ b/scripts/powershell/check-prerequisites.ps1 @@ -58,7 +58,8 @@ EXAMPLES: # Get feature paths and validate branch $paths = Get-FeaturePathsEnv -if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { +$hasGit = Test-Path (Join-Path $paths.REPO_ROOT ".git") +if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$hasGit)) { exit 1 } From 2d242b47324ab223d75281f87c45602eb1af6621 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: Sat, 20 Sep 2025 13:57:05 -0700 Subject: [PATCH 11/35] Update config --- scripts/bash/common.sh | 2 +- scripts/bash/create-new-feature.sh | 2 +- scripts/powershell/common.ps1 | 2 +- scripts/powershell/create-new-feature.ps1 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index 92b00a85d..f0847fdfd 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -8,7 +8,7 @@ get_repo_root() { else # Fall back to script location for non-git repos local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - cd "$script_dir/../.." && pwd + cd "$script_dir/../../.." && pwd fi } diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 321636ab8..0c3de6133 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -22,7 +22,7 @@ fi # to the script location so the workflow still functions in repositories that # were initialised with --no-git. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -FALLBACK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +FALLBACK_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" if git rev-parse --show-toplevel >/dev/null 2>&1; then REPO_ROOT=$(git rev-parse --show-toplevel) HAS_GIT=true diff --git a/scripts/powershell/common.ps1 b/scripts/powershell/common.ps1 index a8d475002..fa51fe231 100644 --- a/scripts/powershell/common.ps1 +++ b/scripts/powershell/common.ps1 @@ -12,7 +12,7 @@ function Get-RepoRoot { } # Fall back to script location for non-git repos - return (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path + return (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path } function Get-CurrentBranch { diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 997c6e8f6..6c71123c7 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -17,7 +17,7 @@ $featureDesc = ($FeatureDescription -join ' ').Trim() # Resolve repository root. Prefer git information when available, but fall back # to the script location so the workflow still functions in repositories that # were initialised with --no-git. -$fallbackRoot = (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path +$fallbackRoot = (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path try { $repoRoot = git rev-parse --show-toplevel 2>$null From 1a71b03195adc8b29a013a22001a552567b7f94a 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: Sat, 20 Sep 2025 15:04:25 -0700 Subject: [PATCH 12/35] Script and template tweaks --- scripts/bash/common.sh | 40 ++++++++++++++- scripts/bash/create-new-feature.sh | 4 ++ scripts/bash/update-agent-context.sh | 55 ++++++++++++--------- scripts/powershell/common.ps1 | 31 +++++++++++- scripts/powershell/create-new-feature.ps1 | 4 ++ scripts/powershell/update-agent-context.ps1 | 49 ++++++++++-------- templates/plan-template.md | 3 +- 7 files changed, 138 insertions(+), 48 deletions(-) diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index f0847fdfd..4458b0c08 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -14,11 +14,47 @@ get_repo_root() { # 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 - else - echo "main" # Default branch name for non-git repos + 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 diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 0c3de6133..6a8e913af 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -67,10 +67,14 @@ TEMPLATE="$REPO_ROOT/templates/spec-template.md" SPEC_FILE="$FEATURE_DIR/spec.md" if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi +# Set the SPECIFY_FEATURE environment variable for the current session +export SPECIFY_FEATURE="$BRANCH_NAME" + if $JSON_MODE; then printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" else echo "BRANCH_NAME: $BRANCH_NAME" echo "SPEC_FILE: $SPEC_FILE" echo "FEATURE_NUM: $FEATURE_NUM" + echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME" fi diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 4f9e6e31c..d68250d9e 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -48,13 +48,17 @@ set -o pipefail # Configuration and Global Variables #============================================================================== -REPO_ROOT=$(git rev-parse --show-toplevel) -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) -FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH" -NEW_PLAN="$FEATURE_DIR/plan.md" +# Get script directory and load common functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# 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-specific file paths +# Agent-specific file paths CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" GEMINI_FILE="$REPO_ROOT/GEMINI.md" COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md" @@ -108,22 +112,24 @@ trap cleanup EXIT INT TERM #============================================================================== validate_environment() { - # Check if we're in a git repository - 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 + # Check if we have a current branch/feature (git or non-git) 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 fi # Check if plan.md exists if [[ ! -f "$NEW_PLAN" ]]; then 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 fi @@ -142,9 +148,9 @@ extract_plan_field() { local field_pattern="$1" local plan_file="$2" - grep "^**${field_pattern}**: " "$plan_file" 2>/dev/null | \ + grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \ head -1 | \ - sed "s/^**${field_pattern}**: //" | \ + sed "s|^\*\*${field_pattern}\*\*: ||" | \ grep -v "NEEDS CLARIFICATION" | \ grep -v "^N/A$" || echo "" } @@ -196,12 +202,9 @@ get_project_structure() { local project_type="$1" if [[ "$project_type" == *"web"* ]]; then - echo "backend/ -frontend/ -tests/" + echo "backend/\\nfrontend/\\ntests/" else - echo "src/ -tests/" + echo "src/\\ntests/" fi } @@ -267,22 +270,26 @@ create_new_agent_file() { "s/\[PROJECT NAME\]/$project_name/" "s/\[DATE\]/$current_date/" "s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/" - "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|" + "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" "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|" ) 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" rm -f "$temp_file" "$temp_file.bak" return 1 fi done + # Convert \n sequences to actual newlines + sed -i.bak2 's/\\n/\ +/g' "$temp_file" + # Clean up backup files - rm -f "$temp_file.bak" + rm -f "$temp_file.bak" "$temp_file.bak2" return 0 } diff --git a/scripts/powershell/common.ps1 b/scripts/powershell/common.ps1 index fa51fe231..c8e34b26b 100644 --- a/scripts/powershell/common.ps1 +++ b/scripts/powershell/common.ps1 @@ -16,6 +16,12 @@ function Get-RepoRoot { } function Get-CurrentBranch { + # 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) { @@ -25,7 +31,30 @@ function Get-CurrentBranch { # Git command failed } - # Default branch name for non-git repos + # 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" } diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 6c71123c7..124396f44 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -73,6 +73,9 @@ if (Test-Path $template) { New-Item -ItemType File -Path $specFile | Out-Null } +# Set the SPECIFY_FEATURE environment variable for the current session +$env:SPECIFY_FEATURE = $branchName + if ($Json) { $obj = [PSCustomObject]@{ BRANCH_NAME = $branchName @@ -86,4 +89,5 @@ if ($Json) { Write-Output "SPEC_FILE: $specFile" Write-Output "FEATURE_NUM: $featureNum" Write-Output "HAS_GIT: $hasGit" + Write-Output "SPECIFY_FEATURE environment variable set to: $branchName" } diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 1e35b25a6..923a43781 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -3,21 +3,30 @@ param([string]$AgentType) $ErrorActionPreference = 'Stop' -$repoRoot = git rev-parse --show-toplevel -$currentBranch = git rev-parse --abbrev-ref HEAD -$featureDir = Join-Path $repoRoot "specs/$currentBranch" -$newPlan = Join-Path $featureDir 'plan.md' -if (-not (Test-Path $newPlan)) { Write-Error "ERROR: No plan.md found at $newPlan"; exit 1 } +# Load common functions +. "$PSScriptRoot/common.ps1" -$claudeFile = Join-Path $repoRoot 'CLAUDE.md' -$geminiFile = Join-Path $repoRoot 'GEMINI.md' -$copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md' -$cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc' -$qwenFile = Join-Path $repoRoot 'QWEN.md' -$agentsFile = Join-Path $repoRoot 'AGENTS.md' -$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md' +# Get all paths and variables from common functions +$paths = Get-FeaturePathsEnv -Write-Output "=== Updating agent context files for feature $currentBranch ===" +$newPlan = $paths.IMPL_PLAN +if (-not (Test-Path $newPlan)) { + Write-Error "ERROR: No plan.md found at $newPlan" + if (-not $paths.HAS_GIT) { + Write-Output "Use: `$env:SPECIFY_FEATURE='your-feature-name' or create a new feature first" + } + exit 1 +} + +$claudeFile = Join-Path $paths.REPO_ROOT 'CLAUDE.md' +$geminiFile = Join-Path $paths.REPO_ROOT 'GEMINI.md' +$copilotFile = Join-Path $paths.REPO_ROOT '.github/copilot-instructions.md' +$cursorFile = Join-Path $paths.REPO_ROOT '.cursor/rules/specify-rules.mdc' +$qwenFile = Join-Path $paths.REPO_ROOT 'QWEN.md' +$agentsFile = Join-Path $paths.REPO_ROOT 'AGENTS.md' +$windsurfFile = Join-Path $paths.REPO_ROOT '.windsurf/rules/specify-rules.md' + +Write-Output "=== Updating agent context files for feature $($paths.CURRENT_BRANCH) ===" function Get-PlanValue($pattern) { if (-not (Test-Path $newPlan)) { return '' } @@ -34,12 +43,12 @@ $newProjectType = Get-PlanValue 'Project Type' function Initialize-AgentFile($targetFile, $agentName) { if (Test-Path $targetFile) { return } - $template = Join-Path $repoRoot '.specify/templates/agent-file-template.md' + $template = Join-Path $paths.REPO_ROOT '.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('[PROJECT NAME]', (Split-Path $paths.REPO_ROOT -Leaf)) $content = $content.Replace('[DATE]', (Get-Date -Format 'yyyy-MM-dd')) - $content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $newLang + $newFramework ($currentBranch)") + $content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $newLang + $newFramework ($($paths.CURRENT_BRANCH))") 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 .' } @@ -48,18 +57,18 @@ function Initialize-AgentFile($targetFile, $agentName) { 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 = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', "- $($paths.CURRENT_BRANCH): Added ${newLang} + ${newFramework}") $content | Set-Content $targetFile -Encoding UTF8 } function Update-AgentFile($targetFile, $agentName) { if (-not (Test-Path $targetFile)) { Initialize-AgentFile $targetFile $agentName; return } $content = Get-Content $targetFile -Raw - if ($newLang -and ($content -notmatch [regex]::Escape($newLang))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newLang + $newFramework ($currentBranch)`n" } - if ($newDb -and $newDb -ne 'N/A' -and ($content -notmatch [regex]::Escape($newDb))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($currentBranch)`n" } + if ($newLang -and ($content -notmatch [regex]::Escape($newLang))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newLang + $newFramework ($($paths.CURRENT_BRANCH))`n" } + if ($newDb -and $newDb -ne 'N/A' -and ($content -notmatch [regex]::Escape($newDb))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($($paths.CURRENT_BRANCH))`n" } if ($content -match '## Recent Changes\n([\s\S]*?)(\n\n|$)') { $changesBlock = $matches[1].Trim().Split("`n") - $changesBlock = ,"- ${currentBranch}: Added ${newLang} + ${newFramework}" + $changesBlock + $changesBlock = ,"- $($paths.CURRENT_BRANCH): 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") diff --git a/templates/plan-template.md b/templates/plan-template.md index cb3eca1f9..8f82c7763 100644 --- a/templates/plan-template.md +++ b/templates/plan-template.md @@ -151,7 +151,8 @@ ios/ or android/ - Quickstart test = story validation steps 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 - Preserve manual additions between markers - Update recent changes (keep last 3) From 406521c66402a29b012cd124e925a57498358b08 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: Sat, 20 Sep 2025 15:29:37 -0700 Subject: [PATCH 13/35] Simplification --- scripts/bash/update-agent-context.sh | 269 +++++++++----------- scripts/powershell/update-agent-context.ps1 | 53 +++- 2 files changed, 167 insertions(+), 155 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index d68250d9e..bba241576 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -151,6 +151,7 @@ extract_plan_field() { grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \ head -1 | \ sed "s|^\*\*${field_pattern}\*\*: ||" | \ + sed 's/^[ \t]*//;s/[ \t]*$//' | \ grep -v "NEEDS CLARIFICATION" | \ grep -v "^N/A$" || echo "" } @@ -194,6 +195,31 @@ parse_plan_data() { log_info "Found project type: $NEW_PROJECT_TYPE" 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 #============================================================================== @@ -293,145 +319,9 @@ create_new_agent_file() { 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 "" "$target_file" 2>/dev/null | cut -d: -f1 || echo "") - manual_end=$(grep -n "" "$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 '//,//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() { local target_file="$1" @@ -439,26 +329,105 @@ update_existing_agent_file() { log_info "Updating existing agent context file..." - local temp_file1="/tmp/agent_update_1_$$" - local temp_file2="/tmp/agent_update_2_$$" + # Use a single temporary file for atomic update + local temp_file + temp_file=$(mktemp) || { + log_error "Failed to create temporary file" + return 1 + } - # Step 1: Update Active Technologies section - update_active_technologies "$target_file" "$temp_file1" + # Process the file in one pass + local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK") + local new_tech_entries=() + local new_change_entry="" - # Step 2: Update Recent Changes section - update_recent_changes "$temp_file1" "$temp_file2" + # Prepare new technology entries + if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then + new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)") + fi - # Step 3: Update timestamp - update_last_updated "$temp_file2" "$current_date" + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then + new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)") + fi - # Step 4: Preserve manual additions - preserve_manual_additions "$target_file" "$temp_file2" + # Prepare new change entry + 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 - mv "$temp_file2" "$target_file" + # Process file line by line + 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 - rm -f "$temp_file1" + while IFS= read -r line || [[ -n "$line" ]]; do + # 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" + + # 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 diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 923a43781..9ea3220bb 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -41,6 +41,23 @@ $newTesting = Get-PlanValue 'Testing' $newDb = Get-PlanValue 'Storage' $newProjectType = Get-PlanValue 'Project Type' +function Format-TechnologyStack($lang, $framework) { + $parts = @() + + # Add non-empty parts (excluding "NEEDS CLARIFICATION" and "N/A") + if ($lang -and $lang -ne 'NEEDS CLARIFICATION') { $parts += $lang } + if ($framework -and $framework -ne 'NEEDS CLARIFICATION' -and $framework -ne 'N/A') { $parts += $framework } + + # Join with proper formatting + if ($parts.Count -eq 0) { + return '' + } elseif ($parts.Count -eq 1) { + return $parts[0] + } else { + return ($parts -join ' + ') + } +} + function Initialize-AgentFile($targetFile, $agentName) { if (Test-Path $targetFile) { return } $template = Join-Path $paths.REPO_ROOT '.specify/templates/agent-file-template.md' @@ -48,7 +65,13 @@ function Initialize-AgentFile($targetFile, $agentName) { $content = Get-Content $template -Raw $content = $content.Replace('[PROJECT NAME]', (Split-Path $paths.REPO_ROOT -Leaf)) $content = $content.Replace('[DATE]', (Get-Date -Format 'yyyy-MM-dd')) - $content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $newLang + $newFramework ($($paths.CURRENT_BRANCH))") + + $techStack = Format-TechnologyStack $newLang $newFramework + if ($techStack) { + $content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $techStack ($($paths.CURRENT_BRANCH))") + } else { + $content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', '') + } 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 .' } @@ -57,18 +80,38 @@ function Initialize-AgentFile($targetFile, $agentName) { 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]', "- $($paths.CURRENT_BRANCH): Added ${newLang} + ${newFramework}") + + $techStack = Format-TechnologyStack $newLang $newFramework + if ($techStack) { + $content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', "- $($paths.CURRENT_BRANCH): Added $techStack") + } else { + $content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', '') + } $content | Set-Content $targetFile -Encoding UTF8 } function Update-AgentFile($targetFile, $agentName) { if (-not (Test-Path $targetFile)) { Initialize-AgentFile $targetFile $agentName; return } $content = Get-Content $targetFile -Raw - if ($newLang -and ($content -notmatch [regex]::Escape($newLang))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newLang + $newFramework ($($paths.CURRENT_BRANCH))`n" } - if ($newDb -and $newDb -ne 'N/A' -and ($content -notmatch [regex]::Escape($newDb))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($($paths.CURRENT_BRANCH))`n" } + + $techStack = Format-TechnologyStack $newLang $newFramework + if ($techStack -and ($content -notmatch [regex]::Escape($techStack))) { + $content = $content -replace '(## Active Technologies\n)', "`$1- $techStack ($($paths.CURRENT_BRANCH))`n" + } + + if ($newDb -and $newDb -ne 'N/A' -and $newDb -ne 'NEEDS CLARIFICATION' -and ($content -notmatch [regex]::Escape($newDb))) { + $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($($paths.CURRENT_BRANCH))`n" + } + if ($content -match '## Recent Changes\n([\s\S]*?)(\n\n|$)') { $changesBlock = $matches[1].Trim().Split("`n") - $changesBlock = ,"- $($paths.CURRENT_BRANCH): Added ${newLang} + ${newFramework}" + $changesBlock + + if ($techStack) { + $changesBlock = ,"- $($paths.CURRENT_BRANCH): Added $techStack" + $changesBlock + } elseif ($newDb -and $newDb -ne 'N/A' -and $newDb -ne 'NEEDS CLARIFICATION') { + $changesBlock = ,"- $($paths.CURRENT_BRANCH): Added $newDb" + $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") From 8d529599f13a6162959e470de1d338761e6b0a09 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:54:10 -0700 Subject: [PATCH 14/35] Update scripts/powershell/create-new-feature.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/create-new-feature.ps1 | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 124396f44..780f39992 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -17,7 +17,28 @@ $featureDesc = ($FeatureDescription -join ' ').Trim() # Resolve repository root. Prefer git information when available, but fall back # to the script location so the workflow still functions in repositories that # were initialised with --no-git. -$fallbackRoot = (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path +function Find-RepositoryRoot { + param( + [string]$StartDir, + [string[]]$Markers = @('.git', 'README.md') + ) + $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) { + break + } + $current = $parent + } + # If no marker found, fall back to script root + return (Resolve-Path $StartDir) +} +$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot) try { $repoRoot = git rev-parse --show-toplevel 2>$null From 4cc15bab9868f34c64006d9e8b5db949fad1b3c0 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:54:33 -0700 Subject: [PATCH 15/35] Update scripts/bash/update-agent-context.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/update-agent-context.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index bba241576..4997158a2 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -311,8 +311,8 @@ create_new_agent_file() { done # Convert \n sequences to actual newlines - sed -i.bak2 's/\\n/\ -/g' "$temp_file" + newline=$(printf '\n') + sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" # Clean up backup files rm -f "$temp_file.bak" "$temp_file.bak2" From 0e5f7cee9a250a5481d4f4322f97f81e2aecaf4b Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:54:43 -0700 Subject: [PATCH 16/35] Update templates/plan-template.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- templates/plan-template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/plan-template.md b/templates/plan-template.md index 8f82c7763..e812b4126 100644 --- a/templates/plan-template.md +++ b/templates/plan-template.md @@ -152,7 +152,7 @@ ios/ or android/ 5. **Update agent file incrementally** (O(1) operation): - Run `{SCRIPT}` - **IMPORTANT**: Execute it exactly as specified above. Do not add or remove any arguments. + **IMPORTANT**: Execute it exactly as specified above. Do not add or remove any arguments. - If exists: Add only NEW tech from current plan - Preserve manual additions between markers - Update recent changes (keep last 3) From 3bdb1d9f3feae3f6b32130ae65e9fd7f6b7ea6fe 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: Sat, 20 Sep 2025 20:14:11 -0700 Subject: [PATCH 17/35] Root detection logic --- scripts/bash/create-new-feature.sh | 23 ++++++++++++++++++++--- scripts/powershell/create-new-feature.ps1 | 13 ++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 6a8e913af..6670550e4 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -18,16 +18,33 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then exit 1 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 -# 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. 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 REPO_ROOT=$(git rev-parse --show-toplevel) HAS_GIT=true 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 fi diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index 780f39992..f1c8e04e3 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -15,12 +15,12 @@ if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) { $featureDesc = ($FeatureDescription -join ' ').Trim() # 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. function Find-RepositoryRoot { param( [string]$StartDir, - [string[]]$Markers = @('.git', 'README.md') + [string[]]$Markers = @('.git', '.specify') ) $current = Resolve-Path $StartDir while ($true) { @@ -31,14 +31,17 @@ function Find-RepositoryRoot { } $parent = Split-Path $current -Parent if ($parent -eq $current) { - break + # Reached filesystem root without finding markers + return $null } $current = $parent } - # If no marker found, fall back to script root - return (Resolve-Path $StartDir) } $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 From ee9f83929ad88d33077f7b16f3afa2507d4381af 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: Sat, 20 Sep 2025 21:20:43 -0700 Subject: [PATCH 18/35] Update contribution guidelines. --- CONTRIBUTING.md | 5 +++++ scripts/bash/update-agent-context.sh | 15 ++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9853fd110..76023b43c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,11 @@ These are one time installations required to be able to test your changes locall >[!NOTE] >If your pull request introduces a large change that materially impacts the work of the CLI or the rest of the repository (e.g., you're introducing new templates, arguments, or otherwise major changes), make sure that it was **discussed and agreed upon** by the project maintainers. Pull requests with large changes that did not have a prior conversation and agreement will be closed. +>[!IMPORTANT] +>We leverage AI and AI agents for help with this project and value contributions that also use AI to detect issues and improve Spec Kit. However, to help the team focus on important issues and features, we will close issues and PRs that are **low-effort AI-generated changes**. If you are submitting an AI generated issue or pull request, please include **concrete test cases**, **scenarios**, and an outline of the **end-to-end developer experience** with your suggested change. +> +>This means that you need to be supervising the changes and understanding why the change is necessary within the Spec Kit scope. + 1. Fork and clone the repository 1. Configure and install the dependencies: `uv sync` 1. Make sure the CLI works on your machine: `uv run specify --help` diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 4997158a2..e69e912b8 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -291,15 +291,20 @@ create_new_agent_file() { local language_conventions 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') + local substitutions=( - "s/\[PROJECT NAME\]/$project_name/" - "s/\[DATE\]/$current_date/" - "s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/" + "s|\[PROJECT NAME\]|$project_name|" + "s|\[DATE\]|$current_date|" + "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|- $escaped_lang + $escaped_framework ($escaped_branch)|" "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" "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\]|- $escaped_branch: Added $escaped_lang + $escaped_framework|" ) for substitution in "${substitutions[@]}"; do From 5eccac5524f60e3e92deba9c2a11d9c8c24296c5 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: Sat, 20 Sep 2025 21:21:35 -0700 Subject: [PATCH 19/35] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76023b43c..85855defb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ These are one time installations required to be able to test your changes locall >[!IMPORTANT] >We leverage AI and AI agents for help with this project and value contributions that also use AI to detect issues and improve Spec Kit. However, to help the team focus on important issues and features, we will close issues and PRs that are **low-effort AI-generated changes**. If you are submitting an AI generated issue or pull request, please include **concrete test cases**, **scenarios**, and an outline of the **end-to-end developer experience** with your suggested change. > ->This means that you need to be supervising the changes and understanding why the change is necessary within the Spec Kit scope. +>This means that you need to be supervising the changes and be able to explain why the change is necessary within the Spec Kit scope. 1. Fork and clone the repository 1. Configure and install the dependencies: `uv sync` From 7c2fd502c85dda77d3487a1dcef5ad8afac941b7 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: Sat, 20 Sep 2025 21:23:04 -0700 Subject: [PATCH 20/35] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85855defb..7c8e55ee4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,8 @@ These are one time installations required to be able to test your changes locall >We leverage AI and AI agents for help with this project and value contributions that also use AI to detect issues and improve Spec Kit. However, to help the team focus on important issues and features, we will close issues and PRs that are **low-effort AI-generated changes**. If you are submitting an AI generated issue or pull request, please include **concrete test cases**, **scenarios**, and an outline of the **end-to-end developer experience** with your suggested change. > >This means that you need to be supervising the changes and be able to explain why the change is necessary within the Spec Kit scope. +> +>If you are identified as submitting a large number of AI-generated changes that fall in the category above, the team might, at their discretion, block your account from further contributing to Spec Kit. 1. Fork and clone the repository 1. Configure and install the dependencies: `uv sync` From f4b16080da73778decef1c838e8a44b3a4caecaa 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: Sat, 20 Sep 2025 21:34:55 -0700 Subject: [PATCH 21/35] Update CONTRIBUTING.md --- CONTRIBUTING.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c8e55ee4..69c9024a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,13 +18,6 @@ These are one time installations required to be able to test your changes locall >[!NOTE] >If your pull request introduces a large change that materially impacts the work of the CLI or the rest of the repository (e.g., you're introducing new templates, arguments, or otherwise major changes), make sure that it was **discussed and agreed upon** by the project maintainers. Pull requests with large changes that did not have a prior conversation and agreement will be closed. ->[!IMPORTANT] ->We leverage AI and AI agents for help with this project and value contributions that also use AI to detect issues and improve Spec Kit. However, to help the team focus on important issues and features, we will close issues and PRs that are **low-effort AI-generated changes**. If you are submitting an AI generated issue or pull request, please include **concrete test cases**, **scenarios**, and an outline of the **end-to-end developer experience** with your suggested change. -> ->This means that you need to be supervising the changes and be able to explain why the change is necessary within the Spec Kit scope. -> ->If you are identified as submitting a large number of AI-generated changes that fall in the category above, the team might, at their discretion, block your account from further contributing to Spec Kit. - 1. Fork and clone the repository 1. Configure and install the dependencies: `uv sync` 1. Make sure the CLI works on your machine: `uv run specify --help` @@ -52,6 +45,18 @@ When working on spec-kit: 3. Test script functionality in the `scripts/` directory 4. Ensure memory files (`memory/constitution.md`) are updated if major process changes are made +## AI Contributions in Spec Kit + +We leverage AI and AI agents for help with this project and value contributions that also use AI to detect issues and improve Spec Kit. + +However, to help the team focus on important issues and features, we will close issues and PRs that are **low-effort AI-generated changes**. This includes changes or reports that have not been tested, clearly had no human input, and that aren't aligned with the overall project. + +If you are submitting an AI generated issue or pull request, please include **concrete test cases**, **scenarios**, and an outline of the **end-to-end developer experience** with your suggested change that were **written by you**. If someone can easily spot that a contribution is entirely AI-generated without your input, you have more work to do. + +In practice, this means that you need to understand the changes that you're requesting and be able to explain why the change is necessary within the Spec Kit scope. + +If you are identified as submitting a large number of AI-generated changes that fall in the category above, the team might, at their discretion, block your account from further contributing to Spec Kit. + ## Resources - [Spec-Driven Development Methodology](./spec-driven.md) From f9c9cd3b61b1db36102b94c482e501dac41a7624 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: Sat, 20 Sep 2025 21:41:30 -0700 Subject: [PATCH 22/35] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69c9024a2..18fca7d23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ When working on spec-kit: 3. Test script functionality in the `scripts/` directory 4. Ensure memory files (`memory/constitution.md`) are updated if major process changes are made -## AI Contributions in Spec Kit +## AI contributions in Spec Kit We leverage AI and AI agents for help with this project and value contributions that also use AI to detect issues and improve Spec Kit. From d92d6f57dbaab4a8e395c271a112df24e06bc30a 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: Sat, 20 Sep 2025 21:43:15 -0700 Subject: [PATCH 23/35] Update CONTRIBUTING.md --- CONTRIBUTING.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18fca7d23..06461ee76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,15 +47,30 @@ When working on spec-kit: ## AI contributions in Spec Kit -We leverage AI and AI agents for help with this project and value contributions that also use AI to detect issues and improve 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. -However, to help the team focus on important issues and features, we will close issues and PRs that are **low-effort AI-generated changes**. This includes changes or reports that have not been tested, clearly had no human input, and that aren't aligned with the overall project. +### What we're looking for -If you are submitting an AI generated issue or pull request, please include **concrete test cases**, **scenarios**, and an outline of the **end-to-end developer experience** with your suggested change that were **written by you**. If someone can easily spot that a contribution is entirely AI-generated without your input, you have more work to do. +When submitting AI-assisted contributions, please ensure they include: -In practice, this means that you need to understand the changes that you're requesting and be able to explain why the change is necessary within the Spec Kit scope. +- **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 -If you are identified as submitting a large number of AI-generated changes that fall in the category above, the team might, at their discretion, block your account from further contributing to Spec Kit. +### 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 From 90f06521a26e3023a5b9ff5e79623946015d84e1 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: Sat, 20 Sep 2025 23:38:31 -0700 Subject: [PATCH 24/35] Update update-agent-context.ps1 --- scripts/powershell/update-agent-context.ps1 | 516 +++++++++++++++----- 1 file changed, 383 insertions(+), 133 deletions(-) diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 9ea3220bb..9420a227e 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -1,161 +1,411 @@ #!/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')] + [string]$AgentType +) + $ErrorActionPreference = 'Stop' -# Load common functions -. "$PSScriptRoot/common.ps1" +# Import common helpers +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +. (Join-Path $ScriptDir 'common.ps1') -# Get all paths and variables from common functions -$paths = Get-FeaturePathsEnv +# Acquire environment paths +$envData = Get-FeaturePathsEnv +$REPO_ROOT = $envData.REPO_ROOT +$CURRENT_BRANCH = $envData.CURRENT_BRANCH +$HAS_GIT = $envData.HAS_GIT +$IMPL_PLAN = $envData.IMPL_PLAN +$NEW_PLAN = $IMPL_PLAN -$newPlan = $paths.IMPL_PLAN -if (-not (Test-Path $newPlan)) { - Write-Error "ERROR: No plan.md found at $newPlan" - if (-not $paths.HAS_GIT) { - Write-Output "Use: `$env:SPECIFY_FEATURE='your-feature-name' or create a new feature first" +# 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' + +$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md' + +# Parsed plan data placeholders +$script:NEW_LANG = '' +$script:NEW_FRAMEWORK = '' +$script:NEW_DB = '' +$script:NEW_PROJECT_TYPE = '' + +function Write-Info { + param( + [Parameter(Mandatory=$true)] + [string]$Message + ) + Write-Host "INFO: $Message" +} + +function Write-Success { + param( + [Parameter(Mandatory=$true)] + [string]$Message + ) + Write-Host "$([char]0x2713) $Message" +} + +function Write-WarningMsg { + param( + [Parameter(Mandatory=$true)] + [string]$Message + ) + Write-Warning $Message +} + +function Write-Err { + param( + [Parameter(Mandatory=$true)] + [string]$Message + ) + Write-Host "ERROR: $Message" -ForegroundColor Red +} + +function Validate-Environment { + if (-not $CURRENT_BRANCH) { + Write-Err 'Unable to determine current feature' + 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' } + 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 } -$claudeFile = Join-Path $paths.REPO_ROOT 'CLAUDE.md' -$geminiFile = Join-Path $paths.REPO_ROOT 'GEMINI.md' -$copilotFile = Join-Path $paths.REPO_ROOT '.github/copilot-instructions.md' -$cursorFile = Join-Path $paths.REPO_ROOT '.cursor/rules/specify-rules.mdc' -$qwenFile = Join-Path $paths.REPO_ROOT 'QWEN.md' -$agentsFile = Join-Path $paths.REPO_ROOT 'AGENTS.md' -$windsurfFile = Join-Path $paths.REPO_ROOT '.windsurf/rules/specify-rules.md' - -Write-Output "=== Updating agent context files for feature $($paths.CURRENT_BRANCH) ===" - -function Get-PlanValue($pattern) { - if (-not (Test-Path $newPlan)) { return '' } - $line = Select-String -Path $newPlan -Pattern $pattern | Select-Object -First 1 - if ($line) { return ($line.Line -replace "^\*\*$pattern\*\*: ", '') } - return '' +function Extract-PlanField { + 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 } -$newLang = Get-PlanValue 'Language/Version' -$newFramework = Get-PlanValue 'Primary Dependencies' -$newTesting = Get-PlanValue 'Testing' -$newDb = Get-PlanValue 'Storage' -$newProjectType = Get-PlanValue 'Project Type' +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 -function Format-TechnologyStack($lang, $framework) { + 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 = @() - - # Add non-empty parts (excluding "NEEDS CLARIFICATION" and "N/A") - if ($lang -and $lang -ne 'NEEDS CLARIFICATION') { $parts += $lang } - if ($framework -and $framework -ne 'NEEDS CLARIFICATION' -and $framework -ne 'N/A') { $parts += $framework } - - # Join with proper formatting - if ($parts.Count -eq 0) { - return '' - } elseif ($parts.Count -eq 1) { - return $parts[0] - } else { - return ($parts -join ' + ') + 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 Initialize-AgentFile($targetFile, $agentName) { - if (Test-Path $targetFile) { return } - $template = Join-Path $paths.REPO_ROOT '.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 $paths.REPO_ROOT -Leaf)) - $content = $content.Replace('[DATE]', (Get-Date -Format 'yyyy-MM-dd')) +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') - $techStack = Format-TechnologyStack $newLang $newFramework + # 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) { - $content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $techStack ($($paths.CURRENT_BRANCH))") + $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) + } + + 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 { - $content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', '') + if (-not (Get-Item $TargetFile).Attributes) { Write-Err "Cannot access existing file: $TargetFile"; return $false } + if (Update-ExistingAgentFile -TargetFile $TargetFile -Date $date) { Write-Success "Updated existing $AgentName context file" } else { Write-Err 'Failed to update agent file'; return $false } } - 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") - - $techStack = Format-TechnologyStack $newLang $newFramework - if ($techStack) { - $content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', "- $($paths.CURRENT_BRANCH): Added $techStack") - } else { - $content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', '') - } - $content | Set-Content $targetFile -Encoding UTF8 + return $true } -function Update-AgentFile($targetFile, $agentName) { - if (-not (Test-Path $targetFile)) { Initialize-AgentFile $targetFile $agentName; return } - $content = Get-Content $targetFile -Raw - - $techStack = Format-TechnologyStack $newLang $newFramework - if ($techStack -and ($content -notmatch [regex]::Escape($techStack))) { - $content = $content -replace '(## Active Technologies\n)', "`$1- $techStack ($($paths.CURRENT_BRANCH))`n" +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' } + default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf'; return $false } } - - if ($newDb -and $newDb -ne 'N/A' -and $newDb -ne 'NEEDS CLARIFICATION' -and ($content -notmatch [regex]::Escape($newDb))) { - $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($($paths.CURRENT_BRANCH))`n" - } - - if ($content -match '## Recent Changes\n([\s\S]*?)(\n\n|$)') { - $changesBlock = $matches[1].Trim().Split("`n") - - if ($techStack) { - $changesBlock = ,"- $($paths.CURRENT_BRANCH): Added $techStack" + $changesBlock - } elseif ($newDb -and $newDb -ne 'N/A' -and $newDb -ne 'NEEDS CLARIFICATION') { - $changesBlock = ,"- $($paths.CURRENT_BRANCH): Added $newDb" + $changesBlock - } - - $changesBlock = $changesBlock | Where-Object { $_ } | Select-Object -First 3 - $joined = ($changesBlock -join "`n") - $content = [regex]::Replace($content, '## Recent Changes\n([\s\S]*?)(\n\n|$)', "## Recent Changes`n$joined`n`n") - } - $content = [regex]::Replace($content, 'Last updated: \d{4}-\d{2}-\d{2}', "Last updated: $(Get-Date -Format 'yyyy-MM-dd')") - $content | Set-Content $targetFile -Encoding UTF8 - Write-Output "✓ $agentName context file updated successfully" } -switch ($AgentType) { - 'claude' { Update-AgentFile $claudeFile 'Claude Code' } - 'gemini' { Update-AgentFile $geminiFile 'Gemini CLI' } - 'copilot' { Update-AgentFile $copilotFile 'GitHub Copilot' } - 'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' } - 'qwen' { Update-AgentFile $qwenFile 'Qwen Code' } - 'opencode' { Update-AgentFile $agentsFile 'opencode' } - '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' - } +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 (-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 } } - Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode, windsurf, codex or leave empty for all."; exit 1 } + return $ok } -Write-Output '' -Write-Output 'Summary of changes:' -if ($newLang) { Write-Output "- Added language: $newLang" } -if ($newFramework) { Write-Output "- Added framework: $newFramework" } -if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" } +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]' +} -Write-Output '' -Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode|windsurf|codex]' +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 From 895bcbef0015e1abfabc2063d837e38fbb461c66 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: Sun, 21 Sep 2025 00:02:33 -0700 Subject: [PATCH 25/35] Update prompts --- templates/commands/constitution.md | 6 ++++++ templates/commands/implement.md | 6 +++++- templates/commands/plan.md | 6 ++++++ templates/commands/specify.md | 6 ++++++ templates/commands/tasks.md | 6 +++++- 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/templates/commands/constitution.md b/templates/commands/constitution.md index 583ff24cb..671d2c9ab 100644 --- a/templates/commands/constitution.md +++ b/templates/commands/constitution.md @@ -2,6 +2,12 @@ description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync. --- +The user input to you can be provided directly by the agent or in `$ARGUMENTS` - 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. Follow this execution flow: diff --git a/templates/commands/implement.md b/templates/commands/implement.md index e017d8ea6..19e45f7fe 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -5,7 +5,11 @@ scripts: ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks --- -Given the current feature context, do this: +The user input to you can be provided directly by the agent or in `$ARGUMENTS` - 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. diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 18a0b5c64..afb9c900b 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -5,6 +5,12 @@ scripts: ps: scripts/powershell/setup-plan.ps1 -Json --- +The user input to you can be provided directly by the agent or in `$ARGUMENTS` - 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: 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. diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 9a6a3b361..474f7b026 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -5,6 +5,12 @@ scripts: ps: scripts/powershell/create-new-feature.ps1 -Json "{ARGS}" --- +The user input to you can be provided directly by the agent or in `$ARGUMENTS` - 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. Given that feature description, do this: diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 3e2b226e5..792dd2393 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -5,7 +5,11 @@ scripts: 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 in `$ARGUMENTS` - 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. 2. Load and analyze available design documents: From d682f5a164871f34e5c821397cdd0d30e9e19533 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: Sun, 21 Sep 2025 00:44:20 -0700 Subject: [PATCH 26/35] Clarification --- .../scripts/create-release-packages.sh | 4 +-- src/specify_cli/__init__.py | 29 ++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index 6b66eae9c..0021c5601 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -161,8 +161,8 @@ build_variant() { mkdir -p "$base_dir/.windsurf/workflows" generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;; codex) - mkdir -p "$base_dir/.codex/commands" - generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/commands" "$script" ;; + mkdir -p "$base_dir/.codex/prompts" + generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/prompts" "$script" ;; esac ( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . ) echo "Created $GENRELEASES_DIR/spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 4eb36fbdd..4e1d111d6 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -28,6 +28,7 @@ import sys import zipfile import tempfile import shutil +import shlex import json from pathlib import Path from typing import Optional, Tuple @@ -983,12 +984,24 @@ def init( # Boxed "Next steps" section steps_lines = [] 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 else: steps_lines.append("1. You're already in the project directory!") 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(" 2.1 [cyan]/constitution[/] - Establish project principles") steps_lines.append(" 2.2 [cyan]/specify[/] - Create specifications") @@ -1000,6 +1013,20 @@ def init( console.print() console.print(steps_panel) + # Add Codex warning if using Codex + 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() def check(): """Check that all required tools are installed.""" From 0672bfc6aa5222e0b2712e6a11159408d91a5250 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:45:31 -0700 Subject: [PATCH 27/35] Update scripts/powershell/update-agent-context.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/update-agent-context.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 9420a227e..9c2447aa7 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -340,8 +340,12 @@ function Update-AgentFile { 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 { - if (-not (Get-Item $TargetFile).Attributes) { Write-Err "Cannot access existing file: $TargetFile"; return $false } - if (Update-ExistingAgentFile -TargetFile $TargetFile -Date $date) { Write-Success "Updated existing $AgentName context file" } else { Write-Err 'Failed to update agent file'; return $false } + 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 } From 84b61bcd20a135cba8272863155dbf8f50c7d00a Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:45:49 -0700 Subject: [PATCH 28/35] Update scripts/powershell/update-agent-context.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/update-agent-context.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 9c2447aa7..ec11c7f54 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -107,6 +107,7 @@ function Validate-Environment { 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 } } From da60d35bc1e81106efe487e7550d0fbeb8bc5ea4 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:46:22 -0700 Subject: [PATCH 29/35] Update scripts/bash/common.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index 4458b0c08..34e5d4bb7 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -8,7 +8,7 @@ get_repo_root() { else # Fall back to script location for non-git repos local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - cd "$script_dir/../../.." && pwd + (cd "$script_dir/../../.." && pwd) fi } From 93e41567d998aea51c785f0670c93f7effa07b1f 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: Sun, 21 Sep 2025 00:51:56 -0700 Subject: [PATCH 30/35] Fix script config --- scripts/bash/update-agent-context.sh | 32 +++++++++++++++++++-- scripts/powershell/update-agent-context.ps1 | 5 ++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index e69e912b8..8fe11e00e 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -297,14 +297,37 @@ create_new_agent_file() { 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=( "s|\[PROJECT NAME\]|$project_name|" "s|\[DATE\]|$current_date|" - "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|- $escaped_lang + $escaped_framework ($escaped_branch)|" + "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|" "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|" - "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $escaped_branch: Added $escaped_lang + $escaped_framework|" + "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|" ) for substitution in "${substitutions[@]}"; do @@ -425,6 +448,11 @@ update_existing_agent_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" diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index ec11c7f54..5204b4715 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -319,6 +319,11 @@ function Update-ExistingAgentFile { $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 } From 0e49e7961064441c4e413d6b65a1901cbc619e51 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:52:35 -0700 Subject: [PATCH 31/35] Update scripts/bash/update-agent-context.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/bash/update-agent-context.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 8fe11e00e..96131f5a4 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -293,9 +293,9 @@ create_new_agent_file() { # 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') + 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 From 5243137f25aab6d02d779796f83280b48a587e80 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: Sun, 21 Sep 2025 01:28:23 -0700 Subject: [PATCH 32/35] Update changelog --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f494345e..a46cceae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to the Specify CLI will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.12] - 2025-09-20 + +### Fixed + +- 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 ### Added diff --git a/pyproject.toml b/pyproject.toml index bac191094..e44820b7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.0.10" +version = "0.0.12" description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." requires-python = ">=3.11" dependencies = [ From a810b1bd1ac265074c97d1f9109196ded19fc996 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: Sun, 21 Sep 2025 01:28:31 -0700 Subject: [PATCH 33/35] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a46cceae9..51b4071b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to the Specify CLI will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.0.12] - 2025-09-20 +## [0.0.12] - 2025-09-21 ### Fixed From 2d890751063af92c77d373b34e38a581fc2ee852 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: Sun, 21 Sep 2025 01:28:59 -0700 Subject: [PATCH 34/35] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b4071b2..07e6a2986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.0.12] - 2025-09-21 -### Fixed +### 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). From b03bba37ce93b0cf1c921950d7d8b3729b1c8721 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Sun, 21 Sep 2025 01:48:21 -0700 Subject: [PATCH 35/35] Update scripts/powershell/check-prerequisites.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/check-prerequisites.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/powershell/check-prerequisites.ps1 b/scripts/powershell/check-prerequisites.ps1 index 3ce11634f..f039419f7 100644 --- a/scripts/powershell/check-prerequisites.ps1 +++ b/scripts/powershell/check-prerequisites.ps1 @@ -58,8 +58,8 @@ EXAMPLES: # Get feature paths and validate branch $paths = Get-FeaturePathsEnv -$hasGit = Test-Path (Join-Path $paths.REPO_ROOT ".git") -if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$hasGit)) { + +if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$paths.HAS_GIT)) { exit 1 }