@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.
- package/out/mcp-server.js +639 -27
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +12 -6
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +4 -5
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
- package/resources/claude-customizations/.claude/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +95 -66
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
- package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
- package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
- package/resources/claude-customizations/.claude/rules/powershell.md +3 -3
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +2 -0
- package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +7 -4
- package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +32 -18
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
- package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +6 -6
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +1 -2
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -2
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +165 -80
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +7 -7
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +8 -10
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
- package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +2 -2
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
- package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +3 -3
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +12 -0
package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1
ADDED
|
@@ -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
|
+
}
|