diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index f0847fd..4458b0c 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 0c3de61..6a8e913 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 4f9e6e3..d68250d 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 fa51fe2..c8e34b2 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 6c71123..124396f 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 1e35b25..923a437 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 cb3eca1..8f82c77 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)