@danmoisan/drm-copilot-mcp 1.1.16 → 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/python-typed-engineer.md +3 -3
- package/resources/claude-customizations/.claude/hooks/{enforce-powershell-batch-budget-route.ps1 → enforce-batch-budget-route.ps1} +12 -10
- 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 +3 -3
- 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/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 +4 -0
- 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/python-change-budget-router/SKILL.md +11 -35
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/claude-customizations/pack-manifests/powershell.json +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
- 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 +4 -124
- 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/python-orchestrator.agent.md +4 -4
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +12 -1
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Halt selection and input shape guards for parallel radius-drift detection.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Destination-runtime PowerShell port (issue #763) of the halt-selection half of
|
|
7
|
+
parallel drift detection. It ports select_halted_item and _start_rank from
|
|
8
|
+
scripts/dev_tools/parallel_drift_halt.py, the halted_item_keys selection from
|
|
9
|
+
scripts/dev_tools/parallel_drift_detection_cli.py, and the guards
|
|
10
|
+
require_item_key, require_text, require_paths, require_enum_member,
|
|
11
|
+
as_item_key, and canonical_pair from scripts/dev_tools/_parallel_drift_shape.py.
|
|
12
|
+
The Python modules remain the repository authority and the parity reference.
|
|
13
|
+
|
|
14
|
+
Every function is pure: no filesystem, subprocess, network, or clock access,
|
|
15
|
+
and no input is mutated. A guard failure throws with a message naming the
|
|
16
|
+
offending field. Booleans are never accepted where an integer is required,
|
|
17
|
+
matching the Python reference, which rejects bool although it subclasses int.
|
|
18
|
+
A function that returns an array uses the unary comma so a one-element or
|
|
19
|
+
empty array is not unrolled; it declares both the array type and
|
|
20
|
+
System.Object[] as output types.
|
|
21
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
22
|
+
#>
|
|
23
|
+
|
|
24
|
+
Set-StrictMode -Version Latest
|
|
25
|
+
$ErrorActionPreference = 'Stop'
|
|
26
|
+
|
|
27
|
+
# Integer runtime types a deserialized checkpoint value may carry. Boolean is
|
|
28
|
+
# deliberately absent, so a boolean in an integer slot is malformed.
|
|
29
|
+
$script:IntegerTypeName = @('System.Int16', 'System.Int32', 'System.Int64', 'System.Byte',
|
|
30
|
+
'System.SByte', 'System.UInt16', 'System.UInt32', 'System.UInt64')
|
|
31
|
+
|
|
32
|
+
function Test-ParallelDriftIntegerValue {
|
|
33
|
+
<#
|
|
34
|
+
.SYNOPSIS
|
|
35
|
+
Report whether a value is a non-boolean integer.
|
|
36
|
+
.PARAMETER Value
|
|
37
|
+
Any deserialized value.
|
|
38
|
+
.OUTPUTS
|
|
39
|
+
System.Boolean.
|
|
40
|
+
#>
|
|
41
|
+
[CmdletBinding()]
|
|
42
|
+
[OutputType([bool])]
|
|
43
|
+
param([AllowNull()][object] $Value)
|
|
44
|
+
|
|
45
|
+
return ($null -ne $Value) -and ($script:IntegerTypeName -contains $Value.GetType().FullName)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function Format-ParallelDriftValue {
|
|
49
|
+
<#
|
|
50
|
+
.SYNOPSIS
|
|
51
|
+
Render a value for an error message; null renders as None, as in Python.
|
|
52
|
+
.PARAMETER Value
|
|
53
|
+
Any value.
|
|
54
|
+
.OUTPUTS
|
|
55
|
+
System.String.
|
|
56
|
+
#>
|
|
57
|
+
[CmdletBinding()]
|
|
58
|
+
[OutputType([string])]
|
|
59
|
+
param([AllowNull()][object] $Value)
|
|
60
|
+
|
|
61
|
+
if ($null -eq $Value) { return 'None' }
|
|
62
|
+
return [string]$Value
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ConvertTo-ParallelDriftSortedDistinct {
|
|
66
|
+
<#
|
|
67
|
+
.SYNOPSIS
|
|
68
|
+
Deduplicate and ordinally sort a string collection.
|
|
69
|
+
.DESCRIPTION
|
|
70
|
+
Port of the tuple(sorted(set(...))) idiom. Ordinal comparison matches
|
|
71
|
+
Python's code-point ordering; culture-sensitive sorting would not.
|
|
72
|
+
.PARAMETER Entry
|
|
73
|
+
The validated entries.
|
|
74
|
+
.OUTPUTS
|
|
75
|
+
System.String[]. Always an array, including when empty.
|
|
76
|
+
#>
|
|
77
|
+
[CmdletBinding()]
|
|
78
|
+
[OutputType([string[]], [System.Object[]])]
|
|
79
|
+
param([Parameter(Mandatory = $true)][AllowEmptyCollection()][string[]] $Entry)
|
|
80
|
+
|
|
81
|
+
$unique = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
|
82
|
+
# Collapse duplicates before sorting so each path appears once.
|
|
83
|
+
foreach ($item in $Entry) { [void]$unique.Add($item) }
|
|
84
|
+
$sorted = [System.Collections.Generic.List[string]]::new($unique)
|
|
85
|
+
$sorted.Sort([System.StringComparer]::Ordinal)
|
|
86
|
+
return , [string[]]$sorted.ToArray()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function Assert-ParallelDriftItemKey {
|
|
90
|
+
<#
|
|
91
|
+
.SYNOPSIS
|
|
92
|
+
Require an issue_num primary key: a positive, non-boolean integer.
|
|
93
|
+
.DESCRIPTION
|
|
94
|
+
Port of require_item_key.
|
|
95
|
+
.PARAMETER Value
|
|
96
|
+
The candidate key.
|
|
97
|
+
.PARAMETER FieldName
|
|
98
|
+
Field label rendered into the error message.
|
|
99
|
+
.OUTPUTS
|
|
100
|
+
System.Int64. The validated key.
|
|
101
|
+
#>
|
|
102
|
+
[CmdletBinding()]
|
|
103
|
+
[OutputType([long])]
|
|
104
|
+
param(
|
|
105
|
+
[Parameter(Mandatory = $true)][AllowNull()][object] $Value,
|
|
106
|
+
[Parameter(Mandatory = $true)][string] $FieldName
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if (-not (Test-ParallelDriftIntegerValue -Value $Value) -or [long]$Value -le 0) {
|
|
110
|
+
throw "$FieldName must be a positive integer issue_num; found: $(Format-ParallelDriftValue -Value $Value)."
|
|
111
|
+
}
|
|
112
|
+
return [long]$Value
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function Assert-ParallelDriftText {
|
|
116
|
+
<#
|
|
117
|
+
.SYNOPSIS
|
|
118
|
+
Require a string that carries at least one non-space character.
|
|
119
|
+
.DESCRIPTION
|
|
120
|
+
Port of require_text.
|
|
121
|
+
.PARAMETER Value
|
|
122
|
+
The candidate string.
|
|
123
|
+
.PARAMETER FieldName
|
|
124
|
+
Field label rendered into the error message.
|
|
125
|
+
.OUTPUTS
|
|
126
|
+
System.String. The validated value, unchanged.
|
|
127
|
+
#>
|
|
128
|
+
[CmdletBinding()]
|
|
129
|
+
[OutputType([string])]
|
|
130
|
+
param(
|
|
131
|
+
[Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][object] $Value,
|
|
132
|
+
[Parameter(Mandatory = $true)][string] $FieldName
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if ($Value -isnot [string] -or [string]::IsNullOrWhiteSpace($Value)) {
|
|
136
|
+
throw "$FieldName must be a non-empty string; found: $(Format-ParallelDriftValue -Value $Value)."
|
|
137
|
+
}
|
|
138
|
+
return $Value
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function Assert-ParallelDriftPathList {
|
|
142
|
+
<#
|
|
143
|
+
.SYNOPSIS
|
|
144
|
+
Require a collection of non-blank repository paths.
|
|
145
|
+
.DESCRIPTION
|
|
146
|
+
Port of require_paths. A bare string is rejected rather than iterated,
|
|
147
|
+
because iterating one would treat each character as a path. A blank or
|
|
148
|
+
non-string entry is rejected rather than dropped, because dropping it
|
|
149
|
+
would narrow the compared set and under-report drift.
|
|
150
|
+
.PARAMETER Value
|
|
151
|
+
The candidate collection.
|
|
152
|
+
.PARAMETER FieldName
|
|
153
|
+
Field label rendered into the error messages.
|
|
154
|
+
.PARAMETER AllowEmpty
|
|
155
|
+
Accept an empty collection. It is not accepted for escaped paths, where
|
|
156
|
+
emptiness means there was no drift.
|
|
157
|
+
.OUTPUTS
|
|
158
|
+
System.String[]. The entries deduplicated and ordinally sorted.
|
|
159
|
+
#>
|
|
160
|
+
[CmdletBinding()]
|
|
161
|
+
[OutputType([string[]], [System.Object[]])]
|
|
162
|
+
param(
|
|
163
|
+
[Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $Value,
|
|
164
|
+
[Parameter(Mandatory = $true)][string] $FieldName,
|
|
165
|
+
[switch] $AllowEmpty
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if ($Value -is [string]) {
|
|
169
|
+
throw "$FieldName must be a collection of paths, not a single string; found: '$Value'."
|
|
170
|
+
}
|
|
171
|
+
if ($Value -isnot [System.Collections.IEnumerable] -or $Value -is [System.Collections.IDictionary]) {
|
|
172
|
+
throw "$FieldName must be a collection of paths; found: $(Format-ParallelDriftValue -Value $Value)."
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
$entry = [System.Collections.Generic.List[string]]::new()
|
|
176
|
+
# Validate every entry before normalizing so an error names the offending value.
|
|
177
|
+
foreach ($item in $Value) {
|
|
178
|
+
if ($item -isnot [string] -or [string]::IsNullOrWhiteSpace($item)) {
|
|
179
|
+
throw "$FieldName entries must be non-empty strings; found: $(Format-ParallelDriftValue -Value $item)."
|
|
180
|
+
}
|
|
181
|
+
$entry.Add($item)
|
|
182
|
+
}
|
|
183
|
+
if ($entry.Count -eq 0 -and -not $AllowEmpty) {
|
|
184
|
+
throw "$FieldName must not be empty."
|
|
185
|
+
}
|
|
186
|
+
return , (ConvertTo-ParallelDriftSortedDistinct -Entry $entry.ToArray())
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function Assert-ParallelDriftEnumMember {
|
|
190
|
+
<#
|
|
191
|
+
.SYNOPSIS
|
|
192
|
+
Require a member of a fixed vocabulary.
|
|
193
|
+
.DESCRIPTION
|
|
194
|
+
Port of require_enum_member. Matching is case-sensitive.
|
|
195
|
+
.PARAMETER Value
|
|
196
|
+
The candidate member.
|
|
197
|
+
.PARAMETER Vocabulary
|
|
198
|
+
The permitted members.
|
|
199
|
+
.PARAMETER FieldName
|
|
200
|
+
Field label rendered into the error message.
|
|
201
|
+
.OUTPUTS
|
|
202
|
+
System.String. The validated member, unchanged.
|
|
203
|
+
#>
|
|
204
|
+
[CmdletBinding()]
|
|
205
|
+
[OutputType([string])]
|
|
206
|
+
param(
|
|
207
|
+
[Parameter(Mandatory = $true)][AllowNull()][object] $Value,
|
|
208
|
+
[Parameter(Mandatory = $true)][string[]] $Vocabulary,
|
|
209
|
+
[Parameter(Mandatory = $true)][string] $FieldName
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if ($Value -isnot [string] -or $Vocabulary -cnotcontains $Value) {
|
|
213
|
+
throw "$FieldName must be one of $($Vocabulary -join ', '); found: $(Format-ParallelDriftValue -Value $Value)."
|
|
214
|
+
}
|
|
215
|
+
return $Value
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function ConvertTo-ParallelDriftItemKey {
|
|
219
|
+
<#
|
|
220
|
+
.SYNOPSIS
|
|
221
|
+
Read a value as an item key without throwing.
|
|
222
|
+
.DESCRIPTION
|
|
223
|
+
Port of as_item_key.
|
|
224
|
+
.PARAMETER Value
|
|
225
|
+
Any deserialized checkpoint value.
|
|
226
|
+
.OUTPUTS
|
|
227
|
+
System.Int64, or $null when the value is not a positive, non-boolean integer.
|
|
228
|
+
#>
|
|
229
|
+
[CmdletBinding()]
|
|
230
|
+
[OutputType([long])]
|
|
231
|
+
param([AllowNull()][object] $Value)
|
|
232
|
+
|
|
233
|
+
if ((Test-ParallelDriftIntegerValue -Value $Value) -and [long]$Value -gt 0) {
|
|
234
|
+
return [long]$Value
|
|
235
|
+
}
|
|
236
|
+
return $null
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function Get-ParallelDriftCanonicalPair {
|
|
240
|
+
<#
|
|
241
|
+
.SYNOPSIS
|
|
242
|
+
Normalize two item keys into the canonical a < b edge identity.
|
|
243
|
+
.DESCRIPTION
|
|
244
|
+
Port of canonical_pair, so an edge recorded in either order has one identity.
|
|
245
|
+
.PARAMETER First
|
|
246
|
+
One item key.
|
|
247
|
+
.PARAMETER Second
|
|
248
|
+
The other item key.
|
|
249
|
+
.OUTPUTS
|
|
250
|
+
System.Int64[]. The two keys in ascending order.
|
|
251
|
+
#>
|
|
252
|
+
[CmdletBinding()]
|
|
253
|
+
[OutputType([long[]], [System.Object[]])]
|
|
254
|
+
param([Parameter(Mandatory = $true)][long] $First, [Parameter(Mandatory = $true)][long] $Second)
|
|
255
|
+
|
|
256
|
+
if ($First -lt $Second) { return , [long[]]@($First, $Second) }
|
|
257
|
+
return , [long[]]@($Second, $First)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function Get-ParallelDriftStartRank {
|
|
261
|
+
<#
|
|
262
|
+
.SYNOPSIS
|
|
263
|
+
Render a start marker as the comparable rank halt selection maximizes.
|
|
264
|
+
.DESCRIPTION
|
|
265
|
+
Port of _start_rank plus the ItemStart construction checks. The rank is
|
|
266
|
+
(Unknown, Timestamp, ItemKey); Unknown is 1 when no usable timestamp
|
|
267
|
+
exists, which ranks an item of unknown start above every timestamped
|
|
268
|
+
item and so makes the timestamped item earlier-started.
|
|
269
|
+
.PARAMETER Start
|
|
270
|
+
A hashtable carrying ItemKey and WorktreeCreatedAt (null, blank, or a
|
|
271
|
+
timestamp string).
|
|
272
|
+
.OUTPUTS
|
|
273
|
+
System.Management.Automation.PSCustomObject with Unknown, Timestamp, ItemKey.
|
|
274
|
+
#>
|
|
275
|
+
[CmdletBinding()]
|
|
276
|
+
[OutputType([pscustomobject])]
|
|
277
|
+
param([Parameter(Mandatory = $true)][System.Collections.IDictionary] $Start)
|
|
278
|
+
|
|
279
|
+
$itemKey = Assert-ParallelDriftItemKey -Value $Start['ItemKey'] -FieldName 'ItemStart.item_key'
|
|
280
|
+
$timestamp = $Start['WorktreeCreatedAt']
|
|
281
|
+
if ($null -ne $timestamp -and $timestamp -isnot [string]) {
|
|
282
|
+
throw "ItemStart.worktree_created_at must be a string or None; found: $timestamp."
|
|
283
|
+
}
|
|
284
|
+
if ([string]::IsNullOrWhiteSpace($timestamp)) {
|
|
285
|
+
return [pscustomobject]@{ Unknown = 1; Timestamp = ''; ItemKey = $itemKey }
|
|
286
|
+
}
|
|
287
|
+
return [pscustomobject]@{ Unknown = 0; Timestamp = $timestamp; ItemKey = $itemKey }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function Select-ParallelDriftHaltedItem {
|
|
291
|
+
<#
|
|
292
|
+
.SYNOPSIS
|
|
293
|
+
Return the issue_num of the later-started item of a pair.
|
|
294
|
+
.DESCRIPTION
|
|
295
|
+
Port of select_halted_item: argmax over (start unknown, ordinal start
|
|
296
|
+
timestamp, item key). Equal timestamps halt the larger key; a start
|
|
297
|
+
present on exactly one item makes that item earlier-started; both
|
|
298
|
+
unknown falls through to the item-key tie-break.
|
|
299
|
+
.PARAMETER First
|
|
300
|
+
One start marker.
|
|
301
|
+
.PARAMETER Second
|
|
302
|
+
The other start marker.
|
|
303
|
+
.OUTPUTS
|
|
304
|
+
System.Int64. The halted item key.
|
|
305
|
+
#>
|
|
306
|
+
[CmdletBinding()]
|
|
307
|
+
[OutputType([long])]
|
|
308
|
+
param(
|
|
309
|
+
[Parameter(Mandatory = $true)][System.Collections.IDictionary] $First,
|
|
310
|
+
[Parameter(Mandatory = $true)][System.Collections.IDictionary] $Second
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
$left = Get-ParallelDriftStartRank -Start $First
|
|
314
|
+
$right = Get-ParallelDriftStartRank -Start $Second
|
|
315
|
+
if ($left.ItemKey -eq $right.ItemKey) {
|
|
316
|
+
throw "select_halted_item requires two distinct items; the pair names one item twice (item_key $($left.ItemKey))."
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
# Compare the ranks field by field in priority order; the larger rank is halted.
|
|
320
|
+
$order = $left.Unknown.CompareTo($right.Unknown)
|
|
321
|
+
if ($order -eq 0) { $order = [string]::CompareOrdinal($left.Timestamp, $right.Timestamp) }
|
|
322
|
+
if ($order -eq 0) { $order = $left.ItemKey.CompareTo($right.ItemKey) }
|
|
323
|
+
if ($order -gt 0) { return $left.ItemKey }
|
|
324
|
+
return $right.ItemKey
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function Get-ParallelDriftHaltedItemKey {
|
|
328
|
+
<#
|
|
329
|
+
.SYNOPSIS
|
|
330
|
+
Select the halted item of every newly conflicting pair, never the drifter.
|
|
331
|
+
.DESCRIPTION
|
|
332
|
+
Port of halted_item_keys. The drifting key is dropped from each pair's
|
|
333
|
+
candidates before the comparator runs, because halting the drifting item
|
|
334
|
+
would deadlock the remediation that resolves the drift. A canonical pair
|
|
335
|
+
holds two distinct keys, so one or two candidates remain: one is halted
|
|
336
|
+
directly, two go through Select-ParallelDriftHaltedItem.
|
|
337
|
+
.PARAMETER Item
|
|
338
|
+
The checkpoint's item records, read for issue_num and worktree_created_at.
|
|
339
|
+
Every record's start marker is validated, as the Python reference does.
|
|
340
|
+
.PARAMETER Pair
|
|
341
|
+
The canonical newly conflicting pairs, each a two-element key array.
|
|
342
|
+
.PARAMETER DriftingItemKey
|
|
343
|
+
The issue_num of the item whose diff escaped; never returned.
|
|
344
|
+
.OUTPUTS
|
|
345
|
+
System.Int64[]. The halted keys, deduplicated and ascending; always an array.
|
|
346
|
+
#>
|
|
347
|
+
[CmdletBinding()]
|
|
348
|
+
[OutputType([long[]], [System.Object[]])]
|
|
349
|
+
param(
|
|
350
|
+
[Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]] $Item,
|
|
351
|
+
[Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]] $Pair,
|
|
352
|
+
[Parameter(Mandatory = $true)][long] $DriftingItemKey
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if ($Pair.Count -eq 0) { return , [long[]]@() }
|
|
356
|
+
|
|
357
|
+
$start = @{}
|
|
358
|
+
# Build and validate one start marker per item so a pair resolves by key lookup.
|
|
359
|
+
foreach ($record in $Item) {
|
|
360
|
+
$marker = @{ ItemKey = $record['issue_num']; WorktreeCreatedAt = $record['worktree_created_at'] }
|
|
361
|
+
$rank = Get-ParallelDriftStartRank -Start $marker
|
|
362
|
+
$start[$rank.ItemKey] = $marker
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
$halted = [System.Collections.Generic.SortedSet[long]]::new()
|
|
366
|
+
# Apply the rule to each pair independently; the set collapses an item that is
|
|
367
|
+
# the halted member of several pairs.
|
|
368
|
+
foreach ($entry in $Pair) {
|
|
369
|
+
$candidate = @($entry | Where-Object { $_ -ne $DriftingItemKey })
|
|
370
|
+
if ($candidate.Count -eq 1) {
|
|
371
|
+
[void]$halted.Add([long]$candidate[0])
|
|
372
|
+
} else {
|
|
373
|
+
[void]$halted.Add((Select-ParallelDriftHaltedItem -First $start[[long]$candidate[0]] -Second $start[[long]$candidate[1]]))
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return , [long[]]@($halted)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
Export-ModuleMember -Function `
|
|
380
|
+
Assert-ParallelDriftItemKey, `
|
|
381
|
+
Assert-ParallelDriftText, `
|
|
382
|
+
Assert-ParallelDriftPathList, `
|
|
383
|
+
Assert-ParallelDriftEnumMember, `
|
|
384
|
+
ConvertTo-ParallelDriftItemKey, `
|
|
385
|
+
Get-ParallelDriftCanonicalPair, `
|
|
386
|
+
Get-ParallelDriftStartRank, `
|
|
387
|
+
Select-ParallelDriftHaltedItem, `
|
|
388
|
+
Get-ParallelDriftHaltedItemKey
|
package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1
CHANGED
|
@@ -8,22 +8,26 @@
|
|
|
8
8
|
for one gated call, whether the call is an operation of that epic and, if so, returns
|
|
9
9
|
the absolute path and parsed content of the governing epic checkpoint.
|
|
10
10
|
|
|
11
|
-
A call is epic scope only when the
|
|
12
|
-
route_id "epic" and a non-empty integration_branch, and that branch equals
|
|
13
|
-
call's branch signal (--head, --branch, or a
|
|
14
|
-
leg, the HEAD branch of the effective worktree
|
|
15
|
-
otherwise the session root). A command or path
|
|
16
|
-
its merge probe inspects the worktree it operates
|
|
17
|
-
so the caller's existing per-feature resolution
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
A call is epic scope only when the epic checkpoint governing the matched branch parses,
|
|
12
|
+
carries route_id "epic" and a non-empty integration_branch, and that branch equals the
|
|
13
|
+
matched branch. The matched branch is the call's branch signal (--head, --branch, or a
|
|
14
|
+
branch: label) or, for a command or path leg, the HEAD branch of the effective worktree
|
|
15
|
+
(the -C selector worktree when present, otherwise the session root). A command or path
|
|
16
|
+
leg ignores any text branch signal, so its merge probe inspects the worktree it operates
|
|
17
|
+
on. Anything else is not epic scope, so the caller's existing per-feature resolution
|
|
18
|
+
runs unchanged.
|
|
19
|
+
|
|
20
|
+
The checkpoint is located by Resolve-WorktreeEpicTarget (WorktreeRunResolution.psm1,
|
|
21
|
+
issue #690) keyed on the matched branch: the live worktree whose epic checkpoint records
|
|
22
|
+
that integration branch. Its path is never composed from the session root and never read
|
|
23
|
+
from command or prompt text (the issue #554 posture and the issue #673 invariant). An
|
|
24
|
+
unresolved target is not epic scope; an ambiguous one is not epic scope with reason
|
|
25
|
+
target-worktree-ambiguous. Exactly one checkpoint read happens per resolution.
|
|
22
26
|
|
|
23
27
|
.NOTES
|
|
24
28
|
PowerShell 7+. Every filesystem contact passes through the exported seams of the
|
|
25
|
-
sibling WorktreeResolution.psm1, so tests model any
|
|
26
|
-
inside this module and create no file. No subprocess, no network, no clock read, and
|
|
29
|
+
sibling WorktreeResolution.psm1 and WorktreeRunResolution.psm1, so tests model any
|
|
30
|
+
topology by mocking those seams inside this module and create no file. No subprocess, no network, no clock read, and
|
|
27
31
|
no environment read. Mirrored byte-identically under
|
|
28
32
|
extensions/drm-copilot/resources/claude-customizations/.
|
|
29
33
|
AUTHORITY: PowerShell-authoritative. The epic readiness predicates have no Python reference implementation, and no enforcement hook invokes Python (issue #663, D5).
|
|
@@ -35,6 +39,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
35
39
|
|
|
36
40
|
Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
|
|
37
41
|
Import-Module (Join-Path $PSScriptRoot 'WorktreeTargetResolution.psm1') -ErrorAction Stop
|
|
42
|
+
Import-Module (Join-Path $PSScriptRoot 'WorktreeRunResolution.psm1') -ErrorAction Stop
|
|
38
43
|
|
|
39
44
|
# The one definition of the epic checkpoint location relative to a worktree root.
|
|
40
45
|
$script:EpicCheckpointRelativePath = 'artifacts/orchestration/epic-orchestrator-state.json'
|
|
@@ -278,10 +283,13 @@ function Resolve-EpicScopeCheckpoint {
|
|
|
278
283
|
Returns an object with IsEpicScope, CheckpointPath, Checkpoint, WorktreeRoot,
|
|
279
284
|
Branch, MergeInProgress, and Reason. The first matching step decides: no branch
|
|
280
285
|
signal without head matching (no-branch-signal, and no checkpoint read); an
|
|
281
|
-
unresolvable session root (session-root-unresolved); an
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
unresolvable session root (session-root-unresolved); an unresolvable selector
|
|
287
|
+
(selector-unresolved); no candidate branch (branch-mismatch); no worktree whose epic
|
|
288
|
+
checkpoint records the candidate branch (epic-checkpoint-absent-or-unparseable); more
|
|
289
|
+
than one such worktree (target-worktree-ambiguous); an absent or unparseable epic
|
|
290
|
+
checkpoint at the resolved root (epic-checkpoint-absent-or-unparseable); a route_id
|
|
291
|
+
other than epic (route_id); an empty integration_branch (integration_branch); a
|
|
292
|
+
branch that does not match (branch-mismatch).
|
|
285
293
|
.PARAMETER Text
|
|
286
294
|
The command or prompt text; only its branch signal is read.
|
|
287
295
|
.PARAMETER SessionRoot
|
|
@@ -319,20 +327,8 @@ function Resolve-EpicScopeCheckpoint {
|
|
|
319
327
|
if ($null -eq $root) {
|
|
320
328
|
return (New-EpicScopeResult -IsEpicScope $false -Reason 'session-root-unresolved')
|
|
321
329
|
}
|
|
322
|
-
$path = Join-WorktreeResolutionPath -WorktreeRoot $root -RepoRelativePath (Get-EpicScopeCheckpointRelativePath)
|
|
323
|
-
$checkpoint = ConvertFrom-EpicScopeCheckpointText -Text (Get-EpicScopeCheckpointText -Path $path)
|
|
324
|
-
if ($null -eq $checkpoint) {
|
|
325
|
-
return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'epic-checkpoint-absent-or-unparseable')
|
|
326
|
-
}
|
|
327
|
-
if ([string](Get-EpicScopePropertyValue -InputObject $checkpoint -Name 'route_id') -cne $script:EpicRouteId) {
|
|
328
|
-
return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'route_id')
|
|
329
|
-
}
|
|
330
|
-
$integrationBranch = [string](Get-EpicScopePropertyValue -InputObject $checkpoint -Name 'integration_branch')
|
|
331
|
-
if ([string]::IsNullOrWhiteSpace($integrationBranch)) {
|
|
332
|
-
return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'integration_branch')
|
|
333
|
-
}
|
|
334
330
|
|
|
335
|
-
# Decide the
|
|
331
|
+
# Decide the candidate branch. A command or path leg (-MatchWorktreeHead) matches the
|
|
336
332
|
# effective worktree's HEAD, the selector worktree taking precedence, and ignores any
|
|
337
333
|
# text branch signal so the merge probe inspects the worktree the call operates on
|
|
338
334
|
# (issue #663 remediation CR-2); otherwise the explicit branch signal decides.
|
|
@@ -344,11 +340,36 @@ function Resolve-EpicScopeCheckpoint {
|
|
|
344
340
|
$effectiveRoot = Find-WorktreeResolutionRoot -Path $WorktreeSelector
|
|
345
341
|
}
|
|
346
342
|
if ($null -eq $effectiveRoot) {
|
|
347
|
-
return (New-EpicScopeResult -IsEpicScope $false -
|
|
343
|
+
return (New-EpicScopeResult -IsEpicScope $false -Reason 'selector-unresolved')
|
|
348
344
|
}
|
|
349
345
|
$candidate = Get-EpicScopeWorktreeHeadBranch -WorktreeRoot $effectiveRoot
|
|
350
346
|
}
|
|
351
|
-
if ($null -eq $candidate
|
|
347
|
+
if ($null -eq $candidate) {
|
|
348
|
+
return (New-EpicScopeResult -IsEpicScope $false -Reason 'branch-mismatch')
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# Locate the governing checkpoint by the candidate branch (issue #690), never by
|
|
352
|
+
# composing it from the session root.
|
|
353
|
+
$target = Resolve-WorktreeEpicTarget -IntegrationBranch $candidate -SessionRoot $root
|
|
354
|
+
if ($target.Status -eq 'NoTarget') {
|
|
355
|
+
return (New-EpicScopeResult -IsEpicScope $false -Reason 'epic-checkpoint-absent-or-unparseable')
|
|
356
|
+
}
|
|
357
|
+
if ($target.Status -eq 'Ambiguous') {
|
|
358
|
+
return (New-EpicScopeResult -IsEpicScope $false -Reason 'target-worktree-ambiguous')
|
|
359
|
+
}
|
|
360
|
+
$path = Join-WorktreeResolutionPath -WorktreeRoot $target.WorktreeRoot -RepoRelativePath (Get-EpicScopeCheckpointRelativePath)
|
|
361
|
+
$checkpoint = ConvertFrom-EpicScopeCheckpointText -Text (Get-EpicScopeCheckpointText -Path $path)
|
|
362
|
+
if ($null -eq $checkpoint) {
|
|
363
|
+
return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'epic-checkpoint-absent-or-unparseable')
|
|
364
|
+
}
|
|
365
|
+
if ([string](Get-EpicScopePropertyValue -InputObject $checkpoint -Name 'route_id') -cne $script:EpicRouteId) {
|
|
366
|
+
return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'route_id')
|
|
367
|
+
}
|
|
368
|
+
$integrationBranch = [string](Get-EpicScopePropertyValue -InputObject $checkpoint -Name 'integration_branch')
|
|
369
|
+
if ([string]::IsNullOrWhiteSpace($integrationBranch)) {
|
|
370
|
+
return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'integration_branch')
|
|
371
|
+
}
|
|
372
|
+
if ($candidate -cne $integrationBranch) {
|
|
352
373
|
return (New-EpicScopeResult -IsEpicScope $false -CheckpointPath $path -Reason 'branch-mismatch')
|
|
353
374
|
}
|
|
354
375
|
|
package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1
CHANGED
|
@@ -213,11 +213,25 @@ function Get-WorktreeItemLiveRoot {
|
|
|
213
213
|
return , [string[]] $live.ToArray()
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
# Private: convert a resolved root and session path into a result, labelling it
|
|
217
|
-
# SessionRoot or OtherWorktree so that test is written once, as the sibling normalisation
|
|
218
|
-
# constructor does. The ConvertTo verb is deliberate: a New- verb is state-changing to
|
|
219
|
-
# PSScriptAnalyzer and this function changes nothing.
|
|
220
216
|
function ConvertTo-WorktreeItemResolvedResult {
|
|
217
|
+
<#
|
|
218
|
+
.SYNOPSIS
|
|
219
|
+
Convert a resolved root and session path into a SessionRoot or OtherWorktree result.
|
|
220
|
+
.DESCRIPTION
|
|
221
|
+
Labels the result SessionRoot when the root is the session's own worktree and
|
|
222
|
+
OtherWorktree otherwise, so that test is written once. Exported for issue #690 so
|
|
223
|
+
the run resolver shares this one labelling definition rather than a second copy.
|
|
224
|
+
The ConvertTo verb is deliberate: a New- verb is state-changing to PSScriptAnalyzer
|
|
225
|
+
and this function changes nothing.
|
|
226
|
+
.PARAMETER WorktreeRoot
|
|
227
|
+
The resolved worktree root.
|
|
228
|
+
.PARAMETER SessionRoot
|
|
229
|
+
The calling process's path.
|
|
230
|
+
.PARAMETER Detail
|
|
231
|
+
A prose clause safe to append to a gate's deny reason.
|
|
232
|
+
.PARAMETER Branch
|
|
233
|
+
Optional. The branch signal that produced the target.
|
|
234
|
+
#>
|
|
221
235
|
[CmdletBinding()]
|
|
222
236
|
[OutputType([pscustomobject])]
|
|
223
237
|
param(
|
|
@@ -389,4 +403,5 @@ Export-ModuleMember -Function `
|
|
|
389
403
|
Get-WorktreeItemCheckpointText, `
|
|
390
404
|
Get-WorktreeItemCheckpointIssue, `
|
|
391
405
|
Get-WorktreeItemLiveRoot, `
|
|
406
|
+
ConvertTo-WorktreeItemResolvedResult, `
|
|
392
407
|
Resolve-WorktreeItemTarget
|