svharness 0.14.13 → 0.14.17
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 +148 -9
- package/assets/codechat-agent.ico +0 -0
- package/dist/adapters/codex.js +19 -0
- package/dist/adapters/index.js +2 -0
- package/dist/commands/doctor/utils.js +2 -0
- package/dist/commands/init.js +37 -129
- package/dist/commands/shell-integration.js +40 -3
- package/dist/commands/wiki.js +127 -0
- package/dist/config/index.js +2 -1
- package/dist/config/merge-options.js +19 -0
- package/dist/config/normalize.js +18 -0
- package/dist/core/state.js +39 -0
- package/dist/index.js +58 -10
- package/dist/lib/agent-launcher.js +14 -16
- package/dist/lib/codechat-runner-resolver.js +208 -0
- package/dist/lib/wiki/run-wiki-generation.js +161 -0
- package/dist/lib/win-registry.js +10 -0
- package/dist/utils/validate-args.js +1 -0
- package/dist/wiki/repowikiIndexer.js +19 -150
- package/dist/wiki/repowikiPrompts.js +296 -0
- package/dist/wiki/repowikiStructureNormalize.js +31 -1
- package/dist/wiki/wikiTasksWriter.js +59 -22
- package/docs/agent-launcher-design.md +127 -26
- package/docs/standalone-codechat-ps1.md +123 -0
- package/package.json +2 -1
- package/scripts/preuninstall.js +45 -5
- package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +2 -2
- package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +1 -1
- package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +1 -1
- package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +15 -6
- package/templates/codechat/Start-CodeChat.ps1 +359 -0
- package/templates/svharness.config.example.yaml +7 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Standalone CodeChat launcher (no Node / svharness required).
|
|
2
|
+
# UTF-8 BOM required for PS 5.1 when menu labels contain non-ASCII.
|
|
3
|
+
param(
|
|
4
|
+
[ValidateSet('Start', 'InstallMenu', 'UninstallMenu', 'Status')]
|
|
5
|
+
[string]$Action = 'Start',
|
|
6
|
+
[string]$WorkDir = (Get-Location).Path,
|
|
7
|
+
[switch]$NoSyncEnv,
|
|
8
|
+
[switch]$NoSkipPermissions
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
$ErrorActionPreference = 'Stop'
|
|
12
|
+
|
|
13
|
+
$script:MenuLabel = '在此启动 CodeChat CLI'
|
|
14
|
+
$script:MenuKeyBg = 'HKCU:\Software\Classes\Directory\Background\shell\CodeChatStandaloneAgent'
|
|
15
|
+
$script:MenuKeyDir = 'HKCU:\Software\Classes\Directory\shell\CodeChatStandaloneAgent'
|
|
16
|
+
$script:SvharnessMenuKeyBg = 'HKCU:\Software\Classes\Directory\Background\shell\SvharnessLaunchCodeChatAgent'
|
|
17
|
+
$script:SvharnessMenuKeyDir = 'HKCU:\Software\Classes\Directory\shell\SvharnessLaunchCodeChatAgent'
|
|
18
|
+
$script:StandaloneDir = Join-Path $env:LOCALAPPDATA 'codechat-standalone'
|
|
19
|
+
$script:InstalledScript = Join-Path $script:StandaloneDir 'Start-CodeChat.ps1'
|
|
20
|
+
$script:InstalledIcon = Join-Path $script:StandaloneDir 'codechat-agent.ico'
|
|
21
|
+
$script:RunnerEnvVar = 'CODECHAT_CLI_RUNNER'
|
|
22
|
+
|
|
23
|
+
function Test-RunnerPath {
|
|
24
|
+
param([string]$Path)
|
|
25
|
+
if (-not $Path) { return $false }
|
|
26
|
+
try {
|
|
27
|
+
$null = Get-Item -LiteralPath $Path -ErrorAction Stop
|
|
28
|
+
return $true
|
|
29
|
+
} catch {
|
|
30
|
+
return $false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Get-IdeVersionArray {
|
|
35
|
+
param(
|
|
36
|
+
[string]$InstallDirName,
|
|
37
|
+
[string]$DirPrefix
|
|
38
|
+
)
|
|
39
|
+
if (-not $InstallDirName.StartsWith($DirPrefix)) {
|
|
40
|
+
return @(0)
|
|
41
|
+
}
|
|
42
|
+
$rest = $InstallDirName.Substring($DirPrefix.Length)
|
|
43
|
+
if ($rest -match '^(\d+)(?:\.(\d+))?(?:\.(\d+))?') {
|
|
44
|
+
$parts = @()
|
|
45
|
+
if ($matches[1]) { $parts += [int]$matches[1] }
|
|
46
|
+
if ($matches[2]) { $parts += [int]$matches[2] }
|
|
47
|
+
if ($matches[3]) { $parts += [int]$matches[3] }
|
|
48
|
+
return $parts
|
|
49
|
+
}
|
|
50
|
+
return @(0)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Compare-VersionArraysDesc {
|
|
54
|
+
param(
|
|
55
|
+
[int[]]$A,
|
|
56
|
+
[int[]]$B
|
|
57
|
+
)
|
|
58
|
+
$len = [Math]::Max($A.Count, $B.Count)
|
|
59
|
+
for ($i = 0; $i -lt $len; $i++) {
|
|
60
|
+
$av = if ($i -lt $A.Count) { $A[$i] } else { 0 }
|
|
61
|
+
$bv = if ($i -lt $B.Count) { $B[$i] } else { 0 }
|
|
62
|
+
if ($av -ne $bv) {
|
|
63
|
+
return $bv - $av
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function Get-IdeScanRoots {
|
|
70
|
+
$roaming = if ($env:APPDATA) { $env:APPDATA } else { Join-Path $HOME 'AppData\Roaming' }
|
|
71
|
+
return @(
|
|
72
|
+
@{ Root = Join-Path $roaming 'Google'; DirPrefix = 'AndroidStudio'; LabelPrefix = 'Android Studio' },
|
|
73
|
+
@{ Root = Join-Path $roaming 'JetBrains'; DirPrefix = 'IntelliJIdea'; LabelPrefix = 'IntelliJ IDEA' },
|
|
74
|
+
@{ Root = Join-Path $roaming 'JetBrains'; DirPrefix = 'IdeaIC'; LabelPrefix = 'IntelliJ IDEA Community' }
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Find-CodeChatRunner {
|
|
79
|
+
$standalone = Join-Path $HOME '.codechat\cli_app\run.bat'
|
|
80
|
+
|
|
81
|
+
if ($env:CODECHAT_CLI_RUNNER) {
|
|
82
|
+
$envRunner = $env:CODECHAT_CLI_RUNNER.Trim()
|
|
83
|
+
if (-not (Test-RunnerPath -Path $envRunner)) {
|
|
84
|
+
throw "$($script:RunnerEnvVar) is not accessible: $envRunner"
|
|
85
|
+
}
|
|
86
|
+
return [PSCustomObject]@{
|
|
87
|
+
Runner = (Resolve-Path -LiteralPath $envRunner).Path
|
|
88
|
+
Source = 'env'
|
|
89
|
+
Label = $script:RunnerEnvVar
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (Test-RunnerPath -Path $standalone) {
|
|
94
|
+
return [PSCustomObject]@{
|
|
95
|
+
Runner = (Resolve-Path -LiteralPath $standalone).Path
|
|
96
|
+
Source = 'standalone'
|
|
97
|
+
Label = $null
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
$candidates = @()
|
|
102
|
+
foreach ($scan in Get-IdeScanRoots) {
|
|
103
|
+
if (-not (Test-Path -LiteralPath $scan.Root)) { continue }
|
|
104
|
+
Get-ChildItem -LiteralPath $scan.Root -Directory -ErrorAction SilentlyContinue |
|
|
105
|
+
Where-Object { $_.Name -like ($scan.DirPrefix + '*') } |
|
|
106
|
+
ForEach-Object {
|
|
107
|
+
$runner = Join-Path $_.FullName 'plugins\CodeChat\cli\run.bat'
|
|
108
|
+
if (-not (Test-RunnerPath -Path $runner)) { return }
|
|
109
|
+
$mtime = 0
|
|
110
|
+
try { $mtime = (Get-Item -LiteralPath $runner).LastWriteTimeUtc.Ticks } catch {}
|
|
111
|
+
$verPart = $_.Name.Substring($scan.DirPrefix.Length)
|
|
112
|
+
$label = if ($verPart) { "$($scan.LabelPrefix) $verPart" } else { $scan.LabelPrefix }
|
|
113
|
+
$candidates += [PSCustomObject]@{
|
|
114
|
+
Runner = (Resolve-Path -LiteralPath $runner).Path
|
|
115
|
+
Source = 'ide-plugin'
|
|
116
|
+
Label = $label
|
|
117
|
+
Version = Get-IdeVersionArray -InstallDirName $_.Name -DirPrefix $scan.DirPrefix
|
|
118
|
+
Mtime = $mtime
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if ($candidates.Count -eq 0) {
|
|
124
|
+
$lines = @(
|
|
125
|
+
'CodeChat CLI (run.bat) not found. Tried:',
|
|
126
|
+
" 1. env $script:RunnerEnvVar (not set)",
|
|
127
|
+
" 2. standalone $standalone",
|
|
128
|
+
' 3. IDE plugin scan:'
|
|
129
|
+
)
|
|
130
|
+
foreach ($scan in Get-IdeScanRoots) {
|
|
131
|
+
$lines += " $(Join-Path $scan.Root ($scan.DirPrefix + '*\plugins\CodeChat\cli\run.bat'))"
|
|
132
|
+
}
|
|
133
|
+
$lines += ''
|
|
134
|
+
$lines += "Install CodeChat CLI or IDE plugin, or set $script:RunnerEnvVar."
|
|
135
|
+
throw ($lines -join [Environment]::NewLine)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
$best = $null
|
|
139
|
+
foreach ($c in $candidates) {
|
|
140
|
+
if (-not $best) {
|
|
141
|
+
$best = $c
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
$vc = Compare-VersionArraysDesc -A $c.Version -B $best.Version
|
|
145
|
+
if ($vc -lt 0) {
|
|
146
|
+
$best = $c
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
if ($vc -eq 0 -and $c.Mtime -gt $best.Mtime) {
|
|
150
|
+
$best = $c
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return [PSCustomObject]@{
|
|
155
|
+
Runner = $best.Runner
|
|
156
|
+
Source = $best.Source
|
|
157
|
+
Label = $best.Label
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function Sync-ProjectEnv {
|
|
162
|
+
param(
|
|
163
|
+
[string]$ProjectRoot,
|
|
164
|
+
[switch]$SkipSync
|
|
165
|
+
)
|
|
166
|
+
$projectEnv = Join-Path $ProjectRoot '.claude\.env'
|
|
167
|
+
$userEnv = Join-Path $HOME '.claude\.env'
|
|
168
|
+
|
|
169
|
+
if (-not $SkipSync -and (Test-Path -LiteralPath $projectEnv)) {
|
|
170
|
+
$userDir = Split-Path -Parent $userEnv
|
|
171
|
+
if (-not (Test-Path -LiteralPath $userDir)) {
|
|
172
|
+
New-Item -ItemType Directory -Force -Path $userDir | Out-Null
|
|
173
|
+
}
|
|
174
|
+
Copy-Item -LiteralPath $projectEnv -Destination $userEnv -Force
|
|
175
|
+
Write-Host "[info] synced project env -> $userEnv (project file unchanged)"
|
|
176
|
+
return $userEnv
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (Test-Path -LiteralPath $userEnv) {
|
|
180
|
+
return $userEnv
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
throw "No env file. Create $projectEnv or $userEnv"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function Initialize-ConsoleUtf8 {
|
|
187
|
+
try { chcp 65001 | Out-Null } catch {}
|
|
188
|
+
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
189
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
190
|
+
$script:OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function Invoke-CodeChatStart {
|
|
194
|
+
param(
|
|
195
|
+
[string]$TargetWorkDir,
|
|
196
|
+
[switch]$SkipEnvSync,
|
|
197
|
+
[switch]$SkipPermissions
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
Initialize-ConsoleUtf8
|
|
201
|
+
|
|
202
|
+
$resolvedWorkDir = (Resolve-Path -LiteralPath $TargetWorkDir).Path
|
|
203
|
+
$envFile = Sync-ProjectEnv -ProjectRoot $resolvedWorkDir -SkipSync:$SkipEnvSync
|
|
204
|
+
$resolved = Find-CodeChatRunner
|
|
205
|
+
|
|
206
|
+
$sourceLog = $resolved.Source
|
|
207
|
+
if ($resolved.Label) {
|
|
208
|
+
$sourceLog = "$sourceLog`: $($resolved.Label)"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Write-Host "CodeChat CLI: $($resolved.Runner) ($sourceLog)"
|
|
212
|
+
Write-Host "workdir: $resolvedWorkDir"
|
|
213
|
+
Write-Host "env: $envFile"
|
|
214
|
+
|
|
215
|
+
$args = @(
|
|
216
|
+
"--env-file=`"$envFile`"",
|
|
217
|
+
"--cwd=`"$resolvedWorkDir`""
|
|
218
|
+
)
|
|
219
|
+
if (-not $SkipPermissions) {
|
|
220
|
+
$args += '--dangerously-skip-permissions'
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
& $resolved.Runner @args
|
|
224
|
+
exit $LASTEXITCODE
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function Get-WtExecutable {
|
|
228
|
+
try {
|
|
229
|
+
$where = (& where.exe wt 2>$null | Select-Object -First 1)
|
|
230
|
+
if ($where -and (Test-RunnerPath -Path $where)) {
|
|
231
|
+
return $where.Trim()
|
|
232
|
+
}
|
|
233
|
+
} catch {}
|
|
234
|
+
|
|
235
|
+
$localWt = Join-Path $env:LOCALAPPDATA 'Microsoft\WindowsApps\wt.exe'
|
|
236
|
+
if (Test-RunnerPath -Path $localWt) {
|
|
237
|
+
return $localWt
|
|
238
|
+
}
|
|
239
|
+
return $null
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function Copy-OptionalIcon {
|
|
243
|
+
param([string]$SourceScriptPath)
|
|
244
|
+
$candidates = @(
|
|
245
|
+
(Join-Path (Split-Path -Parent $SourceScriptPath) 'codechat-agent.ico'),
|
|
246
|
+
(Join-Path (Split-Path -Parent $SourceScriptPath) '..\assets\codechat-agent.ico'),
|
|
247
|
+
(Join-Path (Split-Path -Parent $SourceScriptPath) '..\..\assets\codechat-agent.ico')
|
|
248
|
+
)
|
|
249
|
+
foreach ($icon in $candidates) {
|
|
250
|
+
try {
|
|
251
|
+
$resolved = Resolve-Path -LiteralPath $icon -ErrorAction Stop
|
|
252
|
+
Copy-Item -LiteralPath $resolved.Path -Destination $script:InstalledIcon -Force
|
|
253
|
+
return $true
|
|
254
|
+
} catch {}
|
|
255
|
+
}
|
|
256
|
+
return $false
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function Register-ExplorerMenu {
|
|
260
|
+
param(
|
|
261
|
+
[string]$MenuKey,
|
|
262
|
+
[string]$Command,
|
|
263
|
+
[string]$IconPath
|
|
264
|
+
)
|
|
265
|
+
New-Item -Path $MenuKey -Force | Out-Null
|
|
266
|
+
Set-ItemProperty -Path $MenuKey -Name '(default)' -Value $script:MenuLabel
|
|
267
|
+
if ($IconPath -and (Test-Path -LiteralPath $IconPath)) {
|
|
268
|
+
Set-ItemProperty -Path $MenuKey -Name 'Icon' -Value "$IconPath,0"
|
|
269
|
+
}
|
|
270
|
+
New-Item -Path "$MenuKey\command" -Force | Out-Null
|
|
271
|
+
Set-ItemProperty -Path "$MenuKey\command" -Name '(default)' -Value $Command
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function Install-CodeChatContextMenu {
|
|
275
|
+
$sourceScript = $PSCommandPath
|
|
276
|
+
if (-not (Test-Path -LiteralPath $sourceScript)) {
|
|
277
|
+
throw "Cannot resolve script path: $sourceScript"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Avoid duplicate menu entries: overwrite svharness menu if present.
|
|
281
|
+
foreach ($key in @($script:SvharnessMenuKeyBg, $script:SvharnessMenuKeyDir, $script:MenuKeyBg, $script:MenuKeyDir)) {
|
|
282
|
+
if (Test-Path -LiteralPath $key) {
|
|
283
|
+
try { Remove-Item -LiteralPath $key -Recurse -Force } catch {}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (-not (Test-Path -LiteralPath $script:StandaloneDir)) {
|
|
288
|
+
New-Item -ItemType Directory -Force -Path $script:StandaloneDir | Out-Null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
Copy-Item -LiteralPath $sourceScript -Destination $script:InstalledScript -Force
|
|
292
|
+
$hasIcon = Copy-OptionalIcon -SourceScriptPath $sourceScript
|
|
293
|
+
|
|
294
|
+
$psArgs = "-NoExit -NoProfile -ExecutionPolicy Bypass -File `"$($script:InstalledScript)`" -Action Start -WorkDir"
|
|
295
|
+
$wt = Get-WtExecutable
|
|
296
|
+
|
|
297
|
+
function Build-MenuCommand {
|
|
298
|
+
param([string]$DirPlaceholder)
|
|
299
|
+
$fullPs = "$psArgs `"$DirPlaceholder`""
|
|
300
|
+
if ($wt) {
|
|
301
|
+
return "`"$wt`" -d $DirPlaceholder --title CodeChat -- powershell.exe $fullPs"
|
|
302
|
+
}
|
|
303
|
+
return "powershell.exe $fullPs"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
Register-ExplorerMenu -MenuKey $script:MenuKeyBg -Command (Build-MenuCommand -DirPlaceholder '"%V"') -IconPath $(if ($hasIcon) { $script:InstalledIcon } else { $null })
|
|
307
|
+
Register-ExplorerMenu -MenuKey $script:MenuKeyDir -Command (Build-MenuCommand -DirPlaceholder '"%1"') -IconPath $(if ($hasIcon) { $script:InstalledIcon } else { $null })
|
|
308
|
+
|
|
309
|
+
Write-Host "[ok] Context menu registered: $($script:MenuLabel)"
|
|
310
|
+
Write-Host "[info] script: $($script:InstalledScript)"
|
|
311
|
+
if ($wt) {
|
|
312
|
+
Write-Host "[info] terminal: Windows Terminal"
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function Uninstall-CodeChatContextMenu {
|
|
317
|
+
foreach ($key in @($script:MenuKeyBg, $script:MenuKeyDir)) {
|
|
318
|
+
if (Test-Path -LiteralPath $key) {
|
|
319
|
+
Remove-Item -LiteralPath $key -Recurse -Force
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (Test-Path -LiteralPath $script:StandaloneDir) {
|
|
323
|
+
Remove-Item -LiteralPath $script:StandaloneDir -Recurse -Force
|
|
324
|
+
}
|
|
325
|
+
Write-Host '[ok] Standalone context menu removed (svharness menu unchanged)'
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function Show-CodeChatStatus {
|
|
329
|
+
Write-Host '>> Standalone CodeChat launcher status'
|
|
330
|
+
Write-Host " installed script: $(if (Test-Path -LiteralPath $script:InstalledScript) { $script:InstalledScript } else { '(not installed)' })"
|
|
331
|
+
Write-Host " background menu: $(if (Test-Path -LiteralPath $script:MenuKeyBg) { 'yes' } else { 'no' })"
|
|
332
|
+
Write-Host " directory menu: $(if (Test-Path -LiteralPath $script:MenuKeyDir) { 'yes' } else { 'no' })"
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
$runner = Find-CodeChatRunner
|
|
336
|
+
$label = if ($runner.Label) { "$($runner.Source): $($runner.Label)" } else { $runner.Source }
|
|
337
|
+
Write-Host " runner: $($runner.Runner) ($label)"
|
|
338
|
+
} catch {
|
|
339
|
+
Write-Host " runner: (not found)"
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
$userEnv = Join-Path $HOME '.claude\.env'
|
|
343
|
+
Write-Host " user env: $(if (Test-Path -LiteralPath $userEnv) { $userEnv } else { '(missing)' })"
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
switch ($Action) {
|
|
347
|
+
'Start' {
|
|
348
|
+
Invoke-CodeChatStart -TargetWorkDir $WorkDir -SkipEnvSync:$NoSyncEnv -SkipPermissions:$NoSkipPermissions
|
|
349
|
+
}
|
|
350
|
+
'InstallMenu' {
|
|
351
|
+
Install-CodeChatContextMenu
|
|
352
|
+
}
|
|
353
|
+
'UninstallMenu' {
|
|
354
|
+
Uninstall-CodeChatContextMenu
|
|
355
|
+
}
|
|
356
|
+
'Status' {
|
|
357
|
+
Show-CodeChatStatus
|
|
358
|
+
}
|
|
359
|
+
}
|