Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions .claude/scripts/bootstrap.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ if (-not (Test-Path $libDir) -or -not (Get-ChildItem $libDir -ErrorAction Silent
New-Item -ItemType Directory -Force -Path $libDir | Out-Null
$libUrls = @(
'Bootstrap-Common', 'Confirm-Admin', 'Install-Winget',
'Update-Path', 'Stage-OAuth', 'Fix-Shell', 'Drop-Plugin'
'Update-Path', 'Stage-OAuth', 'Fix-Shell', 'Drop-Plugin', 'Track-Issue'
)
foreach ($name in $libUrls) {
$url = "https://raw.githubusercontent.com/databayt/kun/main/.claude/scripts/lib/$name.ps1"
Expand All @@ -40,7 +40,7 @@ if (-not (Test-Path $libDir) -or -not (Get-ChildItem $libDir -ErrorAction Silent
}
}

foreach ($mod in 'Bootstrap-Common','Confirm-Admin','Install-Winget','Update-Path','Stage-OAuth','Fix-Shell','Drop-Plugin') {
foreach ($mod in 'Bootstrap-Common','Confirm-Admin','Install-Winget','Update-Path','Stage-OAuth','Fix-Shell','Drop-Plugin','Track-Issue') {
$p = Join-Path $libDir "$mod.ps1"
if (Test-Path $p) { . $p }
}
Expand Down Expand Up @@ -101,6 +101,19 @@ Write-Step 2 "Windows $($os.ToString()) · PowerShell $psVer" 'ok'
# ── [3] Log dir (already created above) ──────────────────────────
Write-Step 3 'Logs directory ready' 'ok' (Split-Path $logFile -Parent)

# ── -Track: create GitHub tracking issue ─────────────────────────
$trackingIssue = $null
if ($Track -and -not $DryRun) {
$trackingIssue = New-TrackingIssue
if (-not $trackingIssue) {
Write-Host ' -Track requested but gh not authed yet — continuing without tracking issue' -ForegroundColor Yellow
Write-Host ' (you can re-run with -Track after gh auth login)' -ForegroundColor Gray
} else {
# Steps 0-3 are already complete; flip their checkboxes now
foreach ($i in 0..3) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex $i }
}
}

# ── [4] winget bundle ────────────────────────────────────────────
Write-Step 4 'Installing CLI tools via winget' 'start'
$packages = @(
Expand Down Expand Up @@ -129,11 +142,13 @@ foreach ($pkg in $packages) {
Write-Step 4 " $($pkg.Name)" 'warn' "winget exit $($result.ExitCode)"
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 4 }

# ── [5] PATH refresh ─────────────────────────────────────────────
Write-Step 5 'Refreshing PATH from registry' 'start'
if (-not $DryRun) { Update-SessionPath }
Write-Step 5 'PATH refreshed' 'ok'
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 5 }

# ── [6] pnpm via npm ─────────────────────────────────────────────
Write-Step 6 'Installing pnpm globally' 'start'
Expand All @@ -149,6 +164,7 @@ if (Get-Command pnpm -ErrorAction SilentlyContinue) {
Write-Step 6 'pnpm install failed (try again later)' 'warn'
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 6 }

# ── [7] WebStorm ─────────────────────────────────────────────────
if (-not $SkipWebStorm) {
Expand All @@ -169,6 +185,7 @@ if (-not $SkipWebStorm) {
} else {
Write-Step 7 'WebStorm — skipped via flag' 'skip'
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 7 }

# ── [8] Plugin pre-drop ──────────────────────────────────────────
Write-Step 8 'Pre-dropping Claude Code [Beta] plugin' 'start'
Expand All @@ -184,6 +201,7 @@ if ($SkipWebStorm) {
'fail' { Write-Step 8 'Plugin install failed (install from Marketplace inside WebStorm)' 'warn' $result.Reason }
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 8 }

# ── [9] Kun config (install.ps1) ─────────────────────────────────
Write-Step 9 'Installing ~/.claude/ config via install.ps1' 'start'
Expand All @@ -198,12 +216,15 @@ if ($DryRun) {
Write-Step 9 'install.ps1 done' 'ok'
} catch {
Write-Step 9 'install.ps1 failed' 'fail' $_.Exception.Message
if ($trackingIssue) { Close-TrackingIssue -IssueNumber $trackingIssue -Outcome failure -LogPath $logFile }
exit 1
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 9 }

# ── [10] Auto-accept settings.json (informational — install.ps1 already wrote one) ──
Write-Step 10 'settings.json (handled by install.ps1)' 'ok'
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 10 }

# ── [11] $PROFILE c/cc block ─────────────────────────────────────
Write-Step 11 'Append c/cc functions to $PROFILE' 'start'
Expand All @@ -213,6 +234,7 @@ if ($DryRun) {
$changed = Repair-Profile
if ($changed) { Write-Step 11 '$PROFILE updated' 'ok' } else { Write-Step 11 '$PROFILE already has c/cc' 'skip' }
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 11 }

# ── [12] OAuth batch ─────────────────────────────────────────────
Write-Step 12 'OAuth batch (3 sign-ins)' 'start'
Expand All @@ -222,6 +244,16 @@ if ($DryRun) {
Invoke-OAuthBatch -Skip:$SkipOAuth | Out-Null
Write-Step 12 'OAuth batch complete' 'ok'
}
# Tracking issue may be created retroactively here if -Track was set but gh wasn't
# authed at step 3. Now that step 12 has finished, gh should be authed.
if ($Track -and -not $trackingIssue -and -not $DryRun) {
$trackingIssue = New-TrackingIssue
if ($trackingIssue) {
Write-Host " Tracking issue created retroactively after OAuth: filling 0-12" -ForegroundColor Cyan
foreach ($i in 0..12) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex $i }
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 12 }

# ── [13] Secrets ─────────────────────────────────────────────────
Write-Step 13 'Pulling .env via secrets.ps1' 'start'
Expand All @@ -238,6 +270,7 @@ if (-not (Test-Path $secretsScript)) {
Write-Step 13 'secrets.ps1 failed' 'warn' $_.Exception.Message
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 13 }

# ── [14] Repo clone ──────────────────────────────────────────────
Write-Step 14 'Cloning org repos via sync-repos.ps1' 'start'
Expand All @@ -254,6 +287,7 @@ if (-not (Test-Path $syncScript)) {
Write-Step 14 'sync-repos.ps1 had errors (some repos may need re-clone)' 'warn'
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 14 }

# ── [15] maintain -Install ───────────────────────────────────────
Write-Step 15 'Arming kun-maintain scheduled task' 'start'
Expand All @@ -270,6 +304,7 @@ if (-not (Test-Path $maintainScript)) {
Write-Step 15 'scheduled task creation failed' 'warn' $_.Exception.Message
}
}
if ($trackingIssue) { Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 15 }

# ── [16] Final verify via doctor ─────────────────────────────────
Write-Step 16 'Verifying via doctor.ps1' 'start'
Expand Down Expand Up @@ -314,4 +349,11 @@ Write-Host ' • Or open WebStorm in any cloned repo, then Ctrl+Esc'
Write-Host ' • Run `doctor` any time to re-check health'
Write-Host ''

# Close tracking issue with the final outcome
if ($trackingIssue) {
Update-TrackingIssue -IssueNumber $trackingIssue -StepIndex 16
$outcome = if ($doctorExit -eq 0 -or $doctorExit -eq 3) { 'success' } else { 'failure' }
Close-TrackingIssue -IssueNumber $trackingIssue -Outcome $outcome -LogPath $logFile
}

exit $doctorExit
15 changes: 8 additions & 7 deletions .claude/scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,14 @@ if ($Role -eq "engineer") {
Copy-Item "$KUN_DIR\.claude\mcp-ops.json" "$CLAUDE_DIR\mcp.json" -Force
}

# Add PowerShell alias
$profilePath = $PROFILE.CurrentUserAllHosts
if (-not (Test-Path $profilePath)) {
New-Item -ItemType File -Force -Path $profilePath | Out-Null
}
if (-not (Select-String -Path $profilePath -Pattern "claude" -Quiet -ErrorAction SilentlyContinue)) {
Add-Content -Path $profilePath -Value "`n# Claude Code (Kun Engine)"
# Add PowerShell c/cc functions to $PROFILE via Fix-Shell.ps1 (idempotent)
Write-Host "Installing c/cc functions to `$PROFILE..."
$fixShell = "$CLAUDE_DIR\scripts\lib\Fix-Shell.ps1"
if (Test-Path $fixShell) {
. $fixShell
try { Repair-Profile | Out-Null } catch { Write-Host " Profile repair skipped: $_" -ForegroundColor Yellow }
} else {
Write-Host " Fix-Shell.ps1 missing — run doctor -Fix after install to add c function" -ForegroundColor Yellow
}

# Clone codebase reference (engineers only)
Expand Down
94 changes: 94 additions & 0 deletions .claude/scripts/lib/Track-Issue.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Optional GitHub tracking issue for bootstrap progress. Requires pre-existing gh auth.
# When -Track is on, bootstrap creates an issue at step 3 and edits its body
# (checkbox list) after each successful step. On final success, closes the issue.
# On final failure, leaves it open with a log dump comment for forensics.

$script:TrackRepo = 'databayt/kun'
$script:TrackLabel = 'bootstrap-run'

$script:StepNames = @(
'[0] ExecutionPolicy → RemoteSigned'
'[1] Self-elevation (UAC)'
'[2] OS + PowerShell version check'
'[3] Logs directory + log file'
'[4] winget bundle (Git, Node, gh, pwsh, Claude CLI, Claude Desktop)'
'[5] PATH refresh'
'[6] pnpm via npm'
'[7] WebStorm install'
'[8] Claude Code [Beta] plugin pre-drop'
'[9] install.ps1 (kun config)'
'[10] settings.json'
'[11] $PROFILE c/cc block'
'[12] OAuth batch (gh + claude + JetBrains)'
'[13] secrets.ps1 (.env from Gist)'
'[14] sync-repos.ps1'
'[15] maintain -Install'
'[16] doctor verify'
)

function Test-GhAvailable {
$gh = Get-Command gh -ErrorAction SilentlyContinue
if (-not $gh) { return $false }
$status = gh auth status 2>&1 | Out-String
$status -match 'Logged in'
}

function New-TrackingIssue {
if (-not (Test-GhAvailable)) {
return $null # -Track requested but gh unauthed; caller logs and continues
}
$title = "bootstrap: $($env:COMPUTERNAME) — $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
$body = (@('Bootstrap run in progress. Checkboxes update live.', '') +
($script:StepNames | ForEach-Object { "- [ ] $_" })) -join "`n"

$num = gh issue create --repo $script:TrackRepo `
--title $title --body $body `
--label $script:TrackLabel 2>$null |
ForEach-Object { if ($_ -match '/issues/(\d+)') { $Matches[1] } }

if ($num) {
Write-Host " Tracking: https://github.com/$($script:TrackRepo)/issues/$num" -ForegroundColor Cyan
}
$num
}

function Update-TrackingIssue {
param(
[Parameter(Mandatory)][string]$IssueNumber,
[Parameter(Mandatory)][int]$StepIndex
)
if (-not $IssueNumber) { return }
try {
# Pull current body, flip the matching checkbox, write back
$current = gh issue view $IssueNumber --repo $script:TrackRepo --json body -q .body 2>$null
if (-not $current) { return }
$marker = $script:StepNames[$StepIndex]
if (-not $marker) { return }
$updated = $current -replace ([regex]::Escape("- [ ] $marker")), "- [x] $marker"
if ($updated -eq $current) { return } # no change
$updated | gh issue edit $IssueNumber --repo $script:TrackRepo --body-file - 2>$null | Out-Null
} catch {
# Tracking failures are silent — we don't want to abort bootstrap over a 5xx from GitHub
}
}

function Close-TrackingIssue {
param(
[Parameter(Mandatory)][string]$IssueNumber,
[Parameter(Mandatory)][ValidateSet('success','failure')]$Outcome,
[string]$LogPath = ''
)
if (-not $IssueNumber) { return }
try {
if ($Outcome -eq 'success') {
gh issue close $IssueNumber --repo $script:TrackRepo --comment '✅ Bootstrap completed successfully.' 2>$null | Out-Null
} else {
$msg = "❌ Bootstrap exited with errors."
if ($LogPath -and (Test-Path $LogPath)) {
$tail = Get-Content $LogPath -Tail 40 -ErrorAction SilentlyContinue | Out-String
$msg += "`n`n<details><summary>Last 40 log lines</summary>`n`n```$tail````n</details>"
}
gh issue comment $IssueNumber --repo $script:TrackRepo --body $msg 2>$null | Out-Null
}
} catch { }
}