From 2f8ec17efd2971c5444cd7d82e04387b45cf1574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=89=E8=97=A4=20=E5=81=A5=E5=A4=AA=E9=83=8E?= Date: Tue, 14 Apr 2026 12:02:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(dashboard):=20Issue=20#71=20=E2=80=94?= =?UTF-8?q?=20Step=209=20Dashboard=20=E3=81=AB=20state.json=20KPI/?= =?UTF-8?q?=E3=83=95=E3=82=A7=E3=83=BC=E3=82=BA/=E3=83=AB=E3=83=BC?= =?UTF-8?q?=E3=83=97=E5=B1=A5=E6=AD=B4=E3=82=92=E7=B5=B1=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Start-ClaudeOS.ps1: - Get-StateJsonDashboard ヘルパー関数を追加 (state.json → PSObject 変換) - Write-BootDashboard に -Root パラメータを追加 - state.json 存在時: Goal & KPI / Execution Status / Token Budget / Current Work / Last Loop を色分け表示 - state.json 不在時: 従来の Boot Summary のみ (後方互換を維持) - tests/StartScripts.Tests.ps1: - Step 9 Dashboard が state.json なしでも正常終了するテストを追加 - state.json の Goal title がダッシュボードに表示されるテストを追加 Closes #71 Co-Authored-By: Claude Sonnet 4.6 --- scripts/main/Start-ClaudeOS.ps1 | 79 ++++++++++++++++++++++++++++++++- tests/StartScripts.Tests.ps1 | 34 ++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/scripts/main/Start-ClaudeOS.ps1 b/scripts/main/Start-ClaudeOS.ps1 index 6174a25d..2300c85a 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,64 @@ 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) { + Write-Host ' ── Goal & KPI ──────────────────────────────' -ForegroundColor Magenta + if ($state.goal -and $state.goal.title) { + Write-Host (' Goal : {0}' -f $state.goal.title) -ForegroundColor White + } + if ($state.kpi) { + $kpi = $state.kpi + $successTarget = if ($null -ne $kpi.success_rate_target) { '{0:P0}' -f $kpi.success_rate_target } else { 'n/a' } + $ciTarget = if ($null -ne $kpi.ci_pass_rate) { '{0:P0}' -f $kpi.ci_pass_rate } else { 'n/a' } + $openP1 = if ($null -ne $kpi.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 ($state.execution) { + $exec = $state.execution + $phase = if ($exec.phase) { $exec.phase } else { 'unknown' } + $remaining = if ($null -ne $exec.remaining_minutes) { "$($exec.remaining_minutes)min" } else { 'n/a' } + $elapsed = if ($null -ne $exec.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 ($state.token) { + $tok = $state.token + $used = if ($null -ne $tok.used) { $tok.used } else { '?' } + $remaining = if ($null -ne $tok.remaining) { $tok.remaining } else { '?' } + $total = if ($null -ne $tok.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 ($state.current_work) { + $work = $state.current_work + Write-Host '' + Write-Host ' ── Current Work ────────────────────────────' -ForegroundColor Magenta + if ($work.issue) { + Write-Host (" Issue #{0}: {1}" -f $work.issue, $work.title) -ForegroundColor White + } + if ($work.pr) { + Write-Host (" PR #{0} [{1}]" -f $work.pr, $work.branch) -ForegroundColor Cyan + } + } + + if ($state.loop_history -and @($state.loop_history).Count -gt 0) { + $lastLoop = @($state.loop_history)[-1] + Write-Host '' + Write-Host ' ── Last Loop ───────────────────────────────' -ForegroundColor Magenta + Write-Host (" Loop {0} [{1}]: {2}" -f $lastLoop.loop, $lastLoop.phase, $lastLoop.outcome) -ForegroundColor DarkGray + } + Write-Host '' + } + return @{ OK = $ok; SKIP = $skip; FAIL = $fail } } @@ -255,7 +330,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..be5802fa 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.*&.*KPI|Goal & KPI|Goal.*KPI' + } + 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' { From 8be433fa200f710b34ffc97de5ca824507571941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=89=E8=97=A4=20=E5=81=A5=E5=A4=AA=E9=83=8E?= Date: Tue, 14 Apr 2026 12:07:23 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(dashboard):=20PSObject=20=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E5=AD=98=E5=9C=A8=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E3=81=A7=20StrictMode=20=E4=BE=8B?= =?UTF-8?q?=E5=A4=96=E3=82=92=E9=98=B2=E6=AD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Write-BootDashboard で各プロパティを .PSObject.Properties.Name で検査してから アクセスするよう変更 (Set-StrictMode -Version Latest 対策) - 全体を try/catch でラップしてダッシュボード描画エラーが boot 終了コードに 影響しないよう保護 - テストの regex を簡潔化: 'Goal.*KPI' → 'Goal' Co-Authored-By: Claude Sonnet 4.6 --- scripts/main/Start-ClaudeOS.ps1 | 106 +++++++++++++++++++------------- tests/StartScripts.Tests.ps1 | 2 +- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/scripts/main/Start-ClaudeOS.ps1 b/scripts/main/Start-ClaudeOS.ps1 index 2300c85a..771b1bd4 100644 --- a/scripts/main/Start-ClaudeOS.ps1 +++ b/scripts/main/Start-ClaudeOS.ps1 @@ -254,58 +254,76 @@ function Write-BootDashboard { # state.json integration (Issue #71) $state = if ($Root) { Get-StateJsonDashboard -Root $Root } else { $null } if ($state) { - Write-Host ' ── Goal & KPI ──────────────────────────────' -ForegroundColor Magenta - if ($state.goal -and $state.goal.title) { - Write-Host (' Goal : {0}' -f $state.goal.title) -ForegroundColor White - } - if ($state.kpi) { - $kpi = $state.kpi - $successTarget = if ($null -ne $kpi.success_rate_target) { '{0:P0}' -f $kpi.success_rate_target } else { 'n/a' } - $ciTarget = if ($null -ne $kpi.ci_pass_rate) { '{0:P0}' -f $kpi.ci_pass_rate } else { 'n/a' } - $openP1 = if ($null -ne $kpi.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 - } + try { + $stateProps = $state.PSObject.Properties.Name - if ($state.execution) { - $exec = $state.execution - $phase = if ($exec.phase) { $exec.phase } else { 'unknown' } - $remaining = if ($null -ne $exec.remaining_minutes) { "$($exec.remaining_minutes)min" } else { 'n/a' } - $elapsed = if ($null -ne $exec.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 - } + 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 ($state.token) { - $tok = $state.token - $used = if ($null -ne $tok.used) { $tok.used } else { '?' } - $remaining = if ($null -ne $tok.remaining) { $tok.remaining } else { '?' } - $total = if ($null -ne $tok.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 '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 ($state.current_work) { - $work = $state.current_work - Write-Host '' - Write-Host ' ── Current Work ────────────────────────────' -ForegroundColor Magenta - if ($work.issue) { - Write-Host (" Issue #{0}: {1}" -f $work.issue, $work.title) -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 ($work.pr) { - Write-Host (" PR #{0} [{1}]" -f $work.pr, $work.branch) -ForegroundColor Cyan + + 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 ($state.loop_history -and @($state.loop_history).Count -gt 0) { - $lastLoop = @($state.loop_history)[-1] + 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 '' - Write-Host ' ── Last Loop ───────────────────────────────' -ForegroundColor Magenta - Write-Host (" Loop {0} [{1}]: {2}" -f $lastLoop.loop, $lastLoop.phase, $lastLoop.outcome) -ForegroundColor DarkGray } - Write-Host '' } return @{ OK = $ok; SKIP = $skip; FAIL = $fail } diff --git a/tests/StartScripts.Tests.ps1 b/tests/StartScripts.Tests.ps1 index be5802fa..1c05dfa9 100644 --- a/tests/StartScripts.Tests.ps1 +++ b/tests/StartScripts.Tests.ps1 @@ -238,7 +238,7 @@ Describe 'Start-*.ps1 dry-run flows' { $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.*&.*KPI|Goal & KPI|Goal.*KPI' + $output | Should -Match 'Goal' } finally { if ($null -ne $stateBackup) {