diff --git a/scripts/main/Start-ClaudeOS.ps1 b/scripts/main/Start-ClaudeOS.ps1 index 6174a25d..771b1bd4 100644 --- a/scripts/main/Start-ClaudeOS.ps1 +++ b/scripts/main/Start-ClaudeOS.ps1 @@ -221,8 +221,25 @@ function Invoke-StepAgentInit { } } +function Get-StateJsonDashboard { + param([string]$Root) + $statePath = Join-Path $Root 'state.json' + if (-not (Test-Path $statePath)) { + return $null + } + try { + return Get-Content $statePath -Raw -Encoding UTF8 | ConvertFrom-Json + } + catch { + return $null + } +} + function Write-BootDashboard { - param([array]$Results) + param( + [array]$Results, + [string]$Root + ) Write-BootStep 9 'Dashboard' $ok = @($Results | Where-Object { $_.Status -eq 'OK' }).Count $skip = @($Results | Where-Object { $_.Status -eq 'SKIP' }).Count @@ -233,6 +250,82 @@ function Write-BootDashboard { Write-Host (' SKIP : {0}' -f $skip) -ForegroundColor Yellow Write-Host (' FAIL : {0}' -f $fail) -ForegroundColor Red Write-Host '' + + # state.json integration (Issue #71) + $state = if ($Root) { Get-StateJsonDashboard -Root $Root } else { $null } + if ($state) { + try { + $stateProps = $state.PSObject.Properties.Name + + Write-Host ' ── Goal & KPI ──────────────────────────────' -ForegroundColor Magenta + if (($stateProps -contains 'goal') -and $state.goal -and $state.goal.title) { + Write-Host (' Goal : {0}' -f $state.goal.title) -ForegroundColor White + } + if (($stateProps -contains 'kpi') -and $state.kpi) { + $kpi = $state.kpi + $kpiProps = $kpi.PSObject.Properties.Name + $successTarget = if ($kpiProps -contains 'success_rate_target') { '{0:P0}' -f $kpi.success_rate_target } else { 'n/a' } + $ciTarget = if ($kpiProps -contains 'ci_pass_rate') { '{0:P0}' -f $kpi.ci_pass_rate } else { 'n/a' } + $openP1 = if ($kpiProps -contains 'open_p1_issues') { $kpi.open_p1_issues } else { 'n/a' } + Write-Host (" KPI : success>={0} CI>={1} P1-issues={2}" -f $successTarget, $ciTarget, $openP1) -ForegroundColor Cyan + } + + if (($stateProps -contains 'execution') -and $state.execution) { + $exec = $state.execution + $execProps = $exec.PSObject.Properties.Name + $phase = if ($execProps -contains 'phase') { $exec.phase } else { 'unknown' } + $remaining = if ($execProps -contains 'remaining_minutes') { "$($exec.remaining_minutes)min" } else { 'n/a' } + $elapsed = if ($execProps -contains 'elapsed_minutes') { "$($exec.elapsed_minutes)min" } else { 'n/a' } + Write-Host '' + Write-Host ' ── Execution Status ────────────────────────' -ForegroundColor Magenta + Write-Host (" Phase : {0}" -f $phase) -ForegroundColor White + Write-Host (" Elapsed : {0} / Remaining: {1}" -f $elapsed, $remaining) -ForegroundColor White + } + + if (($stateProps -contains 'token') -and $state.token) { + $tok = $state.token + $tokProps = $tok.PSObject.Properties.Name + $used = if ($tokProps -contains 'used') { $tok.used } else { '?' } + $remaining = if ($tokProps -contains 'remaining') { $tok.remaining } else { '?' } + $total = if ($tokProps -contains 'total_budget') { $tok.total_budget } else { '?' } + Write-Host '' + Write-Host ' ── Token Budget ────────────────────────────' -ForegroundColor Magenta + Write-Host (" Used/Total: {0}/{1} Remaining: {2}%" -f $used, $total, $remaining) -ForegroundColor White + } + + if (($stateProps -contains 'current_work') -and $state.current_work) { + $work = $state.current_work + $workProps = $work.PSObject.Properties.Name + Write-Host '' + Write-Host ' ── Current Work ────────────────────────────' -ForegroundColor Magenta + if ($workProps -contains 'issue') { + $issueTitle = if ($workProps -contains 'title') { $work.title } else { '' } + Write-Host (" Issue #{0}: {1}" -f $work.issue, $issueTitle) -ForegroundColor White + } + if ($workProps -contains 'pr') { + $branch = if ($workProps -contains 'branch') { $work.branch } else { '' } + Write-Host (" PR #{0} [{1}]" -f $work.pr, $branch) -ForegroundColor Cyan + } + } + + if (($stateProps -contains 'loop_history') -and $state.loop_history -and @($state.loop_history).Count -gt 0) { + $lastLoop = @($state.loop_history)[-1] + $loopProps = $lastLoop.PSObject.Properties.Name + $loopNum = if ($loopProps -contains 'loop') { $lastLoop.loop } else { '?' } + $loopPhase = if ($loopProps -contains 'phase') { $lastLoop.phase } else { '?' } + $loopOutcome = if ($loopProps -contains 'outcome') { $lastLoop.outcome } else { '' } + Write-Host '' + Write-Host ' ── Last Loop ───────────────────────────────' -ForegroundColor Magenta + Write-Host (" Loop {0} [{1}]: {2}" -f $loopNum, $loopPhase, $loopOutcome) -ForegroundColor DarkGray + } + Write-Host '' + } + catch { + Write-Host (' [WARN] Dashboard state.json read error: {0}' -f $_.Exception.Message) -ForegroundColor Yellow + Write-Host '' + } + } + return @{ OK = $ok; SKIP = $skip; FAIL = $fail } } @@ -255,7 +348,7 @@ $results += Invoke-StepAgentInit -Root $ScriptRoot $results += Invoke-StepPlaceholder -Number 8 -Name 'Loop Engine Start' ` -Reason 'Loop orchestration handled by Claude Code /loop harness' -$summary = Write-BootDashboard -Results $results +$summary = Write-BootDashboard -Results $results -Root $ScriptRoot if ($summary.FAIL -gt 0) { Write-Host 'Boot sequence completed with errors.' -ForegroundColor Red diff --git a/tests/StartScripts.Tests.ps1 b/tests/StartScripts.Tests.ps1 index 84cf29fa..1c05dfa9 100644 --- a/tests/StartScripts.Tests.ps1 +++ b/tests/StartScripts.Tests.ps1 @@ -215,6 +215,40 @@ Describe 'Start-*.ps1 dry-run flows' { $output | Should -Match '\[Step 7\].*Agent Init' $output | Should -Not -Match '\[Step 7\].*Agent Init.*SKIP' } + + It 'Start-ClaudeOS.ps1 の Step 9 Dashboard が state.json なしでも正常終了すること (Issue #71)' { + $scriptPath = Join-Path $script:RepoRoot 'scripts\main\Start-ClaudeOS.ps1' + $output = & $script:PowerShellExe -NoProfile -File $scriptPath -NonInteractive 2>&1 | Out-String + $LASTEXITCODE | Should -Be 0 + $output | Should -Match '\[Step 9\].*Dashboard' + $output | Should -Match 'Boot Summary' + } + + It 'Start-ClaudeOS.ps1 の Step 9 Dashboard が state.json の Goal を表示すること (Issue #71)' { + $scriptPath = Join-Path $script:RepoRoot 'scripts\main\Start-ClaudeOS.ps1' + $statePath = Join-Path $script:RepoRoot 'state.json' + $stateBackup = if (Test-Path $statePath) { Get-Content $statePath -Raw -Encoding UTF8 } else { $null } + try { + @{ + goal = @{ title = 'Dashboard UI Test Goal' } + kpi = @{ success_rate_target = 0.9; ci_pass_rate = 1.0; open_p1_issues = 0 } + execution = @{ phase = 'Verify'; elapsed_minutes = 10; remaining_minutes = 290 } + token = @{ used = 5; total_budget = 100; remaining = 95 } + } | ConvertTo-Json -Depth 5 | Set-Content -Path $statePath -Encoding UTF8 + $output = & $script:PowerShellExe -NoProfile -File $scriptPath -NonInteractive 2>&1 | Out-String + $LASTEXITCODE | Should -Be 0 + $output | Should -Match 'Dashboard UI Test Goal' + $output | Should -Match 'Goal' + } + finally { + if ($null -ne $stateBackup) { + Set-Content -Path $statePath -Value $stateBackup -Encoding UTF8 + } + else { + Remove-Item $statePath -Force -ErrorAction SilentlyContinue + } + } + } } Describe 'Start-Menu helper flows' {