Refactor with platform-specific constraints

This commit is contained in:
Den Delimarsky 🌺
2025-09-12 10:27:43 -07:00
parent 57024454bf
commit 5787bb5537
28 changed files with 730 additions and 758 deletions

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env pwsh
[CmdletBinding()]
param([switch]$Json)
$ErrorActionPreference = 'Stop'
. "$PSScriptRoot/common.ps1"
$paths = Get-FeaturePathsEnv
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
Write-Output "Run /specify first to create the feature structure."
exit 1
}
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
Write-Output "Run /plan first to create the plan."
exit 1
}
if ($Json) {
$docs = @()
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) { $docs += 'contracts/' }
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
[PSCustomObject]@{ FEATURE_DIR=$paths.FEATURE_DIR; AVAILABLE_DOCS=$docs } | ConvertTo-Json -Compress
} else {
Write-Output "FEATURE_DIR:$($paths.FEATURE_DIR)"
Write-Output "AVAILABLE_DOCS:"
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
}

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env pwsh
# Common PowerShell functions analogous to common.sh (moved to powershell/)
function Get-RepoRoot {
git rev-parse --show-toplevel
}
function Get-CurrentBranch {
git rev-parse --abbrev-ref HEAD
}
function Test-FeatureBranch {
param([string]$Branch)
if ($Branch -notmatch '^[0-9]{3}-') {
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
Write-Output "Feature branches should be named like: 001-feature-name"
return $false
}
return $true
}
function Get-FeatureDir {
param([string]$RepoRoot, [string]$Branch)
Join-Path $RepoRoot "specs/$Branch"
}
function Get-FeaturePathsEnv {
$repoRoot = Get-RepoRoot
$currentBranch = Get-CurrentBranch
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
[PSCustomObject]@{
REPO_ROOT = $repoRoot
CURRENT_BRANCH = $currentBranch
FEATURE_DIR = $featureDir
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
IMPL_PLAN = Join-Path $featureDir 'plan.md'
TASKS = Join-Path $featureDir 'tasks.md'
RESEARCH = Join-Path $featureDir 'research.md'
DATA_MODEL = Join-Path $featureDir 'data-model.md'
QUICKSTART = Join-Path $featureDir 'quickstart.md'
CONTRACTS_DIR = Join-Path $featureDir 'contracts'
}
}
function Test-FileExists {
param([string]$Path, [string]$Description)
if (Test-Path -Path $Path -PathType Leaf) {
Write-Output "$Description"
return $true
} else {
Write-Output "$Description"
return $false
}
}
function Test-DirHasFiles {
param([string]$Path, [string]$Description)
if ((Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Select-Object -First 1)) {
Write-Output "$Description"
return $true
} else {
Write-Output "$Description"
return $false
}
}

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env pwsh
# Create a new feature (moved to powershell/)
[CmdletBinding()]
param(
[switch]$Json,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$FeatureDescription
)
$ErrorActionPreference = 'Stop'
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
Write-Error "Usage: ./create-new-feature.ps1 [-Json] <feature description>"; exit 1
}
$featureDesc = ($FeatureDescription -join ' ').Trim()
$repoRoot = git rev-parse --show-toplevel
$specsDir = Join-Path $repoRoot 'specs'
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
$highest = 0
if (Test-Path $specsDir) {
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
if ($_.Name -match '^(\d{3})') {
$num = [int]$matches[1]
if ($num -gt $highest) { $highest = $num }
}
}
}
$next = $highest + 1
$featureNum = ('{0:000}' -f $next)
$branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
$branchName = "$featureNum-$([string]::Join('-', $words))"
git checkout -b $branchName | Out-Null
$featureDir = Join-Path $specsDir $branchName
New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
$template = Join-Path $repoRoot 'templates/spec-template.md'
$specFile = Join-Path $featureDir 'spec.md'
if (Test-Path $template) { Copy-Item $template $specFile -Force } else { New-Item -ItemType File -Path $specFile | Out-Null }
if ($Json) {
$obj = [PSCustomObject]@{ BRANCH_NAME = $branchName; SPEC_FILE = $specFile; FEATURE_NUM = $featureNum }
$obj | ConvertTo-Json -Compress
} else {
Write-Output "BRANCH_NAME: $branchName"
Write-Output "SPEC_FILE: $specFile"
Write-Output "FEATURE_NUM: $featureNum"
}

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env pwsh
param()
$ErrorActionPreference = 'Stop'
. "$PSScriptRoot/common.ps1"
$paths = Get-FeaturePathsEnv
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
Write-Output "REPO_ROOT: $($paths.REPO_ROOT)"
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
Write-Output "TASKS: $($paths.TASKS)"

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env pwsh
[CmdletBinding()]
param([switch]$Json)
$ErrorActionPreference = 'Stop'
. "$PSScriptRoot/common.ps1"
$paths = Get-FeaturePathsEnv
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
New-Item -ItemType Directory -Path $paths.FEATURE_DIR -Force | Out-Null
$template = Join-Path $paths.REPO_ROOT 'templates/plan-template.md'
if (Test-Path $template) { Copy-Item $template $paths.IMPL_PLAN -Force }
if ($Json) {
[PSCustomObject]@{ FEATURE_SPEC=$paths.FEATURE_SPEC; IMPL_PLAN=$paths.IMPL_PLAN; SPECS_DIR=$paths.FEATURE_DIR; BRANCH=$paths.CURRENT_BRANCH } | ConvertTo-Json -Compress
} else {
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)"
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
}

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env pwsh
[CmdletBinding()]
param([string]$AgentType)
$ErrorActionPreference = 'Stop'
$repoRoot = git rev-parse --show-toplevel
$currentBranch = git rev-parse --abbrev-ref HEAD
$featureDir = Join-Path $repoRoot "specs/$currentBranch"
$newPlan = Join-Path $featureDir 'plan.md'
if (-not (Test-Path $newPlan)) { Write-Error "ERROR: No plan.md found at $newPlan"; exit 1 }
$claudeFile = Join-Path $repoRoot 'CLAUDE.md'
$geminiFile = Join-Path $repoRoot 'GEMINI.md'
$copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md'
Write-Output "=== Updating agent context files for feature $currentBranch ==="
function Get-PlanValue($pattern) {
if (-not (Test-Path $newPlan)) { return '' }
$line = Select-String -Path $newPlan -Pattern $pattern | Select-Object -First 1
if ($line) { return ($line.Line -replace "^\*\*$pattern\*\*: ", '') }
return ''
}
$newLang = Get-PlanValue 'Language/Version'
$newFramework = Get-PlanValue 'Primary Dependencies'
$newTesting = Get-PlanValue 'Testing'
$newDb = Get-PlanValue 'Storage'
$newProjectType = Get-PlanValue 'Project Type'
function Initialize-AgentFile($targetFile, $agentName) {
if (Test-Path $targetFile) { return }
$template = Join-Path $repoRoot 'templates/agent-file-template.md'
if (-not (Test-Path $template)) { Write-Error "Template not found: $template"; return }
$content = Get-Content $template -Raw
$content = $content.Replace('[PROJECT NAME]', (Split-Path $repoRoot -Leaf))
$content = $content.Replace('[DATE]', (Get-Date -Format 'yyyy-MM-dd'))
$content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $newLang + $newFramework ($currentBranch)")
if ($newProjectType -match 'web') { $structure = "backend/`nfrontend/`ntests/" } else { $structure = "src/`ntests/" }
$content = $content.Replace('[ACTUAL STRUCTURE FROM PLANS]', $structure)
if ($newLang -match 'Python') { $commands = 'cd src && pytest && ruff check .' }
elseif ($newLang -match 'Rust') { $commands = 'cargo test && cargo clippy' }
elseif ($newLang -match 'JavaScript|TypeScript') { $commands = 'npm test && npm run lint' }
else { $commands = "# Add commands for $newLang" }
$content = $content.Replace('[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]', $commands)
$content = $content.Replace('[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]', "$newLang: Follow standard conventions")
$content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', "- $currentBranch: Added $newLang + $newFramework")
$content | Set-Content $targetFile -Encoding UTF8
}
function Update-AgentFile($targetFile, $agentName) {
if (-not (Test-Path $targetFile)) { Initialize-AgentFile $targetFile $agentName; return }
$content = Get-Content $targetFile -Raw
if ($newLang -and ($content -notmatch [regex]::Escape($newLang))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newLang + $newFramework ($currentBranch)`n" }
if ($newDb -and $newDb -ne 'N/A' -and ($content -notmatch [regex]::Escape($newDb))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($currentBranch)`n" }
if ($content -match '## Recent Changes\n([\s\S]*?)(\n\n|$)') {
$changesBlock = $matches[1].Trim().Split("`n")
$changesBlock = ,"- $currentBranch: Added $newLang + $newFramework" + $changesBlock
$changesBlock = $changesBlock | Where-Object { $_ } | Select-Object -First 3
$joined = ($changesBlock -join "`n")
$content = [regex]::Replace($content, '## Recent Changes\n([\s\S]*?)(\n\n|$)', "## Recent Changes`n$joined`n`n")
}
$content = [regex]::Replace($content, 'Last updated: \d{4}-\d{2}-\d{2}', "Last updated: $(Get-Date -Format 'yyyy-MM-dd')")
$content | Set-Content $targetFile -Encoding UTF8
Write-Output "$agentName context file updated successfully"
}
switch ($AgentType) {
'claude' { Update-AgentFile $claudeFile 'Claude Code' }
'gemini' { Update-AgentFile $geminiFile 'Gemini CLI' }
'copilot' { Update-AgentFile $copilotFile 'GitHub Copilot' }
'' {
foreach ($pair in @(@{file=$claudeFile; name='Claude Code'}, @{file=$geminiFile; name='Gemini CLI'}, @{file=$copilotFile; name='GitHub Copilot'})) {
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
}
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile)) {
Write-Output 'No agent context files found. Creating Claude Code context file by default.'
Update-AgentFile $claudeFile 'Claude Code'
}
}
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, or leave empty for all."; exit 1 }
}
Write-Output ''
Write-Output 'Summary of changes:'
if ($newLang) { Write-Output "- Added language: $newLang" }
if ($newFramework) { Write-Output "- Added framework: $newFramework" }
if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" }
Write-Output ''
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot]'