prizmkit 1.1.70 → 1.1.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/lib/common.sh +427 -0
- package/bundled/dev-pipeline/lib/heartbeat.sh +36 -0
- package/bundled/dev-pipeline/run-feature.sh +109 -29
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +160 -3
- package/bundled/dev-pipeline/scripts/update-feature-status.py +27 -3
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +11 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +11 -0
- package/bundled/dev-pipeline-windows/lib/common.ps1 +61 -1
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +299 -14
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +160 -3
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +27 -3
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +11 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +11 -0
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,9 @@ $ErrorActionPreference = 'Stop'
|
|
|
3
3
|
|
|
4
4
|
$script:PrizmAiProcess = $null
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
function Initialize-PrizmLineLogBridgeType {
|
|
7
|
+
if ('PrizmLineLogBridge' -as [type]) { return }
|
|
8
|
+
|
|
7
9
|
Add-Type -TypeDefinition @'
|
|
8
10
|
using System;
|
|
9
11
|
using System.Diagnostics;
|
|
@@ -164,6 +166,63 @@ function Test-PrizmInfraError {
|
|
|
164
166
|
return ($haystack -match '(?i)auth_unavailable|no auth available|502 Bad Gateway|503 Service Unavailable|504 Gateway Timeout|gateway timeout|upstream (connect )?error|connection reset|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|rate limit|rate_limit|temporarily unavailable|overloaded')
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
function Test-PrizmAiRuntimeError {
|
|
170
|
+
param([string]$SessionLog, [string]$ProgressJson)
|
|
171
|
+
|
|
172
|
+
if ($ProgressJson -and (Test-Path $ProgressJson)) {
|
|
173
|
+
try {
|
|
174
|
+
$progress = Get-Content $ProgressJson -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
175
|
+
if ($progress.PSObject.Properties['fatal_error_code'] -and $progress.fatal_error_code) {
|
|
176
|
+
return $true
|
|
177
|
+
}
|
|
178
|
+
} catch {}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
$parts = @()
|
|
182
|
+
if ($SessionLog -and (Test-Path $SessionLog)) {
|
|
183
|
+
try {
|
|
184
|
+
$text = Get-Content $SessionLog -Raw -ErrorAction Stop
|
|
185
|
+
if ($text.Length -gt 65536) { $text = $text.Substring($text.Length - 65536) }
|
|
186
|
+
$parts += $text
|
|
187
|
+
} catch {}
|
|
188
|
+
}
|
|
189
|
+
if ($ProgressJson -and (Test-Path $ProgressJson)) {
|
|
190
|
+
try { $parts += (Get-Content $ProgressJson -Raw -ErrorAction Stop) } catch {}
|
|
191
|
+
}
|
|
192
|
+
if ($parts.Count -eq 0) { return $false }
|
|
193
|
+
|
|
194
|
+
$haystack = $parts -join "`n"
|
|
195
|
+
$contextPattern = '(?i)context_too_large|model_context_window_exceeded|input exceeds the context window|context window of this model|context window (was )?exceeded|exceeded (the )?context window|invalid_request_error.*context window|context window.*invalid_request_error'
|
|
196
|
+
$errorPattern = '(?i)api error|invalid_request_error|api_error_status|api_error_code|status\s*[:=]?\s*(400|413)|last_result_is_error\s*["'':=]?\s*true|is_error\s*["'':=]?\s*true'
|
|
197
|
+
if (($haystack -match $contextPattern) -and ($haystack -match $errorPattern)) { return $true }
|
|
198
|
+
return $false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function Get-PrizmProgressFatalErrorCode {
|
|
202
|
+
param([string]$ProgressFile)
|
|
203
|
+
if (-not (Test-Path $ProgressFile)) { return '' }
|
|
204
|
+
try {
|
|
205
|
+
$progress = Get-Content $ProgressFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
206
|
+
if ($progress.PSObject.Properties['fatal_error_code'] -and $progress.fatal_error_code) {
|
|
207
|
+
return [string]$progress.fatal_error_code
|
|
208
|
+
}
|
|
209
|
+
} catch {}
|
|
210
|
+
return ''
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function Write-PrizmFatalErrorMarker {
|
|
214
|
+
param([string]$MarkerPath, [string]$FatalErrorCode, [int]$StaleSeconds, [int]$Threshold)
|
|
215
|
+
$markerDir = Split-Path $MarkerPath -Parent
|
|
216
|
+
if ($markerDir) { New-Item -ItemType Directory -Force -Path $markerDir | Out-Null }
|
|
217
|
+
[ordered]@{
|
|
218
|
+
killed_at = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
|
|
219
|
+
reason = $FatalErrorCode
|
|
220
|
+
fatal_error_code = $FatalErrorCode
|
|
221
|
+
stale_seconds = $StaleSeconds
|
|
222
|
+
threshold = $Threshold
|
|
223
|
+
} | ConvertTo-Json -Compress | Set-Content -Path $MarkerPath -Encoding UTF8
|
|
224
|
+
}
|
|
225
|
+
|
|
167
226
|
function Get-PrizmConfigValue {
|
|
168
227
|
param([string]$ConfigPath, [string]$Key)
|
|
169
228
|
if (-not (Test-Path $ConfigPath)) { return $null }
|
|
@@ -638,6 +697,7 @@ function Invoke-PrizmAiSession {
|
|
|
638
697
|
$psi.Arguments = $argumentString
|
|
639
698
|
}
|
|
640
699
|
|
|
700
|
+
Initialize-PrizmLineLogBridgeType
|
|
641
701
|
$process = [System.Diagnostics.Process]::Start($psi)
|
|
642
702
|
$script:PrizmAiProcess = $process
|
|
643
703
|
$logBridge = [PrizmLineLogBridge]::new($LogPath)
|
|
@@ -335,6 +335,242 @@ function Invoke-PrizmPipeline {
|
|
|
335
335
|
return $LASTEXITCODE -eq 0
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
function Get-PrizmFeatureSlugFromList {
|
|
339
|
+
param([string]$ListPath, [string]$FeatureId)
|
|
340
|
+
if (-not (Test-Path $ListPath)) { return '' }
|
|
341
|
+
try { $data = Get-Content $ListPath -Raw | ConvertFrom-Json } catch { return '' }
|
|
342
|
+
foreach ($feature in @($data.features)) {
|
|
343
|
+
if ($feature.id -eq $FeatureId) {
|
|
344
|
+
$number = ([string]$feature.id).Replace('F-', '').Replace('f-', '').PadLeft(3, '0')
|
|
345
|
+
$title = ([string]$feature.title).ToLowerInvariant()
|
|
346
|
+
$title = [regex]::Replace($title, '[^a-z0-9\s-]', '')
|
|
347
|
+
$title = [regex]::Replace($title.Trim(), '[\s]+', '-')
|
|
348
|
+
$title = [regex]::Replace($title, '-+', '-').Trim('-')
|
|
349
|
+
if ($title) { return "$number-$title" }
|
|
350
|
+
return $number
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return ''
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function Test-PrizmCheckpointComplete {
|
|
357
|
+
param([string]$CheckpointPath)
|
|
358
|
+
if (-not (Test-Path $CheckpointPath)) { return $false }
|
|
359
|
+
try { $checkpoint = Get-Content $CheckpointPath -Raw | ConvertFrom-Json } catch { return $false }
|
|
360
|
+
if (-not $checkpoint.steps) { return $false }
|
|
361
|
+
foreach ($step in @($checkpoint.steps)) {
|
|
362
|
+
if ($step.status -notin @('completed', 'skipped')) { return $false }
|
|
363
|
+
}
|
|
364
|
+
return $true
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function Get-PrizmFeatureTitleFromList {
|
|
368
|
+
param([string]$ListPath, [string]$FeatureId)
|
|
369
|
+
if (-not (Test-Path $ListPath)) { return '' }
|
|
370
|
+
try { $data = Get-Content $ListPath -Raw | ConvertFrom-Json } catch { return '' }
|
|
371
|
+
foreach ($feature in @($data.features)) {
|
|
372
|
+
if ($feature.id -eq $FeatureId) { return [string]$feature.title }
|
|
373
|
+
}
|
|
374
|
+
return ''
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function Get-PrizmTitleWords {
|
|
378
|
+
param([string]$Text)
|
|
379
|
+
$matches = [regex]::Matches(([string]$Text).ToLowerInvariant(), '[a-z0-9]{3,}')
|
|
380
|
+
$words = @()
|
|
381
|
+
foreach ($match in $matches) { $words += [string]$match.Value }
|
|
382
|
+
return @($words)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function Test-PrizmCommitMatchesFeatureTitle {
|
|
386
|
+
param([string]$Subject, [string]$FeatureTitle)
|
|
387
|
+
$titleWords = @(Get-PrizmTitleWords $FeatureTitle)
|
|
388
|
+
if ($titleWords.Count -eq 0) { return $false }
|
|
389
|
+
$subjectWords = @(Get-PrizmTitleWords $Subject)
|
|
390
|
+
$subjectSet = @{}
|
|
391
|
+
foreach ($word in $subjectWords) { $subjectSet[$word] = $true }
|
|
392
|
+
$required = if ($titleWords.Count -le 3) { $titleWords.Count } else { [Math]::Max(3, [int][Math]::Ceiling($titleWords.Count * 0.75)) }
|
|
393
|
+
$matched = 0
|
|
394
|
+
foreach ($word in $titleWords) {
|
|
395
|
+
if ($subjectSet.ContainsKey($word)) { $matched++ }
|
|
396
|
+
}
|
|
397
|
+
return $matched -ge $required
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function Get-PrizmFeatureCommit {
|
|
401
|
+
param([string]$ProjectRoot, [string]$BaseCommit, [string]$FeatureId, [bool]$AllowFallback = $false, [string]$FeatureTitle = '')
|
|
402
|
+
$range = if ($BaseCommit) { "$BaseCommit..HEAD" } else { 'HEAD' }
|
|
403
|
+
$lines = & git -C $ProjectRoot log $range '--format=%H%x09%s' 2>$null
|
|
404
|
+
if ($LASTEXITCODE -ne 0) { return '' }
|
|
405
|
+
foreach ($line in @($lines)) {
|
|
406
|
+
$parts = ([string]$line).Split("`t", 2)
|
|
407
|
+
if ($parts.Count -lt 2) { continue }
|
|
408
|
+
$subject = $parts[1]
|
|
409
|
+
if ($subject.Contains($FeatureId) -and $subject -notmatch '^wip(\(|:)') { return $parts[0] }
|
|
410
|
+
}
|
|
411
|
+
if ($AllowFallback -and $FeatureTitle) {
|
|
412
|
+
foreach ($line in @($lines)) {
|
|
413
|
+
$parts = ([string]$line).Split("`t", 2)
|
|
414
|
+
if ($parts.Count -lt 2) { continue }
|
|
415
|
+
$subject = $parts[1]
|
|
416
|
+
if ($subject -notmatch '^wip(\(|:)' -and (Test-PrizmCommitMatchesFeatureTitle $subject $FeatureTitle)) { return $parts[0] }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return ''
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function Get-PrizmFeatureSemanticCompletion {
|
|
423
|
+
param([string]$ProjectRoot, [string]$ListPath, [string]$FeatureId, [string]$BaseCommit, [string]$PrizmkitDir)
|
|
424
|
+
$slug = Get-PrizmFeatureSlugFromList $ListPath $FeatureId
|
|
425
|
+
if (-not $slug) { return $null }
|
|
426
|
+
$checkpointPath = Join-Path $PrizmkitDir "specs\$slug\workflow-checkpoint.json"
|
|
427
|
+
if (-not (Test-PrizmCheckpointComplete $checkpointPath)) { return $null }
|
|
428
|
+
$featureTitle = Get-PrizmFeatureTitleFromList $ListPath $FeatureId
|
|
429
|
+
$commitSha = Get-PrizmFeatureCommit $ProjectRoot $BaseCommit $FeatureId $true $featureTitle
|
|
430
|
+
if (-not $commitSha) { return $null }
|
|
431
|
+
return [pscustomobject]@{ Slug = $slug; CommitSha = $commitSha; CheckpointPath = $checkpointPath }
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function Save-PrizmPostCompletionDirtyArtifacts {
|
|
435
|
+
param([string]$ProjectRoot, [string]$ArtifactDir, [string]$ItemId, [string]$SessionId)
|
|
436
|
+
$status = & git -C $ProjectRoot status --porcelain --untracked-files=all 2>$null
|
|
437
|
+
if ([string]::IsNullOrWhiteSpace(($status -join "`n"))) { return $true }
|
|
438
|
+
|
|
439
|
+
New-Item -ItemType Directory -Force -Path $ArtifactDir | Out-Null
|
|
440
|
+
($status -join "`n") | Set-Content -Path (Join-Path $ArtifactDir 'post-completion-status.txt') -Encoding UTF8
|
|
441
|
+
& git -C $ProjectRoot diff --binary | Set-Content -Path (Join-Path $ArtifactDir 'post-completion-dirty.patch') -Encoding UTF8
|
|
442
|
+
if ($LASTEXITCODE -ne 0) { return $false }
|
|
443
|
+
& git -C $ProjectRoot diff --cached --binary | Set-Content -Path (Join-Path $ArtifactDir 'post-completion-staged.patch') -Encoding UTF8
|
|
444
|
+
if ($LASTEXITCODE -ne 0) { return $false }
|
|
445
|
+
|
|
446
|
+
$untracked = & git -C $ProjectRoot ls-files --others --exclude-standard 2>$null
|
|
447
|
+
$manifest = Join-Path $ArtifactDir 'post-completion-untracked.txt'
|
|
448
|
+
@($untracked) | Set-Content -Path $manifest -Encoding UTF8
|
|
449
|
+
$untrackedDir = Join-Path $ArtifactDir 'untracked'
|
|
450
|
+
foreach ($rel in @($untracked)) {
|
|
451
|
+
if (-not $rel) { continue }
|
|
452
|
+
$source = Join-Path $ProjectRoot $rel
|
|
453
|
+
$dest = Join-Path $untrackedDir $rel
|
|
454
|
+
$destParent = Split-Path $dest -Parent
|
|
455
|
+
if ($destParent) { New-Item -ItemType Directory -Force -Path $destParent | Out-Null }
|
|
456
|
+
if (Test-Path $source -PathType Leaf) { Copy-Item -LiteralPath $source -Destination $dest -Force }
|
|
457
|
+
elseif (Test-Path $source -PathType Container) { New-Item -ItemType Directory -Force -Path $dest | Out-Null }
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
@(
|
|
461
|
+
'# Post-completion dirty changes preserved',
|
|
462
|
+
'',
|
|
463
|
+
"- Feature: $ItemId",
|
|
464
|
+
"- Session: $SessionId",
|
|
465
|
+
'- Reason: workflow checkpoint and feature commit were already complete, but delayed post-commit activity left the working tree dirty.',
|
|
466
|
+
'',
|
|
467
|
+
'## Recovery guidance',
|
|
468
|
+
'',
|
|
469
|
+
'The finalized feature commit was kept unchanged for merge. Review these follow-up artifacts separately; do not assume they were merged:',
|
|
470
|
+
'',
|
|
471
|
+
'- `post-completion-status.txt` — original dirty working tree status',
|
|
472
|
+
'- `post-completion-dirty.patch` — unstaged tracked changes',
|
|
473
|
+
'- `post-completion-staged.patch` — staged changes',
|
|
474
|
+
'- `post-completion-untracked.txt` and `untracked/` — untracked files copied before cleanup'
|
|
475
|
+
) | Set-Content -Path (Join-Path $ArtifactDir 'post-completion-findings.md') -Encoding UTF8
|
|
476
|
+
|
|
477
|
+
& git -C $ProjectRoot reset --hard *> $null
|
|
478
|
+
if ($LASTEXITCODE -ne 0) { return $false }
|
|
479
|
+
foreach ($rel in @($untracked)) {
|
|
480
|
+
if (-not $rel -or $rel -like '.prizmkit/*') { continue }
|
|
481
|
+
$target = Join-Path $ProjectRoot $rel
|
|
482
|
+
if (Test-Path $target) { Remove-Item -LiteralPath $target -Recurse -Force -ErrorAction SilentlyContinue }
|
|
483
|
+
}
|
|
484
|
+
$remaining = & git -C $ProjectRoot status --porcelain --untracked-files=all 2>$null | Where-Object { $_ -notmatch '^\?\? \.prizmkit/' }
|
|
485
|
+
return [string]::IsNullOrWhiteSpace(($remaining -join "`n"))
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function Write-PrizmRuntimeFailureLog {
|
|
489
|
+
param(
|
|
490
|
+
[string]$FailureLog,
|
|
491
|
+
[string]$FeatureId,
|
|
492
|
+
[string]$SessionId,
|
|
493
|
+
[string]$SessionStatus,
|
|
494
|
+
[int]$ExitCode,
|
|
495
|
+
[string]$StaleKillMarker,
|
|
496
|
+
[string]$ProgressJson,
|
|
497
|
+
[string]$CheckpointPath,
|
|
498
|
+
[string]$ProjectRoot,
|
|
499
|
+
[string]$BaseCommit
|
|
500
|
+
)
|
|
501
|
+
if (-not $FailureLog -or (Test-Path $FailureLog)) { return }
|
|
502
|
+
$dir = Split-Path $FailureLog -Parent
|
|
503
|
+
if ($dir) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
|
504
|
+
$stale = if (Test-Path $StaleKillMarker) { Get-Content $StaleKillMarker -Raw } else { 'No stale-kill marker.' }
|
|
505
|
+
$progressLines = @('Progress data unavailable.')
|
|
506
|
+
if (Test-Path $ProgressJson) {
|
|
507
|
+
try {
|
|
508
|
+
$progress = Get-Content $ProgressJson -Raw | ConvertFrom-Json
|
|
509
|
+
$progressLines = @()
|
|
510
|
+
foreach ($key in @('fatal_error_code','api_error_status','api_error_code','current_phase','current_tool','last_text_snippet','terminal_result_text')) {
|
|
511
|
+
if ($progress.PSObject.Properties[$key] -and $progress.$key) { $progressLines += "- ${key}: $($progress.$key)" }
|
|
512
|
+
}
|
|
513
|
+
if ($progressLines.Count -eq 0) { $progressLines = @('Progress data contained no terminal fields.') }
|
|
514
|
+
} catch { $progressLines = @("Progress parse error: $($_.Exception.Message)") }
|
|
515
|
+
}
|
|
516
|
+
$checkpointLines = @('No checkpoint file found.')
|
|
517
|
+
if (Test-Path $CheckpointPath) {
|
|
518
|
+
try {
|
|
519
|
+
$checkpoint = Get-Content $CheckpointPath -Raw | ConvertFrom-Json
|
|
520
|
+
$steps = @($checkpoint.steps)
|
|
521
|
+
$complete = @($steps | Where-Object { $_.status -in @('completed','skipped') }).Count
|
|
522
|
+
$checkpointLines = @("$complete/$($steps.Count) steps completed_or_skipped")
|
|
523
|
+
foreach ($step in $steps) {
|
|
524
|
+
if ($step.status -notin @('completed','skipped')) { $checkpointLines += "- incomplete: $($step.id) $($step.skill) = $($step.status)" }
|
|
525
|
+
}
|
|
526
|
+
} catch { $checkpointLines = @("Checkpoint parse error: $($_.Exception.Message)") }
|
|
527
|
+
}
|
|
528
|
+
$latestCommit = (& git -C $ProjectRoot rev-parse --short HEAD 2>$null | Select-Object -First 1)
|
|
529
|
+
if (-not $latestCommit) { $latestCommit = 'unavailable' }
|
|
530
|
+
$featureCommit = if (Get-PrizmFeatureCommit $ProjectRoot $BaseCommit $FeatureId $false) { 'yes' } else { 'no' }
|
|
531
|
+
$dirty = & git -C $ProjectRoot status --short 2>$null
|
|
532
|
+
if ([string]::IsNullOrWhiteSpace(($dirty -join "`n"))) { $dirty = @('clean') }
|
|
533
|
+
@(
|
|
534
|
+
'# Runtime-synthesized failure log',
|
|
535
|
+
'',
|
|
536
|
+
'## Session',
|
|
537
|
+
'',
|
|
538
|
+
"- feature_id: $FeatureId",
|
|
539
|
+
"- session_id: $SessionId",
|
|
540
|
+
"- session_status: $SessionStatus",
|
|
541
|
+
"- exit_code: $ExitCode",
|
|
542
|
+
'',
|
|
543
|
+
'## Stale kill marker',
|
|
544
|
+
'',
|
|
545
|
+
'```json',
|
|
546
|
+
$stale,
|
|
547
|
+
'```',
|
|
548
|
+
'',
|
|
549
|
+
'## Progress',
|
|
550
|
+
'',
|
|
551
|
+
$progressLines,
|
|
552
|
+
'',
|
|
553
|
+
'## Checkpoint',
|
|
554
|
+
'',
|
|
555
|
+
$checkpointLines,
|
|
556
|
+
'',
|
|
557
|
+
'## Git state',
|
|
558
|
+
'',
|
|
559
|
+
"- feature_commit_exists: $featureCommit",
|
|
560
|
+
"- latest_commit: $latestCommit",
|
|
561
|
+
'',
|
|
562
|
+
'```text',
|
|
563
|
+
$dirty,
|
|
564
|
+
'```',
|
|
565
|
+
'',
|
|
566
|
+
'## Recommended recovery action',
|
|
567
|
+
'',
|
|
568
|
+
'- If this is an AI runtime/provider error before checkpoint completion, retry the session with a fresh context.',
|
|
569
|
+
'- If checkpoint completion and a feature commit both exist, inspect post-completion artifacts and finalize manually rather than rebuilding from scratch.',
|
|
570
|
+
'- If the working tree is dirty, preserve or review those changes before any reset or merge.'
|
|
571
|
+
) | Set-Content -Path $FailureLog -Encoding UTF8
|
|
572
|
+
}
|
|
573
|
+
|
|
338
574
|
function New-PrizmDefaultDevBranchName {
|
|
339
575
|
param([string]$Kind, [string]$CurrentItemId)
|
|
340
576
|
$timestamp = Get-Date -Format 'yyyyMMddHHmm'
|
|
@@ -588,6 +824,17 @@ function Invoke-PrizmPipeline {
|
|
|
588
824
|
}
|
|
589
825
|
|
|
590
826
|
$effectiveStaleKillThreshold = Get-PrizmEffectiveStaleKillThreshold -ProgressFile $progressJson -BaseThreshold $staleKillThreshold
|
|
827
|
+
$fatalErrorCode = Get-PrizmProgressFatalErrorCode -ProgressFile $progressJson
|
|
828
|
+
if ($fatalErrorCode) {
|
|
829
|
+
$wasStaleKilled = $true
|
|
830
|
+
Write-PrizmWarn "Session hit fatal AI runtime error: $fatalErrorCode"
|
|
831
|
+
$fatalErrorMarker = Join-Path $logsDir 'fatal-error.json'
|
|
832
|
+
Write-PrizmFatalErrorMarker $fatalErrorMarker $fatalErrorCode $staleSeconds $effectiveStaleKillThreshold
|
|
833
|
+
Write-PrizmFatalErrorMarker $staleKillMarker $fatalErrorCode $staleSeconds $effectiveStaleKillThreshold
|
|
834
|
+
Stop-PrizmSessionProcess $pidPath
|
|
835
|
+
if ($staleKillGraceSeconds -gt 0) { Start-Sleep -Seconds $staleKillGraceSeconds }
|
|
836
|
+
break
|
|
837
|
+
}
|
|
591
838
|
if ($effectiveStaleKillThreshold -gt 0 -and $staleSeconds -ge $effectiveStaleKillThreshold) {
|
|
592
839
|
$wasStaleKilled = $true
|
|
593
840
|
Write-PrizmWarn "Session stale-killed (no progress for ${effectiveStaleKillThreshold}s)"
|
|
@@ -619,9 +866,23 @@ function Invoke-PrizmPipeline {
|
|
|
619
866
|
Stop-PrizmProgressParser $parserProcess
|
|
620
867
|
|
|
621
868
|
$wasInfraError = ($exitCode -ne 0 -and (Test-PrizmInfraError -SessionLog $sessionLog -ProgressJson $progressJson))
|
|
869
|
+
$wasAiRuntimeError = Test-PrizmAiRuntimeError -SessionLog $sessionLog -ProgressJson $progressJson
|
|
870
|
+
$semanticCompletion = if ($Kind -eq 'feature' -and $isGitRepository) {
|
|
871
|
+
Get-PrizmFeatureSemanticCompletion $paths.ProjectRoot $listPath $CurrentItemId $baseCommit $paths.PrizmkitDir
|
|
872
|
+
} else { $null }
|
|
622
873
|
|
|
623
874
|
$status = 'crashed'
|
|
624
|
-
if ($
|
|
875
|
+
if ($semanticCompletion) {
|
|
876
|
+
$status = 'success'
|
|
877
|
+
if ($exitCode -ne 0 -or $wasStaleKilled -or $wasTimedOut -or $wasAiRuntimeError) {
|
|
878
|
+
Write-PrizmWarn "Session ended with a failure signal after semantic completion; treating as finalized success"
|
|
879
|
+
Write-PrizmWarn "Semantic completion commit: $($semanticCompletion.CommitSha)"
|
|
880
|
+
}
|
|
881
|
+
} elseif ($wasAiRuntimeError) {
|
|
882
|
+
$status = 'infra_error'
|
|
883
|
+
Write-PrizmWarn "AI session failed due to structured AI runtime/context error"
|
|
884
|
+
Write-PrizmWarn "AI runtime errors are retried without consuming code retry budget"
|
|
885
|
+
} elseif ($wasTimedOut) {
|
|
625
886
|
$status = 'timed_out'
|
|
626
887
|
Write-PrizmWarn "AI session timed out after $timeoutSeconds seconds"
|
|
627
888
|
} elseif ($wasInfraError) {
|
|
@@ -653,13 +914,17 @@ function Invoke-PrizmPipeline {
|
|
|
653
914
|
$mergeSucceeded = $true
|
|
654
915
|
$itemListStatus = ''
|
|
655
916
|
if ($status -eq 'success') {
|
|
656
|
-
$updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs)
|
|
657
|
-
if ($updateResult -and $updateResult.PSObject.Properties['new_status']) {
|
|
658
|
-
$itemListStatus = [string]$updateResult.new_status
|
|
659
|
-
}
|
|
660
|
-
|
|
661
917
|
if (Test-PrizmGitDirty $paths.ProjectRoot) {
|
|
662
|
-
if ($
|
|
918
|
+
if ($semanticCompletion) {
|
|
919
|
+
$artifactDir = Join-Path $paths.PrizmkitDir "specs\$($semanticCompletion.Slug)"
|
|
920
|
+
if (Save-PrizmPostCompletionDirtyArtifacts $paths.ProjectRoot $artifactDir $CurrentItemId $sessionId) {
|
|
921
|
+
Write-PrizmWarn "Post-completion dirty changes preserved under $artifactDir"
|
|
922
|
+
Write-PrizmWarn "They were not included in the finalized feature commit."
|
|
923
|
+
} else {
|
|
924
|
+
Write-PrizmWarn "Could not safely preserve post-completion dirty changes; preserving dev branch for manual finalization"
|
|
925
|
+
$status = 'finalization_needed'
|
|
926
|
+
}
|
|
927
|
+
} elseif ($hadDirtyBaseline) {
|
|
663
928
|
Write-PrizmInfo "Auto-committing pipeline bookkeeping artifacts only."
|
|
664
929
|
Invoke-PrizmGitIncludeBookkeepingArtifacts $paths.ProjectRoot $stateDir $listPath
|
|
665
930
|
} else {
|
|
@@ -668,13 +933,20 @@ function Invoke-PrizmPipeline {
|
|
|
668
933
|
}
|
|
669
934
|
}
|
|
670
935
|
|
|
671
|
-
if ($
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
936
|
+
if ($status -eq 'success') {
|
|
937
|
+
$updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs)
|
|
938
|
+
if ($updateResult -and $updateResult.PSObject.Properties['new_status']) {
|
|
939
|
+
$itemListStatus = [string]$updateResult.new_status
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if ($isGitRepository -and $devBranchName) {
|
|
943
|
+
if (Merge-PrizmDevBranch $paths.ProjectRoot $devBranchName $originalBranch $autoPush) {
|
|
944
|
+
$devBranchName = ''
|
|
945
|
+
} else {
|
|
946
|
+
$mergeSucceeded = $false
|
|
947
|
+
$status = 'merge_conflict'
|
|
948
|
+
Write-PrizmWarn "Auto-merge failed - dev branch preserved for inspection"
|
|
949
|
+
}
|
|
678
950
|
}
|
|
679
951
|
}
|
|
680
952
|
} elseif ($isGitRepository -and $devBranchName) {
|
|
@@ -685,7 +957,20 @@ function Invoke-PrizmPipeline {
|
|
|
685
957
|
Restore-PrizmOriginalBranch $paths.ProjectRoot $originalBranch $devBranchName | Out-Null
|
|
686
958
|
}
|
|
687
959
|
|
|
960
|
+
if ($status -eq 'success' -and $mergeSucceeded -and $isGitRepository) {
|
|
961
|
+
Invoke-PrizmGitCommitPath $paths.ProjectRoot $listPath "chore($CurrentItemId): update $idName status" | Out-Null
|
|
962
|
+
}
|
|
963
|
+
|
|
688
964
|
if ($status -ne 'success') {
|
|
965
|
+
if ($Kind -eq 'feature') {
|
|
966
|
+
$failureSlug = if ($semanticCompletion) { [string]$semanticCompletion.Slug } else { Get-PrizmFeatureSlugFromList $listPath $CurrentItemId }
|
|
967
|
+
if ($failureSlug) {
|
|
968
|
+
$featureArtifactDir = Join-Path $paths.PrizmkitDir "specs\$failureSlug"
|
|
969
|
+
$failureLog = Join-Path $featureArtifactDir 'failure-log.md'
|
|
970
|
+
$checkpointPath = Join-Path $featureArtifactDir 'workflow-checkpoint.json'
|
|
971
|
+
Write-PrizmRuntimeFailureLog $failureLog $CurrentItemId $sessionId $status $exitCode $staleKillMarker $progressJson $checkpointPath $paths.ProjectRoot $baseCommit
|
|
972
|
+
}
|
|
973
|
+
}
|
|
689
974
|
$updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs)
|
|
690
975
|
if ($updateResult -and $updateResult.PSObject.Properties['new_status']) {
|
|
691
976
|
$itemListStatus = [string]$updateResult.new_status
|