Skip to content

Commit 879e315

Browse files
Remove backup and original Draw.io files for the flow diagram to clean up the media directory.
1 parent 4e957db commit 879e315

File tree

7 files changed

+394
-661
lines changed

7 files changed

+394
-661
lines changed

.specify/scripts/powershell/check-prerequisites.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ param(
2525

2626
$ErrorActionPreference = 'Stop'
2727

28+
# Source common functions
29+
. "$PSScriptRoot/common.ps1"
30+
2831
# Show help if requested
2932
if ($Help) {
3033
Write-Output @'
@@ -53,9 +56,6 @@ EXAMPLES:
5356
exit 0
5457
}
5558

56-
# Source common functions
57-
. "$PSScriptRoot/common.ps1"
58-
5959
# Get feature paths and validate branch
6060
$paths = Get-FeaturePathsEnv
6161

.specify/scripts/powershell/common.ps1

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,374 @@ function Test-DirHasFiles {
134134
return $false
135135
}
136136
}
137+
138+
# Resolve repository root. Prefer git information when available, but fall back
139+
# to searching for repository markers so the workflow still functions in repositories that
140+
# were initialised with --no-git.
141+
function Find-RepositoryRoot {
142+
param(
143+
[string]$StartDir,
144+
[string[]]$Markers = @('.git', '.specify')
145+
)
146+
$current = Resolve-Path $StartDir
147+
while ($true) {
148+
foreach ($marker in $Markers) {
149+
if (Test-Path (Join-Path $current $marker)) {
150+
return $current
151+
}
152+
}
153+
$parent = Split-Path $current -Parent
154+
if ($parent -eq $current) {
155+
# Reached filesystem root without finding markers
156+
return $null
157+
}
158+
$current = $parent
159+
}
160+
}
161+
162+
function Write-Info {
163+
param(
164+
[Parameter(Mandatory = $true)]
165+
[string]$Message
166+
)
167+
Write-Host "INFO: $Message"
168+
}
169+
170+
function Write-Success {
171+
param(
172+
[Parameter(Mandatory = $true)]
173+
[string]$Message
174+
)
175+
Write-Host "$([char]0x2713) $Message"
176+
}
177+
178+
function Write-WarningMsg {
179+
param(
180+
[Parameter(Mandatory = $true)]
181+
[string]$Message
182+
)
183+
Write-Warning $Message
184+
}
185+
186+
function Write-Err {
187+
param(
188+
[Parameter(Mandatory = $true)]
189+
[string]$Message
190+
)
191+
Write-Host "ERROR: $Message" -ForegroundColor Red
192+
}
193+
194+
function Validate-Environment {
195+
if (-not $CURRENT_BRANCH) {
196+
Write-Err 'Unable to determine current feature'
197+
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' }
198+
exit 1
199+
}
200+
if (-not (Test-Path $NEW_PLAN)) {
201+
Write-Err "No plan.md found at $NEW_PLAN"
202+
Write-Info 'Ensure you are working on a feature with a corresponding spec directory'
203+
if (-not $HAS_GIT) { Write-Info 'Use: $env:SPECIFY_FEATURE=your-feature-name or create a new feature first' }
204+
exit 1
205+
}
206+
if (-not (Test-Path $TEMPLATE_FILE)) {
207+
Write-Err "Template file not found at $TEMPLATE_FILE"
208+
Write-Info 'Run specify init to scaffold .specify/templates, or add agent-file-template.md there.'
209+
exit 1
210+
}
211+
}
212+
213+
function Extract-PlanField {
214+
param(
215+
[Parameter(Mandatory = $true)]
216+
[string]$FieldPattern,
217+
[Parameter(Mandatory = $true)]
218+
[string]$PlanFile
219+
)
220+
if (-not (Test-Path $PlanFile)) { return '' }
221+
# Lines like **Language/Version**: Python 3.12
222+
$regex = "^\*\*$([Regex]::Escape($FieldPattern))\*\*: (.+)$"
223+
Get-Content -LiteralPath $PlanFile -Encoding utf8 | ForEach-Object {
224+
if ($_ -match $regex) {
225+
$val = $Matches[1].Trim()
226+
if ($val -notin @('NEEDS CLARIFICATION', 'N/A')) { return $val }
227+
}
228+
} | Select-Object -First 1
229+
}
230+
231+
function Parse-PlanData {
232+
param(
233+
[Parameter(Mandatory = $true)]
234+
[string]$PlanFile
235+
)
236+
if (-not (Test-Path $PlanFile)) { Write-Err "Plan file not found: $PlanFile"; return $false }
237+
Write-Info "Parsing plan data from $PlanFile"
238+
$script:NEW_LANG = Extract-PlanField -FieldPattern 'Language/Version' -PlanFile $PlanFile
239+
$script:NEW_FRAMEWORK = Extract-PlanField -FieldPattern 'Primary Dependencies' -PlanFile $PlanFile
240+
$script:NEW_DB = Extract-PlanField -FieldPattern 'Storage' -PlanFile $PlanFile
241+
$script:NEW_PROJECT_TYPE = Extract-PlanField -FieldPattern 'Project Type' -PlanFile $PlanFile
242+
243+
if ($NEW_LANG) { Write-Info "Found language: $NEW_LANG" } else { Write-WarningMsg 'No language information found in plan' }
244+
if ($NEW_FRAMEWORK) { Write-Info "Found framework: $NEW_FRAMEWORK" }
245+
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Info "Found database: $NEW_DB" }
246+
if ($NEW_PROJECT_TYPE) { Write-Info "Found project type: $NEW_PROJECT_TYPE" }
247+
return $true
248+
}
249+
250+
function Format-TechnologyStack {
251+
param(
252+
[Parameter(Mandatory = $false)]
253+
[string]$Lang,
254+
[Parameter(Mandatory = $false)]
255+
[string]$Framework
256+
)
257+
$parts = @()
258+
if ($Lang -and $Lang -ne 'NEEDS CLARIFICATION') { $parts += $Lang }
259+
if ($Framework -and $Framework -notin @('NEEDS CLARIFICATION', 'N/A')) { $parts += $Framework }
260+
if (-not $parts) { return '' }
261+
return ($parts -join ' + ')
262+
}
263+
264+
function Get-ProjectStructure {
265+
param(
266+
[Parameter(Mandatory = $false)]
267+
[string]$ProjectType
268+
)
269+
if ($ProjectType -match 'web') { return "backend/`nfrontend/`ntests/" } else { return "src/`ntests/" }
270+
}
271+
272+
function Get-CommandsForLanguage {
273+
param(
274+
[Parameter(Mandatory = $false)]
275+
[string]$Lang
276+
)
277+
switch -Regex ($Lang) {
278+
'Python' { return 'cd src; pytest; ruff check .' }
279+
'Rust' { return 'cargo test; cargo clippy' }
280+
'JavaScript|TypeScript' { return 'npm test; npm run lint' }
281+
default { return "# Add commands for $Lang" }
282+
}
283+
}
284+
285+
function Get-LanguageConventions {
286+
param(
287+
[Parameter(Mandatory = $false)]
288+
[string]$Lang
289+
)
290+
if ($Lang) { "${Lang}: Follow standard conventions" } else { 'General: Follow standard conventions' }
291+
}
292+
293+
function New-AgentFile {
294+
param(
295+
[Parameter(Mandatory = $true)]
296+
[string]$TargetFile,
297+
[Parameter(Mandatory = $true)]
298+
[string]$ProjectName,
299+
[Parameter(Mandatory = $true)]
300+
[datetime]$Date
301+
)
302+
if (-not (Test-Path $TEMPLATE_FILE)) { Write-Err "Template not found at $TEMPLATE_FILE"; return $false }
303+
$temp = New-TemporaryFile
304+
Copy-Item -LiteralPath $TEMPLATE_FILE -Destination $temp -Force
305+
306+
$projectStructure = Get-ProjectStructure -ProjectType $NEW_PROJECT_TYPE
307+
$commands = Get-CommandsForLanguage -Lang $NEW_LANG
308+
$languageConventions = Get-LanguageConventions -Lang $NEW_LANG
309+
310+
$escaped_lang = $NEW_LANG
311+
$escaped_framework = $NEW_FRAMEWORK
312+
$escaped_branch = $CURRENT_BRANCH
313+
314+
$content = Get-Content -LiteralPath $temp -Raw -Encoding utf8
315+
$content = $content -replace '\[PROJECT NAME\]', $ProjectName
316+
$content = $content -replace '\[DATE\]', $Date.ToString('yyyy-MM-dd')
317+
318+
# Build the technology stack string safely
319+
$techStackForTemplate = ''
320+
if ($escaped_lang -and $escaped_framework) {
321+
$techStackForTemplate = "- $escaped_lang + $escaped_framework ($escaped_branch)"
322+
} elseif ($escaped_lang) {
323+
$techStackForTemplate = "- $escaped_lang ($escaped_branch)"
324+
} elseif ($escaped_framework) {
325+
$techStackForTemplate = "- $escaped_framework ($escaped_branch)"
326+
}
327+
328+
$content = $content -replace '\[EXTRACTED FROM ALL PLAN.MD FILES\]', $techStackForTemplate
329+
# For project structure we manually embed (keep newlines)
330+
$escapedStructure = [Regex]::Escape($projectStructure)
331+
$content = $content -replace '\[ACTUAL STRUCTURE FROM PLANS\]', $escapedStructure
332+
# Replace escaped newlines placeholder after all replacements
333+
$content = $content -replace '\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]', $commands
334+
$content = $content -replace '\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]', $languageConventions
335+
336+
# Build the recent changes string safely
337+
$recentChangesForTemplate = ''
338+
if ($escaped_lang -and $escaped_framework) {
339+
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang} + ${escaped_framework}"
340+
} elseif ($escaped_lang) {
341+
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang}"
342+
} elseif ($escaped_framework) {
343+
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_framework}"
344+
}
345+
346+
$content = $content -replace '\[LAST 3 FEATURES AND WHAT THEY ADDED\]', $recentChangesForTemplate
347+
# Convert literal \n sequences introduced by Escape to real newlines
348+
$content = $content -replace '\\n', [Environment]::NewLine
349+
350+
$parent = Split-Path -Parent $TargetFile
351+
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
352+
Set-Content -LiteralPath $TargetFile -Value $content -NoNewline -Encoding utf8
353+
Remove-Item $temp -Force
354+
return $true
355+
}
356+
357+
function Update-ExistingAgentFile {
358+
param(
359+
[Parameter(Mandatory = $true)]
360+
[string]$TargetFile,
361+
[Parameter(Mandatory = $true)]
362+
[datetime]$Date
363+
)
364+
if (-not (Test-Path $TargetFile)) { return (New-AgentFile -TargetFile $TargetFile -ProjectName (Split-Path $REPO_ROOT -Leaf) -Date $Date) }
365+
366+
$techStack = Format-TechnologyStack -Lang $NEW_LANG -Framework $NEW_FRAMEWORK
367+
$newTechEntries = @()
368+
if ($techStack) {
369+
$escapedTechStack = [Regex]::Escape($techStack)
370+
if (-not (Select-String -Pattern $escapedTechStack -Path $TargetFile -Quiet)) {
371+
$newTechEntries += "- $techStack ($CURRENT_BRANCH)"
372+
}
373+
}
374+
if ($NEW_DB -and $NEW_DB -notin @('N/A', 'NEEDS CLARIFICATION')) {
375+
$escapedDB = [Regex]::Escape($NEW_DB)
376+
if (-not (Select-String -Pattern $escapedDB -Path $TargetFile -Quiet)) {
377+
$newTechEntries += "- $NEW_DB ($CURRENT_BRANCH)"
378+
}
379+
}
380+
$newChangeEntry = ''
381+
if ($techStack) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${techStack}" }
382+
elseif ($NEW_DB -and $NEW_DB -notin @('N/A', 'NEEDS CLARIFICATION')) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${NEW_DB}" }
383+
384+
$lines = Get-Content -LiteralPath $TargetFile -Encoding utf8
385+
$output = New-Object System.Collections.Generic.List[string]
386+
$inTech = $false; $inChanges = $false; $techAdded = $false; $changeAdded = $false; $existingChanges = 0
387+
388+
for ($i = 0; $i -lt $lines.Count; $i++) {
389+
$line = $lines[$i]
390+
if ($line -eq '## Active Technologies') {
391+
$output.Add($line)
392+
$inTech = $true
393+
continue
394+
}
395+
if ($inTech -and $line -match '^##\s') {
396+
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
397+
$output.Add($line); $inTech = $false; continue
398+
}
399+
if ($inTech -and [string]::IsNullOrWhiteSpace($line)) {
400+
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
401+
$output.Add($line); continue
402+
}
403+
if ($line -eq '## Recent Changes') {
404+
$output.Add($line)
405+
if ($newChangeEntry) { $output.Add($newChangeEntry); $changeAdded = $true }
406+
$inChanges = $true
407+
continue
408+
}
409+
if ($inChanges -and $line -match '^##\s') { $output.Add($line); $inChanges = $false; continue }
410+
if ($inChanges -and $line -match '^- ') {
411+
if ($existingChanges -lt 2) { $output.Add($line); $existingChanges++ }
412+
continue
413+
}
414+
if ($line -match '\*\*Last updated\*\*: .*\d{4}-\d{2}-\d{2}') {
415+
$output.Add(($line -replace '\d{4}-\d{2}-\d{2}', $Date.ToString('yyyy-MM-dd')))
416+
continue
417+
}
418+
$output.Add($line)
419+
}
420+
421+
# Post-loop check: if we're still in the Active Technologies section and haven't added new entries
422+
if ($inTech -and -not $techAdded -and $newTechEntries.Count -gt 0) {
423+
$newTechEntries | ForEach-Object { $output.Add($_) }
424+
}
425+
426+
Set-Content -LiteralPath $TargetFile -Value ($output -join [Environment]::NewLine) -Encoding utf8
427+
return $true
428+
}
429+
430+
function Update-AgentFile {
431+
param(
432+
[Parameter(Mandatory = $true)]
433+
[string]$TargetFile,
434+
[Parameter(Mandatory = $true)]
435+
[string]$AgentName
436+
)
437+
if (-not $TargetFile -or -not $AgentName) { Write-Err 'Update-AgentFile requires TargetFile and AgentName'; return $false }
438+
Write-Info "Updating $AgentName context file: $TargetFile"
439+
$projectName = Split-Path $REPO_ROOT -Leaf
440+
$date = Get-Date
441+
442+
$dir = Split-Path -Parent $TargetFile
443+
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
444+
445+
if (-not (Test-Path $TargetFile)) {
446+
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 }
447+
} else {
448+
try {
449+
if (Update-ExistingAgentFile -TargetFile $TargetFile -Date $date) { Write-Success "Updated existing $AgentName context file" } else { Write-Err 'Failed to update agent file'; return $false }
450+
} catch {
451+
Write-Err "Cannot access or update existing file: $TargetFile. $_"
452+
return $false
453+
}
454+
}
455+
return $true
456+
}
457+
458+
function Update-SpecificAgent {
459+
param(
460+
[Parameter(Mandatory = $true)]
461+
[string]$Type
462+
)
463+
switch ($Type) {
464+
'claude' { Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code' }
465+
'gemini' { Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI' }
466+
'copilot' { Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot' }
467+
'cursor' { Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE' }
468+
'qwen' { Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code' }
469+
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
470+
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
471+
'windsurf' { Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf' }
472+
'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' }
473+
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
474+
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
475+
default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo'; return $false }
476+
}
477+
}
478+
479+
function Update-AllExistingAgents {
480+
$found = $false
481+
$ok = $true
482+
if (Test-Path $CLAUDE_FILE) { if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }; $found = $true }
483+
if (Test-Path $GEMINI_FILE) { if (-not (Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI')) { $ok = $false }; $found = $true }
484+
if (Test-Path $COPILOT_FILE) { if (-not (Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot')) { $ok = $false }; $found = $true }
485+
if (Test-Path $CURSOR_FILE) { if (-not (Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false }; $found = $true }
486+
if (Test-Path $QWEN_FILE) { if (-not (Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }; $found = $true }
487+
if (Test-Path $AGENTS_FILE) { if (-not (Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex/opencode')) { $ok = $false }; $found = $true }
488+
if (Test-Path $WINDSURF_FILE) { if (-not (Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }; $found = $true }
489+
if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true }
490+
if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true }
491+
if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true }
492+
if (-not $found) {
493+
Write-Info 'No existing agent files found, creating default Claude file...'
494+
if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
495+
}
496+
return $ok
497+
}
498+
499+
function Show-Summary {
500+
Write-Host ''
501+
Write-Info 'Summary of changes:'
502+
if ($NEW_LANG) { Write-Host " - Added language: $NEW_LANG" }
503+
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
504+
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
505+
Write-Host ''
506+
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo]'
507+
}

0 commit comments

Comments
 (0)