prizmkit 1.1.62 → 1.1.64

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.
@@ -0,0 +1,222 @@
1
+ Set-StrictMode -Version 2.0
2
+ $ErrorActionPreference = 'Stop'
3
+
4
+ function Get-PrizmCurrentBranch {
5
+ param([string]$ProjectRoot)
6
+ $branch = & git -C $ProjectRoot rev-parse --abbrev-ref HEAD 2>$null
7
+ if ($LASTEXITCODE -eq 0 -and $branch) { return [string]($branch | Select-Object -First 1) }
8
+ return ''
9
+ }
10
+
11
+ function Test-PrizmTrackedDirty {
12
+ param([string]$ProjectRoot)
13
+ $trackedDirty = & git -C $ProjectRoot diff --name-only 2>$null
14
+ $stagedDirty = & git -C $ProjectRoot diff --cached --name-only 2>$null
15
+ return -not [string]::IsNullOrWhiteSpace((@($trackedDirty) + @($stagedDirty) -join "`n"))
16
+ }
17
+
18
+ function New-PrizmDevBranch {
19
+ param(
20
+ [string]$ProjectRoot,
21
+ [string]$DevBranch,
22
+ [string]$SourceBranch
23
+ )
24
+
25
+ if (-not $DevBranch -or -not $SourceBranch) { return $false }
26
+
27
+ & git -C $ProjectRoot rev-parse --verify $DevBranch *> $null
28
+ if ($LASTEXITCODE -eq 0) {
29
+ Write-PrizmInfo "Branch already exists: $DevBranch - checking out"
30
+ & git -C $ProjectRoot checkout $DevBranch *> $null
31
+ if ($LASTEXITCODE -ne 0) {
32
+ Write-PrizmError "Failed to checkout existing branch: $DevBranch"
33
+ return $false
34
+ }
35
+ return $true
36
+ }
37
+
38
+ & git -C $ProjectRoot checkout -b $DevBranch $SourceBranch *> $null
39
+ if ($LASTEXITCODE -ne 0) {
40
+ Write-PrizmWarn "Failed to create branch: $DevBranch from $SourceBranch"
41
+ return $false
42
+ }
43
+
44
+ Write-PrizmInfo "Created and checked out branch: $DevBranch (from $SourceBranch)"
45
+ return $true
46
+ }
47
+
48
+ function Switch-PrizmOriginalBranch {
49
+ param(
50
+ [string]$ProjectRoot,
51
+ [string]$OriginalBranch
52
+ )
53
+
54
+ if (-not $OriginalBranch) { return $false }
55
+ $currentBranch = Get-PrizmCurrentBranch $ProjectRoot
56
+ if ($currentBranch -eq $OriginalBranch) { return $true }
57
+
58
+ & git -C $ProjectRoot checkout $OriginalBranch *> $null
59
+ if ($LASTEXITCODE -ne 0) {
60
+ Write-PrizmError "Failed to checkout original branch: $OriginalBranch"
61
+ return $false
62
+ }
63
+
64
+ Write-PrizmInfo "Returned to branch: $OriginalBranch"
65
+ return $true
66
+ }
67
+
68
+ function Save-PrizmBranchWip {
69
+ param(
70
+ [string]$ProjectRoot,
71
+ [string]$DevBranch
72
+ )
73
+
74
+ if (-not $DevBranch) { return $true }
75
+
76
+ $currentBranch = Get-PrizmCurrentBranch $ProjectRoot
77
+ if ($currentBranch -ne $DevBranch) { return $true }
78
+
79
+ $changes = & git -C $ProjectRoot status --porcelain 2>$null
80
+ if ([string]::IsNullOrWhiteSpace(($changes -join "`n"))) { return $true }
81
+
82
+ Write-PrizmWarn "Saving uncommitted work-in-progress on branch: $DevBranch"
83
+ & git -C $ProjectRoot add -A *> $null
84
+ if ($LASTEXITCODE -ne 0) {
85
+ Write-PrizmWarn "git add -A failed - uncommitted work may remain on $DevBranch"
86
+ return $true
87
+ }
88
+
89
+ & git -C $ProjectRoot commit --no-verify `
90
+ -m "wip($DevBranch): interrupted - uncommitted work saved" `
91
+ -m "Pipeline was interrupted or failed. This commit preserves work-in-progress." `
92
+ -m "To resume: git checkout $DevBranch" *> $null
93
+
94
+ if ($LASTEXITCODE -eq 0) {
95
+ Write-PrizmInfo "Saved uncommitted work on branch $DevBranch"
96
+ } else {
97
+ Write-PrizmWarn "git commit failed - uncommitted work may remain on $DevBranch"
98
+ }
99
+
100
+ return $true
101
+ }
102
+
103
+ function Merge-PrizmDevBranch {
104
+ param(
105
+ [string]$ProjectRoot,
106
+ [string]$DevBranch,
107
+ [string]$OriginalBranch,
108
+ [bool]$AutoPush = $false
109
+ )
110
+
111
+ if (-not $DevBranch -or -not $OriginalBranch) { return $false }
112
+
113
+ $hadStash = $false
114
+ if (Test-PrizmTrackedDirty $ProjectRoot) {
115
+ & git -C $ProjectRoot stash push -m 'pipeline-merge-stash' *> $null
116
+ if ($LASTEXITCODE -eq 0) {
117
+ $hadStash = $true
118
+ } else {
119
+ Write-PrizmWarn "git stash failed - tracked changes may block branch merge"
120
+ }
121
+ }
122
+
123
+ Write-PrizmInfo "Merging $DevBranch into $OriginalBranch..."
124
+ & git -C $ProjectRoot rebase $OriginalBranch $DevBranch
125
+ if ($LASTEXITCODE -ne 0) {
126
+ Write-PrizmError "Rebase of $DevBranch onto $OriginalBranch failed - resolve manually"
127
+ & git -C $ProjectRoot rebase --abort *> $null
128
+ if ($hadStash) {
129
+ & git -C $ProjectRoot stash pop *> $null
130
+ if ($LASTEXITCODE -ne 0) { Write-PrizmWarn "git stash pop failed after rebase abort" }
131
+ }
132
+ return $false
133
+ }
134
+
135
+ & git -C $ProjectRoot checkout $OriginalBranch *> $null
136
+ if ($LASTEXITCODE -ne 0) {
137
+ Write-PrizmError "Failed to checkout $OriginalBranch for merge"
138
+ if ($hadStash) {
139
+ & git -C $ProjectRoot stash pop *> $null
140
+ if ($LASTEXITCODE -ne 0) { Write-PrizmWarn "git stash pop failed after checkout failure" }
141
+ }
142
+ return $false
143
+ }
144
+
145
+ & git -C $ProjectRoot merge --ff-only $DevBranch
146
+ if ($LASTEXITCODE -ne 0) {
147
+ Write-PrizmError "Merge failed after rebase - resolve manually"
148
+ if ($hadStash) {
149
+ & git -C $ProjectRoot stash pop *> $null
150
+ if ($LASTEXITCODE -ne 0) { Write-PrizmWarn "git stash pop failed after merge failure" }
151
+ }
152
+ return $false
153
+ }
154
+
155
+ Write-PrizmSuccess "Merged $DevBranch into $OriginalBranch"
156
+
157
+ if ($AutoPush) {
158
+ Write-PrizmInfo "Pushing $OriginalBranch to remote..."
159
+ & git -C $ProjectRoot push *> $null
160
+ if ($LASTEXITCODE -eq 0) {
161
+ Write-PrizmSuccess "Pushed $OriginalBranch to remote"
162
+ } else {
163
+ Write-PrizmWarn "Push failed - run git push manually"
164
+ }
165
+ }
166
+
167
+ & git -C $ProjectRoot branch -d $DevBranch *> $null
168
+ if ($LASTEXITCODE -eq 0) { Write-PrizmInfo "Deleted merged branch: $DevBranch" }
169
+
170
+ if ($hadStash) {
171
+ & git -C $ProjectRoot stash pop *> $null
172
+ if ($LASTEXITCODE -ne 0) { Write-PrizmWarn "git stash pop failed after merge" }
173
+ }
174
+
175
+ return $true
176
+ }
177
+
178
+ function Restore-PrizmOriginalBranch {
179
+ param(
180
+ [string]$ProjectRoot,
181
+ [string]$OriginalBranch,
182
+ [string]$DevBranch = ''
183
+ )
184
+
185
+ if (-not $OriginalBranch) { return $true }
186
+
187
+ & git -C $ProjectRoot rebase --show-current-patch *> $null
188
+ if ($LASTEXITCODE -eq 0) {
189
+ Write-PrizmWarn "Aborting in-progress rebase..."
190
+ & git -C $ProjectRoot rebase --abort *> $null
191
+ }
192
+
193
+ $currentBranch = Get-PrizmCurrentBranch $ProjectRoot
194
+ if ($currentBranch -eq $OriginalBranch) { return $true }
195
+
196
+ $wipBranch = if ($DevBranch) { $DevBranch } else { $currentBranch }
197
+ if ($wipBranch -and $wipBranch -ne $OriginalBranch) {
198
+ Save-PrizmBranchWip $ProjectRoot $wipBranch | Out-Null
199
+ }
200
+
201
+ Write-PrizmInfo "Ensuring return to original branch: $OriginalBranch (currently on: $currentBranch)"
202
+
203
+ $hadStash = $false
204
+ if (Test-PrizmTrackedDirty $ProjectRoot) {
205
+ & git -C $ProjectRoot stash push -m 'pipeline-ensure-return-stash' *> $null
206
+ if ($LASTEXITCODE -eq 0) { $hadStash = $true }
207
+ }
208
+
209
+ & git -C $ProjectRoot checkout $OriginalBranch *> $null
210
+ if ($LASTEXITCODE -eq 0) {
211
+ Write-PrizmInfo "Returned to branch: $OriginalBranch"
212
+ } else {
213
+ Write-PrizmError "Failed to checkout $OriginalBranch - manual recovery needed"
214
+ }
215
+
216
+ if ($hadStash) {
217
+ & git -C $ProjectRoot stash pop *> $null
218
+ if ($LASTEXITCODE -ne 0) { Write-PrizmWarn "git stash pop failed during branch return" }
219
+ }
220
+
221
+ return $true
222
+ }
@@ -298,6 +298,90 @@ function Get-PrizmSessionPlatform {
298
298
  return Get-PrizmPlatformFromCli $CliCommand
299
299
  }
300
300
 
301
+ function Get-PrizmCodexSubagentTimeoutSeconds {
302
+ $configuredTimeout = 0
303
+ if ([int]::TryParse($env:CODEX_SUBAGENT_TIMEOUT_SECONDS, [ref]$configuredTimeout) -and $configuredTimeout -gt 0) {
304
+ return $configuredTimeout
305
+ }
306
+
307
+ $outerThreshold = 0
308
+ $outerThresholdText = if ($env:STALE_KILL_THRESHOLD) { $env:STALE_KILL_THRESHOLD } else { $env:SESSION_TIMEOUT }
309
+ if ([int]::TryParse($outerThresholdText, [ref]$outerThreshold) -and $outerThreshold -gt 120) {
310
+ return ($outerThreshold - 60)
311
+ }
312
+
313
+ return 840
314
+ }
315
+
316
+ function Test-PrizmCodexJsonSupport {
317
+ param([string]$CliExecutable)
318
+ try {
319
+ $helpOutput = & $CliExecutable exec --help 2>&1
320
+ return (($helpOutput -join "`n") -match '--json')
321
+ } catch {
322
+ return $false
323
+ }
324
+ }
325
+
326
+ function Test-PrizmStreamJsonSupport {
327
+ param([string]$CliCommand)
328
+ $sessionPlatform = Get-PrizmSessionPlatform $CliCommand
329
+ $parsedCli = Split-PrizmCommandLine $CliCommand
330
+
331
+ if ($sessionPlatform -eq 'codebuddy') { return $true }
332
+ if ($sessionPlatform -eq 'codex') { return (Test-PrizmCodexJsonSupport $parsedCli.Command) }
333
+
334
+ try {
335
+ $helpOutput = & $parsedCli.Command --help 2>&1
336
+ return (($helpOutput -join "`n") -match 'stream-json')
337
+ } catch {
338
+ return $false
339
+ }
340
+ }
341
+
342
+ function Start-PrizmProgressParser {
343
+ param(
344
+ [string[]]$PythonCommand,
345
+ [string]$ScriptsDir,
346
+ [string]$SessionLog,
347
+ [string]$ProgressFile,
348
+ [string]$CliCommand
349
+ )
350
+
351
+ if (-not (Test-PrizmStreamJsonSupport $CliCommand)) { return $null }
352
+
353
+ $parserScript = Join-Path $ScriptsDir 'parse-stream-progress.py'
354
+ if (-not (Test-Path $parserScript)) { return $null }
355
+
356
+ $cmd = $PythonCommand[0]
357
+ $prefix = @()
358
+ if ($PythonCommand.Count -gt 1) { $prefix = $PythonCommand[1..($PythonCommand.Count - 1)] }
359
+
360
+ $progressDir = Split-Path $ProgressFile -Parent
361
+ if ($progressDir) { New-Item -ItemType Directory -Force -Path $progressDir | Out-Null }
362
+
363
+ $psi = [System.Diagnostics.ProcessStartInfo]::new()
364
+ $psi.UseShellExecute = $false
365
+ $psi.CreateNoWindow = $true
366
+ $psi.FileName = $cmd
367
+ $psi.Arguments = Join-PrizmProcessArguments ($prefix + @($parserScript, '--session-log', $SessionLog, '--progress-file', $ProgressFile))
368
+ return [System.Diagnostics.Process]::Start($psi)
369
+ }
370
+
371
+ function Stop-PrizmProgressParser {
372
+ param($Process)
373
+ if ($Process -eq $null) { return }
374
+ try {
375
+ if (-not $Process.HasExited) {
376
+ try {
377
+ $Process.Kill($true)
378
+ } catch {
379
+ Stop-PrizmProcessTreeById -ProcessId ([int]$Process.Id)
380
+ }
381
+ }
382
+ } catch {}
383
+ }
384
+
301
385
  function Stop-PrizmProcessTreeById {
302
386
  param([int]$ProcessId)
303
387
  if ($ProcessId -le 0) { return }
@@ -328,6 +412,28 @@ function Stop-PrizmProcessTreeById {
328
412
  }
329
413
  }
330
414
 
415
+ function Stop-PrizmSessionProcess {
416
+ param([string]$PidPath)
417
+ if (-not (Test-Path $PidPath)) { return }
418
+ $rawPid = Get-Content $PidPath -ErrorAction SilentlyContinue | Select-Object -First 1
419
+ $aiPid = 0
420
+ if ([int]::TryParse($rawPid, [ref]$aiPid)) {
421
+ Stop-PrizmProcessTreeById -ProcessId $aiPid
422
+ }
423
+ }
424
+
425
+ function Write-PrizmStaleKillMarker {
426
+ param([string]$MarkerPath, [int]$StaleSeconds, [int]$Threshold)
427
+ $markerDir = Split-Path $MarkerPath -Parent
428
+ if ($markerDir) { New-Item -ItemType Directory -Force -Path $markerDir | Out-Null }
429
+ [ordered]@{
430
+ killed_at = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
431
+ reason = 'stale_session'
432
+ stale_seconds = $StaleSeconds
433
+ threshold = $Threshold
434
+ } | ConvertTo-Json -Compress | Set-Content -Path $MarkerPath -Encoding UTF8
435
+ }
436
+
331
437
  function Invoke-PrizmAiSession {
332
438
  param(
333
439
  [string]$CliCommand,
@@ -352,16 +458,27 @@ function Invoke-PrizmAiSession {
352
458
  $parsedCli = Split-PrizmCommandLine $CliCommand
353
459
  $cliExecutable = $parsedCli.Command
354
460
  $sessionPlatform = Get-PrizmSessionPlatform $CliCommand
461
+ $useStreamJson = Test-PrizmStreamJsonSupport $CliCommand
355
462
 
356
463
  if ($sessionPlatform -eq 'claude') {
357
464
  $cliArgs += @('-p', '--dangerously-skip-permissions')
465
+ if ($env:VERBOSE -in @('1','true','yes','on') -or $useStreamJson) { $cliArgs += '--verbose' }
466
+ if ($useStreamJson) { $cliArgs += @('--output-format', 'stream-json') }
358
467
  if ($Model) { $cliArgs += @('--model', $Model) }
359
468
  } elseif ($sessionPlatform -eq 'codex') {
360
- $cliArgs += @('--ask-for-approval', 'never', '--sandbox', 'danger-full-access', 'exec', '--cd', $ProjectRoot, '--skip-git-repo-check')
469
+ $cliArgs += @('--ask-for-approval', 'never', '--sandbox', 'danger-full-access')
470
+ $codexSubagentTimeout = Get-PrizmCodexSubagentTimeoutSeconds
471
+ if ($codexSubagentTimeout -gt 0) {
472
+ $cliArgs += @('--config', "agents.job_max_runtime_seconds=$codexSubagentTimeout")
473
+ }
474
+ $cliArgs += @('exec', '--cd', $ProjectRoot, '--skip-git-repo-check')
475
+ if ($useStreamJson) { $cliArgs += '--json' }
361
476
  if ($Model) { $cliArgs += @('--model', $Model) }
362
477
  $cliArgs += '-'
363
478
  } else {
364
479
  $cliArgs += @('--print', '-y')
480
+ if ($env:VERBOSE -in @('1','true','yes','on')) { $cliArgs += '--verbose' }
481
+ if ($useStreamJson) { $cliArgs += @('--output-format', 'stream-json') }
365
482
  if ($Model) { $cliArgs += @('--model', $Model) }
366
483
  }
367
484
  $generatedArgs = (($cliArgs | ForEach-Object { ConvertTo-PrizmProcessArgument $_ }) -join ' ')
@@ -390,20 +507,53 @@ function Invoke-PrizmAiSession {
390
507
 
391
508
  $process = [System.Diagnostics.Process]::Start($psi)
392
509
  $script:PrizmAiProcess = $process
510
+ $logWriter = [System.IO.StreamWriter]::new($LogPath, $false, [System.Text.UTF8Encoding]::new($false))
511
+ $logLock = [object]::new()
512
+ $outputHandler = [System.Diagnostics.DataReceivedEventHandler]{
513
+ param($sender, $eventArgs)
514
+ if ($eventArgs.Data -ne $null) {
515
+ [System.Threading.Monitor]::Enter($logLock)
516
+ try {
517
+ $logWriter.WriteLine($eventArgs.Data)
518
+ $logWriter.Flush()
519
+ } finally {
520
+ [System.Threading.Monitor]::Exit($logLock)
521
+ }
522
+ }
523
+ }
524
+ $errorHandler = [System.Diagnostics.DataReceivedEventHandler]{
525
+ param($sender, $eventArgs)
526
+ if ($eventArgs.Data -ne $null) {
527
+ [System.Threading.Monitor]::Enter($logLock)
528
+ try {
529
+ $logWriter.WriteLine($eventArgs.Data)
530
+ $logWriter.Flush()
531
+ } finally {
532
+ [System.Threading.Monitor]::Exit($logLock)
533
+ }
534
+ }
535
+ }
536
+ $process.add_OutputDataReceived($outputHandler)
537
+ $process.add_ErrorDataReceived($errorHandler)
393
538
  if ($PidPath) {
394
539
  $pidDir = Split-Path $PidPath -Parent
395
540
  if ($pidDir) { New-Item -ItemType Directory -Force -Path $pidDir | Out-Null }
396
541
  Set-Content -Path $PidPath -Value ([string]$process.Id) -Encoding UTF8
397
542
  }
398
- $process.StandardInput.Write($prompt)
399
- $process.StandardInput.Close()
400
- $stdoutTask = $process.StandardOutput.ReadToEndAsync()
401
- $stderrTask = $process.StandardError.ReadToEndAsync()
402
- $process.WaitForExit()
403
- $stdout = $stdoutTask.Result
404
- $stderr = $stderrTask.Result
405
- Set-Content -Path $LogPath -Value ($stdout + $stderr) -Encoding UTF8
406
- return $process.ExitCode
543
+ try {
544
+ $process.BeginOutputReadLine()
545
+ $process.BeginErrorReadLine()
546
+ $process.StandardInput.Write($prompt)
547
+ $process.StandardInput.Close()
548
+ $process.WaitForExit()
549
+ $process.WaitForExit()
550
+ return $process.ExitCode
551
+ } finally {
552
+ $process.remove_OutputDataReceived($outputHandler)
553
+ $process.remove_ErrorDataReceived($errorHandler)
554
+ $logWriter.Close()
555
+ $logWriter.Dispose()
556
+ }
407
557
  }
408
558
 
409
559
  function New-PrizmSessionId {