metallkart-erp/scripts/orchestrator.ps1
2026-04-08 15:48:24 +03:00

210 lines
9.3 KiB
PowerShell

#!/usr/bin/env pwsh
# orchestrator.ps1 — Pipeline V2: CODEUR → VERIFICATEUR → retry si echec
# Usage: .\orchestrator.ps1 specs\ma-spec.md
# Usage: .\orchestrator.ps1 specs\ma-spec.md -MaxRetries 3
#
# Passe 1 (CODEUR): execute la spec
# Passe 2 (VERIFICATEUR): verifie build, tests, grep checks
# Si echec: re-lance le CODEUR avec le rapport d'erreur (max N retries)
param(
[Parameter(Mandatory=$true, Position=0)]
[string]$SpecFile,
[int]$MaxRetries = 2,
[switch]$NoCommit,
[switch]$NoPush,
[switch]$NoNotify
)
$ErrorActionPreference = "Continue"
$REPO = "C:\Projects\metallkart-erp"
$SCRIPTS = "$REPO\scripts"
$LOGS = "$REPO\logs"
$VPS = "root@76.13.55.81"
# Resolve spec
if (-not [System.IO.Path]::IsPathRooted($SpecFile)) {
$SpecFile = Join-Path $REPO $SpecFile
}
if (-not (Test-Path $SpecFile)) {
Write-Host "ERREUR: $SpecFile introuvable" -ForegroundColor Red
exit 1
}
$specName = [System.IO.Path]::GetFileNameWithoutExtension($SpecFile)
$startTime = Get-Date
Write-Host "╔════════════════════════════════════════╗" -ForegroundColor Magenta
Write-Host "║ ORCHESTRATOR V2 — $specName" -ForegroundColor Magenta
Write-Host "║ MaxRetries: $MaxRetries" -ForegroundColor DarkMagenta
Write-Host "╚════════════════════════════════════════╝" -ForegroundColor Magenta
if (-not $NoNotify) {
& "$SCRIPTS\notify-telegram.ps1" "ORCHESTRATOR [$specName] demarre (max $MaxRetries retries)"
}
function Invoke-Claude($prompt, $label) {
$wrapperPath = "$env:TEMP\orch-$label-$specName.ps1"
$outputPath = "$env:TEMP\orch-output-$label-$specName.txt"
$exitCodePath = "$env:TEMP\orch-exit-$label-$specName.txt"
$escapedPrompt = $prompt -replace "'","''"
$wrapperLines = @(
"Set-Location '$REPO'"
"try {"
" `$out = claude -p '$escapedPrompt' --allowedTools 'Bash(*),Read,Write,Edit,Glob,Grep' 2>&1 | Out-String"
" `$out | Out-File -FilePath '$outputPath' -Encoding utf8"
" `$LASTEXITCODE | Out-File -FilePath '$exitCodePath' -Encoding utf8"
"} catch {"
" `$_.Exception.Message | Out-File -FilePath '$outputPath' -Encoding utf8"
" '1' | Out-File -FilePath '$exitCodePath' -Encoding utf8"
"}"
)
[System.IO.File]::WriteAllLines($wrapperPath, $wrapperLines, [System.Text.Encoding]::UTF8)
$t0 = Get-Date
Start-Process powershell.exe -ArgumentList "-NoProfile","-ExecutionPolicy","Bypass","-File",$wrapperPath -Wait -NoNewWindow
$secs = [math]::Round(((Get-Date) - $t0).TotalSeconds, 1)
$output = ""
$exitCode = 0
if (Test-Path $outputPath) { $output = Get-Content $outputPath -Raw -ErrorAction SilentlyContinue }
if (Test-Path $exitCodePath) {
$codeStr = (Get-Content $exitCodePath -Raw -ErrorAction SilentlyContinue)
if ($codeStr) { $codeStr = $codeStr.Trim(); if ($codeStr -match '^\d+$') { $exitCode = [int]$codeStr } }
}
Remove-Item $wrapperPath,$outputPath,$exitCodePath -Force -ErrorAction SilentlyContinue
return @{ output=$output; exitCode=$exitCode; elapsed=$secs }
}
# ========================
# PASSE 1: CODEUR
# ========================
$attempt = 0
$coderSuccess = $false
while ($attempt -le $MaxRetries -and -not $coderSuccess) {
$attempt++
Write-Host "`n┌─── CODEUR (tentative $attempt/$($MaxRetries+1)) ───" -ForegroundColor Yellow
if ($attempt -eq 1) {
$coderPrompt = "Lis le fichier $SpecFile et execute TOUTES les instructions. Supprime le fichier spec quand termine."
} else {
$coderPrompt = "RETRY $attempt — Le verificateur a trouve des erreurs. Lis $SpecFile (la spec originale) puis corrige les problemes suivants:`n$verifyErrors`nNe recommence PAS tout depuis zero, corrige uniquement les erreurs."
}
$coderResult = Invoke-Claude $coderPrompt "coder-$attempt"
Write-Host " Codeur: exit $($coderResult.exitCode) en $($coderResult.elapsed)s" -ForegroundColor $(if($coderResult.exitCode -eq 0){"Green"}else{"Red"})
if ($coderResult.exitCode -ne 0) {
Write-Host " Codeur CRASH — retry..." -ForegroundColor Red
$verifyErrors = "Le codeur a crash avec exit code $($coderResult.exitCode). Output: $($coderResult.output.Substring(0, [Math]::Min(1000, $coderResult.output.Length)))"
continue
}
# ========================
# PASSE 2: VERIFICATEUR
# ========================
Write-Host "`n├─── VERIFICATEUR ───" -ForegroundColor Cyan
$verifyPrompt = @"
Tu es le VERIFICATEUR. Verifie que la spec '$specName' a ete correctement implementee:
1. BUILD: lance 'npm run build --prefix frontend' (si frontend touche) et verifie 0 erreurs
2. TYPESCRIPT: verifie que 'npx tsc --noEmit' passe (backend)
3. TESTS E2E: lance 'npx tsx tests/e2e-workflow.ts' et verifie tous les checks passent
4. GREP: verifie que les fichiers modifies existent et contiennent le code attendu
5. SERVEUR: verifie que le backend demarre sans erreur sur port 3001 (kill d'abord avec fuser -k 3001/tcp ou equivalent Windows)
Retourne un JSON sur une seule ligne:
{"pass":true/false,"checks":["check1: OK","check2: FAIL reason"],"errors":"description si fail"}
IMPORTANT: retourne UNIQUEMENT le JSON, rien d'autre.
"@
$verifyResult = Invoke-Claude $verifyPrompt "verify-$attempt"
Write-Host " Verificateur: exit $($verifyResult.exitCode) en $($verifyResult.elapsed)s" -ForegroundColor $(if($verifyResult.exitCode -eq 0){"Green"}else{"Red"})
# Parse result
$verifyPass = $false
$verifyErrors = ""
try {
# Extract JSON from output (may have noise around it)
$jsonMatch = [regex]::Match($verifyResult.output, '\{[^{}]*"pass"\s*:\s*(true|false)[^{}]*\}')
if ($jsonMatch.Success) {
$parsed = $jsonMatch.Value | ConvertFrom-Json
$verifyPass = $parsed.pass
if (-not $verifyPass) {
$verifyErrors = $parsed.errors
if ($parsed.checks) { $verifyErrors += "`nChecks: " + ($parsed.checks -join "`n") }
}
} else {
$verifyErrors = "Verificateur n'a pas retourne de JSON valide. Output: $($verifyResult.output.Substring(0, [Math]::Min(500, $verifyResult.output.Length)))"
}
} catch {
$verifyErrors = "Parse error verificateur: $($_.Exception.Message)"
}
if ($verifyPass) {
$coderSuccess = $true
Write-Host " ✓ VERIFICATION REUSSIE" -ForegroundColor Green
} else {
Write-Host " ✗ VERIFICATION ECHOUEE" -ForegroundColor Red
Write-Host " Erreurs: $verifyErrors" -ForegroundColor DarkRed
if ($attempt -le $MaxRetries) {
Write-Host " → Retry codeur..." -ForegroundColor Yellow
}
}
Write-Host "└───────────────────────" -ForegroundColor DarkGray
}
$totalElapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1)
# Save log
$logFile = Join-Path $LOGS "orch-$specName-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
$logContent = "=== ORCHESTRATOR $specName ===`nAttempts: $attempt`nSuccess: $coderSuccess`nElapsed: ${totalElapsed}s`n"
[System.IO.File]::WriteAllText($logFile, $logContent, [System.Text.Encoding]::UTF8)
if (-not $coderSuccess) {
Write-Host "`n╔════════════════════════════════════════╗" -ForegroundColor Red
Write-Host "║ ECHEC apres $attempt tentatives (${totalElapsed}s)" -ForegroundColor Red
Write-Host "╚════════════════════════════════════════╝" -ForegroundColor Red
if (-not $NoNotify) {
& "$SCRIPTS\notify-telegram.ps1" -Status "ERREUR" -TaskId "ORCH-$specName" -Elapsed $totalElapsed -Details "Echec apres $attempt tentatives"
}
exit 1
}
# --- Git commit + push ---
if (-not $NoCommit) {
Write-Host "`nGit commit..." -ForegroundColor Yellow
Set-Location $REPO
$changes = git status --porcelain 2>&1
if ($changes) {
git add -A
git commit -m "spec: $specName (orchestrator v2, $attempt tentative(s))`n`nCo-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>"
Write-Host " Committed" -ForegroundColor Green
}
}
if (-not $NoPush -and -not $NoCommit) {
Write-Host "Git push..." -ForegroundColor Yellow
$unpushed = git log "origin/master..HEAD" --oneline 2>&1
if ($unpushed) {
git push origin master 2>&1
Write-Host " Pushed" -ForegroundColor Green
}
scp "$REPO\MEMORY.md" "${VPS}:/opt/erp/metallkart-erp/MEMORY.md" 2>$null
Write-Host " MEMORY.md synced" -ForegroundColor Magenta
}
# --- Final notification ---
if (-not $NoNotify) {
& "$SCRIPTS\notify-telegram.ps1" -Status "OK" -TaskId "ORCH-$specName" -Elapsed $totalElapsed -Details "$attempt tentative(s)"
}
Write-Host "`n╔════════════════════════════════════════╗" -ForegroundColor Green
Write-Host "║ SUCCES: $specName (${totalElapsed}s, $attempt tentative(s))" -ForegroundColor Green
Write-Host "╚════════════════════════════════════════╝" -ForegroundColor Green