@darkrei08/setup-ai 3.0.5 → 3.2.0

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/setup-ai.ps1 CHANGED
@@ -1,12 +1,12 @@
1
- #Requires -Version 7.0
1
+ #Requires -Version 7.3
2
2
  <#
3
3
  ==============================================================================
4
4
  AI Dev Suite — Engineering Excellence Edition (Windows)
5
- Version: 3.0.5
5
+ Version: 3.2.0
6
6
 
7
7
  Windows-native installer, sibling of setup-ai.sh. Uses each tool's official
8
8
  Windows method: winget for language runtimes, the vendor install.ps1 scripts
9
- for the AI CLIs, `go install` for gentle-ai, npm for opencode, and
9
+ for the AI CLIs, and npm for opencode, and
10
10
  `npx skills` / `pi install` for skills and pi packages.
11
11
 
12
12
  The Node launcher bin/setup-ai.mjs dispatches here on win32 and can pass a
@@ -30,19 +30,29 @@ param(
30
30
  [switch]$Help
31
31
  )
32
32
 
33
+ $OnlySpecified = $PSBoundParameters.ContainsKey('Only')
34
+
33
35
  $ErrorActionPreference = 'Stop'
34
36
  Set-StrictMode -Version Latest
35
37
 
38
+ # PowerShell 7.3+ turns native nonzero exits into terminating errors, so a
39
+ # failed command cannot be hidden by a later successful command in the same step.
40
+ $PSNativeCommandUseErrorActionPreference = $true
41
+
36
42
  # Render child-process UTF-8 output (npx skills box-drawing, banners) correctly
37
43
  # instead of mojibake on the default Windows console codepage.
38
44
  try {
39
45
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
40
46
  $OutputEncoding = [System.Text.Encoding]::UTF8
41
47
  if (Get-Command chcp -ErrorAction SilentlyContinue) { chcp 65001 | Out-Null }
42
- } catch { }
48
+ } catch {
49
+ # Non-fatal: child output may show mojibake, but the run can proceed.
50
+ Write-Warning "Could not set UTF-8 console encoding: $($_.Exception.Message)"
51
+ }
43
52
 
44
- $ScriptVersion = "3.0.5"
45
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
53
+ $ScriptVersion = "3.2.0"
54
+ $ScriptPath = $PSCommandPath
55
+ $ScriptDir = Split-Path -Parent $ScriptPath
46
56
  $LogDir = Join-Path $ScriptDir "logs"
47
57
  New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
48
58
 
@@ -51,11 +61,40 @@ $HumanLog = Join-Path $LogDir "setup_$RunId.log"
51
61
  $JsonlLog = Join-Path $LogDir "setup_$RunId.jsonl"
52
62
  $ReportFile = Join-Path $LogDir "engineering-report_$RunId.md"
53
63
 
54
- $EE_Slug = "micio86dev/Engineering-Excellence"
64
+ $EE_Slug = "darkrei08/Engineering-Excellence"
55
65
  $EE_Skill = "engineering-excellence"
56
- $PiSkillDir = Join-Path $HOME ".pi\agent\skills\$EE_Skill"
57
66
  $PiExtDir = Join-Path $HOME ".pi\agent\extensions"
58
67
 
68
+ # Upstream agent-skill stack mirrored from vekexasia/dotenv setup_env.sh so the
69
+ # same skills land on every OS (dotenv itself is Linux-only). Installed via
70
+ # `npx skills add`.
71
+ $UpstreamSkillSources = @(
72
+ @{ Source = 'herdrdev/herdr'; Skills = @('herdr') }
73
+ @{ Source = 'mattpocock/skills'; Skills = @('triage','grill-me','grilling','wayfinder','domain-modeling','prototype','research') }
74
+ @{ Source = 'https://github.com/pedronauck/skills'; Skills = @('typescript-advanced') }
75
+ @{ Source = 'humanlayer/skills'; Skills = @('show-me') }
76
+ )
77
+ $UpstreamSkillNames = @('herdr','triage','grill-me','grilling','wayfinder','domain-modeling','prototype','research','typescript-advanced','show-me')
78
+ $SkillAgentNames = @('pi','claude-code','gemini-cli','cursor','antigravity','codex','opencode')
79
+ $SkillAgentConfigDirs = @{
80
+ pi = Join-Path $HOME ".pi"
81
+ 'claude-code' = Join-Path $HOME ".claude"
82
+ 'gemini-cli' = Join-Path $HOME ".gemini"
83
+ cursor = Join-Path $HOME ".cursor"
84
+ antigravity = Join-Path $HOME ".antigravity"
85
+ codex = Join-Path $HOME ".codex"
86
+ opencode = Join-Path $HOME ".config\opencode"
87
+ }
88
+ $SkillAgentRoots = @{
89
+ pi = Join-Path $HOME ".pi\agent\skills"
90
+ 'claude-code' = Join-Path $HOME ".claude\skills"
91
+ 'gemini-cli' = Join-Path $HOME ".gemini\skills"
92
+ cursor = Join-Path $HOME ".cursor\skills"
93
+ antigravity = Join-Path $HOME ".antigravity\skills"
94
+ codex = Join-Path $HOME ".codex\skills"
95
+ opencode = Join-Path $HOME ".config\opencode\skills"
96
+ }
97
+
59
98
  # ------------------------------------------------------------------------------
60
99
  # Logging (human + JSONL)
61
100
  # ------------------------------------------------------------------------------
@@ -99,7 +138,12 @@ function Invoke-Step {
99
138
  param([string]$Phase, [scriptblock]$Action, [switch]$Optional)
100
139
  Write-Log INFO $Phase "step_start" "Running step"
101
140
  try {
141
+ $global:LASTEXITCODE = 0
102
142
  & $Action 2>&1 | Tee-Object -FilePath $HumanLog -Append | Out-Host
143
+ $nativeExitCode = $global:LASTEXITCODE
144
+ if ($nativeExitCode -ne 0) {
145
+ throw "Native command exited with code $nativeExitCode"
146
+ }
103
147
  Write-Log INFO $Phase "step_ok" "Step completed"
104
148
  return $true
105
149
  } catch {
@@ -112,19 +156,44 @@ function Invoke-Step {
112
156
  }
113
157
  }
114
158
 
115
- function Install-Winget {
159
+ function Test-WingetInstalled {
116
160
  param([string]$Id, [string]$Phase)
161
+ $probe = Join-Path ([IO.Path]::GetTempPath()) ("setup-ai-winget-" + [guid]::NewGuid().ToString("N") + ".log")
162
+ try {
163
+ # Keep the query inside Invoke-Step so its exit status and output are
164
+ # logged; a failed probe is treated as "not installed" and followed
165
+ # by the mandatory install step.
166
+ $listed = Invoke-Step -Phase $Phase -Optional -Action {
167
+ winget list --id $Id -e | Out-File -LiteralPath $probe -Encoding utf8
168
+ }
169
+ return ($listed -and [bool](Select-String -Path $probe -SimpleMatch $Id -Quiet))
170
+ } finally {
171
+ if (Test-Path $probe) {
172
+ try {
173
+ Remove-Item -LiteralPath $probe -Force -ErrorAction Stop
174
+ } catch {
175
+ Write-Log WARN $Phase "cleanup_failed" "Could not remove temporary winget probe: $($_.Exception.Message)"
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ function Install-Winget {
182
+ param([string]$Id, [string]$Phase, [switch]$Upgrade)
117
183
  if (-not (Test-Cmd winget)) {
118
- Write-Log WARN $Phase "winget_missing" "winget not available; install '$Id' manually"
119
- return
184
+ throw "winget not available; cannot install '$Id'"
120
185
  }
121
- # Skip if already installed.
122
- $installed = winget list --id $Id -e 2>$null | Select-String -SimpleMatch $Id
123
- if ($installed) {
124
- Write-Log INFO $Phase "already_present" "$Id already installed"
186
+ if (Test-WingetInstalled -Id $Id -Phase $Phase) {
187
+ if (-not $Upgrade) {
188
+ Write-Log INFO $Phase "already_present" "$Id already installed"
189
+ return
190
+ }
191
+ Invoke-Step -Phase $Phase -Action {
192
+ winget upgrade -e --id $Id --accept-package-agreements --accept-source-agreements --silent
193
+ }
125
194
  return
126
195
  }
127
- Invoke-Step -Phase $Phase -Optional -Action {
196
+ Invoke-Step -Phase $Phase -Action {
128
197
  winget install -e --id $Id --accept-package-agreements --accept-source-agreements --silent
129
198
  }
130
199
  }
@@ -132,33 +201,98 @@ function Install-Winget {
132
201
  function Invoke-RemoteScript {
133
202
  param([string]$Url, [string]$Phase)
134
203
  # Mirrors the vendor's documented `irm <url> | iex`, but logged.
135
- Invoke-Step -Phase $Phase -Optional -Action {
204
+ Invoke-Step -Phase $Phase -Action {
136
205
  $script = Invoke-RestMethod -Uri $Url -UseBasicParsing
137
206
  Invoke-Expression $script
138
207
  }
139
208
  }
140
209
 
210
+ function Test-NodeMinimum {
211
+ if (-not (Test-Cmd node)) { return $false }
212
+ $versionText = ""
213
+ $ok = Invoke-Step -Phase "node" -Optional -Action {
214
+ $script:SetupAiNodeVersionProbe = (node --version).Trim()
215
+ }
216
+ if (-not $ok) { return $false }
217
+ $versionText = $script:SetupAiNodeVersionProbe
218
+ if ($versionText -notmatch '^v?(\d+)\.(\d+)') { return $false }
219
+ $major = [int]$Matches[1]
220
+ $minor = [int]$Matches[2]
221
+ return (($major -gt 22) -or (($major -eq 22) -and ($minor -ge 19)))
222
+ }
223
+
224
+ function Assert-NodeMinimum {
225
+ if (-not (Test-Cmd node)) {
226
+ throw "Node.js is required; install Node.js 22.19 or newer"
227
+ }
228
+ $versionText = ""
229
+ Invoke-Step -Phase "node" -Action {
230
+ $script:SetupAiNodeVersion = (node --version).Trim()
231
+ }
232
+ $versionText = $script:SetupAiNodeVersion
233
+ if ($versionText -notmatch '^v?(\d+)\.(\d+)') {
234
+ throw "Could not parse Node.js version '$versionText'"
235
+ }
236
+ $major = [int]$Matches[1]
237
+ $minor = [int]$Matches[2]
238
+ if (($major -lt 22) -or (($major -eq 22) -and ($minor -lt 19))) {
239
+ throw "Node.js $versionText is too old; pi-extensible-workflows needs >= 22.19"
240
+ }
241
+ Write-Log INFO "node" "runtime_validated" "Node.js version satisfies workflow requirement" 0 "version=$versionText;minimum=22.19"
242
+ }
243
+
244
+ function Get-ReportCommandValue {
245
+ param([string]$Command, [string[]]$Arguments)
246
+ $script:SetupAiReportValue = ""
247
+ $ok = Invoke-Step -Phase "report" -Optional -Action {
248
+ $script:SetupAiReportValue = (& $Command @Arguments).Trim()
249
+ }
250
+ if ($ok -and $script:SetupAiReportValue) { return $script:SetupAiReportValue }
251
+ return "n/a"
252
+ }
253
+
254
+ function Get-TargetSkillAgents {
255
+ $found = @()
256
+ foreach ($agent in $SkillAgentNames) {
257
+ if (Test-Path $SkillAgentConfigDirs[$agent]) { $found += $agent }
258
+ }
259
+ if ($found.Count -eq 0) { return @('pi') }
260
+ return $found
261
+ }
262
+
263
+ function Assert-SkillInstalledForAgents {
264
+ param([string]$Phase, [string]$Skill, [string[]]$Agents)
265
+ foreach ($agent in $Agents) {
266
+ $skillPath = Join-Path (Join-Path $SkillAgentRoots[$agent] $Skill) "SKILL.md"
267
+ if (-not (Test-Path $skillPath)) {
268
+ throw "$Skill SKILL.md missing for targeted agent '$agent' ($skillPath)"
269
+ }
270
+ }
271
+ Write-Log INFO $Phase "skill_verified" "$Skill verified for every targeted agent" 0 "agents=$($Agents -join ',')"
272
+ }
273
+
141
274
  # ==============================================================================
142
275
  # Module registry
143
276
  # ==============================================================================
144
277
 
145
- $ModuleOrder = @('base','node','bun','pi','go','ee','pi-workflows','herdr','gentle-ai','engram','codex','antigravity','opencode','cockpit')
278
+ $ModuleOrder = @('base','node','bun','pi','go','dotenv','ee','skills','pi-workflows','herdr','gentle-ai','codex','antigravity','opencode','cockpit')
146
279
 
147
280
  $ModuleDesc = [ordered]@{
148
- 'base' = 'Core dev tools via winget (git, gh, python, neovim)'
149
- 'node' = 'Node.js LTS (winget OpenJS.NodeJS.LTS) + npm@latest'
150
- 'bun' = 'Bun runtime (bun.sh install.ps1)'
151
- 'pi' = 'pi.dev coding agent CLI (pi.dev install.ps1)'
152
- 'go' = 'Go toolchain (winget GoLang.Go)'
153
- 'ee' = 'Engineering Excellence skill (npx skills add, detected agents)'
154
- 'pi-workflows' = 'pi-extensible-workflows (module resolution fix for pi extensions)'
155
- 'herdr' = 'herdr terminal multiplexer (herdr.dev install.ps1)'
156
- 'gentle-ai' = 'gentle-ai (go install) + gentle-pi package'
157
- 'engram' = 'Engram persistent memory for pi (gentle-engram: /remember /recall)'
158
- 'codex' = 'OpenAI Codex CLI (chatgpt.com install.ps1)'
159
- 'antigravity' = 'Google Antigravity CLI (antigravity.google install.ps1)'
160
- 'opencode' = 'opencode agent CLI (npm opencode-ai)'
161
- 'cockpit' = 'cockpit-tools desktop GUI (optional, .msi, CC BY-NC-SA)'
281
+ 'base' = 'System packages (build tools, git, gh, python, neovim, jq, imagemagick, go)'
282
+ 'node' = 'Node.js v22 + npm@latest (nvm on Unix, winget on Windows)'
283
+ 'bun' = 'Bun runtime'
284
+ 'pi' = 'pi.dev coding agent CLI'
285
+ 'go' = 'Go toolchain'
286
+ 'dotenv' = 'vekexasia/dotenv dotfiles (Linux only: clones + runs setup_env.sh)'
287
+ 'ee' = 'Engineering Excellence skill (npx skills add, all detected agents)'
288
+ 'skills' = 'Upstream agent skills (herdr, grilling, research, typescript-advanced, show-me, ...) via npx skills add'
289
+ 'pi-workflows' = 'pi-extensible-workflows (fix module resolution for pi extensions)'
290
+ 'herdr' = 'herdr terminal multiplexer'
291
+ 'gentle-ai' = 'gentle-ai / gga ecosystem configurator (per-agent select + MCP) + gentle-pi'
292
+ 'codex' = 'OpenAI Codex CLI'
293
+ 'antigravity' = 'Google Antigravity CLI (agy)'
294
+ 'opencode' = 'opencode agent CLI (opencode-ai)'
295
+ 'cockpit' = 'cockpit-tools desktop GUI app (optional, CC BY-NC-SA)'
162
296
  }
163
297
  $ModuleOptional = @{ 'cockpit' = $true }
164
298
 
@@ -172,168 +306,323 @@ function Mod-Base {
172
306
  Install-Winget -Id "GitHub.cli" -Phase "base"
173
307
  Install-Winget -Id "Python.Python.3.12" -Phase "base"
174
308
  Install-Winget -Id "Neovim.Neovim" -Phase "base"
309
+ # Parity with the advertised description and the Bash base module.
310
+ Install-Winget -Id "jqlang.jq" -Phase "base"
311
+ Install-Winget -Id "ImageMagick.ImageMagick" -Phase "base"
312
+ Install-Winget -Id "GoLang.Go" -Phase "base"
313
+ # Build tools (parity with build-essential): VS Build Tools + C++ workload.
314
+ if (-not (Test-Cmd winget)) { throw "winget not available; cannot install build tools" }
315
+ $vsId = "Microsoft.VisualStudio.2022.BuildTools"
316
+ $vsWhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe"
317
+ $workloadPresent = $false
318
+ if (Test-Path $vsWhere) {
319
+ $script:SetupAiVsInstallPath = ""
320
+ $probeOk = Invoke-Step -Phase "base" -Optional -Action {
321
+ $script:SetupAiVsInstallPath = (& $vsWhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath).Trim()
322
+ }
323
+ $workloadPresent = $probeOk -and [bool]$script:SetupAiVsInstallPath
324
+ }
325
+ if (-not $workloadPresent) {
326
+ Invoke-Step -Phase "base" -Action {
327
+ winget install -e --id $vsId --force --accept-package-agreements --accept-source-agreements `
328
+ --override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
329
+ }
330
+ } else {
331
+ Write-Log INFO "base" "already_present" "Visual Studio C++ workload already installed"
332
+ }
333
+ if (-not (Test-Path $vsWhere)) {
334
+ throw "Visual Studio Installer vswhere.exe not found; cannot verify C++ workload"
335
+ }
336
+ Invoke-Step -Phase "base" -Action {
337
+ $script:SetupAiVsInstallPath = (& $vsWhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath).Trim()
338
+ }
339
+ if (-not $script:SetupAiVsInstallPath) {
340
+ throw "Visual Studio C++ workload missing after Build Tools installation"
341
+ }
175
342
  Update-SessionPath
176
343
  }
177
344
 
178
345
  function Mod-Node {
179
346
  Write-Log INFO "node" "start" "Node.js"
347
+ # Upgrade only when the current runtime is below the workflow minimum; a
348
+ # current WinGet package otherwise returns a non-zero "no update" status.
349
+ if (Test-NodeMinimum) {
350
+ Install-Winget -Id "OpenJS.NodeJS.22" -Phase "node"
351
+ } else {
352
+ Install-Winget -Id "OpenJS.NodeJS.22" -Phase "node" -Upgrade
353
+ }
354
+ Update-SessionPath
355
+ # winget's PATH refresh can still lag; add the default install dir directly.
356
+ $nodeDir = Join-Path $env:ProgramFiles "nodejs"
357
+ if ((-not (Test-Cmd node)) -and (Test-Path (Join-Path $nodeDir "node.exe"))) {
358
+ $env:Path = "$nodeDir;$env:Path"
359
+ }
180
360
  if (-not (Test-Cmd node)) {
181
- Install-Winget -Id "OpenJS.NodeJS.LTS" -Phase "node"
182
- Update-SessionPath
183
- # winget's PATH refresh can still lag; add the default install dir directly.
184
- $nodeDir = Join-Path $env:ProgramFiles "nodejs"
185
- if ((-not (Test-Cmd node)) -and (Test-Path (Join-Path $nodeDir "node.exe"))) {
186
- $env:Path = "$nodeDir;$env:Path"
187
- }
361
+ throw "node not found on PATH after install"
188
362
  }
189
- if (Test-Cmd npm) {
190
- Invoke-Step -Phase "node" -Optional -Action { npm install -g npm@latest }
191
- } else {
192
- Write-Log WARN "node" "unresolved" "node/npm not on PATH after install — open a NEW terminal and re-run (node-dependent modules will be skipped)"
363
+ if (-not (Test-Cmd npm)) {
364
+ throw "npm not found on PATH after Node.js install"
193
365
  }
366
+ Invoke-Step -Phase "node" -Action { npm install -g npm@latest }
367
+ Assert-NodeMinimum
194
368
  }
195
369
 
196
370
  function Mod-Bun {
197
371
  Write-Log INFO "bun" "start" "Bun"
198
- if (Test-Cmd bun) { Write-Log INFO "bun" "already_present" "bun already installed"; return }
372
+ if (Test-Cmd bun) {
373
+ Invoke-Step -Phase "bun" -Action { bun --version }
374
+ Write-Log INFO "bun" "already_present" "bun already installed"
375
+ return
376
+ }
199
377
  Invoke-RemoteScript -Url "https://bun.sh/install.ps1" -Phase "bun"
378
+ if (Test-Cmd bun) {
379
+ Invoke-Step -Phase "bun" -Action { bun --version }
380
+ Write-Log INFO "bun" "installed" "bun available after remote installer"
381
+ } else {
382
+ Write-Log ERROR "bun" "install_missing" "bun not found on PATH after remote installer"
383
+ throw "bun not found on PATH after remote installer"
384
+ }
200
385
  }
201
386
 
202
387
  function Mod-Pi {
203
388
  Write-Log INFO "pi" "start" "pi.dev CLI"
204
- if (Test-Cmd pi) { Write-Log INFO "pi" "already_present" "pi already installed"; return }
205
- Invoke-RemoteScript -Url "https://pi.dev/install.ps1" -Phase "pi"
389
+ # Establish the same extension/skill roots as Bash even when pi is already installed.
206
390
  New-Item -ItemType Directory -Force -Path $PiExtDir | Out-Null
207
391
  New-Item -ItemType Directory -Force -Path (Join-Path $HOME ".pi\agent\skills") | Out-Null
392
+ if (Test-Cmd pi) { Write-Log INFO "pi" "already_present" "pi already installed"; return }
393
+ Invoke-RemoteScript -Url "https://pi.dev/install.ps1" -Phase "pi"
394
+ if (Test-Cmd pi) {
395
+ Write-Log INFO "pi" "installed" "pi available after remote installer"
396
+ } else {
397
+ Write-Log ERROR "pi" "install_missing" "pi not found on PATH after remote installer"
398
+ throw "pi not found on PATH after remote installer"
399
+ }
208
400
  }
209
401
 
210
402
  function Mod-Go {
211
403
  Write-Log INFO "go" "start" "Go toolchain"
212
- if (Test-Cmd go) { Write-Log INFO "go" "already_present" "Go already installed"; return }
404
+ if (Test-Cmd go) {
405
+ Invoke-Step -Phase "go" -Action { go version }
406
+ Write-Log INFO "go" "already_present" "Go already installed"
407
+ return
408
+ }
213
409
  Install-Winget -Id "GoLang.Go" -Phase "go"
410
+ Update-SessionPath
411
+ if (Test-Cmd go) {
412
+ Write-Log INFO "go" "installed" "Go available after PATH refresh"
413
+ } else {
414
+ throw "Go not on PATH after install"
415
+ }
416
+ }
417
+
418
+ function Mod-Dotenv {
419
+ Write-Log WARN "dotenv" "skipped_non_linux" "dotenv/setup_env.sh targets Linux package managers; skipped on Windows"
214
420
  }
215
421
 
216
422
  function Mod-Ee {
217
423
  Write-Log INFO "ee" "start" "Engineering Excellence"
218
- if (-not (Test-Cmd npx)) { Write-Log WARN "ee" "npx_missing" "npx not found; install node first"; return }
219
- # Keys are `skills` CLI agent names (claude-code, gemini-cli), not dir names.
220
- $agents = @{ pi = ".pi"; 'claude-code' = ".claude"; 'gemini-cli' = ".gemini"; cursor = ".cursor"; antigravity = ".antigravity"; codex = ".codex"; opencode = ".config\opencode" }
221
- $any = $false
222
- foreach ($a in @('pi','claude-code','gemini-cli','cursor','antigravity','codex','opencode')) {
223
- if (Test-Path (Join-Path $HOME $agents[$a])) {
224
- Invoke-Step -Phase "ee" -Optional -Action {
225
- npx --yes skills@latest add $EE_Slug --skill $EE_Skill --global --agent $a --copy --yes
226
- }
227
- $any = $true
424
+ if (-not (Test-Cmd npx)) { throw "npx not found; ee cannot be installed (install the node module first)" }
425
+ $agents = Get-TargetSkillAgents
426
+ foreach ($a in $agents) {
427
+ Invoke-Step -Phase "ee" -Action {
428
+ npx --yes skills@latest add $EE_Slug --skill $EE_Skill --global --agent $a --copy --yes
228
429
  }
229
430
  }
230
- if (-not $any) {
231
- Invoke-Step -Phase "ee" -Optional -Action {
232
- npx --yes skills@latest add $EE_Slug --skill $EE_Skill --global --agent pi --copy --yes
431
+ Assert-SkillInstalledForAgents -Phase "ee" -Skill $EE_Skill -Agents $agents
432
+ }
433
+
434
+ # Installs vekexasia/dotenv's skill stack on every OS via `npx skills add`.
435
+ # On Linux the dotenv module may already install these; skills add --copy is
436
+ # idempotent, so a re-run is safe.
437
+ function Mod-Skills {
438
+ Write-Log INFO "skills" "start" "Agent skills (upstream stack)"
439
+ if (-not (Test-Cmd npx)) { throw "npx not found; skills cannot be installed (install the node module first)" }
440
+ $agents = Get-TargetSkillAgents
441
+ foreach ($entry in $UpstreamSkillSources) {
442
+ $src = $entry.Source
443
+ $skills = $entry.Skills
444
+ foreach ($a in $agents) {
445
+ Invoke-Step -Phase "skills" -Action {
446
+ npx --yes skills@latest add $src --skill $skills --global --agent $a --copy --yes
447
+ }
233
448
  }
234
449
  }
235
- if (Test-Path (Join-Path $PiSkillDir "SKILL.md")) {
236
- Write-Log INFO "ee" "skill_installed" "EE skill present for pi"
450
+ foreach ($skill in $UpstreamSkillNames) {
451
+ Assert-SkillInstalledForAgents -Phase "skills" -Skill $skill -Agents $agents
237
452
  }
238
453
  }
239
454
 
240
455
  function Mod-PiWorkflows {
241
456
  Write-Log INFO "pi-workflows" "start" "pi-extensible-workflows"
242
- if (-not (Test-Cmd pi)) { Write-Log WARN "pi-workflows" "pi_missing" "pi not found; skipped"; return }
457
+ if (-not (Test-Cmd pi)) { throw "pi not found; pi-workflows cannot be installed" }
458
+ if (-not (Test-Cmd npm)) { throw "npm not found; pi-workflows cannot be installed" }
459
+ Assert-NodeMinimum
243
460
  $ver = ""
244
- try { $ver = (npm view pi-extensible-workflows version).Trim() } catch {}
245
- if (-not $ver) { Write-Log WARN "pi-workflows" "version_unresolved" "Could not resolve version; skipped"; return }
461
+ try {
462
+ Invoke-Step -Phase "pi-workflows" -Action {
463
+ $script:SetupAiWorkflowVersion = (npm view pi-extensible-workflows version).Trim()
464
+ }
465
+ $ver = $script:SetupAiWorkflowVersion
466
+ } catch {
467
+ throw "Could not resolve pi-extensible-workflows version: $($_.Exception.Message)"
468
+ }
469
+ if (-not $ver) { throw "Could not resolve pi-extensible-workflows version" }
246
470
  Write-Log INFO "pi-workflows" "version" "Version $ver"
247
- Invoke-Step -Phase "pi-workflows" -Optional -Action { pi install "npm:pi-extensible-workflows@$ver" }
471
+ Invoke-Step -Phase "pi-workflows" -Action { pi install "npm:pi-extensible-workflows@$ver" }
248
472
  New-Item -ItemType Directory -Force -Path $PiExtDir | Out-Null
249
473
  Set-Content -Path (Join-Path $PiExtDir ".npmrc") -Value "ignore-scripts=false"
474
+ # Mark this dir as an npm project root so `npm install` lands HERE and cannot
475
+ # walk up into an ancestor project (mirrors setup-ai.sh).
476
+ $pkgJson = Join-Path $PiExtDir "package.json"
477
+ if (-not (Test-Path $pkgJson)) {
478
+ Set-Content -Path $pkgJson -Value '{"name":"pi-extensions","private":true}'
479
+ }
250
480
  Push-Location $PiExtDir
251
481
  try {
252
- Invoke-Step -Phase "pi-workflows" -Optional -Action {
482
+ Invoke-Step -Phase "pi-workflows" -Action {
253
483
  npm install --save-exact --no-audit --no-fund "pi-extensible-workflows@$ver"
254
484
  }
255
- Invoke-Step -Phase "pi-workflows" -Optional -Action {
256
- node -e "console.log(require.resolve('pi-extensible-workflows',{paths:[process.cwd()]}))"
257
- }
258
485
  } finally { Pop-Location }
486
+
487
+ # Verify the version that actually landed in THIS dir by reading its local
488
+ # package.json directly. Do NOT use require.resolve: it ascends the tree and
489
+ # can resolve a shadowing ancestor copy (false version_mismatch).
490
+ $localPkg = Join-Path $PiExtDir "node_modules/pi-extensible-workflows/package.json"
491
+ if (-not (Test-Path $localPkg)) {
492
+ Write-Log ERROR "pi-workflows-node" "install_missing" "pi-extensible-workflows not installed in extensions dir ($localPkg)"
493
+ throw "pi-extensible-workflows not installed in extensions dir ($localPkg)"
494
+ } else {
495
+ $installed = (Get-Content -Raw $localPkg | ConvertFrom-Json).version
496
+ if ($installed -ne $ver) {
497
+ Write-Log ERROR "pi-workflows-node" "version_mismatch" "Installed version mismatch (expected=$ver actual=$installed)"
498
+ throw "Installed pi-extensible-workflows version mismatch (expected=$ver actual=$installed)"
499
+ } else {
500
+ Write-Log INFO "pi-workflows-node" "module_resolved" "Installed $installed in $localPkg"
501
+ }
502
+ }
259
503
  }
260
504
 
261
505
  function Mod-Herdr {
262
506
  Write-Log INFO "herdr" "start" "herdr"
263
- if (Test-Cmd herdr) { Write-Log INFO "herdr" "already_present" "herdr already installed"; return }
507
+ if (Test-Cmd herdr) {
508
+ Invoke-Step -Phase "herdr" -Action { herdr --version }
509
+ Write-Log INFO "herdr" "already_present" "herdr already installed"
510
+ return
511
+ }
264
512
  Invoke-RemoteScript -Url "https://herdr.dev/install.ps1" -Phase "herdr"
513
+ if (Test-Cmd herdr) {
514
+ Write-Log INFO "herdr" "installed" "herdr available after remote installer"
515
+ } else {
516
+ Write-Log ERROR "herdr" "install_missing" "herdr not found on PATH after remote installer"
517
+ throw "herdr not found on PATH after remote installer"
518
+ }
265
519
  }
266
520
 
521
+ # Reinstates the Gentle AI ecosystem configurator (sibling of mod_gentle_ai in
522
+ # setup-ai.sh). `gentle-ai install` is the per-agent/per-IDE selector that also
523
+ # wires each selected agent's MCP servers, so tools appear under /mcp. The
524
+ # configurator is a CORE step: it always runs (interactively with a console, or
525
+ # non-interactively over the detected agents in CI/pipes) and a failure fails
526
+ # the module. For pi we additionally guarantee + verify the first-class
527
+ # gentle-pi harness and pi-mcp-adapter. Idempotent: safe to re-run.
267
528
  function Mod-GentleAi {
268
529
  Write-Log INFO "gentle-ai" "start" "gentle-ai"
269
- if (-not (Test-Cmd gentle-ai) -and -not (Test-Cmd gga)) {
270
- if (Test-Cmd go) {
271
- Invoke-Step -Phase "gentle-ai" -Optional -Action {
272
- go install github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai@latest
273
- }
274
- } else {
275
- Write-Log WARN "gentle-ai" "go_missing" "Go not found; cannot 'go install' gentle-ai on Windows"
530
+
531
+ # The per-agent selector and the pi harness both require the gentle-ai CLI
532
+ # itself; a legacy standalone gga is NOT enough, so install whenever the
533
+ # gentle-ai CLI is missing even if an old gga is on PATH.
534
+ if (-not (Test-Cmd gentle-ai)) {
535
+ # Vendor's official Windows method (installs to %LOCALAPPDATA%\gentle-ai\bin).
536
+ Invoke-RemoteScript -Url "https://raw.githubusercontent.com/Gentleman-Programming/gentle-ai/main/scripts/install.ps1" -Phase "gentle-ai"
537
+ Update-SessionPath
538
+ }
539
+
540
+ if (-not (Test-Cmd gentle-ai)) {
541
+ Write-Log ERROR "gentle-ai" "install_missing" "gentle-ai CLI not found on PATH after remote installer"
542
+ throw "gentle-ai CLI not found on PATH after remote installer"
543
+ }
544
+ Invoke-Step -Phase "gentle-ai" -Action { & gentle-ai --version }
545
+
546
+ # Detect the agents/IDEs present on this machine (same mapping as Mod-Ee).
547
+ $detectedAgents = Get-TargetSkillAgents
548
+
549
+ # Per-agent / per-IDE selection + MCP wiring, owned by gentle-ai. This is a
550
+ # core step and must actually run: with a real console we launch the
551
+ # interactive selector (run directly — Invoke-Step pipes output and would
552
+ # hide the prompts); otherwise we run it non-interactively over the detected
553
+ # agents so CI/pipes never hang. A failure fails the module.
554
+ if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected -and -not [Console]::IsOutputRedirected) {
555
+ Write-Log INFO "gentle-ai" "configurator_start" "Launching gentle-ai install (choose agents/IDEs + MCP)"
556
+ & gentle-ai install --scope global
557
+ if ($LASTEXITCODE -ne 0) {
558
+ Write-Log ERROR "gentle-ai" "configurator_failed" "gentle-ai install exited with code $LASTEXITCODE"
559
+ throw "gentle-ai install exited with code $LASTEXITCODE"
276
560
  }
561
+ } else {
562
+ $agentsCsv = ($detectedAgents -join ',')
563
+ Write-Log INFO "gentle-ai" "configurator_noninteractive" "No console; installing gentle-ai for detected agents" 0 "agents=$agentsCsv"
564
+ Invoke-Step -Phase "gentle-ai" -Action { & gentle-ai install --scope global --agents $agentsCsv }
277
565
  }
566
+ Write-Log INFO "gentle-ai" "configurator_done" "gentle-ai install completed"
567
+
568
+ # Guarantee pi reads gentle-ai in its MCP list (/mcp): install the first-class
569
+ # gentle-pi harness + pi-mcp-adapter, then verify the exact target file.
278
570
  if (Test-Cmd pi) {
279
- Invoke-Step -Phase "gentle-ai" -Optional -Action { pi install npm:gentle-pi }
280
- Invoke-Step -Phase "gentle-ai" -Optional -Action { pi install npm:pi-mcp-adapter }
281
- Write-Log INFO "gentle-ai" "pi_enabled" "gentle-pi registered in pi (verify: /gentle-ai:status)"
571
+ Invoke-Step -Phase "gentle-ai" -Action { pi install npm:gentle-pi }
572
+ Invoke-Step -Phase "gentle-ai" -Action { pi install npm:pi-mcp-adapter }
573
+ $piSettings = Join-Path $HOME ".pi\agent\settings.json"
574
+ $piSettingsRaw = if (Test-Path $piSettings) { Get-Content -Raw $piSettings } else { "" }
575
+ if (($piSettingsRaw -match 'npm:gentle-pi') -and ($piSettingsRaw -match 'npm:pi-mcp-adapter')) {
576
+ Write-Log INFO "gentle-ai" "pi_enabled" "gentle-pi + pi-mcp-adapter registered in pi (verify: /mcp, /gentle-ai:status)"
577
+ } else {
578
+ Write-Log ERROR "gentle-ai" "pi_enable_failed" "gentle-pi and/or pi-mcp-adapter not present in pi settings after install ($piSettings)"
579
+ throw "gentle-pi and/or pi-mcp-adapter not present in pi settings after install"
580
+ }
282
581
  }
582
+
283
583
  @"
284
584
  gentle-ai next steps (run yourself, per project):
285
585
  1) Set your API keys
286
586
  2) Run your selected agent
287
- 3) Try: /sdd-new my-feature (in pi: /gentle-ai:status, /gentleman:models)
587
+ 3) Try: /sdd-new my-feature (in pi: /gentle-ai:status, /gentleman:models, /mcp)
288
588
  GGA (per project): gga init then gga install
289
589
  "@ | Tee-Object -FilePath $HumanLog -Append | Out-Host
290
590
  }
291
591
 
292
- function Mod-Engram {
293
- Write-Log INFO "engram" "start" "Engram memory (pi)"
294
- # The gentle-engram pi extension auto-starts `engram serve`; the Engram Go
295
- # binary must be on PATH first, or the extension loads but silently fails.
296
- if (-not (Test-Cmd engram)) {
297
- if (Test-Cmd go) {
298
- Invoke-Step -Phase "engram" -Optional -Action {
299
- go install github.com/Gentleman-Programming/engram/cmd/engram@latest
300
- }
301
- $goBin = Join-Path $HOME "go\bin"
302
- if ((Test-Path (Join-Path $goBin "engram.exe")) -and ($env:Path -notlike "*$goBin*")) {
303
- $env:Path = "$goBin;$env:Path"
304
- }
305
- } else {
306
- Write-Log WARN "engram" "go_missing" "Go not found; cannot install engram binary (needed on PATH)"
307
- }
308
- }
309
- if (-not (Test-Cmd pi)) { Write-Log WARN "engram" "pi_missing" "pi not found; skipped"; return }
310
- # `pi-engram init` is the single source of truth (adds pinned gentle-engram +
311
- # wires the Engram MCP server). Do NOT also `pi install npm:gentle-engram` —
312
- # that adds a second, unversioned entry and duplicates it every run.
313
- Invoke-Step -Phase "engram" -Optional -Action { pi install npm:pi-mcp-adapter }
314
- Invoke-Step -Phase "engram" -Optional -Action { npm exec --yes --package gentle-engram@latest -- pi-engram init }
315
- Write-Log INFO "engram" "enabled" "Engram enabled — RESTART pi, verify: mem_current_project / mem_doctor / 'engram tui'"
316
- }
317
-
318
592
  function Mod-Codex {
319
593
  Write-Log INFO "codex" "start" "Codex CLI"
320
594
  if (Test-Cmd codex) { Write-Log INFO "codex" "already_present" "codex already installed"; return }
321
595
  Invoke-RemoteScript -Url "https://chatgpt.com/codex/install.ps1" -Phase "codex"
596
+ if (Test-Cmd codex) {
597
+ Write-Log INFO "codex" "installed" "codex available after remote installer"
598
+ } else {
599
+ Write-Log ERROR "codex" "install_missing" "codex not found on PATH after remote installer"
600
+ throw "codex not found on PATH after remote installer"
601
+ }
322
602
  }
323
603
 
324
604
  function Mod-Antigravity {
325
605
  Write-Log INFO "antigravity" "start" "Antigravity CLI"
326
606
  if (Test-Cmd agy) { Write-Log INFO "antigravity" "already_present" "agy already installed"; return }
327
607
  Invoke-RemoteScript -Url "https://antigravity.google/cli/install.ps1" -Phase "antigravity"
608
+ if (Test-Cmd agy) {
609
+ Write-Log INFO "antigravity" "installed" "agy available after remote installer"
610
+ } else {
611
+ Write-Log ERROR "antigravity" "install_missing" "agy not found on PATH after remote installer"
612
+ throw "agy not found on PATH after remote installer"
613
+ }
328
614
  }
329
615
 
330
616
  function Mod-Opencode {
331
617
  Write-Log INFO "opencode" "start" "opencode"
332
618
  if (Test-Cmd opencode) { Write-Log INFO "opencode" "already_present" "opencode already installed"; return }
333
- if (Test-Cmd npm) {
334
- Invoke-Step -Phase "opencode" -Optional -Action { npm install -g opencode-ai }
335
- } else {
336
- Write-Log WARN "opencode" "npm_missing" "npm not found; install node first"
619
+ if (-not (Test-Cmd npm)) {
620
+ throw "npm not found; opencode cannot be installed"
621
+ }
622
+ Invoke-Step -Phase "opencode" -Action { npm install -g opencode-ai }
623
+ if (-not (Test-Cmd opencode)) {
624
+ Write-Log ERROR "opencode" "install_missing" "opencode not found on PATH after npm install"
625
+ throw "opencode not found on PATH after npm install"
337
626
  }
338
627
  @"
339
628
  OpenCode Go (paid) is hosted-model access; after install run: opencode auth login
@@ -351,7 +640,10 @@ function Mod-Cockpit {
351
640
  if (-not $asset) { Write-Log WARN "cockpit" "no_msi" "No .msi asset in latest release; download manually"; return }
352
641
  $msi = Join-Path $env:TEMP $asset.name
353
642
  Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $msi
354
- Invoke-Step -Phase "cockpit" -Optional -Action { Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /qb" -Wait }
643
+ Invoke-Step -Phase "cockpit" -Optional -Action {
644
+ $process = Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /qb" -Wait -PassThru
645
+ if ($process.ExitCode -ne 0) { throw "msiexec exited with code $($process.ExitCode)" }
646
+ }
355
647
  } catch {
356
648
  Write-Log WARN "cockpit" "failed" "$($_.Exception.Message)"
357
649
  }
@@ -359,9 +651,10 @@ function Mod-Cockpit {
359
651
 
360
652
  $ModuleFn = @{
361
653
  'base' = ${function:Mod-Base}; 'node' = ${function:Mod-Node}; 'bun' = ${function:Mod-Bun}
362
- 'pi' = ${function:Mod-Pi}; 'go' = ${function:Mod-Go}; 'ee' = ${function:Mod-Ee}
654
+ 'pi' = ${function:Mod-Pi}; 'go' = ${function:Mod-Go}; 'dotenv' = ${function:Mod-Dotenv}; 'ee' = ${function:Mod-Ee}
655
+ 'skills' = ${function:Mod-Skills}
363
656
  'pi-workflows' = ${function:Mod-PiWorkflows}; 'herdr' = ${function:Mod-Herdr}
364
- 'gentle-ai' = ${function:Mod-GentleAi}; 'engram' = ${function:Mod-Engram}
657
+ 'gentle-ai' = ${function:Mod-GentleAi}
365
658
  'codex' = ${function:Mod-Codex}; 'antigravity' = ${function:Mod-Antigravity}
366
659
  'opencode' = ${function:Mod-Opencode}; 'cockpit' = ${function:Mod-Cockpit}
367
660
  }
@@ -376,11 +669,11 @@ function Show-List {
376
669
  $tag = if ($ModuleOptional.ContainsKey($m)) { "optional" } else { "core " }
377
670
  "{0,-10} {1,-14} {2}" -f "[$tag]", $m, $ModuleDesc[$m] | Write-Host
378
671
  }
379
- Write-Host "`nUse: -Only <csv> | -All | (default = core)"
672
+ Write-Host "`nUse: -Only csv | -All | (default = core)"
380
673
  }
381
674
 
382
675
  function Show-Help {
383
- Get-Content $MyInvocation.MyCommand.Path | Select-Object -First 30 | ForEach-Object { $_ }
676
+ Get-Content $ScriptPath | Select-Object -First 30 | ForEach-Object { $_ }
384
677
  Show-List
385
678
  }
386
679
 
@@ -388,13 +681,21 @@ function Resolve-Selection {
388
681
  $requested = @()
389
682
  if ($All) {
390
683
  $requested = $ModuleOrder
391
- } elseif ($Only) {
684
+ } elseif ($OnlySpecified) {
685
+ if ([string]::IsNullOrWhiteSpace($Only)) {
686
+ Write-Log ERROR "selection" "invalid_only" "-Only requires a non-empty comma-separated module list." 2
687
+ exit 2
688
+ }
392
689
  foreach ($r in ($Only -split ',')) {
393
690
  $r = $r.Trim()
394
691
  if (-not $r) { continue }
395
692
  if (-not $ModuleDesc.Contains($r)) { Write-Error "Unknown module: $r"; exit 2 }
396
693
  $requested += $r
397
694
  }
695
+ if ($requested.Count -eq 0) {
696
+ Write-Log ERROR "selection" "invalid_only" "-Only requires a non-empty comma-separated module list." 2
697
+ exit 2
698
+ }
398
699
  } else {
399
700
  $requested = $ModuleOrder | Where-Object { -not $ModuleOptional.ContainsKey($_) }
400
701
  }
@@ -402,6 +703,66 @@ function Resolve-Selection {
402
703
  return $ModuleOrder | Where-Object { $requested -contains $_ }
403
704
  }
404
705
 
706
+ function Invoke-QualityGates {
707
+ param([string[]]$Selected)
708
+ Write-Log INFO "quality" "start" "Running quality gates"
709
+
710
+ if ($Selected -contains 'node' -or $Selected -contains 'pi-workflows') {
711
+ Assert-NodeMinimum
712
+ }
713
+ if ($Selected -contains 'node') {
714
+ Invoke-Step -Phase "quality" -Action { node --version }
715
+ Invoke-Step -Phase "quality" -Action { npm --version }
716
+ }
717
+ if ($Selected -contains 'bun') {
718
+ if (-not (Test-Cmd bun)) { throw "bun quality gate could not find bun" }
719
+ Invoke-Step -Phase "quality" -Action { bun --version }
720
+ }
721
+ if ($Selected -contains 'pi') {
722
+ if (-not (Test-Cmd pi)) { throw "pi quality gate could not find pi" }
723
+ Invoke-Step -Phase "quality" -Action { pi --no-extensions --version }
724
+ }
725
+ if ($Selected -contains 'pi-workflows') {
726
+ $workflowPkg = Join-Path $PiExtDir "node_modules/pi-extensible-workflows/package.json"
727
+ if (-not (Test-Path $workflowPkg)) {
728
+ throw "pi-extensible-workflows package.json missing from extensions dir ($workflowPkg)"
729
+ }
730
+ $workflowVersion = (Get-Content -Raw $workflowPkg | ConvertFrom-Json).version
731
+ Write-Log INFO "quality" "workflow_package_verified" "Verified pi-extensible-workflows package" 0 "path=$workflowPkg;version=$workflowVersion"
732
+ }
733
+ $targetAgents = Get-TargetSkillAgents
734
+ if ($Selected -contains 'ee') {
735
+ Assert-SkillInstalledForAgents -Phase "quality" -Skill $EE_Skill -Agents $targetAgents
736
+ Write-Log INFO "quality" "ee_gate_passed" "Engineering Excellence verified for every targeted agent"
737
+ }
738
+ if ($Selected -contains 'skills') {
739
+ foreach ($sk in $UpstreamSkillNames) {
740
+ Assert-SkillInstalledForAgents -Phase "quality" -Skill $sk -Agents $targetAgents
741
+ }
742
+ Write-Log INFO "quality" "skills_gate_passed" "All upstream skills verified for every targeted agent"
743
+ }
744
+ if ($Selected -contains 'gentle-ai') {
745
+ # The module requires the gentle-ai CLI specifically; a legacy standalone
746
+ # gga cannot prove the per-agent/MCP configuration ran.
747
+ if (-not (Test-Cmd gentle-ai)) {
748
+ throw "gentle-ai quality gate could not find the gentle-ai CLI on PATH"
749
+ }
750
+ if (Test-Cmd pi) {
751
+ $piSettings = Join-Path $HOME ".pi\agent\settings.json"
752
+ $piSettingsRaw = if (Test-Path $piSettings) { Get-Content -Raw $piSettings } else { "" }
753
+ if (-not ($piSettingsRaw -match 'npm:gentle-pi') -or -not ($piSettingsRaw -match 'npm:pi-mcp-adapter')) {
754
+ throw "gentle-pi and/or pi-mcp-adapter not registered in pi settings ($piSettings)"
755
+ }
756
+ Write-Log INFO "quality" "gentle_ai_gate_passed" "gentle-ai verified (CLI present; gentle-pi + pi-mcp-adapter registered in pi)"
757
+ } else {
758
+ # No pi on PATH: pi MCP wiring is genuinely not applicable, so do not
759
+ # claim it was registered.
760
+ Write-Log INFO "quality" "gentle_ai_gate_passed" "gentle-ai CLI verified; pi not present, so pi MCP wiring was not required"
761
+ }
762
+ }
763
+ Write-Log INFO "quality" "gates_done" "Quality gates completed for selected modules"
764
+ }
765
+
405
766
  # ==============================================================================
406
767
  # Main
407
768
  # ==============================================================================
@@ -420,10 +781,29 @@ Write-Log INFO "bootstrap" "modules_selected" "Modules queued" 0 ("modules=" + (
420
781
  $failed = @()
421
782
  foreach ($m in $selected) {
422
783
  try { & $ModuleFn[$m] }
423
- catch { $failed += $m; Write-Log ERROR "modules" "module_failed" "Module $m failed: $($_.Exception.Message)" 1 }
784
+ catch {
785
+ $failed += $m
786
+ Write-Log ERROR "modules" "module_failed" "Module $m failed: $($_.Exception.Message)" 1
787
+ # Fail-fast: mirror the Bash ERR trap so we never run modules whose
788
+ # ordered prerequisites just failed.
789
+ break
790
+ }
791
+ }
792
+
793
+ # Skip quality gates when a module already failed (Bash aborts before them).
794
+ if (-not $failed) {
795
+ try {
796
+ Invoke-QualityGates -Selected $selected
797
+ } catch {
798
+ $failed += 'quality'
799
+ Write-Log ERROR "quality" "gates_failed" $_.Exception.Message 1
800
+ }
424
801
  }
425
802
 
426
803
  # Report
804
+ $reportNode = if (Test-Cmd node) { Get-ReportCommandValue -Command "node" -Arguments @("--version") } else { "n/a" }
805
+ $reportNpm = if (Test-Cmd npm) { Get-ReportCommandValue -Command "npm" -Arguments @("--version") } else { "n/a" }
806
+ $reportGo = if (Test-Cmd go) { Get-ReportCommandValue -Command "go" -Arguments @("version") } else { "n/a" }
427
807
  @"
428
808
  # AI Dev Suite — Engineering Report (Windows)
429
809
 
@@ -433,9 +813,9 @@ foreach ($m in $selected) {
433
813
  **Failed modules:** $(if ($failed) { $failed -join ' ' } else { 'none' })
434
814
 
435
815
  ## Versions
436
- - Node: $(if (Test-Cmd node) { node --version } else { 'n/a' })
437
- - npm: $(if (Test-Cmd npm) { npm --version } else { 'n/a' })
438
- - Go: $(if (Test-Cmd go) { (go version) } else { 'n/a' })
816
+ - Node: $reportNode
817
+ - npm: $reportNpm
818
+ - Go: $reportGo
439
819
  - pi: $(if (Test-Cmd pi) { 'installed' } else { 'n/a' })
440
820
 
441
821
  Logs: $HumanLog ; $JsonlLog
@@ -445,6 +825,12 @@ Write-Host "`n============================================================" -For
445
825
  Write-Host " AI Dev Suite (Windows) setup finished" -ForegroundColor Green
446
826
  Write-Host "============================================================" -ForegroundColor Green
447
827
  Write-Host "Modules : $($selected -join ' ')"
448
- if ($failed) { Write-Host "Failed : $($failed -join ' ')" -ForegroundColor Yellow }
828
+ if ($failed) {
829
+ Write-Host "Failed : $($failed -join ' ')" -ForegroundColor Yellow
830
+ Write-Log ERROR "bootstrap" "completed_with_failures" "Setup finished with failed modules or quality gates" 1 "failed=$($failed -join ',')"
831
+ Write-Host "Report : $ReportFile"
832
+ exit 1
833
+ }
834
+ Write-Log INFO "bootstrap" "completed" "Setup completed successfully" 0
449
835
  Write-Host "Report : $ReportFile"
450
836
  Write-Host "`nNext: open a new terminal so PATH updates apply."