@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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
.DESCRIPTION
|
|
6
6
|
Invoked by the Claude Code PreToolUse hook on the "Agent" matcher before any Agent
|
|
7
|
-
(Task) call runs. Activates only when
|
|
7
|
+
(Task) call runs. Activates only when the envelope's tool_input.subagent_type == "orchestrator"
|
|
8
8
|
and the serialized prompt contains the parallel kickoff marker "Parallel mode: true".
|
|
9
9
|
|
|
10
10
|
Adapted from enforce-epic-wave-barrier.ps1. The parallel surface has no depends_on
|
|
@@ -47,10 +47,16 @@
|
|
|
47
47
|
[CmdletBinding()]
|
|
48
48
|
param()
|
|
49
49
|
|
|
50
|
+
|
|
51
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
50
52
|
$script:ParallelCheckpointPath = 'artifacts/orchestration/parallel-orchestrator-state.json'
|
|
51
53
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
52
54
|
$script:ParallelModeMarker = 'Parallel mode: true'
|
|
53
55
|
|
|
56
|
+
# Dot-source the record-resolution and barrier helpers. Guarded so dot-sourcing this
|
|
57
|
+
# hook in tests loads the helpers too (issue #501 headroom split).
|
|
58
|
+
. (Join-Path $PSScriptRoot 'enforce-parallel-cohort-barrier-helpers.ps1')
|
|
59
|
+
|
|
54
60
|
function Get-ParallelCohortBarrierCheckpointContent {
|
|
55
61
|
<#
|
|
56
62
|
.SYNOPSIS
|
|
@@ -145,255 +151,6 @@ function Find-ParallelCohortBarrierFeatureFolderFromPrompt {
|
|
|
145
151
|
return (Get-ParallelCohortBarrierFolderBasename -FolderPath $candidates[0])
|
|
146
152
|
}
|
|
147
153
|
|
|
148
|
-
function Find-ParallelCohortBarrierItemRecord {
|
|
149
|
-
<#
|
|
150
|
-
.SYNOPSIS
|
|
151
|
-
Locate the items[] record whose feature_folder resolves to the target basename.
|
|
152
|
-
.PARAMETER Checkpoint
|
|
153
|
-
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
154
|
-
.PARAMETER FeatureFolder
|
|
155
|
-
The target feature_folder basename resolved from the prompt.
|
|
156
|
-
.OUTPUTS
|
|
157
|
-
System.Object or $null
|
|
158
|
-
#>
|
|
159
|
-
[CmdletBinding()]
|
|
160
|
-
param(
|
|
161
|
-
[AllowNull()]
|
|
162
|
-
$Checkpoint,
|
|
163
|
-
|
|
164
|
-
[AllowNull()]
|
|
165
|
-
[string] $FeatureFolder
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($FeatureFolder)) {
|
|
169
|
-
return $null
|
|
170
|
-
}
|
|
171
|
-
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
172
|
-
if ($checkpointProps -notcontains 'items') {
|
|
173
|
-
return $null
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
# Scan every recorded item for a feature_folder whose basename equals the target.
|
|
177
|
-
foreach ($item in @($Checkpoint.items)) {
|
|
178
|
-
$itemProps = @($item.PSObject.Properties.Name)
|
|
179
|
-
if ($itemProps -notcontains 'feature_folder') {
|
|
180
|
-
continue
|
|
181
|
-
}
|
|
182
|
-
$itemBasename = Get-ParallelCohortBarrierFolderBasename -FolderPath ([string]$item.feature_folder)
|
|
183
|
-
if ($itemBasename -eq $FeatureFolder) {
|
|
184
|
-
return $item
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return $null
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function Find-ParallelCohortBarrierItemByKey {
|
|
191
|
-
<#
|
|
192
|
-
.SYNOPSIS
|
|
193
|
-
Locate the items[] record whose issue_num equals the supplied key.
|
|
194
|
-
.PARAMETER Checkpoint
|
|
195
|
-
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
196
|
-
.PARAMETER IssueKey
|
|
197
|
-
The item primary key (issue_num) rendered as a string.
|
|
198
|
-
.OUTPUTS
|
|
199
|
-
System.Object or $null
|
|
200
|
-
#>
|
|
201
|
-
[CmdletBinding()]
|
|
202
|
-
param(
|
|
203
|
-
[AllowNull()]
|
|
204
|
-
$Checkpoint,
|
|
205
|
-
|
|
206
|
-
[AllowNull()]
|
|
207
|
-
[string] $IssueKey
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
|
|
211
|
-
return $null
|
|
212
|
-
}
|
|
213
|
-
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
214
|
-
if ($checkpointProps -notcontains 'items') {
|
|
215
|
-
return $null
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
foreach ($item in @($Checkpoint.items)) {
|
|
219
|
-
$itemProps = @($item.PSObject.Properties.Name)
|
|
220
|
-
if ($itemProps -notcontains 'issue_num') {
|
|
221
|
-
continue
|
|
222
|
-
}
|
|
223
|
-
if (([string]$item.issue_num) -eq $IssueKey) {
|
|
224
|
-
return $item
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return $null
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function Find-ParallelCohortBarrierCohortIndex {
|
|
231
|
-
<#
|
|
232
|
-
.SYNOPSIS
|
|
233
|
-
Return the current-generation cohort index that holds the supplied item key.
|
|
234
|
-
.DESCRIPTION
|
|
235
|
-
The cohort projection is restricted to cohorts[] rows whose generation equals the
|
|
236
|
-
top-level recolor_generation, which is the current coloring. Rows from a superseded
|
|
237
|
-
generation are ignored. Returns $null when the checkpoint records no
|
|
238
|
-
recolor_generation, no cohorts, or no current-generation cohort containing the key.
|
|
239
|
-
.PARAMETER Checkpoint
|
|
240
|
-
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
241
|
-
.PARAMETER IssueKey
|
|
242
|
-
The item primary key (issue_num) rendered as a string.
|
|
243
|
-
.OUTPUTS
|
|
244
|
-
System.Int32 or $null
|
|
245
|
-
#>
|
|
246
|
-
[CmdletBinding()]
|
|
247
|
-
[OutputType([int])]
|
|
248
|
-
param(
|
|
249
|
-
[AllowNull()]
|
|
250
|
-
$Checkpoint,
|
|
251
|
-
|
|
252
|
-
[AllowNull()]
|
|
253
|
-
[string] $IssueKey
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
|
|
257
|
-
return $null
|
|
258
|
-
}
|
|
259
|
-
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
260
|
-
if ($checkpointProps -notcontains 'cohorts' -or $checkpointProps -notcontains 'recolor_generation') {
|
|
261
|
-
return $null
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
$currentGeneration = [string]$Checkpoint.recolor_generation
|
|
265
|
-
|
|
266
|
-
foreach ($cohort in @($Checkpoint.cohorts)) {
|
|
267
|
-
$cohortProps = @($cohort.PSObject.Properties.Name)
|
|
268
|
-
if ($cohortProps -notcontains 'index' -or $cohortProps -notcontains 'generation' -or $cohortProps -notcontains 'item_keys') {
|
|
269
|
-
continue
|
|
270
|
-
}
|
|
271
|
-
if (([string]$cohort.generation) -ne $currentGeneration) {
|
|
272
|
-
continue
|
|
273
|
-
}
|
|
274
|
-
$parsedIndex = 0
|
|
275
|
-
if (-not [int]::TryParse(([string]$cohort.index), [ref] $parsedIndex)) {
|
|
276
|
-
continue
|
|
277
|
-
}
|
|
278
|
-
foreach ($key in @($cohort.item_keys)) {
|
|
279
|
-
if (([string]$key) -eq $IssueKey) {
|
|
280
|
-
return $parsedIndex
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return $null
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function Get-ParallelCohortBarrierConflictNeighborList {
|
|
288
|
-
<#
|
|
289
|
-
.SYNOPSIS
|
|
290
|
-
Return the item keys that share a conflict edge with the supplied item key.
|
|
291
|
-
.PARAMETER Checkpoint
|
|
292
|
-
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
293
|
-
.PARAMETER IssueKey
|
|
294
|
-
The item primary key (issue_num) rendered as a string.
|
|
295
|
-
.OUTPUTS
|
|
296
|
-
System.Object[] holding each neighbor item key rendered as a string.
|
|
297
|
-
#>
|
|
298
|
-
[CmdletBinding()]
|
|
299
|
-
[OutputType([object[]])]
|
|
300
|
-
param(
|
|
301
|
-
[AllowNull()]
|
|
302
|
-
$Checkpoint,
|
|
303
|
-
|
|
304
|
-
[AllowNull()]
|
|
305
|
-
[string] $IssueKey
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
$neighborList = @()
|
|
309
|
-
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
|
|
310
|
-
return $neighborList
|
|
311
|
-
}
|
|
312
|
-
$checkpointProps = @($Checkpoint.PSObject.Properties.Name)
|
|
313
|
-
if ($checkpointProps -notcontains 'conflict_edges') {
|
|
314
|
-
return $neighborList
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
# Edges are undirected and canonically normalized to a < b, so both endpoints are
|
|
318
|
-
# inspected and the opposite endpoint is returned as the neighbor.
|
|
319
|
-
foreach ($edge in @($Checkpoint.conflict_edges)) {
|
|
320
|
-
$edgeProps = @($edge.PSObject.Properties.Name)
|
|
321
|
-
if ($edgeProps -notcontains 'a' -or $edgeProps -notcontains 'b') {
|
|
322
|
-
continue
|
|
323
|
-
}
|
|
324
|
-
$endpointA = [string]$edge.a
|
|
325
|
-
$endpointB = [string]$edge.b
|
|
326
|
-
if ($endpointA -eq $IssueKey) {
|
|
327
|
-
$neighborList += $endpointB
|
|
328
|
-
} elseif ($endpointB -eq $IssueKey) {
|
|
329
|
-
$neighborList += $endpointA
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return $neighborList
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function Test-ParallelCohortBarrierClear {
|
|
336
|
-
<#
|
|
337
|
-
.SYNOPSIS
|
|
338
|
-
Decision logic: true only when every conflicting neighbor in a strictly prior
|
|
339
|
-
current-generation cohort has merge_status merged or worktree_removed.
|
|
340
|
-
.PARAMETER Checkpoint
|
|
341
|
-
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
342
|
-
.PARAMETER ItemRecord
|
|
343
|
-
The target item's own items[] record, or $null when not found.
|
|
344
|
-
.OUTPUTS
|
|
345
|
-
System.Boolean
|
|
346
|
-
#>
|
|
347
|
-
[CmdletBinding()]
|
|
348
|
-
[OutputType([bool])]
|
|
349
|
-
param(
|
|
350
|
-
[AllowNull()]
|
|
351
|
-
$Checkpoint,
|
|
352
|
-
|
|
353
|
-
[AllowNull()]
|
|
354
|
-
$ItemRecord
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
if ($null -eq $Checkpoint -or $null -eq $ItemRecord) {
|
|
358
|
-
return $false
|
|
359
|
-
}
|
|
360
|
-
$itemProps = @($ItemRecord.PSObject.Properties.Name)
|
|
361
|
-
if ($itemProps -notcontains 'issue_num') {
|
|
362
|
-
return $false
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
$targetKey = [string]$ItemRecord.issue_num
|
|
366
|
-
$targetIndex = Find-ParallelCohortBarrierCohortIndex -Checkpoint $Checkpoint -IssueKey $targetKey
|
|
367
|
-
if ($null -eq $targetIndex) {
|
|
368
|
-
# No current-generation cohort assignment: position is unknown, so fail closed.
|
|
369
|
-
return $false
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
foreach ($neighborKey in @(Get-ParallelCohortBarrierConflictNeighborList -Checkpoint $Checkpoint -IssueKey $targetKey)) {
|
|
373
|
-
$neighborIndex = Find-ParallelCohortBarrierCohortIndex -Checkpoint $Checkpoint -IssueKey $neighborKey
|
|
374
|
-
if ($null -eq $neighborIndex) {
|
|
375
|
-
# Not part of the current coloring, so not a strictly prior cohort neighbor.
|
|
376
|
-
continue
|
|
377
|
-
}
|
|
378
|
-
if ($neighborIndex -ge $targetIndex) {
|
|
379
|
-
# Same-cohort and later-cohort contention is not a Layer 1 concern.
|
|
380
|
-
continue
|
|
381
|
-
}
|
|
382
|
-
$neighborRecord = Find-ParallelCohortBarrierItemByKey -Checkpoint $Checkpoint -IssueKey $neighborKey
|
|
383
|
-
if ($null -eq $neighborRecord) {
|
|
384
|
-
return $false
|
|
385
|
-
}
|
|
386
|
-
$neighborProps = @($neighborRecord.PSObject.Properties.Name)
|
|
387
|
-
if ($neighborProps -notcontains 'merge_status') {
|
|
388
|
-
return $false
|
|
389
|
-
}
|
|
390
|
-
if ($script:AllowedMergeStatuses -notcontains ([string]$neighborRecord.merge_status)) {
|
|
391
|
-
return $false
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return $true
|
|
395
|
-
}
|
|
396
|
-
|
|
397
154
|
function Get-ParallelCohortBarrierAllowDecision {
|
|
398
155
|
[CmdletBinding()]
|
|
399
156
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -427,7 +184,7 @@ function Get-ParallelCohortBarrierBlockDecision {
|
|
|
427
184
|
function Invoke-ParallelCohortBarrierDecision {
|
|
428
185
|
<#
|
|
429
186
|
.SYNOPSIS
|
|
430
|
-
Parses
|
|
187
|
+
Parses the PreToolUse envelope and returns an allow-or-block decision.
|
|
431
188
|
.PARAMETER ToolInputRaw
|
|
432
189
|
The raw JSON tool payload supplied by Claude Code.
|
|
433
190
|
.OUTPUTS
|
|
@@ -439,22 +196,20 @@ function Invoke-ParallelCohortBarrierDecision {
|
|
|
439
196
|
[string] $ToolInputRaw
|
|
440
197
|
)
|
|
441
198
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
} catch {
|
|
449
|
-
throw "enforce-parallel-cohort-barrier hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
199
|
+
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
200
|
+
if (-not $envelope.IsValid) {
|
|
201
|
+
return Get-ParallelCohortBarrierBlockDecision -Reason (
|
|
202
|
+
'PARALLEL_COHORT_BARRIER_BLOCKED: payload anomaly - ' +
|
|
203
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $envelope.Anomaly) +
|
|
204
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
450
205
|
}
|
|
451
206
|
|
|
452
|
-
$subagent = $
|
|
207
|
+
$subagent = Get-ClaudeHookToolInputString -ToolInput $envelope.Value -Name 'subagent_type'
|
|
453
208
|
if (-not $subagent -or $subagent -ne 'orchestrator') {
|
|
454
209
|
return Get-ParallelCohortBarrierAllowDecision
|
|
455
210
|
}
|
|
456
211
|
|
|
457
|
-
$prompt =
|
|
212
|
+
$prompt = Get-ClaudeHookToolInputString -ToolInput $envelope.Value -Name 'prompt'
|
|
458
213
|
if (-not $prompt -or $prompt -notlike "*$script:ParallelModeMarker*") {
|
|
459
214
|
return Get-ParallelCohortBarrierAllowDecision
|
|
460
215
|
}
|
|
@@ -482,18 +237,47 @@ function Invoke-ParallelCohortBarrierDecision {
|
|
|
482
237
|
return Get-ParallelCohortBarrierBlockDecision -Reason "PARALLEL_COHORT_BARRIER_BLOCKED: '$featureFolder' cannot start until every conflicting item in a strictly prior current-generation cohort is durably confirmed merged or worktree_removed in the parallel checkpoint. The checkpoint was unreadable, the items[] record was not found, the item has no current-generation cohort assignment, or a conflicting prior-cohort item is not yet safe (ci_green does not satisfy the barrier)."
|
|
483
238
|
}
|
|
484
239
|
|
|
240
|
+
function Invoke-ParallelCohortBarrierEntryPoint {
|
|
241
|
+
<#
|
|
242
|
+
.SYNOPSIS
|
|
243
|
+
Runs the hook decision and returns the process exit code.
|
|
244
|
+
.DESCRIPTION
|
|
245
|
+
Acquires the payload through the shared reader unless the caller supplies
|
|
246
|
+
one, emits the compact decision JSON, and returns 0. It never returns 1:
|
|
247
|
+
exit 1 is non-blocking for PreToolUse, so every anomaly is already a deny
|
|
248
|
+
decision by the time control reaches here. The function does not call exit;
|
|
249
|
+
the thin tail converts the returned code into a process exit.
|
|
250
|
+
.PARAMETER ToolInputRaw
|
|
251
|
+
Optional pre-acquired payload text. When omitted the ReadPayload seam runs.
|
|
252
|
+
.PARAMETER ReadPayload
|
|
253
|
+
Seam for payload acquisition, so tests can drive the empty-on-all-transports
|
|
254
|
+
case without touching a console.
|
|
255
|
+
.OUTPUTS
|
|
256
|
+
System.Int32
|
|
257
|
+
#>
|
|
258
|
+
[CmdletBinding()]
|
|
259
|
+
[OutputType([int])]
|
|
260
|
+
param(
|
|
261
|
+
[AllowNull()]
|
|
262
|
+
[AllowEmptyString()]
|
|
263
|
+
[string] $ToolInputRaw,
|
|
264
|
+
|
|
265
|
+
[scriptblock] $ReadPayload = { Read-ClaudeHookRawPayload }
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if (-not $PSBoundParameters.ContainsKey('ToolInputRaw')) {
|
|
269
|
+
$ToolInputRaw = [string](& $ReadPayload)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
$decision = Invoke-ParallelCohortBarrierDecision -ToolInputRaw $ToolInputRaw
|
|
273
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
274
|
+
|
|
275
|
+
return 0
|
|
276
|
+
}
|
|
277
|
+
|
|
485
278
|
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
486
279
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
487
280
|
return
|
|
488
281
|
}
|
|
489
282
|
|
|
490
|
-
|
|
491
|
-
$decision = Invoke-ParallelCohortBarrierDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
492
|
-
} catch {
|
|
493
|
-
Write-Error $_
|
|
494
|
-
exit 1
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
498
|
-
|
|
499
|
-
exit 0
|
|
283
|
+
exit (Invoke-ParallelCohortBarrierEntryPoint)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
.DESCRIPTION
|
|
6
6
|
Invoked by the Claude Code PreToolUse hook on the "Agent" matcher. Activates only when
|
|
7
|
-
|
|
7
|
+
the envelope's nested tool_input.subagent_type == "feature-review" and the prompt carries the
|
|
8
8
|
parallel-mode kickoff marker defined in the "Parallel-Mode Kickoff Parameter" section
|
|
9
9
|
of .claude/skills/parallel-orchestrate/SKILL.md, matched byte-for-byte (ordinal).
|
|
10
10
|
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
[CmdletBinding()]
|
|
62
62
|
param()
|
|
63
63
|
|
|
64
|
+
|
|
65
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
64
66
|
# Dot-source the shape-and-derivation helpers. Guarded so a missing file produces a clear error
|
|
65
67
|
# and so dot-sourcing this hook in tests loads the helpers too.
|
|
66
68
|
$script:ParallelDriftGateHelpersPath = Join-Path $PSScriptRoot 'enforce-parallel-drift-gate-helpers.ps1'
|
|
@@ -270,28 +272,32 @@ function Get-ParallelDriftGateBlockDecision {
|
|
|
270
272
|
function Invoke-ParallelDriftGateDecision {
|
|
271
273
|
<#
|
|
272
274
|
.SYNOPSIS
|
|
273
|
-
Parse the
|
|
275
|
+
Parse the PreToolUse envelope and return an allow-or-block decision.
|
|
274
276
|
#>
|
|
275
277
|
[CmdletBinding()]
|
|
276
278
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
277
|
-
param(
|
|
279
|
+
param(
|
|
280
|
+
[AllowNull()]
|
|
281
|
+
[AllowEmptyString()]
|
|
282
|
+
[string] $ToolInputRaw
|
|
283
|
+
)
|
|
278
284
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
throw "enforce-parallel-drift-gate hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
285
|
+
$envelope = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
286
|
+
if (-not $envelope.IsValid) {
|
|
287
|
+
return Get-ParallelDriftGateBlockDecision -Reason (
|
|
288
|
+
'PARALLEL_DRIFT_GATE_BLOCKED: payload anomaly - ' +
|
|
289
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $envelope.Anomaly) +
|
|
290
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
286
291
|
}
|
|
287
292
|
|
|
288
293
|
# Two cheap disqualifiers first: this gate governs only feature-review delegations, and
|
|
289
294
|
# only under the parallel-mode marker, matched byte-for-byte with an ordinal Contains
|
|
290
295
|
# rather than a wildcard or a culture-sensitive comparison.
|
|
291
|
-
|
|
296
|
+
$subagent = Get-ClaudeHookToolInputString -ToolInput $envelope.Value -Name 'subagent_type'
|
|
297
|
+
if ($subagent -cne $script:ReviewSubagentType) {
|
|
292
298
|
return Get-ParallelDriftGateAllowDecision
|
|
293
299
|
}
|
|
294
|
-
$prompt =
|
|
300
|
+
$prompt = Get-ClaudeHookToolInputString -ToolInput $envelope.Value -Name 'prompt'
|
|
295
301
|
if (-not $prompt -or -not $prompt.Contains($script:ParallelModeMarker, [System.StringComparison]::Ordinal)) {
|
|
296
302
|
return Get-ParallelDriftGateAllowDecision
|
|
297
303
|
}
|
|
@@ -342,18 +348,47 @@ function Invoke-ParallelDriftGateDecision {
|
|
|
342
348
|
return Get-ParallelDriftGateBlockDecision -Reason "PARALLEL_DRIFT_GATE_BLOCKED: item $itemKey ('$featureFolder') has an unresolved radius drift event and no $script:FindingFilePrefix<timestamp>$script:FindingFileSuffix finding dated at or after that event recorded in its feature folder. The synthetic Blocking finding for the current event must be written before review proceeds, or the drift event log was unreadable."
|
|
343
349
|
}
|
|
344
350
|
|
|
351
|
+
function Invoke-ParallelDriftGateEntryPoint {
|
|
352
|
+
<#
|
|
353
|
+
.SYNOPSIS
|
|
354
|
+
Runs the hook decision and returns the process exit code.
|
|
355
|
+
.DESCRIPTION
|
|
356
|
+
Acquires the payload through the shared reader unless the caller supplies
|
|
357
|
+
one, emits the compact decision JSON, and returns 0. It never returns 1:
|
|
358
|
+
exit 1 is non-blocking for PreToolUse, so every anomaly is already a deny
|
|
359
|
+
decision by the time control reaches here. The function does not call exit;
|
|
360
|
+
the thin tail converts the returned code into a process exit.
|
|
361
|
+
.PARAMETER ToolInputRaw
|
|
362
|
+
Optional pre-acquired payload text. When omitted the ReadPayload seam runs.
|
|
363
|
+
.PARAMETER ReadPayload
|
|
364
|
+
Seam for payload acquisition, so tests can drive the empty-on-all-transports
|
|
365
|
+
case without touching a console.
|
|
366
|
+
.OUTPUTS
|
|
367
|
+
System.Int32
|
|
368
|
+
#>
|
|
369
|
+
[CmdletBinding()]
|
|
370
|
+
[OutputType([int])]
|
|
371
|
+
param(
|
|
372
|
+
[AllowNull()]
|
|
373
|
+
[AllowEmptyString()]
|
|
374
|
+
[string] $ToolInputRaw,
|
|
375
|
+
|
|
376
|
+
[scriptblock] $ReadPayload = { Read-ClaudeHookRawPayload }
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if (-not $PSBoundParameters.ContainsKey('ToolInputRaw')) {
|
|
380
|
+
$ToolInputRaw = [string](& $ReadPayload)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
$decision = Invoke-ParallelDriftGateDecision -ToolInputRaw $ToolInputRaw
|
|
384
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
385
|
+
|
|
386
|
+
return 0
|
|
387
|
+
}
|
|
388
|
+
|
|
345
389
|
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
346
390
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
347
391
|
return
|
|
348
392
|
}
|
|
349
393
|
|
|
350
|
-
|
|
351
|
-
$decision = Invoke-ParallelDriftGateDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
352
|
-
} catch {
|
|
353
|
-
Write-Error $_
|
|
354
|
-
exit 1
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
358
|
-
|
|
359
|
-
exit 0
|
|
394
|
+
exit (Invoke-ParallelDriftGateEntryPoint)
|
package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
.DESCRIPTION
|
|
6
6
|
Invoked by the Claude Code PreToolUse hook on the "Bash" matcher before any Bash
|
|
7
|
-
command runs. Regex-matches git worktree remove against
|
|
7
|
+
command runs. Regex-matches git worktree remove against the envelope's tool_input.command,
|
|
8
8
|
extracts the target worktree path argument, reads
|
|
9
9
|
artifacts/orchestration/parallel-orchestrator-state.json, and finds the items[] record
|
|
10
10
|
whose worktree_path matches. Allows removal only when that record's merge_status is
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
[CmdletBinding()]
|
|
29
29
|
param()
|
|
30
30
|
|
|
31
|
+
|
|
32
|
+
Import-Module (Join-Path $PSScriptRoot '../lib/hook-payload/HookPayload.psm1') -Force
|
|
31
33
|
$script:ParallelCheckpointPath = 'artifacts/orchestration/parallel-orchestrator-state.json'
|
|
32
34
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
33
35
|
|
|
@@ -176,7 +178,7 @@ function Get-ParallelWorktreeGateBlockDecision {
|
|
|
176
178
|
function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
177
179
|
<#
|
|
178
180
|
.SYNOPSIS
|
|
179
|
-
Parses
|
|
181
|
+
Parses the PreToolUse envelope and returns an allow-or-block decision.
|
|
180
182
|
.PARAMETER ToolInputRaw
|
|
181
183
|
The raw JSON tool payload supplied by Claude Code.
|
|
182
184
|
.OUTPUTS
|
|
@@ -188,17 +190,15 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
188
190
|
[string] $ToolInputRaw
|
|
189
191
|
)
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} catch {
|
|
198
|
-
throw "enforce-parallel-worktree-removal-gate hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
|
|
193
|
+
$payload = Resolve-ClaudeHookToolInput -Raw $ToolInputRaw
|
|
194
|
+
if (-not $payload.IsValid) {
|
|
195
|
+
return Get-ParallelWorktreeGateBlockDecision -Reason (
|
|
196
|
+
'PARALLEL_WORKTREE_REMOVAL_BLOCKED: payload anomaly - ' +
|
|
197
|
+
(Get-ClaudeHookPayloadAnomalyReason -Anomaly $payload.Anomaly) +
|
|
198
|
+
'. The gate fails closed on an envelope it cannot read.')
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
$commandText = $
|
|
201
|
+
$commandText = Get-ClaudeHookToolInputString -ToolInput $payload.Value -Name 'command'
|
|
202
202
|
if (-not $commandText) {
|
|
203
203
|
return Get-ParallelWorktreeGateAllowDecision
|
|
204
204
|
}
|
|
@@ -227,18 +227,55 @@ function Invoke-ParallelWorktreeRemovalGateDecision {
|
|
|
227
227
|
return Get-ParallelWorktreeGateBlockDecision -Reason "PARALLEL_WORKTREE_REMOVAL_BLOCKED: git worktree remove for '$worktreePath' requires a matching parallel checkpoint items[] record with merge_status in {merged, worktree_removed}. The checkpoint was unreadable, no matching record was found, or merge_status was not yet safe for removal."
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
function Invoke-ParallelWorktreeRemovalGateEntryPoint {
|
|
231
|
+
<#
|
|
232
|
+
.SYNOPSIS
|
|
233
|
+
Runs the hook decision and returns the process exit code.
|
|
234
|
+
.DESCRIPTION
|
|
235
|
+
Acquires the payload through the shared reader unless the caller supplies
|
|
236
|
+
one, emits the compact decision JSON, and returns 0. It never returns 1:
|
|
237
|
+
exit 1 is non-blocking for PreToolUse, so every anomaly is already a deny
|
|
238
|
+
decision by the time control reaches here. The function does not call exit;
|
|
239
|
+
the thin tail converts the returned code into a process exit.
|
|
240
|
+
.PARAMETER ToolInputRaw
|
|
241
|
+
Optional pre-acquired payload text. When omitted the ReadPayload seam runs.
|
|
242
|
+
.PARAMETER ReadPayload
|
|
243
|
+
Seam for payload acquisition, so tests can drive the empty-on-all-transports
|
|
244
|
+
case without touching a console.
|
|
245
|
+
.OUTPUTS
|
|
246
|
+
System.Int32
|
|
247
|
+
#>
|
|
248
|
+
[CmdletBinding()]
|
|
249
|
+
[OutputType([int])]
|
|
250
|
+
param(
|
|
251
|
+
[AllowNull()]
|
|
252
|
+
[AllowEmptyString()]
|
|
253
|
+
[string] $ToolInputRaw,
|
|
254
|
+
|
|
255
|
+
[scriptblock] $ReadPayload = { Read-ClaudeHookRawPayload }
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if (-not $PSBoundParameters.ContainsKey('ToolInputRaw')) {
|
|
259
|
+
$ToolInputRaw = [string](& $ReadPayload)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
$decision = Invoke-ParallelWorktreeRemovalGateDecision -ToolInputRaw $ToolInputRaw
|
|
263
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
264
|
+
|
|
265
|
+
return 0
|
|
266
|
+
}
|
|
267
|
+
|
|
230
268
|
# Guard allows dot-sourcing in tests without executing the entrypoint.
|
|
231
269
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
232
270
|
return
|
|
233
271
|
}
|
|
234
272
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
273
|
+
# The entry point returns its [int] exit code as the last pipeline element and the
|
|
274
|
+
# decision JSON before it. `exit (<call>)` would capture BOTH into the exit
|
|
275
|
+
# expression and emit nothing, so the decision is written explicitly here first.
|
|
276
|
+
$entryPointResult = @(Invoke-ParallelWorktreeRemovalGateEntryPoint)
|
|
277
|
+
if ($entryPointResult.Count -gt 1) {
|
|
278
|
+
$entryPointResult[0..($entryPointResult.Count - 2)] | Write-Output
|
|
240
279
|
}
|
|
241
280
|
|
|
242
|
-
$
|
|
243
|
-
|
|
244
|
-
exit 0
|
|
281
|
+
exit ([int]$entryPointResult[-1])
|