@danmoisan/drm-copilot-mcp 1.1.10 → 1.1.12
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 +3517 -1104
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -1
- package/resources/claude-customizations/.claude/agents/epic-planner.md +4 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +15 -2
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +3 -0
- package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-authorization.ps1 +443 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +82 -51
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +52 -3
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -28
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +103 -6
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +124 -8
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +171 -15
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +50 -10
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +35 -7
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +483 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +483 -0
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +218 -6
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +18 -75
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +290 -0
- package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +415 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMerge.psm1 +355 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1 +318 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 +229 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +66 -2
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +323 -16
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +46 -1
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +75 -8
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/powershell-orchestration-state-machine/SKILL.md +29 -1
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
- package/resources/claude-customizations/config/blast-radius.json +7 -0
- package/resources/claude-customizations/pack-manifests/core.json +16 -0
- package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +41 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -0
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +26 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +242 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +67 -12
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +34 -8
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +34 -7
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +483 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +483 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +130 -2
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +23 -1
- package/resources/config/orchestration-handoff-registry.json +138 -0
- package/resources/config/orchestration-handoff.schema.json +472 -0
- package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
- package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +64 -0
package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Resolve mechanically-mergeable project-file conflicts in a worktree.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Entry point for the parent-side project-file merge step of issue #643. It
|
|
7
|
+
lists the conflicted paths of a worktree left by an in-progress merge,
|
|
8
|
+
refuses the whole set unless every path belongs to the mergeable class, and
|
|
9
|
+
otherwise rewrites each conflicted file as the keyed union of the two sides.
|
|
10
|
+
|
|
11
|
+
The script stages nothing and creates no revision: the caller owns those
|
|
12
|
+
steps, so a resolution this script produces is always inspectable before it
|
|
13
|
+
becomes history. It writes one compressed JSON object to stdout and exits 0
|
|
14
|
+
for both verdicts; the verdict is that object's result field. A non-zero exit
|
|
15
|
+
means an unexpected error, whose message is on stderr.
|
|
16
|
+
|
|
17
|
+
.PARAMETER Worktree
|
|
18
|
+
Absolute or relative path to the worktree holding the conflicted merge.
|
|
19
|
+
|
|
20
|
+
.PARAMETER ConfigPath
|
|
21
|
+
Truth table to read mergeable_paths from. Defaults to the worktree's own
|
|
22
|
+
config/blast-radius.json.
|
|
23
|
+
#>
|
|
24
|
+
param(
|
|
25
|
+
[string] $Worktree,
|
|
26
|
+
[string] $ConfigPath
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
Set-StrictMode -Version Latest
|
|
30
|
+
$ErrorActionPreference = 'Stop'
|
|
31
|
+
|
|
32
|
+
# The merge module is imported before the grammar module it nests. Reversing the
|
|
33
|
+
# two would leave the grammar unavailable here: the nested -Force import inside
|
|
34
|
+
# ProjectFileMerge.psm1 removes an already-loaded grammar module and re-imports it
|
|
35
|
+
# into that module's own scope, taking its commands out of this scope.
|
|
36
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'ProjectFileMerge.psm1') -Force -ErrorAction Stop
|
|
37
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'ProjectFileMergeGrammar.psm1') -Force -ErrorAction Stop
|
|
38
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '../blast-radius/BlastRadiusConflict.psm1') -Force -ErrorAction Stop
|
|
39
|
+
|
|
40
|
+
# The three bytes a UTF-8 byte-order mark occupies, kept as a constant so the
|
|
41
|
+
# reader and the writer agree on one prefix.
|
|
42
|
+
$script:ByteOrderMark = [byte[]] @(0xEF, 0xBB, 0xBF)
|
|
43
|
+
|
|
44
|
+
# Splits a text into lines that each retain their own terminator, which is what
|
|
45
|
+
# lets a CRLF file survive a merge unchanged.
|
|
46
|
+
$script:LinePattern = '[^\r\n]*(?:\r\n|\n|\r)'
|
|
47
|
+
|
|
48
|
+
function Invoke-GitExe {
|
|
49
|
+
<#
|
|
50
|
+
.SYNOPSIS
|
|
51
|
+
The single executable seam of this script.
|
|
52
|
+
.DESCRIPTION
|
|
53
|
+
Every git invocation goes through here, so a test mocks one function
|
|
54
|
+
rather than an executable. A non-zero exit throws with the output.
|
|
55
|
+
#>
|
|
56
|
+
[CmdletBinding()]
|
|
57
|
+
# The unary comma wraps the result so the pipeline does not unroll a
|
|
58
|
+
# one-element collection, which makes the emitted object an Object[]
|
|
59
|
+
# carrying strings rather than a bare string[]. Both are declared.
|
|
60
|
+
[OutputType([string[]], [System.Object[]])]
|
|
61
|
+
param([Parameter(Mandatory = $true)][AllowEmptyCollection()][string[]] $GitArgs)
|
|
62
|
+
|
|
63
|
+
$output = & git @GitArgs 2>&1
|
|
64
|
+
# A failed git call is never a resolvable state, so it stops the run.
|
|
65
|
+
if ($LASTEXITCODE -ne 0) {
|
|
66
|
+
throw ('git {0} exited {1}: {2}' -f ($GitArgs -join ' '), $LASTEXITCODE, ($output -join ' '))
|
|
67
|
+
}
|
|
68
|
+
return , @($output | ForEach-Object { [string] $_ })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Read-ConflictedFile {
|
|
72
|
+
<#
|
|
73
|
+
.SYNOPSIS
|
|
74
|
+
Read a conflicted file as bytes, a mark flag, and terminator-bearing lines.
|
|
75
|
+
.DESCRIPTION
|
|
76
|
+
Decoding is strict UTF-8. A file that does not decode returns $null, which
|
|
77
|
+
the caller turns into an escalation rather than a lossy rewrite.
|
|
78
|
+
#>
|
|
79
|
+
[CmdletBinding()]
|
|
80
|
+
[OutputType([pscustomobject])]
|
|
81
|
+
param([Parameter(Mandatory = $true)][string] $Path)
|
|
82
|
+
|
|
83
|
+
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
|
84
|
+
$hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq $script:ByteOrderMark[0] -and $bytes[1] -eq $script:ByteOrderMark[1] -and $bytes[2] -eq $script:ByteOrderMark[2]
|
|
85
|
+
try {
|
|
86
|
+
$text = [System.Text.UTF8Encoding]::new($false, $true).GetString($bytes)
|
|
87
|
+
} catch [System.Text.DecoderFallbackException] {
|
|
88
|
+
return $null
|
|
89
|
+
}
|
|
90
|
+
# The decoded mark is metadata, not content; the writer restores it.
|
|
91
|
+
if ($hasBom) { $text = $text.Substring(1) }
|
|
92
|
+
|
|
93
|
+
$line = [System.Collections.Generic.List[string]]::new()
|
|
94
|
+
foreach ($match in [regex]::Matches($text, $script:LinePattern)) { $line.Add($match.Value) }
|
|
95
|
+
# A final line with no terminator is not matched above and is added here.
|
|
96
|
+
$consumed = ($line -join '').Length
|
|
97
|
+
if ($consumed -lt $text.Length) { $line.Add($text.Substring($consumed)) }
|
|
98
|
+
|
|
99
|
+
return [pscustomobject]@{ Bytes = $bytes; HasBom = $hasBom; Line = $line.ToArray() }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function Write-MergedFile {
|
|
103
|
+
<#
|
|
104
|
+
.SYNOPSIS
|
|
105
|
+
Write merged lines back over the conflicted file.
|
|
106
|
+
.DESCRIPTION
|
|
107
|
+
The byte array is assembled before the ShouldProcess guard, so -WhatIf
|
|
108
|
+
exercises the whole assembly and suppresses only the write itself.
|
|
109
|
+
#>
|
|
110
|
+
[CmdletBinding(SupportsShouldProcess = $true)]
|
|
111
|
+
[OutputType([void])]
|
|
112
|
+
param(
|
|
113
|
+
[Parameter(Mandatory = $true)][string] $Path,
|
|
114
|
+
[Parameter(Mandatory = $true)][AllowEmptyCollection()][AllowEmptyString()][string[]] $Line,
|
|
115
|
+
[Parameter(Mandatory = $true)][bool] $HasBom
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
$body = [System.Text.UTF8Encoding]::new($false).GetBytes($Line -join '')
|
|
119
|
+
$payload = [System.Collections.Generic.List[byte]]::new()
|
|
120
|
+
# A file that carried a mark keeps it, because dropping it is a byte change
|
|
121
|
+
# no reviewer asked for.
|
|
122
|
+
if ($HasBom) { $payload.AddRange($script:ByteOrderMark) }
|
|
123
|
+
$payload.AddRange($body)
|
|
124
|
+
if ($PSCmdlet.ShouldProcess($Path, 'Write the merged project file')) {
|
|
125
|
+
[System.IO.File]::WriteAllBytes($Path, $payload.ToArray())
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function ConvertTo-ResolutionResult {
|
|
130
|
+
<#
|
|
131
|
+
.SYNOPSIS
|
|
132
|
+
Build the single result record this script emits.
|
|
133
|
+
.DESCRIPTION
|
|
134
|
+
The three keys are the whole published contract, so they are written in
|
|
135
|
+
one place rather than at each return site.
|
|
136
|
+
#>
|
|
137
|
+
[CmdletBinding()]
|
|
138
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
139
|
+
param(
|
|
140
|
+
[Parameter(Mandatory = $true)][string] $ResultName,
|
|
141
|
+
[AllowEmptyCollection()][object[]] $Resolved = @(),
|
|
142
|
+
[AllowEmptyCollection()][AllowEmptyString()][string[]] $EscalatePath = @()
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return [ordered]@{ result = $ResultName; resolved = $Resolved; escalate_paths = $EscalatePath }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function Invoke-MergeableConflictResolution {
|
|
149
|
+
<#
|
|
150
|
+
.SYNOPSIS
|
|
151
|
+
Resolve every conflicted path of a worktree, or escalate the whole set.
|
|
152
|
+
.DESCRIPTION
|
|
153
|
+
The set is all-or-nothing. One non-mergeable path escalates before any
|
|
154
|
+
file is read, and one unresolvable file escalates before any file is
|
|
155
|
+
written, so a partially merged worktree is not a reachable state.
|
|
156
|
+
.PARAMETER Worktree
|
|
157
|
+
The worktree holding the conflicted merge.
|
|
158
|
+
.PARAMETER ConfigPath
|
|
159
|
+
Truth table carrying mergeable_paths; the worktree default is used when
|
|
160
|
+
this is blank.
|
|
161
|
+
.OUTPUTS
|
|
162
|
+
An ordered record carrying result, resolved, and escalate_paths.
|
|
163
|
+
#>
|
|
164
|
+
[CmdletBinding()]
|
|
165
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
166
|
+
param(
|
|
167
|
+
[Parameter(Mandatory = $true)][string] $Worktree,
|
|
168
|
+
[AllowNull()][AllowEmptyString()][string] $ConfigPath
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if ([string]::IsNullOrWhiteSpace($ConfigPath)) { $ConfigPath = Join-Path -Path $Worktree -ChildPath 'config/blast-radius.json' }
|
|
172
|
+
$config = Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json
|
|
173
|
+
$mergeable = @(Get-ConfigMergeablePath -Config $config)
|
|
174
|
+
|
|
175
|
+
# The listing is bound before it is filtered: Invoke-GitExe returns one array
|
|
176
|
+
# object, so piping it directly would hand the whole array to the filter.
|
|
177
|
+
$listed = Invoke-GitExe -GitArgs @('-C', $Worktree, 'diff', '--name-only', '--diff-filter=U')
|
|
178
|
+
$conflicted = @($listed | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
179
|
+
$outside = @($conflicted | Where-Object { -not (Test-MergeablePath -Entry $_ -MergeablePath $mergeable) })
|
|
180
|
+
# Classification happens first so a non-mergeable set costs no file read.
|
|
181
|
+
if ($outside.Count -gt 0) { return (ConvertTo-ResolutionResult -ResultName 'escalate' -EscalatePath $outside) }
|
|
182
|
+
|
|
183
|
+
$pending = [System.Collections.Generic.List[object]]::new()
|
|
184
|
+
foreach ($path in $conflicted) {
|
|
185
|
+
$kind = Get-ProjectFileKind -Path $path
|
|
186
|
+
if ($null -eq $kind) { return (ConvertTo-ResolutionResult -ResultName 'escalate' -EscalatePath @($path)) }
|
|
187
|
+
$file = Read-ConflictedFile -Path (Join-Path -Path $Worktree -ChildPath $path)
|
|
188
|
+
if ($null -eq $file) { return (ConvertTo-ResolutionResult -ResultName 'escalate' -EscalatePath @($path)) }
|
|
189
|
+
|
|
190
|
+
$outcome = Merge-ConflictedText -Line $file.Line -Kind $kind
|
|
191
|
+
if ($outcome.Escalate) { return (ConvertTo-ResolutionResult -ResultName 'escalate' -EscalatePath @($path)) }
|
|
192
|
+
|
|
193
|
+
# The three stages are the only authority on what the merge must keep.
|
|
194
|
+
# Each is bound directly, because Invoke-GitExe already returns one array
|
|
195
|
+
# and an array subexpression around it would nest that array inside another.
|
|
196
|
+
$ours = Invoke-GitExe -GitArgs @('-C', $Worktree, 'show', ":2:$path")
|
|
197
|
+
$theirs = Invoke-GitExe -GitArgs @('-C', $Worktree, 'show', ":3:$path")
|
|
198
|
+
$base = Invoke-GitExe -GitArgs @('-C', $Worktree, 'show', ":1:$path")
|
|
199
|
+
$isKept = Test-NeverDropPostCondition -MergedLine $outcome.Lines -OursLine $ours -TheirsLine $theirs -BaseLine $base -Kind $kind
|
|
200
|
+
if (-not $isKept) { return (ConvertTo-ResolutionResult -ResultName 'escalate' -EscalatePath @($path)) }
|
|
201
|
+
|
|
202
|
+
$pending.Add([pscustomobject]@{
|
|
203
|
+
Path = $path
|
|
204
|
+
HasBom = $file.HasBom
|
|
205
|
+
Line = $outcome.Lines
|
|
206
|
+
Record = [ordered]@{
|
|
207
|
+
path = $path
|
|
208
|
+
entries_added_from_ours = @($outcome.EntriesAddedFromOurs)
|
|
209
|
+
entries_added_from_theirs = @($outcome.EntriesAddedFromTheirs)
|
|
210
|
+
version_resolutions = @($outcome.VersionResolutions)
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Every path resolved, so the worktree can be rewritten in one pass.
|
|
216
|
+
foreach ($item in $pending) {
|
|
217
|
+
Write-MergedFile -Path (Join-Path -Path $Worktree -ChildPath $item.Path) -Line $item.Line -HasBom $item.HasBom
|
|
218
|
+
}
|
|
219
|
+
return (ConvertTo-ResolutionResult -ResultName 'resolved' -Resolved @($pending | ForEach-Object { $_.Record }))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Dot-sourcing loads the functions for a test host; any other invocation runs the
|
|
223
|
+
# step. The guard is the precedent set by Invoke-ReleaseReconciliation.ps1.
|
|
224
|
+
if ($MyInvocation.InvocationName -ne '.') {
|
|
225
|
+
if ([string]::IsNullOrWhiteSpace($Worktree)) { throw 'Worktree is required.' }
|
|
226
|
+
$result = Invoke-MergeableConflictResolution -Worktree $Worktree -ConfigPath $ConfigPath
|
|
227
|
+
Write-Output (ConvertTo-Json -InputObject $result -Depth 6 -Compress)
|
|
228
|
+
exit 0
|
|
229
|
+
}
|
package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pure epic readiness predicates for epic-scope gate decisions (issue #663).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Evaluates the epic checkpoint artifacts/orchestration/epic-orchestrator-state.json
|
|
7
|
+
for the two epic-scope legs that previously had no epic seam: the integration pull
|
|
8
|
+
request (gate 1) and the preimplementation gate's command and path legs (gate 4).
|
|
9
|
+
Each predicate returns an empty string when the checkpoint is ready, or the name of
|
|
10
|
+
the first failed conjunct, so a gate can name the conjunct in its denial reason.
|
|
11
|
+
|
|
12
|
+
The shared conjunct names (route_id, epic_feature_folder, epic_manifest_path,
|
|
13
|
+
integration_branch, features) match Get-EpicOrchestrationReadinessFailure in
|
|
14
|
+
.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1, without its
|
|
15
|
+
delegation-specific target-record conjuncts. That hook-local file is neither imported
|
|
16
|
+
nor modified.
|
|
17
|
+
|
|
18
|
+
.NOTES
|
|
19
|
+
PowerShell 7+. Pure: no import, no filesystem read, no subprocess, no network, no
|
|
20
|
+
clock read, and no environment read. Mirrored byte-identically under
|
|
21
|
+
extensions/drm-copilot/resources/claude-customizations/.
|
|
22
|
+
AUTHORITY: PowerShell-authoritative. The epic readiness predicates have no Python reference implementation, and no enforcement hook invokes Python (issue #663, D5).
|
|
23
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
24
|
+
#>
|
|
25
|
+
|
|
26
|
+
Set-StrictMode -Version Latest
|
|
27
|
+
$ErrorActionPreference = 'Stop'
|
|
28
|
+
|
|
29
|
+
# The only route id that describes an epic checkpoint. Compared case-sensitively.
|
|
30
|
+
$script:EpicRouteId = 'epic'
|
|
31
|
+
|
|
32
|
+
# The feature merge states that count as integrated. Any other value, including a
|
|
33
|
+
# missing one, means a child has not landed on the integration branch.
|
|
34
|
+
$script:TerminalMergeStatuses = @('merged', 'worktree_removed')
|
|
35
|
+
|
|
36
|
+
# An epic manifest lives under the epics tree; the pattern is tested after backslash
|
|
37
|
+
# normalisation so either separator spelling is accepted.
|
|
38
|
+
$script:EpicManifestPattern = '(^|/)docs/features/epics/'
|
|
39
|
+
|
|
40
|
+
function Get-EpicReadinessPropertyValue {
|
|
41
|
+
# Pure: the named property of an object, or $null when the object or the property
|
|
42
|
+
# is absent. StrictMode forbids reading a missing property directly.
|
|
43
|
+
[OutputType([object])]
|
|
44
|
+
param([AllowNull()][object] $InputObject, [string] $Name)
|
|
45
|
+
|
|
46
|
+
if ($null -eq $InputObject) {
|
|
47
|
+
return $null
|
|
48
|
+
}
|
|
49
|
+
$property = $InputObject.PSObject.Properties[$Name]
|
|
50
|
+
if ($null -eq $property) {
|
|
51
|
+
return $null
|
|
52
|
+
}
|
|
53
|
+
return $property.Value
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Get-EpicReadinessFeatureList {
|
|
57
|
+
# Pure: the features array as a list, empty when it is absent or empty.
|
|
58
|
+
[OutputType([object[]])]
|
|
59
|
+
param([AllowNull()][object] $Checkpoint)
|
|
60
|
+
|
|
61
|
+
$features = Get-EpicReadinessPropertyValue -InputObject $Checkpoint -Name 'features'
|
|
62
|
+
if ($null -eq $features) {
|
|
63
|
+
return , @()
|
|
64
|
+
}
|
|
65
|
+
return , @($features)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function Get-EpicPrCreationReadinessFailure {
|
|
69
|
+
<#
|
|
70
|
+
.SYNOPSIS
|
|
71
|
+
Name the first failed conjunct of the epic integration-PR readiness shape, or ''.
|
|
72
|
+
.DESCRIPTION
|
|
73
|
+
Conjuncts, in order: checkpoint-absent (null checkpoint); route_id (not exactly
|
|
74
|
+
epic); integration_branch (empty, or different from a non-empty HeadBranch,
|
|
75
|
+
case-sensitive); features (absent or empty); merge_status (any feature whose
|
|
76
|
+
merge_status is not merged or worktree_removed).
|
|
77
|
+
.PARAMETER Checkpoint
|
|
78
|
+
The parsed epic checkpoint object, or $null.
|
|
79
|
+
.PARAMETER HeadBranch
|
|
80
|
+
The --head branch of the pull request; not compared when empty.
|
|
81
|
+
.OUTPUTS
|
|
82
|
+
System.String
|
|
83
|
+
#>
|
|
84
|
+
[CmdletBinding()]
|
|
85
|
+
[OutputType([string])]
|
|
86
|
+
param(
|
|
87
|
+
[Parameter(Mandatory = $true)]
|
|
88
|
+
[AllowNull()]
|
|
89
|
+
[object] $Checkpoint,
|
|
90
|
+
|
|
91
|
+
[AllowNull()]
|
|
92
|
+
[AllowEmptyString()]
|
|
93
|
+
[string] $HeadBranch = ''
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if ($null -eq $Checkpoint) {
|
|
97
|
+
return 'checkpoint-absent'
|
|
98
|
+
}
|
|
99
|
+
if ([string](Get-EpicReadinessPropertyValue -InputObject $Checkpoint -Name 'route_id') -cne $script:EpicRouteId) {
|
|
100
|
+
return 'route_id'
|
|
101
|
+
}
|
|
102
|
+
$integrationBranch = [string](Get-EpicReadinessPropertyValue -InputObject $Checkpoint -Name 'integration_branch')
|
|
103
|
+
# Decide on the integration branch: it must be present, and when the caller names a
|
|
104
|
+
# head branch the two must be identical.
|
|
105
|
+
if ([string]::IsNullOrWhiteSpace($integrationBranch)) {
|
|
106
|
+
return 'integration_branch'
|
|
107
|
+
} elseif (-not [string]::IsNullOrWhiteSpace($HeadBranch) -and ($integrationBranch -cne $HeadBranch)) {
|
|
108
|
+
return 'integration_branch'
|
|
109
|
+
}
|
|
110
|
+
$features = Get-EpicReadinessFeatureList -Checkpoint $Checkpoint
|
|
111
|
+
if ($features.Count -eq 0) {
|
|
112
|
+
return 'features'
|
|
113
|
+
}
|
|
114
|
+
# Every child feature must have landed; the first non-terminal status fails the set.
|
|
115
|
+
foreach ($feature in $features) {
|
|
116
|
+
$status = [string](Get-EpicReadinessPropertyValue -InputObject $feature -Name 'merge_status')
|
|
117
|
+
if ($script:TerminalMergeStatuses -notcontains $status) {
|
|
118
|
+
return 'merge_status'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return ''
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function Get-EpicCommandLegReadinessFailure {
|
|
125
|
+
<#
|
|
126
|
+
.SYNOPSIS
|
|
127
|
+
Name the first failed conjunct of the epic command-leg readiness shape, or ''.
|
|
128
|
+
.DESCRIPTION
|
|
129
|
+
Conjuncts, in order: checkpoint-absent; route_id; epic_feature_folder (empty);
|
|
130
|
+
epic_manifest_path (empty, or not under docs/features/epics/ after backslash
|
|
131
|
+
normalisation); integration_branch (empty); features (absent or empty);
|
|
132
|
+
merge-in-progress (no merge in progress in the effective worktree, D2).
|
|
133
|
+
.PARAMETER Checkpoint
|
|
134
|
+
The parsed epic checkpoint object, or $null.
|
|
135
|
+
.PARAMETER MergeInProgress
|
|
136
|
+
Whether MERGE_HEAD exists in the effective worktree's git directory.
|
|
137
|
+
.OUTPUTS
|
|
138
|
+
System.String
|
|
139
|
+
#>
|
|
140
|
+
[CmdletBinding()]
|
|
141
|
+
[OutputType([string])]
|
|
142
|
+
param(
|
|
143
|
+
[Parameter(Mandatory = $true)]
|
|
144
|
+
[AllowNull()]
|
|
145
|
+
[object] $Checkpoint,
|
|
146
|
+
|
|
147
|
+
[Parameter(Mandatory = $true)]
|
|
148
|
+
[bool] $MergeInProgress
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if ($null -eq $Checkpoint) {
|
|
152
|
+
return 'checkpoint-absent'
|
|
153
|
+
}
|
|
154
|
+
if ([string](Get-EpicReadinessPropertyValue -InputObject $Checkpoint -Name 'route_id') -cne $script:EpicRouteId) {
|
|
155
|
+
return 'route_id'
|
|
156
|
+
}
|
|
157
|
+
if ([string]::IsNullOrWhiteSpace([string](Get-EpicReadinessPropertyValue -InputObject $Checkpoint -Name 'epic_feature_folder'))) {
|
|
158
|
+
return 'epic_feature_folder'
|
|
159
|
+
}
|
|
160
|
+
$manifestPath = ([string](Get-EpicReadinessPropertyValue -InputObject $Checkpoint -Name 'epic_manifest_path')) -replace '\\', '/'
|
|
161
|
+
if ([string]::IsNullOrWhiteSpace($manifestPath) -or $manifestPath -notmatch $script:EpicManifestPattern) {
|
|
162
|
+
return 'epic_manifest_path'
|
|
163
|
+
}
|
|
164
|
+
if ([string]::IsNullOrWhiteSpace([string](Get-EpicReadinessPropertyValue -InputObject $Checkpoint -Name 'integration_branch'))) {
|
|
165
|
+
return 'integration_branch'
|
|
166
|
+
}
|
|
167
|
+
if ((Get-EpicReadinessFeatureList -Checkpoint $Checkpoint).Count -eq 0) {
|
|
168
|
+
return 'features'
|
|
169
|
+
}
|
|
170
|
+
if (-not $MergeInProgress) {
|
|
171
|
+
return 'merge-in-progress'
|
|
172
|
+
}
|
|
173
|
+
return ''
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Export-ModuleMember -Function Get-EpicPrCreationReadinessFailure, Get-EpicCommandLegReadinessFailure
|