@danmoisan/drm-copilot-mcp 1.1.16 → 1.1.17
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 +639 -27
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +12 -6
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
- package/resources/claude-customizations/.claude/hooks/{enforce-powershell-batch-budget-route.ps1 → enforce-batch-budget-route.ps1} +12 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +3 -3
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
- package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
- package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +2 -0
- package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +32 -18
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/claude-customizations/pack-manifests/powershell.json +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
- package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +4 -124
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +12 -1
package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Portable-identity resolution of the epic or parallel run a gated call belongs to (issue #690).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Selects the worktree whose run checkpoint governs a call from portable identity
|
|
7
|
+
alone: the integration_branch:, epic_feature_folder:, and parallel_slug: literals of
|
|
8
|
+
a kickoff prompt, or a pull request number or worktree path a command names. A
|
|
9
|
+
checkpoint path is always composed beneath a resolved root, so no read binds to the
|
|
10
|
+
calling process's directory.
|
|
11
|
+
|
|
12
|
+
A live worktree matches an epic run when its epic checkpoint records route_id epic
|
|
13
|
+
and the integration branch; several matches are narrowed to the worktree that has
|
|
14
|
+
that branch checked out, because git checks a branch out in at most one worktree. A
|
|
15
|
+
parallel run has no branch, so several matches are ambiguous. A record match keeps
|
|
16
|
+
the live worktrees whose run checkpoint records the pull request number or worktree
|
|
17
|
+
path. There is no session-root-first shortcut: a stale copy at the session root never
|
|
18
|
+
wins over the worktree that owns the run.
|
|
19
|
+
|
|
20
|
+
.NOTES
|
|
21
|
+
PowerShell 7+. Re-uses its siblings for enumeration, the ascent, normalisation, the
|
|
22
|
+
join, the result constructor, the SessionRoot/OtherWorktree labelling, and both
|
|
23
|
+
reason-code accessors, re-implementing none. Its only filesystem read is the
|
|
24
|
+
checkpoint-text seam. Mirrored byte-identically.
|
|
25
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
26
|
+
#>
|
|
27
|
+
|
|
28
|
+
Set-StrictMode -Version Latest
|
|
29
|
+
$ErrorActionPreference = 'Stop'
|
|
30
|
+
|
|
31
|
+
Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
|
|
32
|
+
Import-Module (Join-Path $PSScriptRoot 'WorktreeTargetResolution.psm1') -ErrorAction Stop
|
|
33
|
+
Import-Module (Join-Path $PSScriptRoot 'WorktreeItemResolution.psm1') -ErrorAction Stop
|
|
34
|
+
|
|
35
|
+
# The run checkpoint locations relative to a worktree root.
|
|
36
|
+
$script:EpicCheckpointRelativePath = 'artifacts/orchestration/epic-orchestrator-state.json'
|
|
37
|
+
$script:ParallelCheckpointRelativePath = 'artifacts/orchestration/parallel-orchestrator-state.json'
|
|
38
|
+
|
|
39
|
+
# The route_id each run checkpoint must record.
|
|
40
|
+
$script:EpicRouteId = 'epic'
|
|
41
|
+
$script:ParallelRouteId = 'parallel'
|
|
42
|
+
|
|
43
|
+
# Where a stale checkpoint copy is moved so it stops competing with the owning worktree.
|
|
44
|
+
$script:HandoffRemedy = 'artifacts/orchestration/handoff/'
|
|
45
|
+
|
|
46
|
+
# The kickoff literals, matched case-sensitively and never as the tail of a longer word.
|
|
47
|
+
$script:IntegrationBranchPattern = '(?<![A-Za-z0-9_])integration_branch:[ \t]*(?<value>\S+)'
|
|
48
|
+
$script:EpicSlugPattern = '(?<![A-Za-z0-9_])epic_feature_folder:[ \t]*(?<value>\S+)'
|
|
49
|
+
$script:ParallelSlugPattern = '(?<![A-Za-z0-9_])parallel_slug:[ \t]*(?<value>\S+)'
|
|
50
|
+
|
|
51
|
+
# A drive-rooted or slash-rooted path; anything else is relative to some directory.
|
|
52
|
+
$script:AbsolutePathPattern = '^([A-Za-z]:(/|$)|/)'
|
|
53
|
+
|
|
54
|
+
# Private: the single value a pattern names in a text, or $null when it names none or
|
|
55
|
+
# more than one distinct value (a prompt that names two runs identifies neither).
|
|
56
|
+
function Get-WorktreeRunSignalValue {
|
|
57
|
+
[CmdletBinding()]
|
|
58
|
+
[OutputType([string])]
|
|
59
|
+
param(
|
|
60
|
+
[Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $Text,
|
|
61
|
+
[Parameter(Mandatory = $true)] [string] $Pattern
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
$values = [System.Collections.Generic.List[string]]::new()
|
|
65
|
+
foreach ($match in [regex]::Matches($Text, $Pattern)) {
|
|
66
|
+
$value = $match.Groups['value'].Value
|
|
67
|
+
# The kickoff sentence ends the value with a full stop; one is removed.
|
|
68
|
+
if ($value.EndsWith('.')) { $value = $value.Substring(0, $value.Length - 1) }
|
|
69
|
+
if ($value.Length -gt 0 -and -not $values.Contains($value)) { $values.Add($value) }
|
|
70
|
+
}
|
|
71
|
+
if ($values.Count -eq 1) { return $values[0] }
|
|
72
|
+
return $null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Find-WorktreeRunIdentitySignal {
|
|
76
|
+
<#
|
|
77
|
+
.SYNOPSIS
|
|
78
|
+
Read the run identity literals from a prompt.
|
|
79
|
+
.DESCRIPTION
|
|
80
|
+
Pure. Returns an object with IntegrationBranch, EpicSlug, and ParallelSlug, each
|
|
81
|
+
the single distinct value the prompt names for that literal, or $null.
|
|
82
|
+
.PARAMETER Text
|
|
83
|
+
The prompt text to scan.
|
|
84
|
+
#>
|
|
85
|
+
[CmdletBinding()]
|
|
86
|
+
[OutputType([pscustomobject])]
|
|
87
|
+
param(
|
|
88
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Text
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
$source = [string] $Text
|
|
92
|
+
return [pscustomobject]@{
|
|
93
|
+
IntegrationBranch = Get-WorktreeRunSignalValue -Text $source -Pattern $script:IntegrationBranchPattern
|
|
94
|
+
EpicSlug = Get-WorktreeRunSignalValue -Text $source -Pattern $script:EpicSlugPattern
|
|
95
|
+
ParallelSlug = Get-WorktreeRunSignalValue -Text $source -Pattern $script:ParallelSlugPattern
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Get-WorktreeRunCheckpointText {
|
|
100
|
+
<#
|
|
101
|
+
.SYNOPSIS
|
|
102
|
+
Return the raw text of a file, or $null when it is absent or unreadable; an unreadable file also writes a diagnostic to stderr.
|
|
103
|
+
.DESCRIPTION
|
|
104
|
+
The module's only filesystem read, isolated as the seam a test mocks.
|
|
105
|
+
.PARAMETER Path
|
|
106
|
+
The absolute path to read.
|
|
107
|
+
#>
|
|
108
|
+
[CmdletBinding()]
|
|
109
|
+
[OutputType([string])]
|
|
110
|
+
param(
|
|
111
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Path
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if ([string]::IsNullOrWhiteSpace($Path)) { return $null }
|
|
115
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction SilentlyContinue)) { return $null }
|
|
116
|
+
# An unreadable file is reported on stderr and treated as absent, so resolution stays fail-closed and the hook's stdout stays JSON-only.
|
|
117
|
+
try { return [System.IO.File]::ReadAllText($Path) }
|
|
118
|
+
catch { [Console]::Error.WriteLine(("WORKTREE_RUN_CHECKPOINT_UNREADABLE: '{0}': {1}" -f $Path, $_.Exception.Message)); return $null }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function Get-WorktreeRunCheckpointPath {
|
|
122
|
+
<#
|
|
123
|
+
.SYNOPSIS
|
|
124
|
+
Compose the absolute path of a run or item checkpoint beneath a worktree root.
|
|
125
|
+
.DESCRIPTION
|
|
126
|
+
The single composition of an absolute checkpoint path for the gates. Throws when
|
|
127
|
+
the root is not absolute, through the shared join.
|
|
128
|
+
.PARAMETER Kind
|
|
129
|
+
epic, parallel, or item.
|
|
130
|
+
.PARAMETER WorktreeRoot
|
|
131
|
+
The absolute worktree root, in either separator style.
|
|
132
|
+
#>
|
|
133
|
+
[CmdletBinding()]
|
|
134
|
+
[OutputType([string])]
|
|
135
|
+
param(
|
|
136
|
+
[Parameter(Mandatory = $true)] [ValidateSet('epic', 'parallel', 'item')] [string] $Kind,
|
|
137
|
+
[Parameter(Mandatory = $true)] [string] $WorktreeRoot
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if ($Kind -eq 'item') { return (Get-WorktreeItemCheckpointPath -WorktreeRoot $WorktreeRoot) }
|
|
141
|
+
$relative = if ($Kind -eq 'epic') { $script:EpicCheckpointRelativePath } else { $script:ParallelCheckpointRelativePath }
|
|
142
|
+
return (Join-WorktreeResolutionPath -WorktreeRoot $WorktreeRoot -RepoRelativePath $relative)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Private: parse checkpoint text into an object, or $null for blank, unparseable, or
|
|
146
|
+
# non-object text. -NoEnumerate keeps a one-element JSON array from reading as an object.
|
|
147
|
+
function ConvertFrom-WorktreeRunCheckpointText {
|
|
148
|
+
[CmdletBinding()]
|
|
149
|
+
[OutputType([pscustomobject])]
|
|
150
|
+
param(
|
|
151
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Text
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if ([string]::IsNullOrWhiteSpace($Text)) { return $null }
|
|
155
|
+
$parsed = $null
|
|
156
|
+
try { $parsed = ConvertFrom-Json -InputObject $Text -NoEnumerate } catch { return $null }
|
|
157
|
+
# The full type name is tested, because a wrapped array also satisfies [pscustomobject].
|
|
158
|
+
if ($null -eq $parsed -or $parsed -isnot [System.Management.Automation.PSCustomObject]) { return $null }
|
|
159
|
+
return $parsed
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Private: a named property of a parsed object, or $null when absent (strict mode would
|
|
163
|
+
# otherwise throw on a missing member).
|
|
164
|
+
function Get-WorktreeRunPropertyValue {
|
|
165
|
+
[CmdletBinding()]
|
|
166
|
+
[OutputType([object])]
|
|
167
|
+
param(
|
|
168
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [object] $InputObject,
|
|
169
|
+
[Parameter(Mandatory = $true)] [string] $Name
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if ($null -eq $InputObject -or $InputObject -isnot [System.Management.Automation.PSCustomObject]) { return $null }
|
|
173
|
+
if ($InputObject.PSObject.Properties.Name -notcontains $Name) { return $null }
|
|
174
|
+
return $InputObject.$Name
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Private: read and parse a live root's run checkpoint of one kind, keeping it only when
|
|
178
|
+
# it records the kind's route_id.
|
|
179
|
+
function Get-WorktreeRunCheckpoint {
|
|
180
|
+
[CmdletBinding()]
|
|
181
|
+
[OutputType([pscustomobject])]
|
|
182
|
+
param(
|
|
183
|
+
[Parameter(Mandatory = $true)] [ValidateSet('epic', 'parallel')] [string] $Kind,
|
|
184
|
+
[Parameter(Mandatory = $true)] [string] $WorktreeRoot
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
$text = Get-WorktreeRunCheckpointText -Path (Get-WorktreeRunCheckpointPath -Kind $Kind -WorktreeRoot $WorktreeRoot)
|
|
188
|
+
$checkpoint = ConvertFrom-WorktreeRunCheckpointText -Text $text
|
|
189
|
+
$routeId = if ($Kind -eq 'epic') { $script:EpicRouteId } else { $script:ParallelRouteId }
|
|
190
|
+
if ([string] (Get-WorktreeRunPropertyValue -InputObject $checkpoint -Name 'route_id') -cne $routeId) { return $null }
|
|
191
|
+
return $checkpoint
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Private: the live roots reachable from the session path, assigned before wrapping
|
|
195
|
+
# because the seam returns its array as one object.
|
|
196
|
+
function Get-WorktreeRunLiveRoot {
|
|
197
|
+
[CmdletBinding()]
|
|
198
|
+
[OutputType([string[]], [object[]])]
|
|
199
|
+
param(
|
|
200
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot,
|
|
201
|
+
[string] $Branch
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
$liveRoots = if ([string]::IsNullOrWhiteSpace($Branch)) {
|
|
205
|
+
Get-WorktreeItemLiveRoot -SessionRoot $SessionRoot
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
Get-WorktreeItemLiveRoot -SessionRoot $SessionRoot -Branch $Branch
|
|
209
|
+
}
|
|
210
|
+
return , [string[]] @($liveRoots | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Private: the zero/one/many decision shared by the parallel and record resolvers.
|
|
214
|
+
function Resolve-WorktreeRunMatch {
|
|
215
|
+
[CmdletBinding()]
|
|
216
|
+
[OutputType([pscustomobject])]
|
|
217
|
+
param(
|
|
218
|
+
[Parameter(Mandatory = $true)] [AllowEmptyCollection()] [string[]] $Matched,
|
|
219
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot,
|
|
220
|
+
[Parameter(Mandatory = $true)] [string] $Description
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if ($Matched.Count -eq 0) {
|
|
224
|
+
return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $SessionRoot -Detail (
|
|
225
|
+
'no live worktree''s run checkpoint records {0}' -f $Description))
|
|
226
|
+
}
|
|
227
|
+
if ($Matched.Count -eq 1) {
|
|
228
|
+
return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $Matched[0] -SessionRoot $SessionRoot -Detail (
|
|
229
|
+
'{0} resolves to the live worktree whose run checkpoint records it' -f $Description))
|
|
230
|
+
}
|
|
231
|
+
$detail = ('{0} is recorded by the run checkpoints of {1} live worktrees; move the stale copy to {2}' -f
|
|
232
|
+
$Description, $Matched.Count, $script:HandoffRemedy)
|
|
233
|
+
return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $SessionRoot -Candidate $Matched -Detail $detail)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function Resolve-WorktreeEpicTarget {
|
|
237
|
+
<#
|
|
238
|
+
.SYNOPSIS
|
|
239
|
+
Resolve the live worktree whose epic checkpoint governs an integration branch.
|
|
240
|
+
.DESCRIPTION
|
|
241
|
+
Keeps each live root whose epic checkpoint records route_id epic and the branch.
|
|
242
|
+
Zero matches resolve NoTarget; one resolves; several are narrowed to the root that
|
|
243
|
+
has the branch checked out, and anything other than one survivor is Ambiguous. A
|
|
244
|
+
supplied slug that a match's epic_feature_folder disagrees with is Ambiguous.
|
|
245
|
+
.PARAMETER IntegrationBranch
|
|
246
|
+
The integration branch the call names.
|
|
247
|
+
.PARAMETER EpicSlug
|
|
248
|
+
Optional. The epic_feature_folder the call names, used as a cross-check only.
|
|
249
|
+
.PARAMETER SessionRoot
|
|
250
|
+
The calling process's path, used only to find the repository and label a result.
|
|
251
|
+
#>
|
|
252
|
+
[CmdletBinding()]
|
|
253
|
+
[OutputType([pscustomobject])]
|
|
254
|
+
param(
|
|
255
|
+
[AllowNull()] [AllowEmptyString()] [string] $IntegrationBranch,
|
|
256
|
+
[AllowNull()] [AllowEmptyString()] [string] $EpicSlug,
|
|
257
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if ([string]::IsNullOrWhiteSpace($IntegrationBranch)) {
|
|
261
|
+
return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $SessionRoot -Detail (
|
|
262
|
+
'the call carries no integration_branch: value, so the epic run it belongs to cannot be identified'))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
$kept = [System.Collections.Generic.List[string]]::new()
|
|
266
|
+
$slugs = [System.Collections.Generic.List[string]]::new()
|
|
267
|
+
foreach ($root in (Get-WorktreeRunLiveRoot -SessionRoot $SessionRoot)) {
|
|
268
|
+
$checkpoint = Get-WorktreeRunCheckpoint -Kind 'epic' -WorktreeRoot $root
|
|
269
|
+
if ($null -eq $checkpoint) { continue }
|
|
270
|
+
if ([string] (Get-WorktreeRunPropertyValue -InputObject $checkpoint -Name 'integration_branch') -cne $IntegrationBranch) { continue }
|
|
271
|
+
$kept.Add($root)
|
|
272
|
+
$recordedSlug = Get-WorktreeRunPropertyValue -InputObject $checkpoint -Name 'epic_feature_folder'
|
|
273
|
+
if ($null -ne $recordedSlug) { $slugs.Add([string] $recordedSlug) }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# A slug the call names that a matching checkpoint contradicts is not guessed past.
|
|
277
|
+
if (-not [string]::IsNullOrWhiteSpace($EpicSlug)) {
|
|
278
|
+
$disagreeing = @($slugs | Where-Object { $_ -cne $EpicSlug })
|
|
279
|
+
if ($disagreeing.Count -gt 0) {
|
|
280
|
+
return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $SessionRoot `
|
|
281
|
+
-Signal 'Branch' -SignalValue $IntegrationBranch -Candidate $kept.ToArray() -Detail (
|
|
282
|
+
"the call names epic_feature_folder '{0}', but an epic checkpoint recording integration_branch '{1}' records epic_feature_folder '{2}'" -f
|
|
283
|
+
$EpicSlug, $IntegrationBranch, $disagreeing[0]))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if ($kept.Count -eq 0) {
|
|
288
|
+
return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $SessionRoot -Detail (
|
|
289
|
+
"no live worktree's epic checkpoint records integration_branch '{0}'" -f $IntegrationBranch))
|
|
290
|
+
}
|
|
291
|
+
if ($kept.Count -eq 1) {
|
|
292
|
+
return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $kept[0] -SessionRoot $SessionRoot -Branch $IntegrationBranch -Detail (
|
|
293
|
+
"integration_branch '{0}' resolves to the live worktree whose epic checkpoint records it" -f $IntegrationBranch))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# Several copies: the owning worktree is the one with the integration branch checked out.
|
|
297
|
+
$branchRoots = Get-WorktreeRunLiveRoot -SessionRoot $SessionRoot -Branch $IntegrationBranch
|
|
298
|
+
$checkedOut = @(foreach ($root in $branchRoots) { ConvertTo-WorktreeResolutionNormalizedPath -Path $root })
|
|
299
|
+
$survivors = @($kept | Where-Object { $checkedOut -contains (ConvertTo-WorktreeResolutionNormalizedPath -Path $_) })
|
|
300
|
+
if ($survivors.Count -eq 1) {
|
|
301
|
+
return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $survivors[0] -SessionRoot $SessionRoot -Branch $IntegrationBranch -Detail (
|
|
302
|
+
"integration_branch '{0}' resolves to the live worktree that has it checked out" -f $IntegrationBranch))
|
|
303
|
+
}
|
|
304
|
+
$detail = ("the epic checkpoints of {0} live worktrees record integration_branch '{1}', and {2} of them have it checked out; move the stale copy to {3}" -f
|
|
305
|
+
$kept.Count, $IntegrationBranch, $survivors.Count, $script:HandoffRemedy)
|
|
306
|
+
return (New-WorktreeResolutionTargetResult -Status 'Ambiguous' -SessionRoot $SessionRoot `
|
|
307
|
+
-Signal 'Branch' -SignalValue $IntegrationBranch -Candidate $kept.ToArray() -Detail $detail)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function Resolve-WorktreeParallelTarget {
|
|
311
|
+
<#
|
|
312
|
+
.SYNOPSIS
|
|
313
|
+
Resolve the live worktree whose parallel checkpoint records a parallel slug.
|
|
314
|
+
.DESCRIPTION
|
|
315
|
+
Keeps each live root whose parallel checkpoint records route_id parallel and the
|
|
316
|
+
slug. Zero matches resolve NoTarget, one resolves, and several are Ambiguous.
|
|
317
|
+
.PARAMETER ParallelSlug
|
|
318
|
+
The parallel_slug the call names.
|
|
319
|
+
.PARAMETER SessionRoot
|
|
320
|
+
The calling process's path, used only to find the repository and label a result.
|
|
321
|
+
#>
|
|
322
|
+
[CmdletBinding()]
|
|
323
|
+
[OutputType([pscustomobject])]
|
|
324
|
+
param(
|
|
325
|
+
[AllowNull()] [AllowEmptyString()] [string] $ParallelSlug,
|
|
326
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if ([string]::IsNullOrWhiteSpace($ParallelSlug)) {
|
|
330
|
+
return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $SessionRoot -Detail (
|
|
331
|
+
'the call carries no parallel_slug: value, so the parallel run it belongs to cannot be identified'))
|
|
332
|
+
}
|
|
333
|
+
$matched = [System.Collections.Generic.List[string]]::new()
|
|
334
|
+
foreach ($root in (Get-WorktreeRunLiveRoot -SessionRoot $SessionRoot)) {
|
|
335
|
+
$checkpoint = Get-WorktreeRunCheckpoint -Kind 'parallel' -WorktreeRoot $root
|
|
336
|
+
if ($null -eq $checkpoint) { continue }
|
|
337
|
+
if ([string] (Get-WorktreeRunPropertyValue -InputObject $checkpoint -Name 'parallel_slug') -ceq $ParallelSlug) { $matched.Add($root) }
|
|
338
|
+
}
|
|
339
|
+
return (Resolve-WorktreeRunMatch -Matched $matched.ToArray() -SessionRoot $SessionRoot -Description ("parallel_slug '{0}'" -f $ParallelSlug))
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Private: compare two worktree paths, insensitive to separator style and trailing
|
|
343
|
+
# slashes, and case-insensitive only for drive-letter paths.
|
|
344
|
+
function Test-WorktreeRunPathEqual {
|
|
345
|
+
[CmdletBinding()]
|
|
346
|
+
[OutputType([bool])]
|
|
347
|
+
param(
|
|
348
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Left,
|
|
349
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Right
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if ([string]::IsNullOrWhiteSpace($Left) -or [string]::IsNullOrWhiteSpace($Right)) { return $false }
|
|
353
|
+
$a = ($Left.Trim() -replace '\\', '/').TrimEnd('/')
|
|
354
|
+
$b = ($Right.Trim() -replace '\\', '/').TrimEnd('/')
|
|
355
|
+
$isDrivePath = ($a -match '^[A-Za-z]:') -or ($b -match '^[A-Za-z]:')
|
|
356
|
+
$comparison = if ($isDrivePath) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
|
|
357
|
+
return [string]::Equals($a, $b, $comparison)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
# Private: compare two pull request numbers as integers.
|
|
361
|
+
function Test-WorktreeRunPrNumberEqual {
|
|
362
|
+
[CmdletBinding()]
|
|
363
|
+
[OutputType([bool])]
|
|
364
|
+
param(
|
|
365
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [object] $Recorded,
|
|
366
|
+
[Parameter(Mandatory = $true)] [long] $Expected
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if ($null -eq $Recorded) { return $false }
|
|
370
|
+
$number = [long] 0
|
|
371
|
+
if (-not [long]::TryParse(([string] $Recorded).Trim(), [ref] $number)) { return $false }
|
|
372
|
+
return ($number -eq $Expected)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
# Private: whether a parsed run checkpoint records a pull request number or worktree path.
|
|
376
|
+
function Test-WorktreeRunCheckpointRecord {
|
|
377
|
+
[CmdletBinding()]
|
|
378
|
+
[OutputType([bool])]
|
|
379
|
+
param(
|
|
380
|
+
[Parameter(Mandatory = $true)] [pscustomobject] $Checkpoint,
|
|
381
|
+
[Parameter(Mandatory = $true)] [ValidateSet('epic', 'parallel')] [string] $Kind,
|
|
382
|
+
[Parameter(Mandatory = $true)] [ValidateSet('pr_number', 'worktree_path')] [string] $RecordField,
|
|
383
|
+
[Parameter(Mandatory = $true)] [string] $Value
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
$values = [System.Collections.Generic.List[object]]::new()
|
|
387
|
+
$arrayName = if ($Kind -eq 'epic') { 'features' } else { 'items' }
|
|
388
|
+
foreach ($record in @(Get-WorktreeRunPropertyValue -InputObject $Checkpoint -Name $arrayName)) {
|
|
389
|
+
$values.Add((Get-WorktreeRunPropertyValue -InputObject $record -Name $RecordField))
|
|
390
|
+
}
|
|
391
|
+
if ($Kind -eq 'epic' -and $RecordField -eq 'pr_number') {
|
|
392
|
+
$mergePr = Get-WorktreeRunPropertyValue -InputObject $Checkpoint -Name 'epic_merge_pr'
|
|
393
|
+
$values.Add((Get-WorktreeRunPropertyValue -InputObject $mergePr -Name 'pr_number'))
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
foreach ($recorded in $values) {
|
|
397
|
+
if ($RecordField -eq 'pr_number') {
|
|
398
|
+
if (Test-WorktreeRunPrNumberEqual -Recorded $recorded -Expected ([long] $Value)) { return $true }
|
|
399
|
+
}
|
|
400
|
+
elseif (Test-WorktreeRunPathEqual -Left ([string] $recorded) -Right $Value) { return $true }
|
|
401
|
+
}
|
|
402
|
+
return $false
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function Resolve-WorktreeRunTargetByRecord {
|
|
406
|
+
<#
|
|
407
|
+
.SYNOPSIS
|
|
408
|
+
Resolve the live worktree whose run checkpoint records a pull request number or worktree path.
|
|
409
|
+
.DESCRIPTION
|
|
410
|
+
Epic checkpoints are searched in epic_merge_pr.pr_number and features[].pr_number, or
|
|
411
|
+
features[].worktree_path; parallel checkpoints in items[].pr_number or
|
|
412
|
+
items[].worktree_path. Zero matches resolve NoTarget, one resolves, and several are
|
|
413
|
+
Ambiguous. A blank value, or a pr_number that is not all digits or does not fit a 64-bit integer, is NoTarget.
|
|
414
|
+
.PARAMETER Kind
|
|
415
|
+
epic or parallel.
|
|
416
|
+
.PARAMETER RecordField
|
|
417
|
+
pr_number or worktree_path.
|
|
418
|
+
.PARAMETER Value
|
|
419
|
+
The pull request number or worktree path the command names.
|
|
420
|
+
.PARAMETER SessionRoot
|
|
421
|
+
The calling process's path, used only to find the repository and label a result.
|
|
422
|
+
#>
|
|
423
|
+
[CmdletBinding()]
|
|
424
|
+
[OutputType([pscustomobject])]
|
|
425
|
+
param(
|
|
426
|
+
[Parameter(Mandatory = $true)] [ValidateSet('epic', 'parallel')] [string] $Kind,
|
|
427
|
+
[Parameter(Mandatory = $true)] [ValidateSet('pr_number', 'worktree_path')] [string] $RecordField,
|
|
428
|
+
[AllowNull()] [AllowEmptyString()] [string] $Value,
|
|
429
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
$description = "{0} '{1}' in the {2} checkpoint" -f $RecordField, $Value, $Kind
|
|
433
|
+
$isBlank = [string]::IsNullOrWhiteSpace($Value)
|
|
434
|
+
$parsedNumber = [long] 0
|
|
435
|
+
$isNumber = -not $isBlank -and $Value.Trim() -match '^\d+$' -and [long]::TryParse($Value.Trim(), [ref] $parsedNumber)
|
|
436
|
+
if ($isBlank -or ($RecordField -eq 'pr_number' -and -not $isNumber)) {
|
|
437
|
+
return (New-WorktreeResolutionTargetResult -Status 'NoTarget' -SessionRoot $SessionRoot -Detail (
|
|
438
|
+
'the command names no usable {0}, so the {1} run it belongs to cannot be identified' -f $RecordField, $Kind))
|
|
439
|
+
}
|
|
440
|
+
$needle = $Value.Trim()
|
|
441
|
+
$matched = [System.Collections.Generic.List[string]]::new()
|
|
442
|
+
foreach ($root in (Get-WorktreeRunLiveRoot -SessionRoot $SessionRoot)) {
|
|
443
|
+
$checkpoint = Get-WorktreeRunCheckpoint -Kind $Kind -WorktreeRoot $root
|
|
444
|
+
if ($null -eq $checkpoint) { continue }
|
|
445
|
+
if (Test-WorktreeRunCheckpointRecord -Checkpoint $checkpoint -Kind $Kind -RecordField $RecordField -Value $needle) { $matched.Add($root) }
|
|
446
|
+
}
|
|
447
|
+
return (Resolve-WorktreeRunMatch -Matched $matched.ToArray() -SessionRoot $SessionRoot -Description $description)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function Resolve-WorktreeOperandTarget {
|
|
451
|
+
<#
|
|
452
|
+
.SYNOPSIS
|
|
453
|
+
Place a file_path or git -C operand in the worktree that contains it.
|
|
454
|
+
.DESCRIPTION
|
|
455
|
+
Ascends from the operand to its worktree root. A blank operand, and an operand
|
|
456
|
+
inside no worktree, resolve to the session worktree; a relative operand is joined
|
|
457
|
+
to the session root first.
|
|
458
|
+
.PARAMETER Path
|
|
459
|
+
The operand path, absolute or relative, or blank.
|
|
460
|
+
.PARAMETER SessionRoot
|
|
461
|
+
The calling process's path.
|
|
462
|
+
#>
|
|
463
|
+
[CmdletBinding()]
|
|
464
|
+
[OutputType([pscustomobject])]
|
|
465
|
+
param(
|
|
466
|
+
[AllowNull()] [AllowEmptyString()] [string] $Path,
|
|
467
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
$sessionWorktree = Find-WorktreeResolutionRoot -Path $SessionRoot
|
|
471
|
+
if ($null -eq $sessionWorktree) { $sessionWorktree = ConvertTo-WorktreeResolutionNormalizedPath -Path $SessionRoot }
|
|
472
|
+
if ([string]::IsNullOrWhiteSpace($Path)) {
|
|
473
|
+
return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $sessionWorktree -SessionRoot $SessionRoot -Detail (
|
|
474
|
+
'the call names no operand, so it acts on the session worktree'))
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
$operand = ConvertTo-WorktreeResolutionNormalizedPath -Path $Path
|
|
478
|
+
if ($operand -notmatch $script:AbsolutePathPattern) {
|
|
479
|
+
$operand = Join-WorktreeResolutionPath -WorktreeRoot $SessionRoot -RepoRelativePath $operand
|
|
480
|
+
}
|
|
481
|
+
$found = Find-WorktreeResolutionRoot -Path $operand
|
|
482
|
+
if ($null -eq $found) {
|
|
483
|
+
return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $sessionWorktree -SessionRoot $SessionRoot -Detail (
|
|
484
|
+
"the operand '{0}' lies in no worktree, so the call is evaluated against the session worktree" -f $operand))
|
|
485
|
+
}
|
|
486
|
+
return (ConvertTo-WorktreeItemResolvedResult -WorktreeRoot $found -SessionRoot $SessionRoot -Detail (
|
|
487
|
+
"the operand '{0}' lies in the worktree '{1}'" -f $operand, $found))
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
Export-ModuleMember -Function `
|
|
491
|
+
Find-WorktreeRunIdentitySignal, `
|
|
492
|
+
Get-WorktreeRunCheckpointText, `
|
|
493
|
+
Get-WorktreeRunCheckpointPath, `
|
|
494
|
+
Resolve-WorktreeEpicTarget, `
|
|
495
|
+
Resolve-WorktreeParallelTarget, `
|
|
496
|
+
Resolve-WorktreeRunTargetByRecord, `
|
|
497
|
+
Resolve-WorktreeOperandTarget
|
|
@@ -611,6 +611,27 @@ in `tests/scripts/dev_tools/test_blast_radius_config_parity.py`, mirrored in
|
|
|
611
611
|
union of both copies' top-level keys is exhaustively covered by the three declared classes, and
|
|
612
612
|
an unclassified key or a key present in only one copy fails loudly and names itself.
|
|
613
613
|
|
|
614
|
+
**A destination records its own entries in `config/blast-radius.local.json` (issue #508).** The
|
|
615
|
+
push-down regenerates a destination's `config/blast-radius.json` on every push, so an entry
|
|
616
|
+
hand-added to that file does not survive the next push. The supported extension point is the
|
|
617
|
+
destination-owned overlay `config/blast-radius.local.json`. No payload contains it, and the
|
|
618
|
+
push-down never writes or publishes it: both implementations list it in `EXCLUDED_RELATIVE_PATHS`.
|
|
619
|
+
At push time the overlay is composed onto the freshly generated base document (the derived document
|
|
620
|
+
in TypeScript, the published document in Python), and the composed result is written to
|
|
621
|
+
`config/blast-radius.json`, so consumers continue to read one file. Per-key semantics: the
|
|
622
|
+
string-list keys `shared_surfaces`, `shared_surface_globs`, `mandate_reads`, `mergeable_paths`, and
|
|
623
|
+
`path_roots` take an ordered union (base entries first, then overlay-only entries, duplicates
|
|
624
|
+
removed); an overlay module absent from the base is added and an overlay module with the same name
|
|
625
|
+
replaces that module's glob list, with module names emitted in ordinal order; `conflict_tolerance`
|
|
626
|
+
merges recursively (nested lists by union, nested objects per member, nested scalars overlay-wins);
|
|
627
|
+
the scalars `over_breadth_fraction` and `write_intent_extraction` take the overlay value; overlay-only
|
|
628
|
+
keys are appended after the base keys. An overlay `version` must equal the base `version`, and the
|
|
629
|
+
forbidden-glob guard (`**`, `docs/**`, `tests/**`) applies to the composed module map, including
|
|
630
|
+
overlay-authored modules. A malformed overlay, a `version` mismatch, or a forbidden glob raises an
|
|
631
|
+
error before any write, so `config/blast-radius.json` keeps its prior bytes. With no overlay
|
|
632
|
+
present, the written file is byte-identical to the base document. The overlay cannot remove a
|
|
633
|
+
shipped surface or a derived module.
|
|
634
|
+
|
|
614
635
|
## Enforcement
|
|
615
636
|
|
|
616
637
|
- `scripts/dev_tools/validate_parallel_orchestrator_state.py`, with the helper modules `scripts/dev_tools/_parallel_state_common.py`, `scripts/dev_tools/_parallel_state_structures.py`, and `scripts/dev_tools/_parallel_state_records.py`, appends one error per violated orchestrator invariant. The completion-gate invariants 20 and 21 run only when the caller passes `require_complete=True`.
|
|
@@ -123,6 +123,8 @@ checkpoint write, and on CI-green (S9 step 6) merges its own PR into the integra
|
|
|
123
123
|
recording `epic_merge: { merge_commit_sha, target_branch, merged_at }`. Standalone (non-epic)
|
|
124
124
|
orchestration is unchanged: `epic_mode` absent or `false` makes S9 step 6 a no-op.
|
|
125
125
|
|
|
126
|
+
The run gates `enforce-orchestration-preimplementation-gate.ps1` and `enforce-epic-wave-barrier.ps1` locate the epic checkpoint by the `integration_branch:` value of this line. They select the live worktree whose `artifacts/orchestration/epic-orchestrator-state.json` records `route_id` `epic` and that integration branch, prefer the worktree that has the branch checked out when more than one records it, and deny the delegation with `TARGET_WORKTREE_NOT_DERIVABLE` or `TARGET_WORKTREE_AMBIGUOUS` when none or more than one remains. A stale copy of the epic checkpoint is moved to `artifacts/orchestration/handoff/` rather than left at a worktree root. The child run's own delegations to implementation agents carry the canonical issue-number line and `branch:` label defined in `.claude/skills/orchestrate/SKILL.md` `## Issue Number Consistency`.
|
|
127
|
+
|
|
126
128
|
## Bounded Child Return Contract
|
|
127
129
|
|
|
128
130
|
A child `orchestrator`'s final report is consumed as a fixed eight-field shape and nothing else:
|
|
@@ -42,6 +42,10 @@ The worker must return the following reporting block:
|
|
|
42
42
|
4. Diffs (patch-style or full-file replacements).
|
|
43
43
|
5. QA Gate Results (CSharpier, .NET Analyzers, Nullable Analysis, xUnit, and coverage deltas, or clearly marked **unverified**).
|
|
44
44
|
|
|
45
|
+
## Delegation Identity Lines
|
|
46
|
+
|
|
47
|
+
Every delegation prompt this skill sends to `csharp-typed-engineer` carries the canonical issue-number line (`Canonical issue number for this feature is <issue_num>. All artifact content, file paths, and cross-references must use this number.`) and a `branch: <name>` label naming the branch checked out in the item's worktree, written as the first `branch:` occurrence in the prompt. `enforce-orchestration-preimplementation-gate.ps1` resolves the item's worktree from these two lines and denies a delegation that carries neither with `TARGET_WORKTREE_NOT_DERIVABLE`.
|
|
48
|
+
|
|
45
49
|
## Worker Routing
|
|
46
50
|
|
|
47
51
|
- Worker: `csharp-typed-engineer`
|
|
@@ -41,6 +41,10 @@ The worker must return the `powershell-qa-gate` reporting block:
|
|
|
41
41
|
4. Diffs (patch-style or full-file replacements).
|
|
42
42
|
5. QA Gate Results (PSScriptAnalyzer, Pester, and coverage deltas, or clearly marked **unverified**).
|
|
43
43
|
|
|
44
|
+
## Delegation Identity Lines
|
|
45
|
+
|
|
46
|
+
Every delegation prompt this skill sends to `powershell-typed-engineer` carries the canonical issue-number line (`Canonical issue number for this feature is <issue_num>. All artifact content, file paths, and cross-references must use this number.`) and a `branch: <name>` label naming the branch checked out in the item's worktree, written as the first `branch:` occurrence in the prompt. `enforce-orchestration-preimplementation-gate.ps1` resolves the item's worktree from these two lines and denies a delegation that carries neither with `TARGET_WORKTREE_NOT_DERIVABLE`.
|
|
47
|
+
|
|
44
48
|
## Worker Routing
|
|
45
49
|
|
|
46
50
|
- Worker: `powershell-typed-engineer`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: invoke-python-engineer
|
|
3
|
-
description: Invoke the python-typed-engineer worker to design, implement, and verify Python changes within typed repository boundaries. Applies Black -> Ruff -> Pyright -> Pytest toolchain, the 3
|
|
3
|
+
description: Invoke the python-typed-engineer worker to design, implement, and verify Python changes within typed repository boundaries. Applies Black -> Ruff -> Pyright -> Pytest toolchain, the 1-3 production-file direct-mode budget with routing to the orchestrated large path above it, and zero-regression quality gates.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Implement Python Skill
|
|
@@ -15,7 +15,7 @@ Use this skill when:
|
|
|
15
15
|
- Estimated scope fits the small path (1-3 production files plus corresponding tests).
|
|
16
16
|
- The toolchain (Black, Ruff, Pyright, Pytest) can be run in the current environment, or the user has explicitly authorized an unverified plan-only response.
|
|
17
17
|
|
|
18
|
-
If the estimated scope
|
|
18
|
+
If the estimated scope is more than 3 production files, this skill defers to the orchestrated large path (`/orchestrate`) via `python-change-budget-router` instead of proceeding directly. The large path has no production-file cap, and test files are not counted toward the routing threshold.
|
|
19
19
|
|
|
20
20
|
## Inputs
|
|
21
21
|
|
|
@@ -23,7 +23,6 @@ If the estimated scope exceeds the small-path budget, this skill defers to the o
|
|
|
23
23
|
- Files or entrypoints in scope.
|
|
24
24
|
- Constraints, including public APIs that must be preserved.
|
|
25
25
|
- Optional approved plan. If none is supplied, the worker delegates plan authoring to `atomic_planner` before any edits.
|
|
26
|
-
- Optional budget override in the form `budget: prod=<N>, test=<M>` subject to repo policy compliance.
|
|
27
26
|
|
|
28
27
|
## Output Paths
|
|
29
28
|
|
|
@@ -42,6 +41,10 @@ The worker must return the `python-qa-gate` reporting block:
|
|
|
42
41
|
4. Diffs (patch-style or full-file replacements).
|
|
43
42
|
5. QA Gate Results (Ruff, Pyright, Pytest, and coverage deltas, or clearly marked **unverified**).
|
|
44
43
|
|
|
44
|
+
## Delegation Identity Lines
|
|
45
|
+
|
|
46
|
+
Every delegation prompt this skill sends to `python-typed-engineer` carries the canonical issue-number line (`Canonical issue number for this feature is <issue_num>. All artifact content, file paths, and cross-references must use this number.`) and a `branch: <name>` label naming the branch checked out in the item's worktree, written as the first `branch:` occurrence in the prompt. `enforce-orchestration-preimplementation-gate.ps1` resolves the item's worktree from these two lines and denies a delegation that carries neither with `TARGET_WORKTREE_NOT_DERIVABLE`.
|
|
47
|
+
|
|
45
48
|
## Worker Routing
|
|
46
49
|
|
|
47
50
|
- Worker: `python-typed-engineer`
|
|
@@ -260,7 +260,9 @@ Every delegation prompt to a receipt-gated subagent type — `atomic-planner`, `
|
|
|
260
260
|
|
|
261
261
|
> `Canonical issue number for this feature is <issue_num>. All artifact content, file paths, and cross-references must use this number.`
|
|
262
262
|
|
|
263
|
-
Each such prompt also carries a `branch: <name>` label naming the branch checked out in the item's worktree, written as the first `branch:` occurrence in the prompt. Before promotion assigns an issue number, the branch label alone is required.
|
|
263
|
+
Each such prompt also carries a `branch: <name>` label naming the branch checked out in the item's worktree, written as the first `branch:` occurrence in the prompt. Before promotion assigns an issue number, the branch label alone is required. Three gates, `enforce-model-routing-receipt.ps1`, `enforce-prd-feature-before-planner.ps1`, and `enforce-orchestration-preimplementation-gate.ps1`, identify the item from these two lines: they deny a gated delegation they cannot identify rather than checking it against whichever checkpoint occupies the calling session's root, and they never use a feature-folder path to choose the worktree, because a merged feature folder exists in every checkout branched from main.
|
|
264
|
+
|
|
265
|
+
The same two lines are required on every delegation to an implementation agent (`python-typed-engineer`, `powershell-typed-engineer`, `typescript-engineer`, `csharp-typed-engineer`, and `atomic-executor`) and on every `Agent(orchestrator)` delegation that carries no epic-mode, parallel-mode, or preparation-mode kickoff marker. `enforce-orchestration-preimplementation-gate.ps1` resolves such a delegation's worktree from these lines and denies it with `TARGET_WORKTREE_NOT_DERIVABLE` when neither line is present, or with `TARGET_WORKTREE_AMBIGUOUS` when the lines disagree or match more than one live worktree.
|
|
264
266
|
|
|
265
267
|
If a subagent artifact references a different issue number, the orchestrator rejects it, requests correction, and records the discrepancy under `artifact_errors` in the checkpoint.
|
|
266
268
|
|