@darkrei08/setup-ai 3.0.4 → 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/README.md +35 -28
- package/bin/setup-ai.mjs +47 -23
- package/package.json +4 -3
- package/setup-ai.ps1 +509 -121
- package/setup-ai.sh +508 -205
package/setup-ai.ps1
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#Requires -Version 7.
|
|
1
|
+
#Requires -Version 7.3
|
|
2
2
|
<#
|
|
3
3
|
==============================================================================
|
|
4
4
|
AI Dev Suite — Engineering Excellence Edition (Windows)
|
|
5
|
-
Version: 3.0
|
|
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,
|
|
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
|
|
45
|
-
$
|
|
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 = "
|
|
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
|
|
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
|
-
|
|
119
|
-
return
|
|
184
|
+
throw "winget not available; cannot install '$Id'"
|
|
120
185
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 -
|
|
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 -
|
|
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','
|
|
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' = '
|
|
149
|
-
'node' = 'Node.js
|
|
150
|
-
'bun' = 'Bun runtime
|
|
151
|
-
'pi' = 'pi.dev coding agent CLI
|
|
152
|
-
'go' = 'Go toolchain
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
160
|
-
'
|
|
161
|
-
'
|
|
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,166 +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
|
-
|
|
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
|
-
|
|
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) {
|
|
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
|
-
|
|
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) {
|
|
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)) {
|
|
219
|
-
|
|
220
|
-
$agents
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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)) {
|
|
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 {
|
|
245
|
-
|
|
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" -
|
|
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" -
|
|
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) {
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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" -
|
|
280
|
-
Invoke-Step -Phase "gentle-ai" -
|
|
281
|
-
|
|
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
|
-
Invoke-Step -Phase "engram" -Optional -Action { pi install npm:gentle-engram }
|
|
311
|
-
Invoke-Step -Phase "engram" -Optional -Action { pi install npm:pi-mcp-adapter }
|
|
312
|
-
Invoke-Step -Phase "engram" -Optional -Action { npm exec --yes --package gentle-engram@latest -- pi-engram init }
|
|
313
|
-
Write-Log INFO "engram" "enabled" "Engram enabled — RESTART pi, verify: mem_current_project / mem_doctor / 'engram tui'"
|
|
314
|
-
}
|
|
315
|
-
|
|
316
592
|
function Mod-Codex {
|
|
317
593
|
Write-Log INFO "codex" "start" "Codex CLI"
|
|
318
594
|
if (Test-Cmd codex) { Write-Log INFO "codex" "already_present" "codex already installed"; return }
|
|
319
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
|
+
}
|
|
320
602
|
}
|
|
321
603
|
|
|
322
604
|
function Mod-Antigravity {
|
|
323
605
|
Write-Log INFO "antigravity" "start" "Antigravity CLI"
|
|
324
606
|
if (Test-Cmd agy) { Write-Log INFO "antigravity" "already_present" "agy already installed"; return }
|
|
325
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
|
+
}
|
|
326
614
|
}
|
|
327
615
|
|
|
328
616
|
function Mod-Opencode {
|
|
329
617
|
Write-Log INFO "opencode" "start" "opencode"
|
|
330
618
|
if (Test-Cmd opencode) { Write-Log INFO "opencode" "already_present" "opencode already installed"; return }
|
|
331
|
-
if (Test-Cmd npm) {
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
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"
|
|
335
626
|
}
|
|
336
627
|
@"
|
|
337
628
|
OpenCode Go (paid) is hosted-model access; after install run: opencode auth login
|
|
@@ -349,7 +640,10 @@ function Mod-Cockpit {
|
|
|
349
640
|
if (-not $asset) { Write-Log WARN "cockpit" "no_msi" "No .msi asset in latest release; download manually"; return }
|
|
350
641
|
$msi = Join-Path $env:TEMP $asset.name
|
|
351
642
|
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $msi
|
|
352
|
-
Invoke-Step -Phase "cockpit" -Optional -Action {
|
|
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
|
+
}
|
|
353
647
|
} catch {
|
|
354
648
|
Write-Log WARN "cockpit" "failed" "$($_.Exception.Message)"
|
|
355
649
|
}
|
|
@@ -357,9 +651,10 @@ function Mod-Cockpit {
|
|
|
357
651
|
|
|
358
652
|
$ModuleFn = @{
|
|
359
653
|
'base' = ${function:Mod-Base}; 'node' = ${function:Mod-Node}; 'bun' = ${function:Mod-Bun}
|
|
360
|
-
'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}
|
|
361
656
|
'pi-workflows' = ${function:Mod-PiWorkflows}; 'herdr' = ${function:Mod-Herdr}
|
|
362
|
-
'gentle-ai' = ${function:Mod-GentleAi}
|
|
657
|
+
'gentle-ai' = ${function:Mod-GentleAi}
|
|
363
658
|
'codex' = ${function:Mod-Codex}; 'antigravity' = ${function:Mod-Antigravity}
|
|
364
659
|
'opencode' = ${function:Mod-Opencode}; 'cockpit' = ${function:Mod-Cockpit}
|
|
365
660
|
}
|
|
@@ -374,11 +669,11 @@ function Show-List {
|
|
|
374
669
|
$tag = if ($ModuleOptional.ContainsKey($m)) { "optional" } else { "core " }
|
|
375
670
|
"{0,-10} {1,-14} {2}" -f "[$tag]", $m, $ModuleDesc[$m] | Write-Host
|
|
376
671
|
}
|
|
377
|
-
Write-Host "`nUse: -Only
|
|
672
|
+
Write-Host "`nUse: -Only csv | -All | (default = core)"
|
|
378
673
|
}
|
|
379
674
|
|
|
380
675
|
function Show-Help {
|
|
381
|
-
Get-Content $
|
|
676
|
+
Get-Content $ScriptPath | Select-Object -First 30 | ForEach-Object { $_ }
|
|
382
677
|
Show-List
|
|
383
678
|
}
|
|
384
679
|
|
|
@@ -386,13 +681,21 @@ function Resolve-Selection {
|
|
|
386
681
|
$requested = @()
|
|
387
682
|
if ($All) {
|
|
388
683
|
$requested = $ModuleOrder
|
|
389
|
-
} elseif ($
|
|
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
|
+
}
|
|
390
689
|
foreach ($r in ($Only -split ',')) {
|
|
391
690
|
$r = $r.Trim()
|
|
392
691
|
if (-not $r) { continue }
|
|
393
692
|
if (-not $ModuleDesc.Contains($r)) { Write-Error "Unknown module: $r"; exit 2 }
|
|
394
693
|
$requested += $r
|
|
395
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
|
+
}
|
|
396
699
|
} else {
|
|
397
700
|
$requested = $ModuleOrder | Where-Object { -not $ModuleOptional.ContainsKey($_) }
|
|
398
701
|
}
|
|
@@ -400,6 +703,66 @@ function Resolve-Selection {
|
|
|
400
703
|
return $ModuleOrder | Where-Object { $requested -contains $_ }
|
|
401
704
|
}
|
|
402
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
|
+
|
|
403
766
|
# ==============================================================================
|
|
404
767
|
# Main
|
|
405
768
|
# ==============================================================================
|
|
@@ -418,10 +781,29 @@ Write-Log INFO "bootstrap" "modules_selected" "Modules queued" 0 ("modules=" + (
|
|
|
418
781
|
$failed = @()
|
|
419
782
|
foreach ($m in $selected) {
|
|
420
783
|
try { & $ModuleFn[$m] }
|
|
421
|
-
catch {
|
|
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
|
+
}
|
|
422
801
|
}
|
|
423
802
|
|
|
424
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" }
|
|
425
807
|
@"
|
|
426
808
|
# AI Dev Suite — Engineering Report (Windows)
|
|
427
809
|
|
|
@@ -431,9 +813,9 @@ foreach ($m in $selected) {
|
|
|
431
813
|
**Failed modules:** $(if ($failed) { $failed -join ' ' } else { 'none' })
|
|
432
814
|
|
|
433
815
|
## Versions
|
|
434
|
-
- Node: $
|
|
435
|
-
- npm: $
|
|
436
|
-
- Go: $
|
|
816
|
+
- Node: $reportNode
|
|
817
|
+
- npm: $reportNpm
|
|
818
|
+
- Go: $reportGo
|
|
437
819
|
- pi: $(if (Test-Cmd pi) { 'installed' } else { 'n/a' })
|
|
438
820
|
|
|
439
821
|
Logs: $HumanLog ; $JsonlLog
|
|
@@ -443,6 +825,12 @@ Write-Host "`n============================================================" -For
|
|
|
443
825
|
Write-Host " AI Dev Suite (Windows) setup finished" -ForegroundColor Green
|
|
444
826
|
Write-Host "============================================================" -ForegroundColor Green
|
|
445
827
|
Write-Host "Modules : $($selected -join ' ')"
|
|
446
|
-
if ($failed) {
|
|
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
|
|
447
835
|
Write-Host "Report : $ReportFile"
|
|
448
836
|
Write-Host "`nNext: open a new terminal so PATH updates apply."
|