@danmoisan/drm-copilot-mcp 1.1.16 → 1.1.18

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 (105) hide show
  1. package/out/mcp-server.js +1139 -133
  2. package/package.json +4 -3
  3. package/resources/claude-customizations/.claude/agents/feature-review.md +10 -1
  4. package/resources/claude-customizations/.claude/agents/orchestrator.md +16 -3
  5. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +18 -6
  6. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
  7. package/resources/claude-customizations/.claude/hooks/{enforce-powershell-batch-budget-route.ps1 → enforce-batch-budget-route.ps1} +12 -10
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
  11. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
  14. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
  15. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
  16. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
  17. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
  18. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
  19. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +3 -3
  20. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
  21. package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
  22. package/resources/claude-customizations/.claude/lib/bash/parallel-lane-assertion.sh +4 -2
  23. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
  24. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
  25. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
  26. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -13
  27. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +375 -0
  28. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +22 -15
  29. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +337 -0
  30. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +9 -1
  31. package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
  32. package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
  33. package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
  34. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
  35. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
  36. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
  37. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +119 -3
  38. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
  39. package/resources/claude-customizations/.claude/rules/quality-tiers.md +1 -1
  40. package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +8 -0
  41. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +2 -0
  42. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +11 -3
  43. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +11 -2
  44. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +169 -5
  45. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +1 -1
  46. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +6 -43
  47. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +4 -15
  48. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +15 -6
  49. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
  50. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +10 -0
  51. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +3 -1
  52. package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
  53. package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +4 -0
  54. package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
  55. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +33 -13
  56. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +50 -21
  57. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
  58. package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
  59. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +5 -1
  60. package/resources/claude-customizations/pack-manifests/core.json +10 -0
  61. package/resources/claude-customizations/pack-manifests/powershell.json +0 -1
  62. package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +8 -0
  63. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +5 -3
  64. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
  65. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  66. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +30 -6
  67. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +3 -1
  68. package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +0 -1
  69. package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
  70. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +30 -7
  71. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -2
  72. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +18 -3
  73. package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
  74. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +1 -1
  75. package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +2 -0
  76. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +1 -0
  77. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +1 -0
  78. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +1 -0
  79. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +1 -0
  80. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +1 -0
  81. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +1 -0
  82. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +1 -1
  83. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +1 -1
  84. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +1 -1
  85. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +1 -1
  86. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +1 -1
  87. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +1 -1
  88. package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
  89. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
  90. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
  91. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
  92. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
  93. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
  94. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
  95. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  96. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
  97. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +4 -124
  98. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
  99. package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
  100. package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
  101. package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
  102. package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
  103. package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +8 -0
  104. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
  105. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +16 -1
@@ -0,0 +1,467 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Pure radius-drift detection for the parallel orchestration surface.
4
+
5
+ .DESCRIPTION
6
+ Destination-runtime PowerShell port (issue #763) of parallel drift detection:
7
+ the checkpoint readers of scripts/dev_tools/_parallel_drift_cli_io.py,
8
+ detect_escaped_paths, build_drift_event, and recompute_conflicts_with_observed
9
+ of scripts/dev_tools/parallel_drift_detection.py, the per-peer helpers of
10
+ scripts/dev_tools/_parallel_drift_scheduling.py, build_observed_radius, and
11
+ evaluate_drift of scripts/dev_tools/parallel_drift_detection_cli.py. The
12
+ Python modules remain the repository authority and the parity reference; the
13
+ shared corpus tests/fixtures/parallel_drift binds the two.
14
+
15
+ Blast-radius logic is not re-derived here: subsumption, observed-radius
16
+ construction, radius normalization, the contention relation, and the
17
+ integration-cost pair decision are the functions of .claude/lib/blast-radius.
18
+ Halt selection and the shape guards live in ParallelDriftHalt.psm1.
19
+
20
+ Every function is pure: no filesystem, subprocess, network, or clock access,
21
+ and no input is mutated. Collections are always returned as arrays, and every
22
+ malformed input throws with a message naming the offending field. Each
23
+ function's [OutputType] attribute states its return type.
24
+ CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
25
+ #>
26
+
27
+ Set-StrictMode -Version Latest
28
+ $ErrorActionPreference = 'Stop'
29
+
30
+ # The halt module is imported first, then the blast-radius facade, then the two
31
+ # blast-radius modules whose functions the facade does not re-export.
32
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'ParallelDriftHalt.psm1') -Force -ErrorAction Stop
33
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '../blast-radius/BlastRadius.psm1') -Force -ErrorAction Stop
34
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '../blast-radius/BlastRadiusGlob.psm1') -Force -ErrorAction Stop
35
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '../blast-radius/BlastRadiusValidation.psm1') -Force -ErrorAction Stop
36
+
37
+ # The F3-owned members this module emits or compares, and the three verdicts.
38
+ $script:ItemStateInFlight = 'in_flight'
39
+ $script:ActionRaisedBlockingFinding = 'raised_blocking_finding'
40
+ $script:ActionHaltedLaterStartedItem = 'halted_later_started_item'
41
+ $script:DriftAction = @($script:ActionRaisedBlockingFinding, $script:ActionHaltedLaterStartedItem)
42
+ $script:BandName = @('C1', 'C2', 'C3', 'C4')
43
+ $script:ResultNoEscape = 'no_escape'
44
+ $script:ResultNoNewConflict = 'no_new_conflict'
45
+ $script:ResultHaltRequired = 'halt_required'
46
+
47
+ # Numeric runtime types an issue_num may carry for the equality comparisons the
48
+ # Python readers make with ==. Boolean is excluded.
49
+ $script:NumericTypeName = @('System.Int16', 'System.Int32', 'System.Int64', 'System.Byte', 'System.SByte',
50
+ 'System.UInt16', 'System.UInt32', 'System.UInt64', 'System.Double', 'System.Single', 'System.Decimal')
51
+
52
+ function Test-ParallelDriftList {
53
+ <#
54
+ .SYNOPSIS
55
+ Report whether a deserialized value is a JSON list (an IList that is not a string).
56
+ #>
57
+ [CmdletBinding()]
58
+ [OutputType([bool])]
59
+ param([AllowNull()][object] $Value)
60
+
61
+ return ($Value -is [System.Collections.IList]) -and ($Value -isnot [string])
62
+ }
63
+
64
+ function Test-ParallelDriftKeyEqual {
65
+ <#
66
+ .SYNOPSIS
67
+ Compare a recorded issue_num (Value) with an item key (ItemKey) as the Python == does.
68
+ .DESCRIPTION
69
+ True only for a numeric, non-boolean value numerically equal to the key.
70
+ #>
71
+ [CmdletBinding()]
72
+ [OutputType([bool])]
73
+ param([AllowNull()][object] $Value, [long] $ItemKey)
74
+
75
+ if ($null -eq $Value -or $script:NumericTypeName -notcontains $Value.GetType().FullName) { return $false }
76
+ return [double]$Value -eq [double]$ItemKey
77
+ }
78
+
79
+ function Get-ParallelDriftCheckpointItem {
80
+ <#
81
+ .SYNOPSIS
82
+ Read the checkpoint's items[] collection as object records, in checkpoint order.
83
+ .DESCRIPTION
84
+ Port of checkpoint_items. An unusable collection is rejected rather than
85
+ filtered: a dropped peer would never be evaluated against the observed
86
+ radius, which is the under-reporting failure drift detection exists to catch.
87
+ .PARAMETER State
88
+ The parsed checkpoint.
89
+ #>
90
+ [CmdletBinding()]
91
+ [OutputType([object[]])]
92
+ param([Parameter(Mandatory = $true)][System.Collections.IDictionary] $State)
93
+
94
+ $items = $State['items']
95
+ if (-not (Test-ParallelDriftList -Value $items)) { throw 'Parallel checkpoint items must be a list.' }
96
+ $record = [System.Collections.Generic.List[object]]::new()
97
+ # Reject, rather than skip, any entry that is not an object.
98
+ foreach ($entry in $items) {
99
+ if ($entry -isnot [System.Collections.IDictionary]) {
100
+ throw "Parallel checkpoint items[] entries must be objects; found: $entry."
101
+ }
102
+ $record.Add($entry)
103
+ }
104
+ return , $record.ToArray()
105
+ }
106
+
107
+ function Get-ParallelDriftCheckpointEdge {
108
+ <#
109
+ .SYNOPSIS
110
+ Read the checkpoint's conflict_edges[] collection, read-only.
111
+ .DESCRIPTION
112
+ Port of conflict_edges. A non-object edge is omitted, which leaves any
113
+ conflict over that pair reportable as new (fail closed).
114
+ .PARAMETER State
115
+ The parsed checkpoint.
116
+ #>
117
+ [CmdletBinding()]
118
+ [OutputType([object[]])]
119
+ param([Parameter(Mandatory = $true)][System.Collections.IDictionary] $State)
120
+
121
+ $edges = $State['conflict_edges']
122
+ if (-not (Test-ParallelDriftList -Value $edges)) { throw 'Parallel checkpoint conflict_edges must be a list.' }
123
+ return , @($edges | Where-Object { $_ -is [System.Collections.IDictionary] })
124
+ }
125
+
126
+ function Get-ParallelDriftItemRecord {
127
+ <#
128
+ .SYNOPSIS
129
+ Find the item record (among Item) carrying the issue_num ItemKey.
130
+ .DESCRIPTION
131
+ Port of item_by_key. A key the checkpoint does not track is an error.
132
+ #>
133
+ [CmdletBinding()]
134
+ [OutputType([System.Collections.IDictionary])]
135
+ param([Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]] $Item, [Parameter(Mandatory = $true)][long] $ItemKey)
136
+
137
+ # Scan in checkpoint order; issue numbers are unique, so the first match wins.
138
+ foreach ($record in $Item) {
139
+ if (Test-ParallelDriftKeyEqual -Value $record['issue_num'] -ItemKey $ItemKey) { return $record }
140
+ }
141
+ throw "Parallel checkpoint records no items[] entry with issue_num $ItemKey."
142
+ }
143
+
144
+ function Get-ParallelDriftDeclaredPath {
145
+ <#
146
+ .SYNOPSIS
147
+ Read one item record's declared blast_radius.paths verbatim.
148
+ .DESCRIPTION
149
+ Port of declared_paths. Entry-level shape is checked later by the escape
150
+ detection, which rejects a blank or non-string entry.
151
+ .PARAMETER Item
152
+ The item record.
153
+ #>
154
+ [CmdletBinding()]
155
+ [OutputType([object[]])]
156
+ param([Parameter(Mandatory = $true)][System.Collections.IDictionary] $Item)
157
+
158
+ $radius = $Item['blast_radius']
159
+ if ($radius -isnot [System.Collections.IDictionary]) {
160
+ throw "Parallel checkpoint items[] blast_radius must be an object; found: $radius."
161
+ }
162
+ $paths = $radius['paths']
163
+ if (-not (Test-ParallelDriftList -Value $paths)) {
164
+ throw "Parallel checkpoint items[] blast_radius.paths must be a list; found: $paths."
165
+ }
166
+ return , @($paths)
167
+ }
168
+
169
+ function Get-ParallelDriftEscapedPath {
170
+ <#
171
+ .SYNOPSIS
172
+ Return the changed paths no declared radius entry covers, deduplicated and ordinally sorted.
173
+ .DESCRIPTION
174
+ Port of detect_escaped_paths, using the blast-radius subsumption relation
175
+ Test-PathSubsumed so plan-time coverage and execution-time drift apply one
176
+ predicate.
177
+ .PARAMETER ChangedPath
178
+ Observed repository-relative paths; may be empty.
179
+ .PARAMETER DeclaredPath
180
+ The declared blast_radius.paths; may be empty, which covers nothing.
181
+ #>
182
+ [CmdletBinding()]
183
+ [OutputType([string[]], [System.Object[]])]
184
+ param(
185
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $ChangedPath,
186
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $DeclaredPath
187
+ )
188
+
189
+ $changed = Assert-ParallelDriftPathList -Value $ChangedPath -FieldName 'changed' -AllowEmpty
190
+ $declared = Assert-ParallelDriftPathList -Value $DeclaredPath -FieldName 'declared' -AllowEmpty
191
+ $escaped = [System.Collections.Generic.List[string]]::new()
192
+ # Test each changed path independently against the whole declared collection.
193
+ foreach ($path in $changed) {
194
+ if (-not (Test-PathSubsumed -Path $path -CoveringPath $declared)) { $escaped.Add($path) }
195
+ }
196
+ return , [string[]]$escaped.ToArray()
197
+ }
198
+
199
+ function Get-ParallelDriftEvent {
200
+ <#
201
+ .SYNOPSIS
202
+ Build one drift_events[] record in the six-key design shape.
203
+ .DESCRIPTION
204
+ Port of build_drift_event. ItemKey must be a positive integer; the three
205
+ path collections (DeclaredPath, ObservedPath, EscapedPath) are rendered
206
+ deduplicated and ordinally sorted, and EscapedPath must be non-empty; At
207
+ must be non-blank; Action must be raised_blocking_finding or
208
+ halted_later_started_item.
209
+ #>
210
+ [CmdletBinding()]
211
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
212
+ param(
213
+ [Parameter(Mandatory = $true)][AllowNull()][object] $ItemKey,
214
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $DeclaredPath,
215
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $ObservedPath,
216
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $EscapedPath,
217
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][object] $At,
218
+ [Parameter(Mandatory = $true)][AllowNull()][object] $Action
219
+ )
220
+
221
+ return [ordered]@{
222
+ item_key = Assert-ParallelDriftItemKey -Value $ItemKey -FieldName 'item_key'
223
+ declared = Assert-ParallelDriftPathList -Value $DeclaredPath -FieldName 'declared' -AllowEmpty
224
+ observed = Assert-ParallelDriftPathList -Value $ObservedPath -FieldName 'observed' -AllowEmpty
225
+ escaped_paths = Assert-ParallelDriftPathList -Value $EscapedPath -FieldName 'escaped_paths'
226
+ at = Assert-ParallelDriftText -Value $At -FieldName 'at'
227
+ action = Assert-ParallelDriftEnumMember -Value $Action -Vocabulary $script:DriftAction -FieldName 'action'
228
+ }
229
+ }
230
+
231
+ function Get-ParallelDriftExistingEdgePair {
232
+ <#
233
+ .SYNOPSIS
234
+ Collect the canonical pairs already recorded as conflict edges.
235
+ .DESCRIPTION
236
+ Port of existing_edge_pairs. Returns distinct two-element key arrays,
237
+ lower key first. An edge with unreadable or identical endpoints is
238
+ omitted, leaving a conflict over that pair reportable as new.
239
+ .PARAMETER Edge
240
+ The object-shaped conflict_edges[] records.
241
+ #>
242
+ [CmdletBinding()]
243
+ [OutputType([object[]])]
244
+ param([Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]] $Edge)
245
+
246
+ $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
247
+ $pairs = [System.Collections.Generic.List[object]]::new()
248
+ # Normalize before collecting so a reversed pair is never misread as new.
249
+ foreach ($record in $Edge) {
250
+ $first = ConvertTo-ParallelDriftItemKey -Value $record['a']
251
+ $second = ConvertTo-ParallelDriftItemKey -Value $record['b']
252
+ if ($null -eq $first -or $null -eq $second -or $first -eq $second) { continue }
253
+ $pair = Get-ParallelDriftCanonicalPair -First $first -Second $second
254
+ if ($seen.Add("$($pair[0]),$($pair[1])")) { $pairs.Add($pair) }
255
+ }
256
+ return , $pairs.ToArray()
257
+ }
258
+
259
+ function Get-ParallelDriftItemBand {
260
+ <#
261
+ .SYNOPSIS
262
+ Read the complexity band of the record (among Item) carrying ItemKey.
263
+ .DESCRIPTION
264
+ Port of item_band. A band is a benefit estimate, not a safety input, so an
265
+ absent or unreadable band yields null and the scheduling rule applies
266
+ default_band.
267
+ #>
268
+ [CmdletBinding()]
269
+ [OutputType([string])]
270
+ param([Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]] $Item, [Parameter(Mandatory = $true)][long] $ItemKey)
271
+
272
+ # Return the first matching item's band; issue numbers are unique.
273
+ foreach ($record in $Item) {
274
+ if (-not (Test-ParallelDriftKeyEqual -Value $record['issue_num'] -ItemKey $ItemKey)) { continue }
275
+ $band = $record['complexity_band']
276
+ if ($band -is [string] -and $script:BandName -ccontains $band) { return $band }
277
+ return $null
278
+ }
279
+ return $null
280
+ }
281
+
282
+ function Test-ParallelDriftObservedPairEdge {
283
+ <#
284
+ .SYNOPSIS
285
+ Decide whether a drifting item's observed radius makes a peer pair an edge.
286
+ .DESCRIPTION
287
+ Port of observed_pair_is_edge. A PeerRadius that is not an object, or that
288
+ the blast-radius normalizer rejects, cannot be evaluated and counts as an
289
+ edge (fail closed), because the relation reports no conflict for an empty
290
+ radius. Otherwise the pair goes through the integration-cost decision with
291
+ ObservedRadius first, the parsed Config, both bands (null applies
292
+ default_band), and the contention relation passed explicitly; the edge key
293
+ of the returned hashtable is read, and the hashtable itself is never
294
+ tested for truthiness.
295
+ #>
296
+ [CmdletBinding()]
297
+ [OutputType([bool])]
298
+ param(
299
+ [Parameter(Mandatory = $true)][System.Collections.IDictionary] $ObservedRadius,
300
+ [Parameter(Mandatory = $true)][AllowNull()][object] $PeerRadius,
301
+ [Parameter(Mandatory = $true)][AllowNull()][object] $Config,
302
+ [AllowNull()][object] $ObservedBand,
303
+ [AllowNull()][object] $PeerBand
304
+ )
305
+
306
+ if ($PeerRadius -isnot [System.Collections.IDictionary]) { return $true }
307
+ # Any normalization failure means the peer radius cannot be evaluated; the
308
+ # Python reference catches TypeError and ValueError here for the same reason.
309
+ try {
310
+ $peer = ConvertTo-NormalizedBlastRadius -Radius $PeerRadius
311
+ } catch {
312
+ return $true
313
+ }
314
+ $decision = Get-BlastRadiusPairDecision -RadiusA $ObservedRadius -RadiusB $peer -Config $Config `
315
+ -BandA $ObservedBand -BandB $PeerBand -Relation ${function:Test-BlastRadiusConflict}
316
+ return [bool]$decision['edge']
317
+ }
318
+
319
+ function Get-ParallelDriftObservedRadius {
320
+ <#
321
+ .SYNOPSIS
322
+ Build the observed-source radius from a changed-path set.
323
+ .DESCRIPTION
324
+ Port of build_observed_radius: ObservedPath (may be empty) and ComputedAt
325
+ are validated here and the six-key radius is built from them and the
326
+ parsed Config by the blast-radius library, never by hand.
327
+ #>
328
+ [CmdletBinding()]
329
+ [OutputType([hashtable])]
330
+ param(
331
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $ObservedPath,
332
+ [Parameter(Mandatory = $true)][AllowNull()][object] $Config,
333
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][object] $ComputedAt
334
+ )
335
+
336
+ $paths = Assert-ParallelDriftPathList -Value $ObservedPath -FieldName 'observed_paths' -AllowEmpty
337
+ $timestamp = Assert-ParallelDriftText -Value $ComputedAt -FieldName 'computed_at'
338
+ return Get-BlastRadiusFromObservedPaths -ObservedPaths $paths -Config $Config -ComputedAt $timestamp
339
+ }
340
+
341
+ function Get-ParallelDriftNewConflictPair {
342
+ <#
343
+ .SYNOPSIS
344
+ Return the pairs that newly conflict once the observed radius is used.
345
+ .DESCRIPTION
346
+ Port of recompute_conflicts_with_observed. The observed radius of
347
+ DriftingItemKey, built from ObservedPath and stamped ComputedAt, is
348
+ evaluated against every other in-flight record of Item through the
349
+ scheduling rule with both items' bands and the parsed Config; a pair
350
+ already recorded among the conflict_edges[] records in Edge is skipped.
351
+ Every item's issue_num is validated, in flight or not. Returns canonical
352
+ two-element key arrays in ascending order.
353
+ #>
354
+ [CmdletBinding()]
355
+ [OutputType([object[]])]
356
+ param(
357
+ [Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]] $Item,
358
+ [Parameter(Mandatory = $true)][AllowNull()][object] $DriftingItemKey,
359
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $ObservedPath,
360
+ [Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]] $Edge,
361
+ [Parameter(Mandatory = $true)][AllowNull()][object] $Config,
362
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][object] $ComputedAt
363
+ )
364
+
365
+ $drifting = Assert-ParallelDriftItemKey -Value $DriftingItemKey -FieldName 'drifting_item_key'
366
+ $observed = Get-ParallelDriftObservedRadius -ObservedPath $ObservedPath -Config $Config -ComputedAt $ComputedAt
367
+ $existing = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
368
+ # Index the recorded edges by canonical identity for constant-time skips.
369
+ foreach ($pair in (Get-ParallelDriftExistingEdgePair -Edge $Edge)) { [void]$existing.Add("$($pair[0]),$($pair[1])") }
370
+ $observedBand = Get-ParallelDriftItemBand -Item $Item -ItemKey $drifting
371
+
372
+ $found = [System.Collections.Generic.List[object]]::new()
373
+ # Evaluate every other in-flight peer; only newly introduced contention is kept.
374
+ for ($index = 0; $index -lt $Item.Count; $index++) {
375
+ $record = $Item[$index]
376
+ $itemKey = Assert-ParallelDriftItemKey -Value $record['issue_num'] -FieldName "items[$index].issue_num"
377
+ $state = $record['state']
378
+ $isInFlight = ($state -is [string]) -and ($state -ceq $script:ItemStateInFlight)
379
+ if ($itemKey -eq $drifting -or -not $isInFlight) { continue }
380
+ $pair = Get-ParallelDriftCanonicalPair -First $drifting -Second $itemKey
381
+ if ($existing.Contains("$($pair[0]),$($pair[1])")) { continue }
382
+ $peerBand = Get-ParallelDriftItemBand -Item $Item -ItemKey $itemKey
383
+ if (Test-ParallelDriftObservedPairEdge -ObservedRadius $observed -PeerRadius $record['blast_radius'] -Config $Config `
384
+ -ObservedBand $observedBand -PeerBand $peerBand) {
385
+ $found.Add($pair)
386
+ }
387
+ }
388
+ return , @($found | Sort-Object -Property @{ Expression = { $_[0] } }, @{ Expression = { $_[1] } })
389
+ }
390
+
391
+ function Get-ParallelDriftResult {
392
+ <#
393
+ .SYNOPSIS
394
+ Run the detection steps over already-loaded data and build the nine-key payload.
395
+ .DESCRIPTION
396
+ Port of evaluate_drift. State (the parsed checkpoint, read only) supplies
397
+ items[] and conflict_edges[]; Config is the parsed truth table; ItemKey is
398
+ the evaluated item; ChangedPath is the observed set (may be empty); At is
399
+ recorded on the drift event and ComputedAt on the observed radius.
400
+
401
+ An escape is the precondition for every later step; with none,
402
+ drift_event and observed_radius are null. Otherwise the pairs are
403
+ recomputed, the halted keys selected, the observed radius built, and one
404
+ drift event recorded carrying the strongest action taken.
405
+ #>
406
+ [CmdletBinding()]
407
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
408
+ param(
409
+ [Parameter(Mandatory = $true)][System.Collections.IDictionary] $State,
410
+ [Parameter(Mandatory = $true)][AllowNull()][object] $Config,
411
+ [Parameter(Mandatory = $true)][long] $ItemKey,
412
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyCollection()][object] $ChangedPath,
413
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][object] $At,
414
+ [Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][object] $ComputedAt
415
+ )
416
+
417
+ $items = Get-ParallelDriftCheckpointItem -State $State
418
+ $declared = Get-ParallelDriftDeclaredPath -Item (Get-ParallelDriftItemRecord -Item $items -ItemKey $ItemKey)
419
+ $escaped = Get-ParallelDriftEscapedPath -ChangedPath $ChangedPath -DeclaredPath $declared
420
+
421
+ $pairs = @()
422
+ $halted = [long[]]@()
423
+ $driftEvent = $null
424
+ $observedRadius = $null
425
+ $result = $script:ResultNoEscape
426
+ # Only an escape leads to recomputation, halting, an observed radius, and an event.
427
+ if ($escaped.Count -gt 0) {
428
+ $pairs = Get-ParallelDriftNewConflictPair -Item $items -DriftingItemKey $ItemKey -ObservedPath $ChangedPath `
429
+ -Edge (Get-ParallelDriftCheckpointEdge -State $State) -Config $Config -ComputedAt $ComputedAt
430
+ $halted = Get-ParallelDriftHaltedItemKey -Item $items -Pair $pairs -DriftingItemKey $ItemKey
431
+ $observedRadius = Get-ParallelDriftObservedRadius -ObservedPath $ChangedPath -Config $Config -ComputedAt $ComputedAt
432
+ $action = $script:ActionRaisedBlockingFinding
433
+ $result = $script:ResultNoNewConflict
434
+ if ($pairs.Count -gt 0) {
435
+ $action = $script:ActionHaltedLaterStartedItem
436
+ $result = $script:ResultHaltRequired
437
+ }
438
+ $driftEvent = Get-ParallelDriftEvent -ItemKey $ItemKey -DeclaredPath $declared -ObservedPath $ChangedPath `
439
+ -EscapedPath $escaped -At $At -Action $action
440
+ }
441
+
442
+ return [ordered]@{
443
+ result = $result
444
+ item_key = $ItemKey
445
+ at = $At
446
+ computed_at = $ComputedAt
447
+ escaped_paths = [string[]]$escaped
448
+ newly_conflicting_pairs = [object[]]$pairs
449
+ halted_item_keys = [long[]]$halted
450
+ drift_event = $driftEvent
451
+ observed_radius = $observedRadius
452
+ }
453
+ }
454
+
455
+ Export-ModuleMember -Function `
456
+ Get-ParallelDriftCheckpointItem, `
457
+ Get-ParallelDriftCheckpointEdge, `
458
+ Get-ParallelDriftItemRecord, `
459
+ Get-ParallelDriftDeclaredPath, `
460
+ Get-ParallelDriftEscapedPath, `
461
+ Get-ParallelDriftEvent, `
462
+ Get-ParallelDriftExistingEdgePair, `
463
+ Get-ParallelDriftItemBand, `
464
+ Test-ParallelDriftObservedPairEdge, `
465
+ Get-ParallelDriftObservedRadius, `
466
+ Get-ParallelDriftNewConflictPair, `
467
+ Get-ParallelDriftResult