@danmoisan/drm-copilot-mcp 1.1.12 → 1.1.15
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 +30 -78
- package/package.json +2 -2
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +18 -1
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +16 -8
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +27 -27
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +43 -6
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +486 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +5 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +446 -0
- package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +330 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -2
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +5 -5
- package/resources/claude-customizations/.claude/rules/shell.md +8 -7
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +8 -8
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +234 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_actions_lib.sh +451 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +301 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_dirt_lib.sh +495 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +252 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_lib.sh +496 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_eol_lib.sh +212 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +492 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +476 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +182 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +11 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +32 -17
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +39 -10
- package/resources/claude-customizations/config/blast-radius.json +14 -1
- package/resources/claude-customizations/pack-manifests/core.json +13 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-resolution.ps1 +493 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +189 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +27 -27
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +16 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Integration-cost scheduling layer over the blast-radius contention relation.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Destination-runtime PowerShell mirror of
|
|
7
|
+
scripts/dev_tools/_blast_radius_scheduling.py (issue #722). The unchanged
|
|
8
|
+
relation Test-BlastRadiusConflict decides whether two radii contend; this
|
|
9
|
+
module weighs the cost of integrating two concurrent branches against the
|
|
10
|
+
parallelism benefit and records a conflict as an edge only when the cost
|
|
11
|
+
exceeds the configured tolerance. Conflicts within tolerance are returned as
|
|
12
|
+
tolerated overlaps. Each exported function names its Python counterpart.
|
|
13
|
+
|
|
14
|
+
Edge rule: edge iff conflict AND (hard OR tolerance_percent is 0 OR
|
|
15
|
+
cost * 100 > benefit * tolerance_percent). Hard means the reasons include
|
|
16
|
+
shared_surface_overlap or contract_dependency. The recorded reason is the
|
|
17
|
+
first kind of the relation's reason list, so the reason enum is unchanged.
|
|
18
|
+
|
|
19
|
+
Parity notes for maintainers:
|
|
20
|
+
- The Python module is the reference implementation; the committed
|
|
21
|
+
scheduling fixtures bind the two runtimes together.
|
|
22
|
+
- No overlap semantics are defined here. The entry-pair overlap test, the
|
|
23
|
+
mergeable exclusion, and the mergeable matcher are the sibling modules'.
|
|
24
|
+
- An absent (or null) conflict_tolerance key reads as tolerance 0 with unit
|
|
25
|
+
weights and durations, so the edge set equals the detected-conflict set.
|
|
26
|
+
- Test-BlastRadiusConflict lives in the facade BlastRadius.psm1, which
|
|
27
|
+
imports this module, so the caller supplies it as the [scriptblock]
|
|
28
|
+
-Relation; an omitted relation fails fast naming the facade.
|
|
29
|
+
- Every function is pure: no filesystem, subprocess, network, or clock
|
|
30
|
+
access, and no input is mutated.
|
|
31
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
32
|
+
#>
|
|
33
|
+
|
|
34
|
+
Set-StrictMode -Version Latest
|
|
35
|
+
$ErrorActionPreference = 'Stop'
|
|
36
|
+
|
|
37
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force -ErrorAction Stop
|
|
38
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1') -Force -ErrorAction Stop
|
|
39
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConflict.psm1') -Force -ErrorAction Stop
|
|
40
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusValidation.psm1') -Force -ErrorAction Stop
|
|
41
|
+
|
|
42
|
+
# Truth-table key and the label every reader error carries.
|
|
43
|
+
$script:ToleranceKey = 'conflict_tolerance'
|
|
44
|
+
$script:ToleranceLabel = 'config["conflict_tolerance"]'
|
|
45
|
+
|
|
46
|
+
# Member names of the key and of its two nested maps. Each must be carried
|
|
47
|
+
# exactly, so a misspelled name fails fast instead of reading as absent.
|
|
48
|
+
$script:MemberName = @('tolerance_percent', 'weights', 'band_durations', 'default_band', 'append_only_paths')
|
|
49
|
+
$script:WeightName = @('same_file', 'possible_overlap', 'append_only', 'module')
|
|
50
|
+
$script:BandName = @('C1', 'C2', 'C3', 'C4')
|
|
51
|
+
|
|
52
|
+
# Reason kinds that make a conflict an edge at every tolerance: a shared surface
|
|
53
|
+
# or a contract dependency cannot be reconciled by a merge step.
|
|
54
|
+
$script:HardReasonKind = @('shared_surface_overlap', 'contract_dependency')
|
|
55
|
+
|
|
56
|
+
# The fail-fast message for an omitted relation; the facade defines the relation.
|
|
57
|
+
$script:RelationRequired = '-Relation is required: pass ${function:Test-BlastRadiusConflict} from the facade module BlastRadius.psm1.'
|
|
58
|
+
|
|
59
|
+
# tolerance_percent is a percentage of the benefit.
|
|
60
|
+
$script:Percent = 100
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Reject a boolean or non-integer value, or one below Minimum. Booleans are
|
|
64
|
+
# rejected explicitly because the Python reference rejects them.
|
|
65
|
+
function Get-RequiredInteger {
|
|
66
|
+
[CmdletBinding()]
|
|
67
|
+
[OutputType([long])]
|
|
68
|
+
param(
|
|
69
|
+
[Parameter(Mandatory = $true)]
|
|
70
|
+
[AllowNull()]
|
|
71
|
+
[object] $Value,
|
|
72
|
+
[Parameter(Mandatory = $true)]
|
|
73
|
+
[string] $FieldName,
|
|
74
|
+
[Parameter(Mandatory = $true)]
|
|
75
|
+
[long] $Minimum
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
$isInteger = $Value -is [int] -or $Value -is [long] -or $Value -is [int16] -or $Value -is [byte]
|
|
79
|
+
if ($Value -is [bool] -or -not $isInteger) {
|
|
80
|
+
$actual = if ($null -eq $Value) { 'null' } else { $Value.GetType().Name }
|
|
81
|
+
throw "$FieldName must be an integer, got $actual."
|
|
82
|
+
}
|
|
83
|
+
if ($Value -lt $Minimum) {
|
|
84
|
+
throw "$FieldName must be >= $Minimum, got $Value."
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [long]$Value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Require a mapping to carry exactly Names, reporting both directions of
|
|
91
|
+
# mismatch in one error. Names are compared ordinally.
|
|
92
|
+
function Assert-ExactMemberName {
|
|
93
|
+
[CmdletBinding()]
|
|
94
|
+
param(
|
|
95
|
+
[Parameter(Mandatory = $true)]
|
|
96
|
+
[hashtable] $Mapping,
|
|
97
|
+
[Parameter(Mandatory = $true)]
|
|
98
|
+
[string[]] $Name,
|
|
99
|
+
[Parameter(Mandatory = $true)]
|
|
100
|
+
[string] $FieldName
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
$present = @($Mapping.Keys | ForEach-Object { [string]$_ })
|
|
104
|
+
$missing = @($Name | Where-Object { $present -cnotcontains $_ })
|
|
105
|
+
$unknown = @($present | Where-Object { $Name -cnotcontains $_ } | Sort-Object)
|
|
106
|
+
if ($missing.Count -gt 0 -or $unknown.Count -gt 0) {
|
|
107
|
+
throw "$FieldName must carry exactly $($Name -join ', '); missing [$($missing -join ', ')], unknown [$($unknown -join ', ')]."
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Read a nested map with exactly Names, each an integer >= 1.
|
|
112
|
+
function Get-RequiredIntegerMap {
|
|
113
|
+
[CmdletBinding()]
|
|
114
|
+
[OutputType([hashtable])]
|
|
115
|
+
param(
|
|
116
|
+
[Parameter(Mandatory = $true)]
|
|
117
|
+
[AllowNull()]
|
|
118
|
+
[object] $Value,
|
|
119
|
+
[Parameter(Mandatory = $true)]
|
|
120
|
+
[string[]] $Name,
|
|
121
|
+
[Parameter(Mandatory = $true)]
|
|
122
|
+
[string] $Member
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
$label = "$script:ToleranceLabel[""$Member""]"
|
|
126
|
+
$mapping = Get-RequiredMapping -Value $Value -FieldName $label
|
|
127
|
+
Assert-ExactMemberName -Mapping $mapping -Name $Name -FieldName $label
|
|
128
|
+
$result = @{}
|
|
129
|
+
foreach ($entry in $Name) {
|
|
130
|
+
$result[$entry] = Get-RequiredInteger -Value $mapping[$entry] -FieldName "$label[""$entry""]" -Minimum 1
|
|
131
|
+
}
|
|
132
|
+
return $result
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function Get-ConfigConflictTolerance {
|
|
136
|
+
<#
|
|
137
|
+
.SYNOPSIS
|
|
138
|
+
Read and strictly validate the conflict_tolerance key of the truth table.
|
|
139
|
+
|
|
140
|
+
.DESCRIPTION
|
|
141
|
+
Port of config_conflict_tolerance. An absent or null key returns the
|
|
142
|
+
strict semantics: tolerance 0, every weight 1, every band duration 1,
|
|
143
|
+
default_band C1, and no append-only paths.
|
|
144
|
+
|
|
145
|
+
.PARAMETER Config
|
|
146
|
+
Parsed config/blast-radius.json.
|
|
147
|
+
|
|
148
|
+
.OUTPUTS
|
|
149
|
+
System.Collections.Hashtable. Keys tolerance_percent, weights,
|
|
150
|
+
band_durations, default_band, and append_only_paths. Every error message
|
|
151
|
+
names the conflict_tolerance key.
|
|
152
|
+
#>
|
|
153
|
+
[CmdletBinding()]
|
|
154
|
+
[OutputType([hashtable])]
|
|
155
|
+
param(
|
|
156
|
+
[Parameter(Mandatory = $true)]
|
|
157
|
+
[AllowNull()]
|
|
158
|
+
[object] $Config
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
$mapping = Get-RequiredMapping -Value $Config -FieldName 'config'
|
|
162
|
+
$value = if ($mapping.ContainsKey($script:ToleranceKey)) { $mapping[$script:ToleranceKey] } else { $null }
|
|
163
|
+
if ($null -eq $value) {
|
|
164
|
+
$unit = @{}
|
|
165
|
+
foreach ($entry in $script:WeightName) { $unit[$entry] = [long]1 }
|
|
166
|
+
$band = @{}
|
|
167
|
+
foreach ($entry in $script:BandName) { $band[$entry] = [long]1 }
|
|
168
|
+
return @{ tolerance_percent = [long]0; weights = $unit; band_durations = $band; default_band = $script:BandName[0]; append_only_paths = [string[]]@() }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
$member = Get-RequiredMapping -Value $value -FieldName $script:ToleranceLabel
|
|
172
|
+
Assert-ExactMemberName -Mapping $member -Name $script:MemberName -FieldName $script:ToleranceLabel
|
|
173
|
+
$defaultBand = $member['default_band']
|
|
174
|
+
if ($defaultBand -isnot [string] -or $script:BandName -cnotcontains $defaultBand) {
|
|
175
|
+
throw "$script:ToleranceLabel[""default_band""] must be one of $($script:BandName -join ', ')."
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return @{
|
|
179
|
+
tolerance_percent = Get-RequiredInteger -Value $member['tolerance_percent'] -FieldName "$script:ToleranceLabel[""tolerance_percent""]" -Minimum 0
|
|
180
|
+
weights = Get-RequiredIntegerMap -Value $member['weights'] -Name $script:WeightName -Member 'weights'
|
|
181
|
+
band_durations = Get-RequiredIntegerMap -Value $member['band_durations'] -Name $script:BandName -Member 'band_durations'
|
|
182
|
+
default_band = [string]$defaultBand
|
|
183
|
+
append_only_paths = [string[]]@(Get-RequiredStringList -Value $member['append_only_paths'] -FieldName "$script:ToleranceLabel[""append_only_paths""]")
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Weigh one overlapping path pair: append_only when a concrete entry of the pair
|
|
188
|
+
# is append-only (checked first, so a registry named by both items stays cheap),
|
|
189
|
+
# else same_file for two equal concrete entries, else possible_overlap.
|
|
190
|
+
function Get-PathPairWeight {
|
|
191
|
+
[CmdletBinding()]
|
|
192
|
+
[OutputType([long])]
|
|
193
|
+
param(
|
|
194
|
+
[Parameter(Mandatory = $true)]
|
|
195
|
+
[string] $EntryA,
|
|
196
|
+
[Parameter(Mandatory = $true)]
|
|
197
|
+
[string] $EntryB,
|
|
198
|
+
[Parameter(Mandatory = $true)]
|
|
199
|
+
[hashtable] $Tolerance
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Only a concrete entry can name an append-only file.
|
|
203
|
+
$concrete = @(@($EntryA, $EntryB) | Where-Object { -not (Test-GlobEntry -Entry $_) })
|
|
204
|
+
$appendOnly = [string[]]@($Tolerance['append_only_paths'])
|
|
205
|
+
foreach ($entry in $concrete) {
|
|
206
|
+
if (Test-MergeablePath -Entry $entry -MergeablePath $appendOnly) {
|
|
207
|
+
return [long]$Tolerance['weights']['append_only']
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if ($concrete.Count -eq 2 -and [string]::Equals($EntryA, $EntryB, [System.StringComparison]::Ordinal)) {
|
|
211
|
+
return [long]$Tolerance['weights']['same_file']
|
|
212
|
+
}
|
|
213
|
+
return [long]$Tolerance['weights']['possible_overlap']
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function Get-BlastRadiusPairCost {
|
|
217
|
+
<#
|
|
218
|
+
.SYNOPSIS
|
|
219
|
+
Compute the integer integration cost of running two items concurrently.
|
|
220
|
+
|
|
221
|
+
.DESCRIPTION
|
|
222
|
+
Port of pair_cost. Sums the weight of every overlapping path pair left
|
|
223
|
+
after the mergeable exclusion, then adds the module weight times the
|
|
224
|
+
number of shared modules. Mergeable paths therefore contribute 0.
|
|
225
|
+
|
|
226
|
+
.PARAMETER RadiusA
|
|
227
|
+
First radius record.
|
|
228
|
+
|
|
229
|
+
.PARAMETER RadiusB
|
|
230
|
+
Second radius record.
|
|
231
|
+
|
|
232
|
+
.PARAMETER Config
|
|
233
|
+
Parsed truth table; only mergeable_paths is read, for the exclusion the
|
|
234
|
+
relation uses.
|
|
235
|
+
|
|
236
|
+
.PARAMETER Tolerance
|
|
237
|
+
A value returned by Get-ConfigConflictTolerance.
|
|
238
|
+
|
|
239
|
+
.OUTPUTS
|
|
240
|
+
System.Int64. The cost.
|
|
241
|
+
#>
|
|
242
|
+
[CmdletBinding()]
|
|
243
|
+
[OutputType([long])]
|
|
244
|
+
param(
|
|
245
|
+
[Parameter(Mandatory = $true)]
|
|
246
|
+
[AllowNull()]
|
|
247
|
+
[object] $RadiusA,
|
|
248
|
+
[Parameter(Mandatory = $true)]
|
|
249
|
+
[AllowNull()]
|
|
250
|
+
[object] $RadiusB,
|
|
251
|
+
[Parameter(Mandatory = $true)]
|
|
252
|
+
[AllowNull()]
|
|
253
|
+
[object] $Config,
|
|
254
|
+
[Parameter(Mandatory = $true)]
|
|
255
|
+
[hashtable] $Tolerance
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
$left = ConvertTo-NormalizedBlastRadius -Radius $RadiusA
|
|
259
|
+
$right = ConvertTo-NormalizedBlastRadius -Radius $RadiusB
|
|
260
|
+
$mergeable = [string[]]@(Get-ConfigMergeablePath -Config $Config)
|
|
261
|
+
$pathsA = @(Get-NonMergeablePathEntry -Entry ([string[]]@($left['paths'])) -MergeablePath $mergeable)
|
|
262
|
+
$pathsB = @(Get-NonMergeablePathEntry -Entry ([string[]]@($right['paths'])) -MergeablePath $mergeable)
|
|
263
|
+
|
|
264
|
+
# Enumerate the overlapping pairs with the relation's own primitive, so the
|
|
265
|
+
# cost covers exactly the pairs the relation could report.
|
|
266
|
+
[long]$cost = 0
|
|
267
|
+
foreach ($entryA in $pathsA) {
|
|
268
|
+
foreach ($entryB in $pathsB) {
|
|
269
|
+
if (Test-EntryOverlap -EntryA $entryA -EntryB $entryB) {
|
|
270
|
+
$cost += Get-PathPairWeight -EntryA $entryA -EntryB $entryB -Tolerance $Tolerance
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
$sharedModule = @(@($left['modules']) | Where-Object { @($right['modules']) -ccontains $_ })
|
|
275
|
+
return $cost + ([long]$Tolerance['weights']['module'] * $sharedModule.Count)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function Get-BlastRadiusPairBenefit {
|
|
279
|
+
<#
|
|
280
|
+
.SYNOPSIS
|
|
281
|
+
Compute the parallelism benefit: the shorter of the two band durations.
|
|
282
|
+
|
|
283
|
+
.DESCRIPTION
|
|
284
|
+
Port of pair_benefit. A missing band reads as default_band.
|
|
285
|
+
|
|
286
|
+
.PARAMETER BandA
|
|
287
|
+
First item's complexity band, or null.
|
|
288
|
+
|
|
289
|
+
.PARAMETER BandB
|
|
290
|
+
Second item's complexity band, or null.
|
|
291
|
+
|
|
292
|
+
.PARAMETER Tolerance
|
|
293
|
+
A value returned by Get-ConfigConflictTolerance.
|
|
294
|
+
|
|
295
|
+
.OUTPUTS
|
|
296
|
+
System.Int64. The benefit. Throws when a band is not a configured band.
|
|
297
|
+
#>
|
|
298
|
+
[CmdletBinding()]
|
|
299
|
+
[OutputType([long])]
|
|
300
|
+
param(
|
|
301
|
+
[AllowNull()]
|
|
302
|
+
[object] $BandA,
|
|
303
|
+
[AllowNull()]
|
|
304
|
+
[object] $BandB,
|
|
305
|
+
[Parameter(Mandatory = $true)]
|
|
306
|
+
[hashtable] $Tolerance
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
$duration = foreach ($band in @($BandA, $BandB)) {
|
|
310
|
+
$resolved = if ($null -eq $band) { $Tolerance['default_band'] } else { $band }
|
|
311
|
+
if ($resolved -isnot [string] -or $script:BandName -cnotcontains $resolved) {
|
|
312
|
+
throw "band '$resolved' must be one of $($script:BandName -join ', ')."
|
|
313
|
+
}
|
|
314
|
+
[long]$Tolerance['band_durations'][$resolved]
|
|
315
|
+
}
|
|
316
|
+
return [long]([System.Math]::Min($duration[0], $duration[1]))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function Get-BlastRadiusPairDecision {
|
|
320
|
+
<#
|
|
321
|
+
.SYNOPSIS
|
|
322
|
+
Decide whether one pair is an edge, a tolerated overlap, or neither.
|
|
323
|
+
|
|
324
|
+
.DESCRIPTION
|
|
325
|
+
Port of decide_pair. Reads the tolerance first (so a malformed key fails
|
|
326
|
+
before the relation runs), calls the supplied -Relation exactly once
|
|
327
|
+
with RadiusA first, and applies the edge rule. At tolerance 0 the edge
|
|
328
|
+
flag equals the conflict verdict; cost and benefit are still reported.
|
|
329
|
+
|
|
330
|
+
.PARAMETER RadiusA
|
|
331
|
+
First radius record; passed first to the relation.
|
|
332
|
+
|
|
333
|
+
.PARAMETER RadiusB
|
|
334
|
+
Second radius record.
|
|
335
|
+
|
|
336
|
+
.PARAMETER Config
|
|
337
|
+
Parsed truth table.
|
|
338
|
+
|
|
339
|
+
.PARAMETER BandA
|
|
340
|
+
First item's complexity band, or null for default_band.
|
|
341
|
+
|
|
342
|
+
.PARAMETER BandB
|
|
343
|
+
Second item's complexity band, or null for default_band.
|
|
344
|
+
|
|
345
|
+
.PARAMETER Relation
|
|
346
|
+
The detection relation; pass ${function:Test-BlastRadiusConflict}.
|
|
347
|
+
|
|
348
|
+
.OUTPUTS
|
|
349
|
+
System.Collections.Hashtable. Keys conflict, edge, hard, cost, benefit,
|
|
350
|
+
reason, and reasons. Without a conflict, edge and hard are false, cost
|
|
351
|
+
and benefit are 0, reason is null, and reasons is empty.
|
|
352
|
+
#>
|
|
353
|
+
[CmdletBinding()]
|
|
354
|
+
[OutputType([hashtable])]
|
|
355
|
+
param(
|
|
356
|
+
[Parameter(Mandatory = $true)]
|
|
357
|
+
[AllowNull()]
|
|
358
|
+
[object] $RadiusA,
|
|
359
|
+
[Parameter(Mandatory = $true)]
|
|
360
|
+
[AllowNull()]
|
|
361
|
+
[object] $RadiusB,
|
|
362
|
+
[Parameter(Mandatory = $true)]
|
|
363
|
+
[AllowNull()]
|
|
364
|
+
[object] $Config,
|
|
365
|
+
[AllowNull()]
|
|
366
|
+
[object] $BandA,
|
|
367
|
+
[AllowNull()]
|
|
368
|
+
[object] $BandB,
|
|
369
|
+
[scriptblock] $Relation
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if ($null -eq $Relation) { throw $script:RelationRequired }
|
|
373
|
+
$tolerance = Get-ConfigConflictTolerance -Config $Config
|
|
374
|
+
$result = & $Relation -RadiusA $RadiusA -RadiusB $RadiusB -Config $Config
|
|
375
|
+
if (-not $result['conflict']) {
|
|
376
|
+
return @{ conflict = $false; edge = $false; hard = $false; cost = [long]0; benefit = [long]0; reason = $null; reasons = [string[]]@() }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
# Project the reason records to their kinds, which keep canonical order.
|
|
380
|
+
$reasons = [string[]]@($result['reasons'] | ForEach-Object { $_['kind'] })
|
|
381
|
+
$hard = @($reasons | Where-Object { $script:HardReasonKind -ccontains $_ }).Count -gt 0
|
|
382
|
+
$cost = Get-BlastRadiusPairCost -RadiusA $RadiusA -RadiusB $RadiusB -Config $Config -Tolerance $tolerance
|
|
383
|
+
$benefit = Get-BlastRadiusPairBenefit -BandA $BandA -BandB $BandB -Tolerance $tolerance
|
|
384
|
+
|
|
385
|
+
# Tolerance 0 is stated explicitly rather than left to the inequality, so
|
|
386
|
+
# strict identity holds whatever the cost enumeration yields.
|
|
387
|
+
$percent = [long]$tolerance['tolerance_percent']
|
|
388
|
+
$edge = $hard -or $percent -eq 0 -or ($cost * $script:Percent -gt $benefit * $percent)
|
|
389
|
+
return @{ conflict = $true; edge = $edge; hard = $hard; cost = $cost; benefit = $benefit; reason = $reasons[0]; reasons = $reasons }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# Validate item keys (integers, booleans rejected, distinct) and return the
|
|
393
|
+
# items as records sorted by key.
|
|
394
|
+
function Get-OrderedSchedulingItem {
|
|
395
|
+
[CmdletBinding()]
|
|
396
|
+
[OutputType([System.Object[]])]
|
|
397
|
+
param(
|
|
398
|
+
[Parameter(Mandatory = $true)]
|
|
399
|
+
[AllowEmptyCollection()]
|
|
400
|
+
[object[]] $Item
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
$seen = [System.Collections.Generic.HashSet[long]]::new()
|
|
404
|
+
$record = foreach ($entry in $Item) {
|
|
405
|
+
$mapping = Get-RequiredMapping -Value $entry -FieldName 'scheduling item'
|
|
406
|
+
$key = $mapping['key']
|
|
407
|
+
$isInteger = $key -is [int] -or $key -is [long] -or $key -is [int16] -or $key -is [byte]
|
|
408
|
+
if ($key -is [bool] -or -not $isInteger) {
|
|
409
|
+
throw "scheduling item key must be an integer: $key."
|
|
410
|
+
}
|
|
411
|
+
if (-not $seen.Add([long]$key)) {
|
|
412
|
+
throw "scheduling item keys must be distinct: $key."
|
|
413
|
+
}
|
|
414
|
+
$band = if ($mapping.ContainsKey('band')) { $mapping['band'] } else { $null }
|
|
415
|
+
@{ key = [long]$key; radius = $mapping['radius']; band = $band }
|
|
416
|
+
}
|
|
417
|
+
return @(@($record) | Where-Object { $null -ne $_ } | Sort-Object -Property { $_['key'] })
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function Get-BlastRadiusConflictEdge {
|
|
421
|
+
<#
|
|
422
|
+
.SYNOPSIS
|
|
423
|
+
Build the scheduling edges and tolerated overlaps of a set of items.
|
|
424
|
+
|
|
425
|
+
.DESCRIPTION
|
|
426
|
+
Port of schedule_conflict_edges. Visits every unordered pair once in
|
|
427
|
+
ascending key order. A pair whose decision is an edge is recorded as an
|
|
428
|
+
edge; a conflict within tolerance is recorded as a tolerated overlap; a
|
|
429
|
+
pair that does not conflict appears in neither list.
|
|
430
|
+
|
|
431
|
+
.PARAMETER Item
|
|
432
|
+
The run's items: records carrying key (an integer issue number), radius,
|
|
433
|
+
and an optional band.
|
|
434
|
+
|
|
435
|
+
.PARAMETER Config
|
|
436
|
+
Parsed truth table.
|
|
437
|
+
|
|
438
|
+
.PARAMETER Relation
|
|
439
|
+
The detection relation passed to every pair decision; pass
|
|
440
|
+
${function:Test-BlastRadiusConflict}.
|
|
441
|
+
|
|
442
|
+
.OUTPUTS
|
|
443
|
+
System.Collections.Hashtable. Keys edges (ordered records a, b, reason,
|
|
444
|
+
hard, cost, benefit) and tolerated_overlaps (ordered records a, b,
|
|
445
|
+
reasons, cost, benefit), each sorted by (a, b) with a < b.
|
|
446
|
+
#>
|
|
447
|
+
[CmdletBinding()]
|
|
448
|
+
[OutputType([hashtable])]
|
|
449
|
+
param(
|
|
450
|
+
[Parameter(Mandatory = $true)]
|
|
451
|
+
[AllowEmptyCollection()]
|
|
452
|
+
[object[]] $Item,
|
|
453
|
+
[Parameter(Mandatory = $true)]
|
|
454
|
+
[AllowNull()]
|
|
455
|
+
[object] $Config,
|
|
456
|
+
[scriptblock] $Relation
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
if ($null -eq $Relation) { throw $script:RelationRequired }
|
|
460
|
+
[void](Get-ConfigConflictTolerance -Config $Config)
|
|
461
|
+
$ordered = @(Get-OrderedSchedulingItem -Item $Item)
|
|
462
|
+
|
|
463
|
+
$edge = [System.Collections.Generic.List[object]]::new()
|
|
464
|
+
$tolerated = [System.Collections.Generic.List[object]]::new()
|
|
465
|
+
for ($first = 0; $first -lt $ordered.Count; $first++) {
|
|
466
|
+
for ($second = $first + 1; $second -lt $ordered.Count; $second++) {
|
|
467
|
+
$a = $ordered[$first]
|
|
468
|
+
$b = $ordered[$second]
|
|
469
|
+
$decision = Get-BlastRadiusPairDecision -RadiusA $a['radius'] -RadiusB $b['radius'] -Config $Config -BandA $a['band'] -BandB $b['band'] -Relation $Relation
|
|
470
|
+
if ($decision['edge']) {
|
|
471
|
+
$edge.Add([ordered]@{ a = $a['key']; b = $b['key']; reason = $decision['reason']; hard = $decision['hard']; cost = $decision['cost']; benefit = $decision['benefit'] })
|
|
472
|
+
} elseif ($decision['conflict']) {
|
|
473
|
+
$tolerated.Add([ordered]@{ a = $a['key']; b = $b['key']; reasons = $decision['reasons']; cost = $decision['cost']; benefit = $decision['benefit'] })
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return @{ edges = @($edge.ToArray()); tolerated_overlaps = @($tolerated.ToArray()) }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
Export-ModuleMember -Function `
|
|
482
|
+
Get-ConfigConflictTolerance, `
|
|
483
|
+
Get-BlastRadiusPairCost, `
|
|
484
|
+
Get-BlastRadiusPairBenefit, `
|
|
485
|
+
Get-BlastRadiusPairDecision, `
|
|
486
|
+
Get-BlastRadiusConflictEdge
|
|
@@ -40,6 +40,7 @@ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusExtraction.p
|
|
|
40
40
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force -ErrorAction Stop
|
|
41
41
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1') -Force -ErrorAction Stop
|
|
42
42
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusNormalization.psm1') -Force -ErrorAction Stop
|
|
43
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusWriteIntent.psm1') -Force -ErrorAction Stop
|
|
43
44
|
|
|
44
45
|
# Finding vocabulary. These strings are contract literals consumed by the
|
|
45
46
|
# downstream parallel schema and planner features.
|
|
@@ -354,10 +355,12 @@ function Test-BlastRadius {
|
|
|
354
355
|
# passing V1 and V2 against its own plan (issue #452). The mandate-read
|
|
355
356
|
# exclusion is applied here for the same reason: the derivation harvest drops
|
|
356
357
|
# those citations, so V1 and V2 must not then demand that the radius cover
|
|
357
|
-
# them (issue #489).
|
|
358
|
+
# them (issue #489). The plan-side extractor is chosen by the selector
|
|
359
|
+
# Get-BlastRadius also uses, so write-intent mode stays self-consistent
|
|
360
|
+
# (issue #722).
|
|
358
361
|
$planPath = [string[]]@(Get-NonMandateReadEntry -MandateRead (
|
|
359
362
|
[string[]]@(Get-ConfigMandateRead -Config $Config)) -Entry (
|
|
360
|
-
[string[]]@(Get-
|
|
363
|
+
[string[]]@(Get-PlanPathForConfig -PlanText $PlanText -Config $Config `
|
|
361
364
|
-RootSurface ([string[]]@(Get-ConfigRootSurface -Config $Config)))))
|
|
362
365
|
$planConcrete = [string[]]@(Get-ConcreteEntry -Entry $planPath)
|
|
363
366
|
|