@danmoisan/drm-copilot-mcp 1.0.26 → 1.1.0
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 +542 -42
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +17 -21
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +17 -19
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +17 -19
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +18 -19
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +18 -19
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +54 -32
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +172 -24
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +51 -22
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +56 -19
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +71 -28
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +68 -23
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +402 -0
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +20 -22
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +59 -14
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +17 -20
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +278 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +55 -271
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +57 -22
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +56 -19
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +70 -27
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +228 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +68 -225
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +161 -34
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +59 -22
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +70 -27
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +32 -18
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/hook-payload/HookPayload.psm1 +494 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
- package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +124 -1
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
- package/resources/claude-customizations/.claude/settings.json +5 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +8 -7
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +24 -4
- package/resources/claude-customizations/config/blast-radius.json +16 -2
- package/resources/claude-customizations/pack-manifests/core.json +22 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +31 -1
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[CmdletBinding()]
|
|
6
6
|
param()
|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
8
10
|
$script:CheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
|
|
9
11
|
|
|
10
12
|
function ConvertFrom-CheckpointJson {
|
|
@@ -164,19 +166,23 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
164
166
|
[CmdletBinding()]
|
|
165
167
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
166
168
|
param(
|
|
169
|
+
[AllowNull()]
|
|
170
|
+
[AllowEmptyString()]
|
|
167
171
|
[string] $ToolInputRaw,
|
|
172
|
+
|
|
168
173
|
[string] $CheckpointRaw
|
|
169
174
|
)
|
|
170
175
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
throw "enforce-orchestration-preimplementation-gate hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
176
|
+
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
177
|
+
if (-not $payload.IsValid) {
|
|
178
|
+
return Get-OrchestrationPreimplementationGateBlockDecision -Reason (
|
|
179
|
+
'PREIMPLEMENTATION_GATE_BLOCKED: payload anomaly - ' +
|
|
180
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
181
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
178
182
|
}
|
|
179
183
|
|
|
184
|
+
$toolInput = $payload.Value
|
|
185
|
+
|
|
180
186
|
$requiresReadyCheckpoint = $false
|
|
181
187
|
$filePath = Get-StringProperty -Value $toolInput -Name 'file_path'
|
|
182
188
|
if ($filePath) {
|
|
@@ -210,16 +216,55 @@ function Invoke-OrchestrationPreimplementationGateDecision {
|
|
|
210
216
|
return Get-OrchestrationPreimplementationGateBlockDecision -Reason 'PREIMPLEMENTATION_GATE_BLOCKED: Implementation operations require artifacts/orchestration/orchestrator-state.json to contain issue number, feature folder, route metadata, lifecycle readiness, and checkpoint state before implementation begins.'
|
|
211
217
|
}
|
|
212
218
|
|
|
219
|
+
function Invoke-OrchestrationPreimplementationGateEntryPoint {
|
|
220
|
+
<#
|
|
221
|
+
.SYNOPSIS
|
|
222
|
+
Runs the hook decision and returns the process exit code.
|
|
223
|
+
.DESCRIPTION
|
|
224
|
+
Acquires the payload through the shared reader unless the caller supplies
|
|
225
|
+
one, emits the compact decision JSON, and returns 0. It never returns 1:
|
|
226
|
+
exit 1 is non-blocking for PreToolUse, so every anomaly is already a deny
|
|
227
|
+
decision by the time control reaches here. The function does not call exit;
|
|
228
|
+
the thin tail converts the returned code into a process exit.
|
|
229
|
+
.PARAMETER ToolInputRaw
|
|
230
|
+
Optional pre-acquired payload text. When omitted the ReadPayload seam runs.
|
|
231
|
+
.PARAMETER ReadPayload
|
|
232
|
+
Seam for payload acquisition, so tests can drive the empty-on-all-transports
|
|
233
|
+
case without touching a console.
|
|
234
|
+
.OUTPUTS
|
|
235
|
+
System.Int32
|
|
236
|
+
#>
|
|
237
|
+
[CmdletBinding()]
|
|
238
|
+
[OutputType([int])]
|
|
239
|
+
param(
|
|
240
|
+
[AllowNull()]
|
|
241
|
+
[AllowEmptyString()]
|
|
242
|
+
[string] $ToolInputRaw,
|
|
243
|
+
|
|
244
|
+
[scriptblock] $ReadPayload = { Read-ClaudeHookRawPayload }
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if (-not $PSBoundParameters.ContainsKey('ToolInputRaw')) {
|
|
248
|
+
$ToolInputRaw = [string](& $ReadPayload)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
$decision = Invoke-OrchestrationPreimplementationGateDecision -ToolInputRaw $ToolInputRaw
|
|
252
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
253
|
+
|
|
254
|
+
return 0
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
213
258
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
214
259
|
return
|
|
215
260
|
}
|
|
216
261
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
262
|
+
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
263
|
+
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
264
|
+
# expression and emit nothing, so the decision is written explicitly here first.
|
|
265
|
+
$entryPointResult = @(Invoke-OrchestrationPreimplementationGateEntryPoint)
|
|
266
|
+
if ($entryPointResult.Count -gt 1) {
|
|
267
|
+
$entryPointResult[0..($entryPointResult.Count - 2)] | Write-Output
|
|
222
268
|
}
|
|
223
269
|
|
|
224
|
-
$
|
|
225
|
-
exit 0
|
|
270
|
+
exit ([int]$entryPointResult[-1])
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
.DESCRIPTION
|
|
7
7
|
Invoked by the Claude Code PreToolUse hook on the "Bash" matcher before any Bash
|
|
8
|
-
command runs. Reads
|
|
8
|
+
command runs. Reads the hook payload through the shared reader, extracts the
|
|
9
|
+
command text from the envelope's nested tool_input, and matches it
|
|
9
10
|
against the two tokens declared at the top of this file: the abandon disposition
|
|
10
11
|
token and the confirmation marker. A command carrying the abandon disposition token
|
|
11
12
|
without the confirmation marker in the SAME command is denied with reason prefix
|
|
@@ -32,6 +33,8 @@
|
|
|
32
33
|
[CmdletBinding()]
|
|
33
34
|
param()
|
|
34
35
|
|
|
36
|
+
|
|
37
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
35
38
|
# The two tokens this gate matches on. These are the ONLY places either token literal
|
|
36
39
|
# appears in this file; the seam test extracts the consumer-side values from exactly
|
|
37
40
|
# these two named assignments.
|
|
@@ -44,8 +47,8 @@ $script:AbandonBlockedReasonCode = 'PARALLEL_ABANDON_BLOCKED'
|
|
|
44
47
|
function Get-ParallelAbandonGateToolInput {
|
|
45
48
|
<#
|
|
46
49
|
.SYNOPSIS
|
|
47
|
-
Read the raw JSON
|
|
48
|
-
function (read seam).
|
|
50
|
+
Read the raw JSON hook payload through the shared reader (stdin first, then
|
|
51
|
+
the two environment fallbacks). Tests mock this function (read seam).
|
|
49
52
|
.OUTPUTS
|
|
50
53
|
System.String or $null
|
|
51
54
|
#>
|
|
@@ -53,7 +56,7 @@ function Get-ParallelAbandonGateToolInput {
|
|
|
53
56
|
[OutputType([string])]
|
|
54
57
|
param()
|
|
55
58
|
|
|
56
|
-
return
|
|
59
|
+
return Read-ClaudeHookRawPayload
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
function Get-ParallelAbandonNormalizedCommand {
|
|
@@ -201,7 +204,7 @@ function Get-ParallelAbandonGateBlockReason {
|
|
|
201
204
|
function Invoke-ParallelAbandonGateDecision {
|
|
202
205
|
<#
|
|
203
206
|
.SYNOPSIS
|
|
204
|
-
Parses
|
|
207
|
+
Parses the PreToolUse envelope and returns an allow-or-block decision.
|
|
205
208
|
.PARAMETER ToolInputRaw
|
|
206
209
|
The raw JSON tool payload supplied by Claude Code.
|
|
207
210
|
.OUTPUTS
|
|
@@ -218,17 +221,16 @@ function Invoke-ParallelAbandonGateDecision {
|
|
|
218
221
|
# An absent payload carries no command to gate, so there is nothing in scope. This is
|
|
219
222
|
# allow rather than deny because the gate constrains one specific destructive command
|
|
220
223
|
# shape and must not become a blanket Bash block.
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
225
|
+
if (-not $payload.IsValid) {
|
|
226
|
+
return Get-ParallelAbandonGateBlockDecision -Reason (
|
|
227
|
+
$script:AbandonBlockedReasonCode + ': payload anomaly - ' +
|
|
228
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
229
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
223
230
|
}
|
|
224
231
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
} catch {
|
|
228
|
-
throw "enforce-parallel-abandon-gate hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
$normalizedCommand = Get-ParallelAbandonNormalizedCommand -CommandText ([string]$toolInput.command)
|
|
232
|
+
$commandText = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'command'
|
|
233
|
+
$normalizedCommand = Get-ParallelAbandonNormalizedCommand -CommandText $commandText
|
|
232
234
|
if (-not (Test-ParallelAbandonCommandInScope -NormalizedCommand $normalizedCommand)) {
|
|
233
235
|
return Get-ParallelAbandonGateAllowDecision
|
|
234
236
|
}
|
|
@@ -247,12 +249,7 @@ if ($MyInvocation.InvocationName -eq '.') {
|
|
|
247
249
|
return
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
|
|
251
|
-
$decision = Invoke-ParallelAbandonGateDecision -ToolInputRaw (Get-ParallelAbandonGateToolInput)
|
|
252
|
-
} catch {
|
|
253
|
-
Write-Error $_
|
|
254
|
-
exit 1
|
|
255
|
-
}
|
|
252
|
+
$decision = Invoke-ParallelAbandonGateDecision -ToolInputRaw (Get-ParallelAbandonGateToolInput)
|
|
256
253
|
|
|
257
254
|
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
258
255
|
|
package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Dot-sourced record-resolution and barrier helpers for enforce-parallel-cohort-barrier.ps1.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Holds the five resolution helpers the Layer 1 parallel cohort barrier uses once a
|
|
7
|
+
delegation has been identified as a parallel-mode orchestrator call:
|
|
8
|
+
|
|
9
|
+
- Find-ParallelCohortBarrierItemRecord: resolves the items[] record for a folder.
|
|
10
|
+
- Find-ParallelCohortBarrierItemByKey: resolves an items[] record by issue_num.
|
|
11
|
+
- Find-ParallelCohortBarrierCohortIndex: projects the current-generation coloring.
|
|
12
|
+
- Get-ParallelCohortBarrierConflictNeighborList: collects conflict-edge neighbours.
|
|
13
|
+
- Test-ParallelCohortBarrierClear: applies the per-edge barrier rule.
|
|
14
|
+
|
|
15
|
+
They are split out of the parent hook, .claude/hooks/enforce-parallel-cohort-barrier.ps1,
|
|
16
|
+
because that file stood at 499 lines -- one below the 500-line limit -- with no room
|
|
17
|
+
for the shared payload-reader migration (issue #501). The split is a pure move with no
|
|
18
|
+
behaviour change; the precedent is .claude/hooks/enforce-parallel-drift-gate-helpers.ps1.
|
|
19
|
+
|
|
20
|
+
The helpers read the script-scoped configuration the parent hook assigns
|
|
21
|
+
($script:AllowedMergeStatuses), so this file is only ever dot-sourced from that hook
|
|
22
|
+
and never invoked on its own.
|
|
23
|
+
|
|
24
|
+
.NOTES
|
|
25
|
+
Compatible with PowerShell 7+. No external module dependencies. Every function is
|
|
26
|
+
pure: no filesystem, subprocess, network, or wall-clock access.
|
|
27
|
+
#>
|
|
28
|
+
[CmdletBinding()]
|
|
29
|
+
param()
|
|
30
|
+
|
|
31
|
+
function Find-ParallelCohortBarrierItemRecord {
|
|
32
|
+
<#
|
|
33
|
+
.SYNOPSIS
|
|
34
|
+
Locate the items[] record whose feature_folder resolves to the target basename.
|
|
35
|
+
.PARAMETER Checkpoint
|
|
36
|
+
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
37
|
+
.PARAMETER FeatureFolder
|
|
38
|
+
The target feature_folder basename resolved from the prompt.
|
|
39
|
+
.OUTPUTS
|
|
40
|
+
System.Object or $null
|
|
41
|
+
#>
|
|
42
|
+
[CmdletBinding()]
|
|
43
|
+
param(
|
|
44
|
+
[AllowNull()]
|
|
45
|
+
$Checkpoint,
|
|
46
|
+
|
|
47
|
+
[AllowNull()]
|
|
48
|
+
[string] $FeatureFolder
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($FeatureFolder)) {
|
|
52
|
+
return $null
|
|
53
|
+
}
|
|
54
|
+
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
55
|
+
if ($checkpointProps -notcontains 'items') {
|
|
56
|
+
return $null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Scan every recorded item for a feature_folder whose basename equals the target.
|
|
60
|
+
foreach ($item in @($Checkpoint.items)) {
|
|
61
|
+
$itemProps = @($item.PSObject.Properties.Name)
|
|
62
|
+
if ($itemProps -notcontains 'feature_folder') {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
$itemBasename = Get-ParallelCohortBarrierFolderBasename -FolderPath ([string]$item.feature_folder)
|
|
66
|
+
if ($itemBasename -eq $FeatureFolder) {
|
|
67
|
+
return $item
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return $null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function Find-ParallelCohortBarrierItemByKey {
|
|
74
|
+
<#
|
|
75
|
+
.SYNOPSIS
|
|
76
|
+
Locate the items[] record whose issue_num equals the supplied key.
|
|
77
|
+
.PARAMETER Checkpoint
|
|
78
|
+
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
79
|
+
.PARAMETER IssueKey
|
|
80
|
+
The item primary key (issue_num) rendered as a string.
|
|
81
|
+
.OUTPUTS
|
|
82
|
+
System.Object or $null
|
|
83
|
+
#>
|
|
84
|
+
[CmdletBinding()]
|
|
85
|
+
param(
|
|
86
|
+
[AllowNull()]
|
|
87
|
+
$Checkpoint,
|
|
88
|
+
|
|
89
|
+
[AllowNull()]
|
|
90
|
+
[string] $IssueKey
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
|
|
94
|
+
return $null
|
|
95
|
+
}
|
|
96
|
+
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
97
|
+
if ($checkpointProps -notcontains 'items') {
|
|
98
|
+
return $null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
foreach ($item in @($Checkpoint.items)) {
|
|
102
|
+
$itemProps = @($item.PSObject.Properties.Name)
|
|
103
|
+
if ($itemProps -notcontains 'issue_num') {
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
if (([string]$item.issue_num) -eq $IssueKey) {
|
|
107
|
+
return $item
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return $null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function Find-ParallelCohortBarrierCohortIndex {
|
|
114
|
+
<#
|
|
115
|
+
.SYNOPSIS
|
|
116
|
+
Return the current-generation cohort index that holds the supplied item key.
|
|
117
|
+
.DESCRIPTION
|
|
118
|
+
The cohort projection is restricted to cohorts[] rows whose generation equals the
|
|
119
|
+
top-level recolor_generation, which is the current coloring. Rows from a superseded
|
|
120
|
+
generation are ignored. Returns $null when the checkpoint records no
|
|
121
|
+
recolor_generation, no cohorts, or no current-generation cohort containing the key.
|
|
122
|
+
.PARAMETER Checkpoint
|
|
123
|
+
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
124
|
+
.PARAMETER IssueKey
|
|
125
|
+
The item primary key (issue_num) rendered as a string.
|
|
126
|
+
.OUTPUTS
|
|
127
|
+
System.Int32 or $null
|
|
128
|
+
#>
|
|
129
|
+
[CmdletBinding()]
|
|
130
|
+
[OutputType([int])]
|
|
131
|
+
param(
|
|
132
|
+
[AllowNull()]
|
|
133
|
+
$Checkpoint,
|
|
134
|
+
|
|
135
|
+
[AllowNull()]
|
|
136
|
+
[string] $IssueKey
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
|
|
140
|
+
return $null
|
|
141
|
+
}
|
|
142
|
+
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
143
|
+
if ($checkpointProps -notcontains 'cohorts' -or $checkpointProps -notcontains 'recolor_generation') {
|
|
144
|
+
return $null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
$currentGeneration = [string]$Checkpoint.recolor_generation
|
|
148
|
+
|
|
149
|
+
foreach ($cohort in @($Checkpoint.cohorts)) {
|
|
150
|
+
$cohortProps = @($cohort.PSObject.Properties.Name)
|
|
151
|
+
if ($cohortProps -notcontains 'index' -or $cohortProps -notcontains 'generation' -or $cohortProps -notcontains 'item_keys') {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
if (([string]$cohort.generation) -ne $currentGeneration) {
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
$parsedIndex = 0
|
|
158
|
+
if (-not [int]::TryParse(([string]$cohort.index), [ref] $parsedIndex)) {
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
foreach ($key in @($cohort.item_keys)) {
|
|
162
|
+
if (([string]$key) -eq $IssueKey) {
|
|
163
|
+
return $parsedIndex
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return $null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function Get-ParallelCohortBarrierConflictNeighborList {
|
|
171
|
+
<#
|
|
172
|
+
.SYNOPSIS
|
|
173
|
+
Return the item keys that share a conflict edge with the supplied item key.
|
|
174
|
+
.PARAMETER Checkpoint
|
|
175
|
+
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
176
|
+
.PARAMETER IssueKey
|
|
177
|
+
The item primary key (issue_num) rendered as a string.
|
|
178
|
+
.OUTPUTS
|
|
179
|
+
System.Object[] holding each neighbor item key rendered as a string.
|
|
180
|
+
#>
|
|
181
|
+
[CmdletBinding()]
|
|
182
|
+
[OutputType([object[]])]
|
|
183
|
+
param(
|
|
184
|
+
[AllowNull()]
|
|
185
|
+
$Checkpoint,
|
|
186
|
+
|
|
187
|
+
[AllowNull()]
|
|
188
|
+
[string] $IssueKey
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
$neighborList = @()
|
|
192
|
+
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
|
|
193
|
+
return $neighborList
|
|
194
|
+
}
|
|
195
|
+
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
196
|
+
if ($checkpointProps -notcontains 'conflict_edges') {
|
|
197
|
+
return $neighborList
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Edges are undirected and canonically normalized to a < b, so both endpoints are
|
|
201
|
+
# inspected and the opposite endpoint is returned as the neighbor.
|
|
202
|
+
foreach ($edge in @($Checkpoint.conflict_edges)) {
|
|
203
|
+
$edgeProps = @($edge.PSObject.Properties.Name)
|
|
204
|
+
if ($edgeProps -notcontains 'a' -or $edgeProps -notcontains 'b') {
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
$endpointA = [string]$edge.a
|
|
208
|
+
$endpointB = [string]$edge.b
|
|
209
|
+
if ($endpointA -eq $IssueKey) {
|
|
210
|
+
$neighborList += $endpointB
|
|
211
|
+
} elseif ($endpointB -eq $IssueKey) {
|
|
212
|
+
$neighborList += $endpointA
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return $neighborList
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function Test-ParallelCohortBarrierClear {
|
|
219
|
+
<#
|
|
220
|
+
.SYNOPSIS
|
|
221
|
+
Decision logic: true only when every conflicting neighbor in a strictly prior
|
|
222
|
+
current-generation cohort has merge_status merged or worktree_removed.
|
|
223
|
+
.PARAMETER Checkpoint
|
|
224
|
+
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
225
|
+
.PARAMETER ItemRecord
|
|
226
|
+
The target item's own items[] record, or $null when not found.
|
|
227
|
+
.OUTPUTS
|
|
228
|
+
System.Boolean
|
|
229
|
+
#>
|
|
230
|
+
[CmdletBinding()]
|
|
231
|
+
[OutputType([bool])]
|
|
232
|
+
param(
|
|
233
|
+
[AllowNull()]
|
|
234
|
+
$Checkpoint,
|
|
235
|
+
|
|
236
|
+
[AllowNull()]
|
|
237
|
+
$ItemRecord
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if ($null -eq $Checkpoint -or $null -eq $ItemRecord) {
|
|
241
|
+
return $false
|
|
242
|
+
}
|
|
243
|
+
$itemProps = @($ItemRecord.PSObject.Properties.Name)
|
|
244
|
+
if ($itemProps -notcontains 'issue_num') {
|
|
245
|
+
return $false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
$targetKey = [string]$ItemRecord.issue_num
|
|
249
|
+
$targetIndex = Find-ParallelCohortBarrierCohortIndex -Checkpoint $Checkpoint -IssueKey $targetKey
|
|
250
|
+
if ($null -eq $targetIndex) {
|
|
251
|
+
# No current-generation cohort assignment: position is unknown, so fail closed.
|
|
252
|
+
return $false
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
foreach ($neighborKey in @(Get-ParallelCohortBarrierConflictNeighborList -Checkpoint $Checkpoint -IssueKey $targetKey)) {
|
|
256
|
+
$neighborIndex = Find-ParallelCohortBarrierCohortIndex -Checkpoint $Checkpoint -IssueKey $neighborKey
|
|
257
|
+
if ($null -eq $neighborIndex) {
|
|
258
|
+
# Not part of the current coloring, so not a strictly prior cohort neighbor.
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
if ($neighborIndex -ge $targetIndex) {
|
|
262
|
+
# Same-cohort and later-cohort contention is not a Layer 1 concern.
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
$neighborRecord = Find-ParallelCohortBarrierItemByKey -Checkpoint $Checkpoint -IssueKey $neighborKey
|
|
266
|
+
if ($null -eq $neighborRecord) {
|
|
267
|
+
return $false
|
|
268
|
+
}
|
|
269
|
+
$neighborProps = @($neighborRecord.PSObject.Properties.Name)
|
|
270
|
+
if ($neighborProps -notcontains 'merge_status') {
|
|
271
|
+
return $false
|
|
272
|
+
}
|
|
273
|
+
if ($script:AllowedMergeStatuses -notcontains ([string]$neighborRecord.merge_status)) {
|
|
274
|
+
return $false
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return $true
|
|
278
|
+
}
|