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.
Files changed (32) hide show
  1. package/README.md +148 -9
  2. package/assets/codechat-agent.ico +0 -0
  3. package/dist/adapters/codex.js +19 -0
  4. package/dist/adapters/index.js +2 -0
  5. package/dist/commands/doctor/utils.js +2 -0
  6. package/dist/commands/init.js +37 -129
  7. package/dist/commands/shell-integration.js +40 -3
  8. package/dist/commands/wiki.js +127 -0
  9. package/dist/config/index.js +2 -1
  10. package/dist/config/merge-options.js +19 -0
  11. package/dist/config/normalize.js +18 -0
  12. package/dist/core/state.js +39 -0
  13. package/dist/index.js +58 -10
  14. package/dist/lib/agent-launcher.js +14 -16
  15. package/dist/lib/codechat-runner-resolver.js +208 -0
  16. package/dist/lib/wiki/run-wiki-generation.js +161 -0
  17. package/dist/lib/win-registry.js +10 -0
  18. package/dist/utils/validate-args.js +1 -0
  19. package/dist/wiki/repowikiIndexer.js +19 -150
  20. package/dist/wiki/repowikiPrompts.js +296 -0
  21. package/dist/wiki/repowikiStructureNormalize.js +31 -1
  22. package/dist/wiki/wikiTasksWriter.js +59 -22
  23. package/docs/agent-launcher-design.md +127 -26
  24. package/docs/standalone-codechat-ps1.md +123 -0
  25. package/package.json +2 -1
  26. package/scripts/preuninstall.js +45 -5
  27. package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +2 -2
  28. package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +1 -1
  29. package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +1 -1
  30. package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +15 -6
  31. package/templates/codechat/Start-CodeChat.ps1 +359 -0
  32. 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
+ }
@@ -55,3 +55,10 @@ convert:
55
55
  output: ./my-app-harness
56
56
  type: requirements
57
57
  yes: true
58
+
59
+ # wiki:对任意代码工程目录生成 baseline wiki(独立于 harness 生命周期)
60
+ wiki:
61
+ wikiSource: .
62
+ output: ./wiki
63
+ generateWiki: false
64
+ wikiLang: zh