@ran-sh/dsh-crew 0.5.1 → 0.5.3

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/src/workflow.mjs CHANGED
@@ -58,10 +58,68 @@ export function canTransition(from, to) {
58
58
  return allowed.includes(to);
59
59
  }
60
60
 
61
- function splitSection(value) {
62
- if (typeof value !== 'string' || value.trim() === '') return [];
63
- return value.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
64
- }
61
+ function splitSection(value) {
62
+ if (typeof value !== 'string' || value.trim() === '') return [];
63
+ return value.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
64
+ }
65
+
66
+ const NO_CHANGE_SENTINELS = new Set([
67
+ 'no files changed',
68
+ 'no file changed',
69
+ 'no changes',
70
+ '无文件变更',
71
+ '没有文件变更',
72
+ '未更改任何文件',
73
+ '无变更',
74
+ ]);
75
+
76
+ function deliveryClaimsChanges(outcome) {
77
+ return Array.isArray(outcome?.changes) && outcome.changes.length > 0;
78
+ }
79
+
80
+ export function applyWorkspaceEvidence(outcome, {
81
+ evidenceAvailable = false,
82
+ hasChanges = false,
83
+ allowNoChanges = false,
84
+ requireNoChangeAuthorization = true,
85
+ } = {}) {
86
+ const next = { ...outcome };
87
+ const claimsChanges = deliveryClaimsChanges(next);
88
+ if (evidenceAvailable && next.execution_status === 'completed') {
89
+ next.workspace_evidence_ok = claimsChanges === hasChanges;
90
+ }
91
+ const tests = Array.isArray(next.tests) ? next.tests : [];
92
+ const verifiedNoChange = requireNoChangeAuthorization === true
93
+ && evidenceAvailable === true
94
+ && allowNoChanges === true
95
+ && hasChanges === false
96
+ && claimsChanges === false
97
+ && next.execution_status === 'completed'
98
+ && next.workspace_evidence_ok === true
99
+ && next.delivery?.complete === true
100
+ && tests.some((test) => test.status === 'PASS')
101
+ && !tests.some((test) => test.status === 'FAIL');
102
+ if (verifiedNoChange) {
103
+ next.task_status = 'success';
104
+ next.no_change_verified = true;
105
+ } else if (requireNoChangeAuthorization === true && claimsChanges === false && next.task_status === 'success') {
106
+ next.task_status = 'partial';
107
+ delete next.no_change_verified;
108
+ }
109
+ return next;
110
+ }
111
+
112
+ function parseChanges(section) {
113
+ return splitSection(section).filter((line) => {
114
+ const normalized = line
115
+ .replace(/^(?:[-*+]\s+)+/, '')
116
+ .replace(/[`"'“”‘’]/g, '')
117
+ .replace(/[.!。!]+$/g, '')
118
+ .trim()
119
+ .toLowerCase();
120
+ return !NO_CHANGE_SENTINELS.has(normalized);
121
+ });
122
+ }
65
123
 
66
124
  function parseTests(section) {
67
125
  return splitSection(section).map((line) => {
@@ -106,7 +164,7 @@ export function buildOutcome({ result = '', deliveryMeta, executionStatus, stopR
106
164
  }),
107
165
  confidence: null,
108
166
  needs_escalation: false,
109
- changes: splitSection(parsed.sections.Diff),
167
+ changes: parseChanges(parsed.sections.Diff),
110
168
  tests,
111
169
  tests_status: testsStatus ?? null,
112
170
  risks: splitSection(parsed.sections.Risks),
@@ -0,0 +1,57 @@
1
+ @echo off
2
+ setlocal EnableExtensions
3
+ title DSH Crew Launcher
4
+
5
+ set "LAUNCH_REQUEST=%*"
6
+ set "LAUNCH_MODE=open"
7
+ set "LAUNCH_DIR=%~dp0"
8
+
9
+ if "%~1"=="" goto :run
10
+ if /i "%~1"=="--background" (
11
+ set "LAUNCH_MODE=background"
12
+ shift
13
+ goto :validate
14
+ )
15
+ if /i "%~1"=="--open" (
16
+ set "LAUNCH_MODE=open"
17
+ shift
18
+ goto :validate
19
+ )
20
+ if /i "%~1"=="--watch" (
21
+ set "LAUNCH_MODE=watch"
22
+ shift
23
+ goto :validate
24
+ )
25
+ if /i "%~1"=="--help" goto :help
26
+ goto :invalid_argument
27
+
28
+ :validate
29
+ if not "%~1"=="" goto :invalid_argument
30
+
31
+ :run
32
+ set "LAUNCH_HELPER=%LAUNCH_DIR%start-dsh-crew.ps1"
33
+ set "LAUNCH_LOG=%TEMP%\dsh-crew-launcher.log"
34
+ if not exist "%LAUNCH_HELPER%" (
35
+ >>"%LAUNCH_LOG%" echo [%date% %time%] ERROR Managed launcher helper is missing: %LAUNCH_HELPER%
36
+ echo ERROR: DSH Crew launcher helper is missing.
37
+ echo Repair it with: dsh-crew update
38
+ if /i "%LAUNCH_MODE%"=="open" pause
39
+ exit /b 1
40
+ )
41
+
42
+ powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%LAUNCH_HELPER%" -Mode "%LAUNCH_MODE%"
43
+ set "LAUNCH_EXIT=%ERRORLEVEL%"
44
+ if not "%LAUNCH_EXIT%"=="0" if /i "%LAUNCH_MODE%"=="open" pause
45
+ exit /b %LAUNCH_EXIT%
46
+
47
+ :invalid_argument
48
+ echo ERROR: Unsupported launcher arguments: %LAUNCH_REQUEST%
49
+ echo Use --open, --background, or --watch.
50
+ exit /b 64
51
+
52
+ :help
53
+ echo Usage: %~nx0 [--open ^| --background ^| --watch]
54
+ echo --open Start both services and open http://127.0.0.1:3080/.
55
+ echo --background Start both services silently without opening a browser.
56
+ echo --watch Keep both services healthy and restart them after an exit.
57
+ exit /b 0
@@ -0,0 +1,326 @@
1
+ # DSH Crew managed Windows launcher
2
+ [CmdletBinding()]
3
+ param(
4
+ [ValidateSet('background', 'open', 'watch')]
5
+ [string] $Mode = 'open'
6
+ )
7
+
8
+ Set-StrictMode -Version Latest
9
+ $ErrorActionPreference = 'Stop'
10
+
11
+ $crewHome = Join-Path $env:USERPROFILE '.config\dsh-crew\harness'
12
+ $officialHome = Join-Path $env:USERPROFILE '.dsh'
13
+ $dshCli = Join-Path $crewHome 'runtime\node_modules\.bin\dsh.cmd'
14
+ $logRoot = if ($env:TEMP) { $env:TEMP } else { [System.IO.Path]::GetTempPath() }
15
+ $launcherLog = Join-Path $logRoot 'dsh-crew-launcher.log'
16
+ $startedAt = Get-Date
17
+ $services = @(
18
+ [pscustomobject]@{ Name = 'Crew backend'; Profile = 'dsh-crew'; Home = $crewHome; Port = 3210; Url = 'http://127.0.0.1:3210'; State = 'pending'; Process = $null; RootPid = $null; RootStartedAtUtcTicks = $null; ListenerPid = $null; ListenerStartedAtUtcTicks = $null; ConsecutiveFailures = 0; LastError = $null },
19
+ [pscustomobject]@{ Name = 'Official UI'; Profile = 'web'; Home = $officialHome; Port = 3080; Url = 'http://127.0.0.1:3080'; State = 'pending'; Process = $null; RootPid = $null; RootStartedAtUtcTicks = $null; ListenerPid = $null; ListenerStartedAtUtcTicks = $null; ConsecutiveFailures = 0; LastError = $null }
20
+ )
21
+
22
+ function Write-LaunchLog {
23
+ param([string] $Message, [ValidateSet('INFO', 'WARN', 'ERROR')] [string] $Level = 'INFO')
24
+ $line = '[{0}] [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-ddTHH:mm:ss.fffK'), $Level, $Message
25
+ Add-Content -LiteralPath $launcherLog -Value $line -Encoding UTF8
26
+ if ($Mode -eq 'open') {
27
+ if ($Level -eq 'ERROR') { Write-Host $line -ForegroundColor Red }
28
+ elseif ($Level -eq 'WARN') { Write-Host $line -ForegroundColor Yellow }
29
+ else { Write-Host $line }
30
+ }
31
+ }
32
+
33
+ function Get-HealthState {
34
+ param([pscustomobject] $Service)
35
+ try {
36
+ $response = Invoke-RestMethod -Uri ($Service.Url + '/_dsh/dsh-crew/extension') -TimeoutSec 2
37
+ $version = $response.extension.runtime.runtime_version
38
+ if ($response.ok -eq $true -and $version) {
39
+ return [pscustomobject]@{ Ready = $true; Version = [string] $version; Error = $null }
40
+ }
41
+ return [pscustomobject]@{ Ready = $false; Version = $null; Error = 'Response did not contain a ready runtime contract.' }
42
+ } catch {
43
+ return [pscustomobject]@{ Ready = $false; Version = $null; Error = $_.Exception.Message }
44
+ }
45
+ }
46
+
47
+ function Get-PortState {
48
+ param([int] $Port)
49
+ try {
50
+ $listeners = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().GetActiveTcpListeners()
51
+ $occupied = @($listeners | Where-Object Port -eq $Port).Count -gt 0
52
+ if (-not $occupied) {
53
+ return [pscustomobject]@{ State = 'free'; Error = $null; Pid = $null }
54
+ }
55
+
56
+ $ownerPid = $null
57
+ try {
58
+ $ownerPid = (Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction Stop | Select-Object -First 1).OwningProcess
59
+ } catch { }
60
+ return [pscustomobject]@{ State = 'occupied'; Error = $null; Pid = $ownerPid }
61
+ } catch {
62
+ return [pscustomobject]@{ State = 'unknown'; Error = ('Listener enumeration failed: {0}' -f $_.Exception.Message); Pid = $null }
63
+ }
64
+ }
65
+
66
+ function Test-TrackedProcessIdentity {
67
+ param([int] $ProcessId, [long] $ExpectedStartTicks, [object[]] $ProcessTable)
68
+ if ($ProcessId -lt 1 -or $ExpectedStartTicks -lt 1) { return $false }
69
+ $record = @($ProcessTable | Where-Object { [int] $_.ProcessId -eq $ProcessId } | Select-Object -First 1)
70
+ if ($record.Count -eq 0) { return $false }
71
+ $startTicks = $null
72
+ if ($record[0].PSObject.Properties.Name -contains 'StartTicks') {
73
+ $startTicks = [long] $record[0].StartTicks
74
+ } else {
75
+ try { $startTicks = (Get-Process -Id $ProcessId -ErrorAction Stop).StartTime.ToUniversalTime().Ticks } catch { return $false }
76
+ }
77
+ return $startTicks -eq $ExpectedStartTicks
78
+ }
79
+
80
+ function Get-TrackedProcessTree {
81
+ param([pscustomobject] $Service, [object[]] $ProcessTable = $null)
82
+ $processes = if ($null -ne $ProcessTable) { @($ProcessTable) } else { @(Get-CimInstance Win32_Process -ErrorAction Stop) }
83
+ $owned = [System.Collections.Generic.HashSet[int]]::new()
84
+ $rootMatches = Test-TrackedProcessIdentity -ProcessId $Service.RootPid -ExpectedStartTicks $Service.RootStartedAtUtcTicks -ProcessTable $processes
85
+ $hasTrackedListener = $Service.ListenerPid -and $Service.ListenerStartedAtUtcTicks
86
+ if ($hasTrackedListener) {
87
+ $listenerMatches = Test-TrackedProcessIdentity -ProcessId $Service.ListenerPid -ExpectedStartTicks $Service.ListenerStartedAtUtcTicks -ProcessTable $processes
88
+ if (-not $listenerMatches) { return @() }
89
+ [void] $owned.Add([int] $Service.ListenerPid)
90
+ } elseif (-not $rootMatches) {
91
+ return @()
92
+ }
93
+ if ($rootMatches) { [void] $owned.Add([int] $Service.RootPid) }
94
+ do {
95
+ $added = $false
96
+ foreach ($candidate in $processes) {
97
+ $candidateId = [int] $candidate.ProcessId
98
+ $parentId = [int] $candidate.ParentProcessId
99
+ if ($owned.Contains($parentId) -and $owned.Add($candidateId)) { $added = $true }
100
+ }
101
+ } while ($added)
102
+ return @($owned | ForEach-Object { [int] $_ })
103
+ }
104
+
105
+ function Set-TrackedListenerIdentity {
106
+ param([pscustomobject] $Service)
107
+ $port = Get-PortState $Service.Port
108
+ if ($port.State -ne 'occupied' -or -not $port.Pid) { return $false }
109
+ $tree = @(Get-TrackedProcessTree -Service $Service)
110
+ if ($port.Pid -notin $tree) { return $false }
111
+ try {
112
+ $listener = Get-Process -Id $port.Pid -ErrorAction Stop
113
+ $Service.ListenerPid = [int] $port.Pid
114
+ $Service.ListenerStartedAtUtcTicks = $listener.StartTime.ToUniversalTime().Ticks
115
+ return $true
116
+ } catch {
117
+ return $false
118
+ }
119
+ }
120
+
121
+ function Stop-OwnedListener {
122
+ param([pscustomobject] $Service, [int] $ListenerPid)
123
+ if ($Mode -ne 'watch' -or -not $Service.ListenerPid -or $ListenerPid -ne $Service.ListenerPid) {
124
+ return $false
125
+ }
126
+ try {
127
+ $ownedProcessIds = @(Get-TrackedProcessTree -Service $Service)
128
+ if ($ListenerPid -notin $ownedProcessIds) { return $false }
129
+ Write-LaunchLog ('Supervisor confirmed owned listener PID={0} under tracked root PID={1}; stopping that process tree after {2} consecutive failed health checks.' -f $ListenerPid, $Service.RootPid, $Service.ConsecutiveFailures) 'WARN'
130
+ foreach ($processId in $ownedProcessIds) {
131
+ Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue
132
+ }
133
+ $deadline = (Get-Date).AddSeconds(5)
134
+ do {
135
+ $port = Get-PortState $Service.Port
136
+ if ($port.State -eq 'free') { return $true }
137
+ Start-Sleep -Milliseconds 250
138
+ } while ((Get-Date) -lt $deadline)
139
+ } catch {
140
+ Write-LaunchLog ('Supervisor could not stop its owned listener safely: {0}' -f $_.Exception.Message) 'WARN'
141
+ }
142
+ return $false
143
+ }
144
+
145
+ function Start-CrewService {
146
+ param([pscustomobject] $Service)
147
+ $serviceRunStamp = (Get-Date).ToString('yyyyMMdd-HHmmssfff')
148
+ $stdout = Join-Path $logRoot ('dsh-crew-{0}-{1}-{2}.out.log' -f $Service.Profile, $Service.Port, $serviceRunStamp)
149
+ $stderr = Join-Path $logRoot ('dsh-crew-{0}-{1}-{2}.err.log' -f $Service.Profile, $Service.Port, $serviceRunStamp)
150
+ $previousHome = $env:DSH_HOME
151
+ try {
152
+ $env:DSH_HOME = $Service.Home
153
+ $arguments = @('--profile', $Service.Profile, '--host', '127.0.0.1', '--port', [string] $Service.Port, '--no-open')
154
+ $process = Start-Process -FilePath $dshCli -ArgumentList $arguments -WindowStyle Hidden -PassThru `
155
+ -RedirectStandardOutput $stdout -RedirectStandardError $stderr
156
+ $Service.Process = $process
157
+ $Service.RootPid = $process.Id
158
+ $Service.RootStartedAtUtcTicks = $process.StartTime.ToUniversalTime().Ticks
159
+ $Service.ListenerPid = $null
160
+ $Service.ListenerStartedAtUtcTicks = $null
161
+ $Service.ConsecutiveFailures = 0
162
+ $Service.State = 'starting'
163
+ Write-LaunchLog ('Started {0} on port {1}; PID={2}; stdout={3}; stderr={4}' -f $Service.Profile, $Service.Port, $process.Id, $stdout, $stderr)
164
+ } finally {
165
+ $env:DSH_HOME = $previousHome
166
+ }
167
+ }
168
+
169
+ function Wait-CrewServices {
170
+ $deadline = (Get-Date).AddSeconds(90)
171
+ while (@($services | Where-Object State -eq 'starting').Count -gt 0 -and (Get-Date) -lt $deadline) {
172
+ foreach ($service in ($services | Where-Object State -eq 'starting')) {
173
+ $health = Get-HealthState $service
174
+ $service.LastError = $health.Error
175
+ if ($health.Ready) {
176
+ $service.State = 'ready'
177
+ $service.ConsecutiveFailures = 0
178
+ [void] (Set-TrackedListenerIdentity -Service $service)
179
+ Write-LaunchLog ('{0} ready on {1}; runtime={2}' -f $service.Name, $service.Port, $health.Version)
180
+ } elseif ($service.Process -and $service.Process.HasExited) {
181
+ throw ('{0} exited before becoming ready; PID={1}; exit={2}; last health error: {3}' -f $service.Name, $service.Process.Id, $service.Process.ExitCode, $health.Error)
182
+ }
183
+ }
184
+ if (@($services | Where-Object State -eq 'starting').Count -gt 0) { Start-Sleep -Milliseconds 500 }
185
+ }
186
+
187
+ $notReady = @($services | Where-Object State -ne 'ready')
188
+ if ($notReady.Count -gt 0) {
189
+ $details = ($notReady | ForEach-Object { '{0}:{1} ({2})' -f $_.Profile, $_.Port, $_.LastError }) -join '; '
190
+ throw "Startup health deadline exceeded: $details"
191
+ }
192
+ }
193
+
194
+ function Ensure-CrewServices {
195
+ param([switch] $QuietHealthy)
196
+
197
+ foreach ($service in $services) {
198
+ $health = Get-HealthState $service
199
+ if ($health.Ready) {
200
+ $wasReady = $service.State -eq 'ready'
201
+ $service.State = 'ready'
202
+ $service.ConsecutiveFailures = 0
203
+ $service.LastError = $null
204
+ if (-not $service.ListenerPid) { [void] (Set-TrackedListenerIdentity -Service $service) }
205
+ if (-not $QuietHealthy -or -not $wasReady) {
206
+ Write-LaunchLog ('{0} already ready on {1}; runtime={2}' -f $service.Name, $service.Port, $health.Version)
207
+ }
208
+ continue
209
+ }
210
+
211
+ $service.LastError = $health.Error
212
+ $service.ConsecutiveFailures += 1
213
+ if ($service.Process -and $service.Process.HasExited) {
214
+ Write-LaunchLog ('{0} process exited after startup; PID={1}; exit={2}' -f $service.Name, $service.Process.Id, $service.Process.ExitCode) 'WARN'
215
+ $service.Process = $null
216
+ }
217
+ $service.State = 'pending'
218
+
219
+ $port = Get-PortState $service.Port
220
+ if ($port.State -eq 'occupied') {
221
+ if ($service.ConsecutiveFailures -lt 3) {
222
+ throw ('Health check {0}/3 failed for {1}; owned process remains untouched until failure is confirmed. Health error: {2}' -f $service.ConsecutiveFailures, $service.Name, $health.Error)
223
+ }
224
+ if (Stop-OwnedListener -Service $service -ListenerPid $port.Pid) {
225
+ $service.Process = $null
226
+ $service.RootPid = $null
227
+ $service.RootStartedAtUtcTicks = $null
228
+ $service.ListenerPid = $null
229
+ $service.ListenerStartedAtUtcTicks = $null
230
+ $service.ConsecutiveFailures = 0
231
+ $port = Get-PortState $service.Port
232
+ }
233
+ }
234
+ if ($port.State -eq 'occupied') {
235
+ $owner = if ($port.Pid) { "; listener PID=$($port.Pid)" } else { '' }
236
+ throw ('Port {0} is occupied, but {1} failed its health contract{2}. Health error: {3}' -f $service.Port, $service.Name, $owner, $health.Error)
237
+ }
238
+ if ($port.State -ne 'free') {
239
+ throw ('Could not determine whether port {0} is available: {1}' -f $service.Port, $port.Error)
240
+ }
241
+ if ($Mode -eq 'watch') {
242
+ Write-LaunchLog ('Supervisor detected {0} unavailable on {1}; restarting it.' -f $service.Name, $service.Port) 'WARN'
243
+ }
244
+ Start-CrewService $service
245
+ }
246
+
247
+ Wait-CrewServices
248
+ }
249
+
250
+ function Start-ServiceSupervisor {
251
+ $mutex = [System.Threading.Mutex]::new($false, 'Local\DSHCrewServiceSupervisor')
252
+ $ownsMutex = $false
253
+ try {
254
+ try {
255
+ $ownsMutex = $mutex.WaitOne(0)
256
+ } catch [System.Threading.AbandonedMutexException] {
257
+ $ownsMutex = $true
258
+ }
259
+ if (-not $ownsMutex) {
260
+ Write-LaunchLog 'Supervisor already active; duplicate watcher exiting.'
261
+ return
262
+ }
263
+
264
+ Write-LaunchLog 'Supervisor active; monitoring 3080 and 3210 every 10 seconds.'
265
+ $lastRecoveryError = $null
266
+ while ($true) {
267
+ try {
268
+ Ensure-CrewServices -QuietHealthy
269
+ if ($lastRecoveryError) {
270
+ Write-LaunchLog 'Supervisor recovery succeeded; both services are healthy.'
271
+ $lastRecoveryError = $null
272
+ }
273
+ } catch {
274
+ $recoveryError = $_.Exception.Message
275
+ if ($recoveryError -ne $lastRecoveryError) {
276
+ Write-LaunchLog ('Supervisor recovery failed; will retry: {0}' -f $recoveryError) 'WARN'
277
+ $lastRecoveryError = $recoveryError
278
+ }
279
+ }
280
+ Start-Sleep -Seconds 10
281
+ }
282
+ } finally {
283
+ if ($ownsMutex) { $mutex.ReleaseMutex() }
284
+ $mutex.Dispose()
285
+ }
286
+ }
287
+
288
+ if ($env:DSH_CREW_LAUNCHER_TEST_IMPORT -eq '1') { return }
289
+
290
+ try {
291
+ New-Item -ItemType Directory -Path $logRoot -Force | Out-Null
292
+ Write-LaunchLog ('Launcher started; mode={0}; user={1}' -f $Mode, $env:USERNAME)
293
+
294
+ if (-not (Test-Path -LiteralPath $dshCli -PathType Leaf)) {
295
+ throw "DSH CLI was not found at $dshCli. Run: npm install -g @ran-sh/dsh-crew@latest; dsh-crew update"
296
+ }
297
+ if (-not (Test-Path -LiteralPath (Join-Path $crewHome 'profiles\dsh-crew\package.json') -PathType Leaf)) {
298
+ throw "The isolated dsh-crew profile is missing under $crewHome. Run: dsh-crew update"
299
+ }
300
+ if (-not (Test-Path -LiteralPath (Join-Path $officialHome 'profiles\web\package.json') -PathType Leaf)) {
301
+ throw "The official web profile is missing under $officialHome. Run: dsh-crew integrate"
302
+ }
303
+
304
+ if ($Mode -eq 'watch') {
305
+ Start-ServiceSupervisor
306
+ Write-LaunchLog 'Supervisor stopped.' 'WARN'
307
+ exit 0
308
+ }
309
+
310
+ Ensure-CrewServices
311
+
312
+ if ($Mode -eq 'open') {
313
+ Start-Process 'http://127.0.0.1:3080/' | Out-Null
314
+ Write-LaunchLog 'Opened daily console at http://127.0.0.1:3080/'
315
+ }
316
+ Write-LaunchLog ('Launcher completed successfully in {0:n1}s.' -f ((Get-Date) - $startedAt).TotalSeconds)
317
+ exit 0
318
+ } catch {
319
+ Write-LaunchLog $_.Exception.Message 'ERROR'
320
+ if ($Mode -eq 'open') {
321
+ Write-Host ''
322
+ Write-Host "Diagnostic log: $launcherLog" -ForegroundColor Yellow
323
+ Write-Host 'Startup failed. Review the diagnostic log and the service stdout/stderr paths recorded above.' -ForegroundColor Yellow
324
+ }
325
+ exit 1
326
+ }
@@ -0,0 +1,9 @@
1
+ Option Explicit
2
+
3
+ Dim shell, launcher, command
4
+ Set shell = CreateObject("WScript.Shell")
5
+ launcher = "__LAUNCHER__"
6
+ command = shell.ExpandEnvironmentStrings("%COMSPEC%") & " /d /c " & _
7
+ Chr(34) & Chr(34) & launcher & Chr(34) & " --watch" & Chr(34)
8
+
9
+ shell.Run command, 0, False
@@ -0,0 +1,26 @@
1
+ # Global capability-aware delegation policy for ZCode
2
+
3
+ ZCode is a host adapter for DSH Crew. Use the `dsh-crew` MCP server for Crew
4
+ work only after its live capability and readiness surfaces have been checked.
5
+
6
+ - Discover the current Crew configuration, capabilities, activation state and
7
+ readiness before delegating substantial work. Installed, configured, enabled
8
+ and callable are different states; do not infer one from another.
9
+ - Match a bounded work unit to an available Crew role/model and preserve the
10
+ repository/worktree and Result Contract boundaries. Keep planning, ambiguous
11
+ requirements, integration, external side effects and final communication in
12
+ the host agent.
13
+ - Dispatch selected work asynchronously with `dsh_spawn_worker`, save its
14
+ workflow ID, and poll that same workflow with `dsh_worker_result` using
15
+ `wait_seconds: 10`. A bounded wait that returns a nonterminal state is not a
16
+ failure when `dsh_worker_status` confirms that workflow is still running;
17
+ continue polling it and never start a duplicate workflow.
18
+ - If Crew is selected and any required capability is unavailable, non-callable,
19
+ or returns an unknown/runtime/configuration/credential/routing/timeout error,
20
+ pause. Report the evidence and wait for the operator to choose repair Crew or
21
+ continue locally; never silently fall back or retry blindly.
22
+ - Validate returned evidence, changed scope, tests and completion state before
23
+ accepting delegated work. Do not expose credentials or raw provider payloads.
24
+
25
+ This file is installed as a managed block in `~/.zcode/AGENTS.md`; user-authored
26
+ instructions outside the block are preserved.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: ds-reviewer
3
+ description: Independent DSH Crew reviewer dispatcher. Inspect completed changes read-only and return a structured verdict.
4
+ mcpServers:
5
+ - dsh-crew
6
+ tools:
7
+ - mcp__dsh-crew__dsh_run_worker
8
+ - mcp__dsh-crew__dsh_spawn_worker
9
+ - mcp__dsh-crew__dsh_worker_status
10
+ - mcp__dsh-crew__dsh_worker_result
11
+ - mcp__dsh-crew__dsh_worker_cancel
12
+ - mcp__dsh-crew__dsh_worker_config
13
+ ---
14
+
15
+ You are a thin, read-only dispatcher. Never edit files or implement fixes.
16
+
17
+ Check `dsh_worker_config` before dispatch. Derive one bounded, read-only review
18
+ task that contains only the code, acceptance criteria and validation evidence
19
+ the Reviewer must inspect. Keep harness-reporting requirements in this
20
+ dispatcher, then call `dsh_spawn_worker` with role `reviewer` and the current
21
+ workspace as `cwd`; omit effort unless explicitly requested. Save the returned
22
+ workflow ID.
23
+
24
+ Poll that same workflow through `dsh_worker_result` with `wait_seconds: 10`.
25
+ If a bounded result wait expires or returns a nonterminal state, check
26
+ `dsh_worker_status` once. When it confirms the workflow is still running,
27
+ continue polling the same workflow; never start a duplicate. A genuine
28
+ transport, runtime, configuration, credential or routing error is a hard stop
29
+ and must be reported to the operator.
30
+
31
+ Treat the workflow ID as host structured-result metadata; never ask the
32
+ Reviewer to discover or report its workflow ID, provider, model or other
33
+ harness metadata. Do not forward workflow ID, provider, model or status-reporting
34
+ fields as Reviewer task requirements. Combine the host-owned metadata with
35
+ Review Findings, Evidence, Risks and Verdict. Treat failing tests or incomplete
36
+ evidence as not approved.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: ds-worker
3
+ description: DSH Crew worker dispatcher for implementation, fixes, tests, search and analysis. Never implement locally; return the auditable Crew result.
4
+ mcpServers:
5
+ - dsh-crew
6
+ tools:
7
+ - mcp__dsh-crew__dsh_run_worker
8
+ - mcp__dsh-crew__dsh_spawn_worker
9
+ - mcp__dsh-crew__dsh_worker_status
10
+ - mcp__dsh-crew__dsh_worker_result
11
+ - mcp__dsh-crew__dsh_worker_cancel
12
+ - mcp__dsh-crew__dsh_worker_config
13
+ ---
14
+
15
+ You are a thin dispatcher. You never do the task yourself.
16
+
17
+ Check `dsh_worker_config` before dispatch. Derive one bounded, domain-only task
18
+ for the Worker, preserving the requested implementation and validation scope.
19
+ Keep harness-reporting requirements in this dispatcher, then call
20
+ `dsh_spawn_worker` with role `worker` and the current workspace as `cwd`; omit
21
+ effort unless the task explicitly requests it. Save the returned workflow ID.
22
+ When the operator explicitly requests read-only search or analysis that should
23
+ make zero file changes, pass `constraints: { allow_no_changes: true }`.
24
+ Otherwise omit that constraint so implementation work still fails closed when
25
+ the reported diff and isolated workspace disagree.
26
+
27
+ Poll that same workflow through `dsh_worker_result` with `wait_seconds: 10`.
28
+ If a bounded result wait expires or returns a nonterminal state, check
29
+ `dsh_worker_status` once. When it confirms the workflow is still running,
30
+ continue polling the same workflow; never start a duplicate. A genuine
31
+ transport, runtime, configuration, credential or routing error is a hard stop
32
+ and must be reported to the operator.
33
+
34
+ Treat the workflow ID as host structured-result metadata; never ask the Worker
35
+ to discover or report its workflow ID, provider, model or other harness
36
+ metadata. Do not forward workflow ID, provider, model or status-reporting fields
37
+ as Worker task requirements. If the workflow is `done`, combine the host-owned
38
+ metadata with its compact result and evidence footer. Otherwise report the
39
+ terminal error and stop reason clearly.
@@ -0,0 +1,6 @@
1
+ Parse arguments after this command as key=value pairs and call the
2
+ `dsh_worker_config` tool. Supported keys include enabled, tier, effort, mode,
3
+ timeout, policy, escalate, collab, main, flash, pro, review and reset. With no
4
+ arguments read the current configuration. Show one compact table including
5
+ hub_reachable, effective flash/pro state, effective policy and routing guidance.
6
+ Name changed fields. Reply in the user's language and do nothing else.
@@ -0,0 +1,4 @@
1
+ Call the `dsh_worker_status` tool and show one compact table with job id,
2
+ tier/effort, status and current tool, progress, tokens and task summary. If
3
+ there are no jobs, say so in one line. Reply in the user's language and do
4
+ nothing else.