@danmoisan/drm-copilot-mcp 1.1.15 → 1.1.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 (71) hide show
  1. package/out/mcp-server.js +639 -27
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +12 -6
  4. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +4 -5
  5. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
  6. package/resources/claude-customizations/.claude/hooks/enforce-batch-budget-route.ps1 +139 -0
  7. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
  12. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
  15. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
  16. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
  17. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
  18. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +95 -66
  19. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
  20. package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
  21. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
  22. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
  23. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
  24. package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
  25. package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
  26. package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
  27. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
  28. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
  29. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
  30. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
  31. package/resources/claude-customizations/.claude/rules/powershell.md +3 -3
  32. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +2 -0
  33. package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
  34. package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +7 -4
  35. package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
  36. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +3 -1
  37. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +32 -18
  38. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
  39. package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +6 -6
  40. package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
  41. package/resources/claude-customizations/pack-manifests/core.json +8 -0
  42. package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +1 -2
  43. package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
  44. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -2
  45. package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
  46. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +2 -3
  47. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +2 -3
  48. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +2 -3
  49. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +2 -3
  50. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +2 -3
  51. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -3
  52. package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
  53. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
  54. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
  55. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
  56. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
  57. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
  58. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
  59. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  60. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
  61. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +165 -80
  62. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
  63. package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
  64. package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +7 -7
  65. package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +8 -10
  66. package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
  67. package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
  68. package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +2 -2
  69. package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
  70. package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +3 -3
  71. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +12 -0
@@ -0,0 +1,336 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Detect radius drift for one in-flight parallel item and print one JSON object.
4
+
5
+ .DESCRIPTION
6
+ Destination-runtime entry point (issue #763) for the drift-detection step of
7
+ /parallel-orchestrate, and the PowerShell port of
8
+ scripts/dev_tools/parallel_drift_detection_cli.py. It needs PowerShell 7 and
9
+ no Python interpreter:
10
+
11
+ pwsh -NoProfile -NonInteractive -File .claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 `
12
+ -ItemKey <issue_num> [-CheckpointPath <path>] [-ConfigPath <path>] `
13
+ [-At <yyyy-MM-ddTHH-mm>] [-ComputedAt <yyyy-MM-ddTHH-mm>] <CHANGED_PATH>...
14
+
15
+ The script reads the parallel checkpoint and the blast-radius truth table,
16
+ evaluates drift with the pure functions of ParallelDrift.psm1, and writes one
17
+ JSON object to stdout with the nine keys result, item_key, at, computed_at,
18
+ escaped_paths, newly_conflicting_pairs, halted_item_keys, drift_event, and
19
+ observed_radius. Keys are sorted ordinally and every array stays an array.
20
+ No file is written and no git command is run: the caller supplies the changed
21
+ paths.
22
+
23
+ JSON is read with System.Text.Json rather than ConvertFrom-Json, which turns
24
+ timestamp-shaped strings into DateTime values and does not keep the integer
25
+ and boolean distinction the shape guards rely on. No parameter is Mandatory,
26
+ so a missing -ItemKey exits 2 instead of prompting.
27
+
28
+ Exit codes: 0 on success; 1 when an input is missing or malformed, with one
29
+ stderr line prefixed "parallel drift detection failed: "; 2 on a usage error
30
+ (a missing or non-integer -ItemKey, or a remaining argument that begins with
31
+ -), with one stderr line prefixed "parallel drift detection usage error: ".
32
+
33
+ .PARAMETER ItemKey
34
+ The issue_num of the item whose diff is evaluated. Required.
35
+
36
+ .PARAMETER CheckpointPath
37
+ The parallel checkpoint; defaults to
38
+ artifacts/orchestration/parallel-orchestrator-state.json. A relative path
39
+ resolves against the current location.
40
+
41
+ .PARAMETER ConfigPath
42
+ The blast-radius truth table; defaults to config/blast-radius.json.
43
+
44
+ .PARAMETER At
45
+ The timestamp recorded on the drift event; defaults to the current UTC time
46
+ formatted yyyy-MM-ddTHH-mm, the only clock read.
47
+
48
+ .PARAMETER ComputedAt
49
+ The timestamp recorded on the observed radius; defaults to the resolved At.
50
+
51
+ .PARAMETER ChangedPath
52
+ The observed changed paths, positional and variadic; may be empty.
53
+ #>
54
+ [CmdletBinding(PositionalBinding = $false)]
55
+ param(
56
+ [string] $ItemKey,
57
+ [string] $CheckpointPath,
58
+ [string] $ConfigPath,
59
+ [string] $At,
60
+ [string] $ComputedAt,
61
+ [Parameter(ValueFromRemainingArguments = $true)]
62
+ [string[]] $ChangedPath
63
+ )
64
+
65
+ Set-StrictMode -Version Latest
66
+ $ErrorActionPreference = 'Stop'
67
+
68
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'ParallelDrift.psm1') -Force -ErrorAction Stop
69
+
70
+ $script:DriftDefaultCheckpointPath = 'artifacts/orchestration/parallel-orchestrator-state.json'
71
+ $script:DriftDefaultConfigPath = 'config/blast-radius.json'
72
+ $script:DriftTimestampFormat = 'yyyy-MM-ddTHH-mm'
73
+ $script:DriftUsagePrefix = 'parallel drift detection usage error: '
74
+ $script:DriftFailurePrefix = 'parallel drift detection failed: '
75
+ $script:DriftItemKeyPattern = '^-?[0-9]+$'
76
+ $script:DriftIntegerTypeName = @('System.Int16', 'System.Int32', 'System.Int64', 'System.Byte',
77
+ 'System.SByte', 'System.UInt16', 'System.UInt32', 'System.UInt64')
78
+
79
+ function Read-ParallelDriftJsonText {
80
+ <#
81
+ .SYNOPSIS
82
+ Read one document as UTF-8 text; the script's only filesystem read and its test seam.
83
+ #>
84
+ [CmdletBinding()]
85
+ [OutputType([string])]
86
+ param([string] $Path)
87
+
88
+ return [System.IO.File]::ReadAllText($Path, [System.Text.Encoding]::UTF8)
89
+ }
90
+
91
+ function Get-ParallelDriftUtcNow {
92
+ <#
93
+ .SYNOPSIS
94
+ Return the current UTC instant; the script's only clock read and its test seam.
95
+ #>
96
+ [CmdletBinding()]
97
+ [OutputType([datetime])]
98
+ param()
99
+
100
+ return [datetime]::UtcNow
101
+ }
102
+
103
+ function ConvertFrom-ParallelDriftJsonElement {
104
+ <#
105
+ .SYNOPSIS
106
+ Convert one System.Text.Json element to a PowerShell value.
107
+ .DESCRIPTION
108
+ Objects become ordinal (case-sensitive) hashtables, arrays become object
109
+ arrays, integral numbers become Int64, other numbers Double, and strings
110
+ stay strings unchanged.
111
+ #>
112
+ # The @{} literal is case-insensitive, and JSON object keys are case-sensitive,
113
+ # so an object is built as a hashtable with the ordinal comparer. The rule
114
+ # below flags every case-sensitive hashtable construction.
115
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseLiteralInitializerForHashtable', '', Justification = 'JSON object keys are case-sensitive; the @{} literal is case-insensitive, so an ordinal-comparer hashtable is required (issue #763, D1).')]
116
+ [CmdletBinding()]
117
+ [OutputType([hashtable], [System.Object[]], [string], [long], [double], [bool])]
118
+ param([System.Text.Json.JsonElement] $Element)
119
+
120
+ # Route by JSON kind; the literal kinds (true, false, null) fall through last.
121
+ switch ($Element.ValueKind) {
122
+ ([System.Text.Json.JsonValueKind]::Object) {
123
+ $table = [hashtable]::new([System.StringComparer]::Ordinal)
124
+ # Convert every property; a repeated name keeps the last value, as Python does.
125
+ foreach ($property in $Element.EnumerateObject()) {
126
+ $table[$property.Name] = ConvertFrom-ParallelDriftJsonElement -Element $property.Value
127
+ }
128
+ return $table
129
+ }
130
+ ([System.Text.Json.JsonValueKind]::Array) {
131
+ $list = [System.Collections.Generic.List[object]]::new()
132
+ # Convert every entry in order; a nested array stays one entry.
133
+ foreach ($entry in $Element.EnumerateArray()) { $list.Add((ConvertFrom-ParallelDriftJsonElement -Element $entry)) }
134
+ return , $list.ToArray()
135
+ }
136
+ ([System.Text.Json.JsonValueKind]::String) { return $Element.GetString() }
137
+ ([System.Text.Json.JsonValueKind]::Number) {
138
+ $integral = [long]0
139
+ if ($Element.TryGetInt64([ref] $integral)) { return $integral }
140
+ return $Element.GetDouble()
141
+ }
142
+ ([System.Text.Json.JsonValueKind]::True) { return $true }
143
+ ([System.Text.Json.JsonValueKind]::False) { return $false }
144
+ default { return $null }
145
+ }
146
+ }
147
+
148
+ function ConvertFrom-ParallelDriftJson {
149
+ <#
150
+ .SYNOPSIS
151
+ Parse JSON text with System.Text.Json into PowerShell values.
152
+ .DESCRIPTION
153
+ An array root is returned with the unary comma so it is not unrolled.
154
+ #>
155
+ [CmdletBinding()]
156
+ [OutputType([hashtable], [System.Object[]], [object])]
157
+ param([string] $Text)
158
+
159
+ $document = [System.Text.Json.JsonDocument]::Parse($Text)
160
+ try {
161
+ $value = ConvertFrom-ParallelDriftJsonElement -Element $document.RootElement
162
+ } finally {
163
+ $document.Dispose()
164
+ }
165
+ if ($value -is [array]) { return , $value }
166
+ return $value
167
+ }
168
+
169
+ function Write-ParallelDriftJsonValue {
170
+ <#
171
+ .SYNOPSIS
172
+ Write one value to a JSON writer, sorting object keys ordinally at every depth.
173
+ #>
174
+ [CmdletBinding()]
175
+ [OutputType([void])]
176
+ param([System.Text.Json.Utf8JsonWriter] $Writer, [AllowNull()][object] $Value)
177
+
178
+ # Route by runtime type. Strings are tested before IEnumerable because a string
179
+ # enumerates its characters, and dictionaries before IEnumerable for the same reason.
180
+ if ($null -eq $Value) { $Writer.WriteNullValue(); return }
181
+ if ($Value -is [bool]) { $Writer.WriteBooleanValue($Value); return }
182
+ if ($Value -is [string]) { $Writer.WriteStringValue($Value); return }
183
+ if ($script:DriftIntegerTypeName -contains $Value.GetType().FullName) { $Writer.WriteNumberValue([long]$Value); return }
184
+ if ($Value -is [double] -or $Value -is [single] -or $Value -is [decimal]) { $Writer.WriteNumberValue([double]$Value); return }
185
+ if ($Value -is [System.Collections.IDictionary]) {
186
+ $name = [System.Collections.Generic.List[string]]::new()
187
+ foreach ($key in $Value.Keys) { $name.Add([string]$key) }
188
+ $name.Sort([System.StringComparer]::Ordinal)
189
+ $Writer.WriteStartObject()
190
+ # Emit the properties in ordinal key order.
191
+ foreach ($key in $name) {
192
+ $Writer.WritePropertyName($key)
193
+ Write-ParallelDriftJsonValue -Writer $Writer -Value $Value[$key]
194
+ }
195
+ $Writer.WriteEndObject()
196
+ return
197
+ }
198
+ if ($Value -is [System.Collections.IEnumerable]) {
199
+ $Writer.WriteStartArray()
200
+ # Emit each entry; a nested array is written as a nested JSON array.
201
+ foreach ($entry in $Value) { Write-ParallelDriftJsonValue -Writer $Writer -Value $entry }
202
+ $Writer.WriteEndArray()
203
+ return
204
+ }
205
+ throw "ConvertTo-ParallelDriftJson cannot serialize a value of type $($Value.GetType().FullName)."
206
+ }
207
+
208
+ function ConvertTo-ParallelDriftJson {
209
+ <#
210
+ .SYNOPSIS
211
+ Serialize a value to indented JSON with ordinally sorted keys; every array stays an array.
212
+ #>
213
+ [CmdletBinding()]
214
+ [OutputType([string])]
215
+ param([AllowNull()][object] $Value)
216
+
217
+ $stream = [System.IO.MemoryStream]::new()
218
+ $options = [System.Text.Json.JsonWriterOptions]@{
219
+ Indented = $true
220
+ Encoder = [System.Text.Encodings.Web.JavaScriptEncoder]::UnsafeRelaxedJsonEscaping
221
+ }
222
+ $writer = [System.Text.Json.Utf8JsonWriter]::new($stream, $options)
223
+ try {
224
+ Write-ParallelDriftJsonValue -Writer $writer -Value $Value
225
+ $writer.Flush()
226
+ return [System.Text.Encoding]::UTF8.GetString($stream.ToArray())
227
+ } finally {
228
+ $writer.Dispose()
229
+ $stream.Dispose()
230
+ }
231
+ }
232
+
233
+ function Resolve-ParallelDriftPath {
234
+ <#
235
+ .SYNOPSIS
236
+ Return a rooted path unchanged and join a relative path to the current location.
237
+ #>
238
+ [CmdletBinding()]
239
+ [OutputType([string])]
240
+ param([string] $Path)
241
+
242
+ if ([System.IO.Path]::IsPathRooted($Path)) { return $Path }
243
+ return Join-Path -Path (Get-Location).Path -ChildPath $Path
244
+ }
245
+
246
+ function Read-ParallelDriftDocument {
247
+ <#
248
+ .SYNOPSIS
249
+ Read and parse one document and require a JSON object root.
250
+ .DESCRIPTION
251
+ Port of load_mapping; Label names the document in the error message.
252
+ #>
253
+ [CmdletBinding()]
254
+ [OutputType([System.Collections.IDictionary])]
255
+ param([string] $Path, [string] $Label)
256
+
257
+ $value = ConvertFrom-ParallelDriftJson -Text (Read-ParallelDriftJsonText -Path (Resolve-ParallelDriftPath -Path $Path))
258
+ if ($value -isnot [System.Collections.IDictionary]) { throw "$Label at $Path must be a JSON object." }
259
+ return $value
260
+ }
261
+
262
+ function ConvertTo-ParallelDriftCliOutcome {
263
+ <#
264
+ .SYNOPSIS
265
+ Build the result record the guard writes and exits with.
266
+ #>
267
+ [CmdletBinding()]
268
+ [OutputType([pscustomobject])]
269
+ param([int] $ExitCode, [string] $Stdout = '', [string] $Stderr = '')
270
+
271
+ return [pscustomobject]@{ ExitCode = $ExitCode; Stdout = $Stdout; Stderr = $Stderr }
272
+ }
273
+
274
+ function Invoke-ParallelDriftCli {
275
+ <#
276
+ .SYNOPSIS
277
+ Validate the arguments, run detection, and return ExitCode, Stdout, and Stderr.
278
+ .DESCRIPTION
279
+ Takes the script's parameters. An omitted ChangedPath (which pwsh -File
280
+ leaves null, and the guard passes by name) is normalized to an empty list
281
+ before any validation, so it yields no_escape rather than a blank entry.
282
+ Usage errors are reported before any file is read; every read or data
283
+ error, and every error the pure functions throw, becomes exit 1 with the
284
+ failure prefix.
285
+ #>
286
+ [CmdletBinding()]
287
+ [OutputType([pscustomobject])]
288
+ param([string] $ItemKey, [string] $CheckpointPath, [string] $ConfigPath, [string] $At, [string] $ComputedAt, [string[]] $ChangedPath)
289
+
290
+ if ($null -eq $ChangedPath) { $ChangedPath = [string[]]@() }
291
+
292
+ # Usage checks come first and exit 2 without reading any file.
293
+ $parsedKey = [long]0
294
+ if ([string]::IsNullOrWhiteSpace($ItemKey)) {
295
+ return ConvertTo-ParallelDriftCliOutcome -ExitCode 2 -Stderr "$($script:DriftUsagePrefix)-ItemKey is required."
296
+ }
297
+ $isInteger = $ItemKey -match $script:DriftItemKeyPattern -and [long]::TryParse($ItemKey, [ref] $parsedKey)
298
+ if (-not $isInteger) {
299
+ return ConvertTo-ParallelDriftCliOutcome -ExitCode 2 -Stderr "$($script:DriftUsagePrefix)-ItemKey must be an integer; got '$ItemKey'."
300
+ }
301
+ $unrecognized = @($ChangedPath | Where-Object { $null -ne $_ -and $_.StartsWith('-') })
302
+ if ($unrecognized.Count -gt 0) {
303
+ return ConvertTo-ParallelDriftCliOutcome -ExitCode 2 -Stderr "$($script:DriftUsagePrefix)unrecognized parameter '$($unrecognized[0])'."
304
+ }
305
+
306
+ # Defaults are resolved at this I/O boundary so the pure functions never read a clock.
307
+ if ([string]::IsNullOrEmpty($CheckpointPath)) { $CheckpointPath = $script:DriftDefaultCheckpointPath }
308
+ if ([string]::IsNullOrEmpty($ConfigPath)) { $ConfigPath = $script:DriftDefaultConfigPath }
309
+ if ([string]::IsNullOrEmpty($At)) {
310
+ $At = (Get-ParallelDriftUtcNow).ToString($script:DriftTimestampFormat, [System.Globalization.CultureInfo]::InvariantCulture)
311
+ }
312
+ if ([string]::IsNullOrEmpty($ComputedAt)) { $ComputedAt = $At }
313
+
314
+ # One boundary handler for every data defect, so the caller sees one exit code
315
+ # and one stderr line instead of an unhandled error record.
316
+ try {
317
+ $state = Read-ParallelDriftDocument -Path $CheckpointPath -Label 'Parallel checkpoint'
318
+ $config = Read-ParallelDriftDocument -Path $ConfigPath -Label 'Blast-radius config'
319
+ $result = Get-ParallelDriftResult -State $state -Config $config -ItemKey $parsedKey -ChangedPath $ChangedPath -At $At -ComputedAt $ComputedAt
320
+ $json = ConvertTo-ParallelDriftJson -Value $result
321
+ } catch {
322
+ return ConvertTo-ParallelDriftCliOutcome -ExitCode 1 -Stderr "$($script:DriftFailurePrefix)$($_.Exception.Message)"
323
+ }
324
+ return ConvertTo-ParallelDriftCliOutcome -ExitCode 0 -Stdout $json
325
+ }
326
+
327
+ # Dot-sourcing loads the functions for a test host; any other invocation runs
328
+ # detection. The six parameters are passed by name rather than by splatting
329
+ # $PSBoundParameters, which under CmdletBinding can carry common parameters.
330
+ if ($MyInvocation.InvocationName -ne '.') {
331
+ $outcome = Invoke-ParallelDriftCli -ItemKey $ItemKey -CheckpointPath $CheckpointPath -ConfigPath $ConfigPath `
332
+ -At $At -ComputedAt $ComputedAt -ChangedPath $ChangedPath
333
+ if (-not [string]::IsNullOrEmpty($outcome.Stdout)) { [Console]::Out.WriteLine($outcome.Stdout) }
334
+ if (-not [string]::IsNullOrEmpty($outcome.Stderr)) { [Console]::Error.WriteLine($outcome.Stderr) }
335
+ exit $outcome.ExitCode
336
+ }