@danmoisan/drm-copilot-mcp 1.1.22 → 1.1.23
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 +2568 -1261
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/MEMORY.md +1 -1
- package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_test_files_count_against_500_cap.md +4 -4
- package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/feature-review.md +8 -0
- package/resources/claude-customizations/.claude/agents/orchestrator.md +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +46 -6
- package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/agents/pr-author.md +32 -7
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-green-ci.ps1 +325 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +22 -6
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +23 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +29 -21
- package/resources/claude-customizations/.claude/hooks/enforce-file-size-limit.ps1 +244 -0
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +48 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +22 -11
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +15 -10
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.current-branch.ps1 +149 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-feature-folder.ps1 +146 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +25 -3
- package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +33 -6
- package/resources/claude-customizations/.claude/lib/file-size-policy/FileSizePolicy.psm1 +434 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateFeatureFolderRecord.psm1 +166 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +8 -3
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunRecordPath.psm1 +185 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +11 -21
- package/resources/claude-customizations/.claude/rules/general-code-change.md +4 -1
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +28 -5
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +2 -2
- package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
- package/resources/claude-customizations/.claude/rules/shell.md +1 -1
- package/resources/claude-customizations/.claude/settings.json +4 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +2 -2
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +11 -4
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +4 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +90 -14
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +39 -9
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +1 -1
- package/resources/claude-customizations/pack-manifests/core.json +7 -0
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +4 -1
- package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +8 -0
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/config.toml +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-file-size-limit.ps1 +282 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/file-size-policy.ps1 +421 -0
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +1 -1
- package/resources/codex-and-agents-customizations/AGENTS.md +4 -1
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
- package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
- package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +1 -1
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +1 -1
- package/resources/customizations/.github/instructions/general-code-change.instructions.md +5 -2
- package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +1 -1
- package/resources/customizations/.github/prompts/remediate-comments.prompt.md +1 -1
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +1 -1
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +8 -0
- package/resources/templates/policy_audit/code-review.yyyy-MM-ddTHH-mm.md +4 -4
- package/resources/templates/policy_audit/policy-audit.yyyy-MM-ddTHH-mm.md +4 -4
package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunRecordPath.psm1
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Shared comparison of a recorded worktree_path with a removal target (issue #995).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
A run checkpoint records items[].worktree_path or features[].worktree_path either as
|
|
7
|
+
an absolute path or relative to the main checkout. Every site that compares such a
|
|
8
|
+
record to the absolute path a command names calls Test-WorktreeRunRecordedPathMatchesTarget,
|
|
9
|
+
so a relative record is joined to the same base roots everywhere and no site carries
|
|
10
|
+
its own normalise-and-compare.
|
|
11
|
+
|
|
12
|
+
A rooted record is compared to the target with Test-WorktreeRunPathEqual. A relative
|
|
13
|
+
record is joined to each non-blank base root and compared the same way. A relative
|
|
14
|
+
record that is blank, is . or .., has a . or .. segment, or contains : matches
|
|
15
|
+
nothing, so it can never name the main checkout or a path outside it.
|
|
16
|
+
|
|
17
|
+
.NOTES
|
|
18
|
+
PowerShell 7+. Exports Test-WorktreeRunPathEqual, Test-WorktreeRunRecordedPathMatchesTarget,
|
|
19
|
+
Get-WorktreeRunRecordBaseRoot, and Get-WorktreeRunMainCheckoutRoot. The two comparison
|
|
20
|
+
functions and Get-WorktreeRunRecordBaseRoot are pure. Get-WorktreeRunMainCheckoutRoot
|
|
21
|
+
reads the filesystem only through its siblings' seams (Get-WorktreeItemLiveRoot and
|
|
22
|
+
Get-WorktreeResolutionGitEntryKind). Mirrored byte-identically.
|
|
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
|
+
Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
|
|
30
|
+
Import-Module (Join-Path $PSScriptRoot 'WorktreeItemResolution.psm1') -ErrorAction Stop
|
|
31
|
+
|
|
32
|
+
# A drive-rooted, slash-rooted, or // (UNC) path; anything else is relative to some directory.
|
|
33
|
+
# The same pattern as $script:AbsolutePathPattern in WorktreeRunResolution.psm1.
|
|
34
|
+
$script:AbsolutePathPattern = '^([A-Za-z]:(/|$)|/)'
|
|
35
|
+
|
|
36
|
+
# The administrative entry that is a directory only in the main checkout; a linked
|
|
37
|
+
# worktree carries a .git file instead.
|
|
38
|
+
$script:GitEntryName = '.git'
|
|
39
|
+
|
|
40
|
+
# Private (moved from WorktreeRunResolution.psm1 for issue #995): compare two worktree
|
|
41
|
+
# paths, insensitive to separator style and trailing slashes, and case-insensitive for
|
|
42
|
+
# drive-letter and UNC (//) paths.
|
|
43
|
+
function Test-WorktreeRunPathEqual {
|
|
44
|
+
<#
|
|
45
|
+
.SYNOPSIS
|
|
46
|
+
Compare two worktree paths for equality.
|
|
47
|
+
.DESCRIPTION
|
|
48
|
+
Pure. Insensitive to separator style and trailing slashes; ordinal for single-slash
|
|
49
|
+
POSIX roots and ordinal-ignore-case for drive-letter and UNC (//) roots. A blank
|
|
50
|
+
side never matches.
|
|
51
|
+
.PARAMETER Left
|
|
52
|
+
One path.
|
|
53
|
+
.PARAMETER Right
|
|
54
|
+
The other path.
|
|
55
|
+
#>
|
|
56
|
+
[CmdletBinding()]
|
|
57
|
+
[OutputType([bool])]
|
|
58
|
+
param(
|
|
59
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Left,
|
|
60
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Right
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if ([string]::IsNullOrWhiteSpace($Left) -or [string]::IsNullOrWhiteSpace($Right)) { return $false }
|
|
64
|
+
$a = ($Left.Trim() -replace '\\', '/').TrimEnd('/')
|
|
65
|
+
$b = ($Right.Trim() -replace '\\', '/').TrimEnd('/')
|
|
66
|
+
$isCaseInsensitivePath = ($a -match '^([A-Za-z]:|//)') -or ($b -match '^([A-Za-z]:|//)')
|
|
67
|
+
$comparison = if ($isCaseInsensitivePath) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
|
|
68
|
+
return [string]::Equals($a, $b, $comparison)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Private: whether a relative recorded path (already slash-normalised) is safe to join.
|
|
72
|
+
function Test-WorktreeRunRelativeRecordSafe {
|
|
73
|
+
[CmdletBinding()]
|
|
74
|
+
[OutputType([bool])]
|
|
75
|
+
param(
|
|
76
|
+
[Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $Path
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if ([string]::IsNullOrWhiteSpace($Path) -or $Path.Contains(':')) { return $false }
|
|
80
|
+
foreach ($segment in $Path.TrimEnd('/').Split('/')) {
|
|
81
|
+
if ($segment -eq '.' -or $segment -eq '..') { return $false }
|
|
82
|
+
}
|
|
83
|
+
return ($Path.TrimEnd('/').Length -gt 0)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Test-WorktreeRunRecordedPathMatchesTarget {
|
|
87
|
+
<#
|
|
88
|
+
.SYNOPSIS
|
|
89
|
+
Report whether a recorded worktree_path names the removal target.
|
|
90
|
+
.DESCRIPTION
|
|
91
|
+
Pure. A rooted record (drive-letter, single-slash, or // after backslash-to-slash
|
|
92
|
+
normalisation) matches when Test-WorktreeRunPathEqual holds against the target. A
|
|
93
|
+
relative record matches when it is safe and Test-WorktreeRunPathEqual holds for
|
|
94
|
+
its join to at least one non-blank base root. A relative record that is blank, is
|
|
95
|
+
. or .., has a . or .. segment, or contains : matches nothing, as does a blank target.
|
|
96
|
+
.PARAMETER RecordedPath
|
|
97
|
+
The worktree_path a checkpoint records, absolute or relative.
|
|
98
|
+
.PARAMETER TargetPath
|
|
99
|
+
The absolute path the command names.
|
|
100
|
+
.PARAMETER BaseRoot
|
|
101
|
+
Optional. The roots a relative record is joined to: the main checkout root and the
|
|
102
|
+
root that holds the checkpoint. With none, a relative record matches nothing.
|
|
103
|
+
#>
|
|
104
|
+
[CmdletBinding()]
|
|
105
|
+
[OutputType([bool])]
|
|
106
|
+
param(
|
|
107
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $RecordedPath,
|
|
108
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $TargetPath,
|
|
109
|
+
[AllowNull()] [AllowEmptyCollection()] [string[]] $BaseRoot = @()
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if ([string]::IsNullOrWhiteSpace($RecordedPath) -or [string]::IsNullOrWhiteSpace($TargetPath)) { return $false }
|
|
113
|
+
$recorded = $RecordedPath.Trim() -replace '\\', '/'
|
|
114
|
+
if ($recorded -match $script:AbsolutePathPattern) {
|
|
115
|
+
return (Test-WorktreeRunPathEqual -Left $recorded -Right $TargetPath)
|
|
116
|
+
}
|
|
117
|
+
if (-not (Test-WorktreeRunRelativeRecordSafe -Path $recorded)) { return $false }
|
|
118
|
+
foreach ($root in @($BaseRoot)) {
|
|
119
|
+
if ([string]::IsNullOrWhiteSpace($root)) { continue }
|
|
120
|
+
$joined = (($root.Trim() -replace '\\', '/').TrimEnd('/')) + '/' + $recorded.TrimEnd('/')
|
|
121
|
+
if (Test-WorktreeRunPathEqual -Left $joined -Right $TargetPath) { return $true }
|
|
122
|
+
}
|
|
123
|
+
return $false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function Get-WorktreeRunRecordBaseRoot {
|
|
127
|
+
<#
|
|
128
|
+
.SYNOPSIS
|
|
129
|
+
Return the base roots a relative recorded worktree_path is joined to.
|
|
130
|
+
.DESCRIPTION
|
|
131
|
+
Pure. The main checkout root and the root that holds the checkpoint a read
|
|
132
|
+
resolved (Read.Target.WorktreeRoot), with blank entries dropped. Every gate
|
|
133
|
+
passes the result to Test-WorktreeRunRecordedPathMatchesTarget as -BaseRoot.
|
|
134
|
+
.PARAMETER MainRoot
|
|
135
|
+
The main checkout root, or $null when none was derived.
|
|
136
|
+
.PARAMETER Read
|
|
137
|
+
A run-checkpoint read result carrying Target.WorktreeRoot, or $null.
|
|
138
|
+
.OUTPUTS
|
|
139
|
+
System.String[]
|
|
140
|
+
#>
|
|
141
|
+
[CmdletBinding()]
|
|
142
|
+
[OutputType([string[]], [object[]])]
|
|
143
|
+
param(
|
|
144
|
+
[AllowNull()][AllowEmptyString()][string] $MainRoot,
|
|
145
|
+
[AllowNull()] $Read
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
$readRoot = $null
|
|
149
|
+
if ($null -ne $Read -and @($Read.PSObject.Properties.Name) -contains 'Target' -and $null -ne $Read.Target -and @($Read.Target.PSObject.Properties.Name) -contains 'WorktreeRoot') {
|
|
150
|
+
$readRoot = [string] $Read.Target.WorktreeRoot
|
|
151
|
+
}
|
|
152
|
+
return , [string[]] @(@($MainRoot, $readRoot) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Get-WorktreeRunMainCheckoutRoot {
|
|
156
|
+
<#
|
|
157
|
+
.SYNOPSIS
|
|
158
|
+
Return the main checkout root reachable from a session path, or $null.
|
|
159
|
+
.DESCRIPTION
|
|
160
|
+
The first live root whose .git entry is a directory; a linked worktree's .git entry
|
|
161
|
+
is a file. Reads the filesystem only through Get-WorktreeItemLiveRoot and
|
|
162
|
+
Get-WorktreeResolutionGitEntryKind, the seams a test mocks.
|
|
163
|
+
.PARAMETER SessionRoot
|
|
164
|
+
The calling process's path, used only to find the repository.
|
|
165
|
+
#>
|
|
166
|
+
[CmdletBinding()]
|
|
167
|
+
[OutputType([string])]
|
|
168
|
+
param(
|
|
169
|
+
[Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $SessionRoot
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
$liveRoots = Get-WorktreeItemLiveRoot -SessionRoot $SessionRoot
|
|
173
|
+
foreach ($root in $liveRoots) {
|
|
174
|
+
if ([string]::IsNullOrWhiteSpace($root)) { continue }
|
|
175
|
+
$gitEntry = (($root -replace '\\', '/').TrimEnd('/')) + '/' + $script:GitEntryName
|
|
176
|
+
if ((Get-WorktreeResolutionGitEntryKind -Path $gitEntry) -eq 'Directory') { return $root }
|
|
177
|
+
}
|
|
178
|
+
return $null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
Export-ModuleMember -Function `
|
|
182
|
+
Test-WorktreeRunPathEqual, `
|
|
183
|
+
Test-WorktreeRunRecordedPathMatchesTarget, `
|
|
184
|
+
Get-WorktreeRunRecordBaseRoot, `
|
|
185
|
+
Get-WorktreeRunMainCheckoutRoot
|
package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1
CHANGED
|
@@ -31,6 +31,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
31
31
|
Import-Module (Join-Path $PSScriptRoot 'WorktreeResolution.psm1') -ErrorAction Stop
|
|
32
32
|
Import-Module (Join-Path $PSScriptRoot 'WorktreeTargetResolution.psm1') -ErrorAction Stop
|
|
33
33
|
Import-Module (Join-Path $PSScriptRoot 'WorktreeItemResolution.psm1') -ErrorAction Stop
|
|
34
|
+
Import-Module (Join-Path $PSScriptRoot 'WorktreeRunRecordPath.psm1') -ErrorAction Stop
|
|
34
35
|
|
|
35
36
|
# The run checkpoint locations relative to a worktree root.
|
|
36
37
|
$script:EpicCheckpointRelativePath = 'artifacts/orchestration/epic-orchestrator-state.json'
|
|
@@ -339,24 +340,6 @@ function Resolve-WorktreeParallelTarget {
|
|
|
339
340
|
return (Resolve-WorktreeRunMatch -Matched $matched.ToArray() -SessionRoot $SessionRoot -Description ("parallel_slug '{0}'" -f $ParallelSlug))
|
|
340
341
|
}
|
|
341
342
|
|
|
342
|
-
# Private: compare two worktree paths, insensitive to separator style and trailing
|
|
343
|
-
# slashes, and case-insensitive for drive-letter and UNC (//) 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
|
-
$isCaseInsensitivePath = ($a -match '^([A-Za-z]:|//)') -or ($b -match '^([A-Za-z]:|//)')
|
|
356
|
-
$comparison = if ($isCaseInsensitivePath) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
|
|
357
|
-
return [string]::Equals($a, $b, $comparison)
|
|
358
|
-
}
|
|
359
|
-
|
|
360
343
|
# Private: compare two pull request numbers as integers.
|
|
361
344
|
function Test-WorktreeRunPrNumberEqual {
|
|
362
345
|
[CmdletBinding()]
|
|
@@ -373,6 +356,8 @@ function Test-WorktreeRunPrNumberEqual {
|
|
|
373
356
|
}
|
|
374
357
|
|
|
375
358
|
# Private: whether a parsed run checkpoint records a pull request number or worktree path.
|
|
359
|
+
# A worktree_path record is compared through the shared matcher, so a relative record is
|
|
360
|
+
# joined to each BaseRoot (issue #995); with no BaseRoot a relative record matches nothing.
|
|
376
361
|
function Test-WorktreeRunCheckpointRecord {
|
|
377
362
|
[CmdletBinding()]
|
|
378
363
|
[OutputType([bool])]
|
|
@@ -380,7 +365,8 @@ function Test-WorktreeRunCheckpointRecord {
|
|
|
380
365
|
[Parameter(Mandatory = $true)] [pscustomobject] $Checkpoint,
|
|
381
366
|
[Parameter(Mandatory = $true)] [ValidateSet('epic', 'parallel')] [string] $Kind,
|
|
382
367
|
[Parameter(Mandatory = $true)] [ValidateSet('pr_number', 'worktree_path')] [string] $RecordField,
|
|
383
|
-
[Parameter(Mandatory = $true)] [string] $Value
|
|
368
|
+
[Parameter(Mandatory = $true)] [string] $Value,
|
|
369
|
+
[AllowNull()] [AllowEmptyCollection()] [string[]] $BaseRoot = @()
|
|
384
370
|
)
|
|
385
371
|
|
|
386
372
|
$values = [System.Collections.Generic.List[object]]::new()
|
|
@@ -397,7 +383,7 @@ function Test-WorktreeRunCheckpointRecord {
|
|
|
397
383
|
if ($RecordField -eq 'pr_number') {
|
|
398
384
|
if (Test-WorktreeRunPrNumberEqual -Recorded $recorded -Expected ([long] $Value)) { return $true }
|
|
399
385
|
}
|
|
400
|
-
elseif (Test-
|
|
386
|
+
elseif (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string] $recorded) -TargetPath $Value -BaseRoot $BaseRoot) { return $true }
|
|
401
387
|
}
|
|
402
388
|
return $false
|
|
403
389
|
}
|
|
@@ -438,11 +424,15 @@ function Resolve-WorktreeRunTargetByRecord {
|
|
|
438
424
|
'the command names no usable {0}, so the {1} run it belongs to cannot be identified' -f $RecordField, $Kind))
|
|
439
425
|
}
|
|
440
426
|
$needle = $Value.Trim()
|
|
427
|
+
# A relative worktree_path record is joined to the main checkout root and to the root
|
|
428
|
+
# holding the checkpoint (issue #995); the main root is derived once, and only for paths.
|
|
429
|
+
$mainRoot = if ($RecordField -eq 'worktree_path') { Get-WorktreeRunMainCheckoutRoot -SessionRoot $SessionRoot } else { $null }
|
|
441
430
|
$matched = [System.Collections.Generic.List[string]]::new()
|
|
442
431
|
foreach ($root in (Get-WorktreeRunLiveRoot -SessionRoot $SessionRoot)) {
|
|
443
432
|
$checkpoint = Get-WorktreeRunCheckpoint -Kind $Kind -WorktreeRoot $root
|
|
444
433
|
if ($null -eq $checkpoint) { continue }
|
|
445
|
-
|
|
434
|
+
$baseRoot = [string[]] @(@($mainRoot, $root) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
435
|
+
if (Test-WorktreeRunCheckpointRecord -Checkpoint $checkpoint -Kind $Kind -RecordField $RecordField -Value $needle -BaseRoot $baseRoot) { $matched.Add($root) }
|
|
446
436
|
}
|
|
447
437
|
return (Resolve-WorktreeRunMatch -Matched $matched.ToArray() -SessionRoot $SessionRoot -Description $description)
|
|
448
438
|
}
|
|
@@ -46,7 +46,10 @@ Mutation testing and golden tests run in pre-merge or nightly pipelines, not the
|
|
|
46
46
|
|
|
47
47
|
## File Size Limit
|
|
48
48
|
|
|
49
|
-
-
|
|
49
|
+
- File size follows three tiers for production code, test code, and reusable script files, counted in logical lines (a trailing line terminator does not add a line):
|
|
50
|
+
- **500 lines (guidance):** look for a logical way to divide the file into multiple files, provided the parts can be separated logically. No gate fails at this tier.
|
|
51
|
+
- **700 lines (advisory):** look for a split more aggressively. Reviewers and `feature-review` report a non-blocking Warning for a file at or above 700 lines. No gate fails at this tier.
|
|
52
|
+
- **Above 800 lines (hard block):** split the file regardless of logical separability. The repository CI check and the PreToolUse hook block a file of 801 lines or more; a file of exactly 800 lines passes. Legacy files above 800 lines are listed in `config/file-size-baseline.json` and may shrink (with the entry tightened) or stay the same size, but may not grow.
|
|
50
53
|
- Exceptions: temporary throwaway scripts created and deleted within an agent session; raw text fixtures for language-processing test data; Markdown documentation files.
|
|
51
54
|
|
|
52
55
|
## Error Handling and Logging
|
|
@@ -255,15 +255,30 @@ not a cryptographic or security control.
|
|
|
255
255
|
|
|
256
256
|
Field checks 3 through 9 run in the fixed order `pr_url`, `issue_num`, `branch_name`, `authorized_by`, `authorized_at`, `basis`, `run_slug`, `session_id`; the first failure wins and the gate denies with `STANDALONE_MERGE_AUTHORIZATION_MALFORMED` naming that field.
|
|
257
257
|
|
|
258
|
+
## Green-CI standalone merge (issue #997)
|
|
259
|
+
|
|
260
|
+
A standalone run (neither an epic child nor a parallel item) merges its own pull request at S9 step 7 of `.claude/skills/orchestrate/SKILL.md` without an authorization record. The merge gate evaluates this green-CI condition after its parallel condition and before the `standalone_merge_authorizations` condition, and allows the merge only when all of the following hold:
|
|
261
|
+
|
|
262
|
+
1. The command is a single structural `gh pr merge` naming an explicit pull request number, with no chain, wrapper, inline environment prefix, `-R` or `--repo`, `/pull/` URL selector, `--auto`, or `--admin`.
|
|
263
|
+
2. Exactly one live worktree's per-feature checkpoint records that number, and no epic or parallel checkpoint records the number or that worktree's path.
|
|
264
|
+
3. The per-feature checkpoint has `epic_mode` absent, `null`, or JSON `false`; carries no `parallel_mode: true` and no non-blank `parallel_slug`; records `step9_status` exactly `passed` and `ci_gate.conclusion` exactly `success`; records `pr_gate.pr_number` as a JSON integer equal to the command number (an authorization record does not substitute); records `ci_gate.head_sha` as a lowercase 40 or 64 character object id; and, when `pr_gate.head_sha` is present and not `null`, records it equal to `ci_gate.head_sha`.
|
|
265
|
+
4. The live pull-request head from one bounded `gh pr view` call equals `ci_gate.head_sha`, and a `--match-head-commit` value, when present, equals the live head.
|
|
266
|
+
|
|
267
|
+
`parallel_mode`, `parallel_slug`, and `pr_merge` are tolerated, unvalidated top-level checkpoint keys: no validator reads them. A parallel child records `parallel_mode: true` and `parallel_slug` at kickoff, and a standalone run records `pr_merge: { merge_commit_sha, target_branch, merged_at }` only after its merge is confirmed.
|
|
268
|
+
|
|
269
|
+
Honest disclosure: the parallel exclusion is best-effort. The `Parallel mode: true` marker lives in the delegation prompt and is not present in a Bash `PreToolUse` payload, so a parallel child whose number and worktree path the parallel checkpoint has not yet recorded, and which omits the `parallel_mode` key, is not excluded by the hook. Like the authorization record above, the green-CI condition is a policy-level control, not a security control. A push after the live-head lookup can also change the head unless the command passes `--match-head-commit`, which the hook does not require.
|
|
270
|
+
|
|
258
271
|
## Issue-Adoption Scope and Backward Compatibility
|
|
259
272
|
|
|
260
273
|
These invariants apply only when a checkpoint contains a top-level `issue_adoption` key. The key records that the orchestration adopted a GitHub issue that already existed before orchestration started (transferred, filed by hand, or created by epic decomposition), so the issue was never created through `potential_to_issue`. The key is presence-gated: a checkpoint without it validates byte-identically to before and produces no new errors. It is evaluated only by the routing-contract completion check (`validate_routing_contract` and its TypeScript and PowerShell ports). It is not a member of `REQUIRED_STATE_KEYS`, it is not read by plain (non-completion) validation, and it is not read by the PR-creation-readiness gate. No JSON Schema file is authored, imported, or read for it.
|
|
261
274
|
|
|
262
275
|
Honest disclosure: the validators perform no network I/O. They cannot confirm that the issue exists, so `evidence` and `verified_via` are an auditable declaration of the read-only verification the orchestrator performed, not proof of it. This matches the `standalone_merge_authorizations` precedent above: the record is a policy-level declaration, not a security control.
|
|
263
276
|
|
|
277
|
+
The `feature_folder_record` is likewise declaration-only in every validator: the Python, TypeScript, and PowerShell validators perform no file-system or git access, so the validator checks, the MCP `validate_orchestration_artifacts` path, the Python CLI, and Codex gates cannot confirm that the folder exists or is committed. The folder is probed only under the Claude `SubagentStop` hook `.claude/hooks/validate-orchestrator-output.ps1`, through `.claude/hooks/validate-orchestrator-output-feature-folder.ps1`: when an `orchestrator-state` checkpoint waives `new_active_feature_folder` and passes the routing contract, the hook re-checks the checkpoint `feature-folder` against the path pattern and runs `git -C <worktree root> cat-file -t HEAD:<feature-folder>`. It allows only exit code 0 with the output `tree`; any other outcome blocks with `ROUTING_CONTRACT_BLOCKED: Checkpoint feature-folder is not committed in HEAD: <feature-folder>.`, which the subagent can repair. The probe proves only that `HEAD` of the run worktree holds a tree at that path at stop time; it does not prove which tool created the folder or that its contents are correct.
|
|
278
|
+
|
|
264
279
|
## Invariants (issue_adoption object)
|
|
265
280
|
|
|
266
|
-
The object has the shape `{ issue_num, issue_url, origin, verified_via, verified_at, evidence, waived_tools, potential_record? }`.
|
|
281
|
+
The object has the shape `{ issue_num, issue_url, origin, verified_via, verified_at, evidence, waived_tools, potential_record?, feature_folder_record? }`.
|
|
267
282
|
|
|
268
283
|
| Field | Rule |
|
|
269
284
|
| --- | --- |
|
|
@@ -275,6 +290,7 @@ The object has the shape `{ issue_num, issue_url, origin, verified_via, verified
|
|
|
275
290
|
| `evidence` | A string with non-whitespace content. |
|
|
276
291
|
| `waived_tools` | A non-empty list of non-blank tool names that includes `potential_to_issue`. |
|
|
277
292
|
| `potential_record` | When waiving a promotion-entry tool: required when `origin` is `epic_decomposition`, optional when `origin` is `transferred` or `filed_before_orchestration`, and validated whenever it is present (a path under `docs/features/potential/` ending in `.md`). |
|
|
293
|
+
| `feature_folder_record` | Required when `waived_tools` includes `new_active_feature_folder`, and validated whenever it is an object: `{ path, verified_via, verified_at, evidence }`, where `path` is a folder under `docs/features/active/` equal to the checkpoint `feature-folder`, `verified_via` is `git_cat_file_head` or `git_ls_tree_head`, `verified_at` is present, and `evidence` is a non-empty string. |
|
|
278
294
|
|
|
279
295
|
The rules run in this order, and their errors accumulate:
|
|
280
296
|
|
|
@@ -287,12 +303,19 @@ The rules run in this order, and their errors accumulate:
|
|
|
287
303
|
7. `evidence` must be a non-empty string.
|
|
288
304
|
8. `waived_tools` must be a non-empty list of non-blank strings; otherwise no further rule-8 or rule-9 check runs. Each entry, in list order, receives the first applicable error of: listed more than once; not in the closed waivable set; not required by the selected route after promotion-type resolution; already holding a successful MCP receipt. The list must include `potential_to_issue`.
|
|
289
305
|
9. Only when the rule-8 shape check passed: each distinct waived promotion-entry tool (`new_potential_entry` or `new_potential_bug_entry`) requires a valid `potential_record`, except that rule 9 reports nothing when `origin` is `transferred` or `filed_before_orchestration` and the `potential_record` key is absent. A present `potential_record`, including `null`, is always validated.
|
|
306
|
+
10. Only when the rule-8 shape check passed: when `waived_tools` includes `new_active_feature_folder` and `feature_folder_record` is absent or not an object (including `null`), the error is `Checkpoint issue_adoption.feature_folder_record must be an object when waiving new_active_feature_folder.`
|
|
307
|
+
11. Only when the rule-8 shape check passed: whenever `feature_folder_record` is an object, waived or not, its fields are checked in this order, and the errors accumulate:
|
|
308
|
+
- 11a. `path` must be a string that fully matches `^docs/features/active/[A-Za-z0-9][A-Za-z0-9._-]*(/[A-Za-z0-9][A-Za-z0-9._-]*)*$`, so dot segments, empty segments, a trailing slash, backslashes, drive letters, glob characters, and a leading `-` are rejected: `Checkpoint issue_adoption.feature_folder_record.path must be a folder path under docs/features/active/ without dot segments or a trailing slash.`
|
|
309
|
+
- 11b. Only when `path` passed 11a: the checkpoint `feature-folder` must be the identical string; an absent, non-string, or different value yields `Checkpoint issue_adoption.feature_folder_record.path must equal the checkpoint feature-folder.`
|
|
310
|
+
- 11c. `verified_via` must be `git_cat_file_head` or `git_ls_tree_head`: `Checkpoint issue_adoption.feature_folder_record.verified_via must be one of git_cat_file_head, git_ls_tree_head.`
|
|
311
|
+
- 11d. `verified_at` must be present; absent, `null`, and blank strings are rejected: `Checkpoint issue_adoption.feature_folder_record.verified_at must be present.`
|
|
312
|
+
- 11e. `evidence` must be a non-empty string: `Checkpoint issue_adoption.feature_folder_record.evidence must be a non-empty string.`
|
|
290
313
|
|
|
291
|
-
The closed waivable set is `potential_to_issue`, `new_potential_entry`, and `
|
|
314
|
+
The closed waivable set is `potential_to_issue`, `new_potential_entry`, `new_potential_bug_entry`, and `new_active_feature_folder`; `new_active_feature_folder` is waived only together with a valid `feature_folder_record`. `collect_pr_context`, `validate_orchestration_artifacts`, and every other tool can never be waived. All comparisons are ordinal and case-sensitive.
|
|
292
315
|
|
|
293
316
|
A lifecycle record moved to `docs/features/potential/promoted/` satisfies the `potential_record` path rule, because the rule checks only the `docs/features/potential/` prefix and the `.md` suffix and does not check that the file exists.
|
|
294
317
|
|
|
295
|
-
Fail-closed behavior: any adoption error waives nothing, so each missing receipt is still reported as `Checkpoint missing successful MCP receipt: <tool>.`. Adoption errors are placed after the receipt-loop errors and before the `local_execution_overrides` errors. The waiver affects only receipt presence: the declared `required_mcp_tools` equality check with the routing matrix is unchanged, so a checkpoint must still declare every route tool it waives.
|
|
318
|
+
Fail-closed behavior: any adoption error waives nothing, so each missing receipt is still reported as `Checkpoint missing successful MCP receipt: <tool>.`. Adoption errors are placed after the receipt-loop errors and before the `local_execution_overrides` errors. Rule 10 and rule 11 errors follow the rule 9 errors in the adoption error list, so a checkpoint that waives `new_active_feature_folder` without a valid record reports the missing-receipt line for each unreceipted required tool first, in `required_mcp_tools` order, and the rule 10 or rule 11 error after them. The waiver affects only receipt presence: the declared `required_mcp_tools` equality check with the routing matrix is unchanged, so a checkpoint must still declare every route tool it waives.
|
|
296
319
|
|
|
297
320
|
## Enforcement
|
|
298
321
|
|
|
@@ -302,7 +325,7 @@ Fail-closed behavior: any adoption error waives nothing, so each missing receipt
|
|
|
302
325
|
- `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated `model_routing_receipts` invariant when a `model_routing_receipts` key is present, delegating to `scripts/dev_tools/_orchestrator_state_model_routing.py`, which recomputes the resolved model via `resolve_delegation_model`. The check does not import or read any schema file.
|
|
303
326
|
- `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated `require_model_routing` invariant only when the caller passes `require_model_routing=True`, delegating to `scripts/dev_tools/_orchestrator_state_model_routing_gate.py`, which reuses the complexity and model-routing per-entry validators. When the flag is not passed the gate does not run, so existing calls are byte-identical.
|
|
304
327
|
- The validator is consumed by the MCP tool `validate_orchestration_artifacts`; backward compatibility for existing step-based checkpoints is preserved.
|
|
305
|
-
- The standalone-merge authorization invariants are enforced by the `PreToolUse` merge gate (`.claude/hooks/enforce-epic-merge-gate.ps1` with its dot-sourced helpers file, and `.codex/hooks/enforce-epic-merge-gate.ps1`) at merge time. The Python checkpoint validator
|
|
328
|
+
- The standalone-merge authorization invariants are enforced by the `PreToolUse` merge gate (`.claude/hooks/enforce-epic-merge-gate.ps1` with its dot-sourced helpers file, and `.codex/hooks/enforce-epic-merge-gate.ps1`) at merge time. The green-CI standalone merge conditions are enforced by the same Claude gate through its dot-sourced sibling `.claude/hooks/enforce-epic-merge-gate-green-ci.ps1`; the Codex gate has no green-CI branch. The Python checkpoint validator
|
|
306
329
|
does not currently validate standalone_merge_authorizations
|
|
307
330
|
entries; adding that check is recorded as follow-up FU-3.
|
|
308
|
-
- The `issue_adoption` invariants are enforced at completion by the routing-contract check in all three runtimes: `scripts/dev_tools/_orchestrator_state_issue_adoption.py` (Python authority), `extensions/drm-copilot/src/lib/validate/orchestrator-state-issue-adoption.ts`, and `.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1`, with byte-identical error strings pinned by the shared corpus under `tests/fixtures/orchestrator_state_issue_adoption/`.
|
|
331
|
+
- The `issue_adoption` invariants are enforced at completion by the routing-contract check in all three runtimes: `scripts/dev_tools/_orchestrator_state_issue_adoption.py` (Python authority), `extensions/drm-copilot/src/lib/validate/orchestrator-state-issue-adoption.ts`, and `.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1`, with byte-identical error strings pinned by the shared corpus under `tests/fixtures/orchestrator_state_issue_adoption/`. Rules 10 and 11 live in the record modules `scripts/dev_tools/_orchestrator_state_feature_folder_record.py`, `extensions/drm-copilot/src/lib/validate/orchestrator-state-feature-folder-record.ts`, and `.claude/lib/orchestrator-state/OrchestratorStateFeatureFolderRecord.psm1`. The `HEAD` probe for a waived `new_active_feature_folder` lives in the hook sibling `.claude/hooks/validate-orchestrator-output-feature-folder.ps1`, loaded by `.claude/hooks/validate-orchestrator-output.ps1`.
|
|
@@ -686,5 +686,5 @@ shipped surface or a derived module.
|
|
|
686
686
|
- The TypeScript parity port at `extensions/drm-copilot/src/lib/validate/parallel-state-shared.ts`, `parallel-state-structures.ts`, `parallel-state-records.ts`, `parallel-orchestrator-state-core.ts`, and `parallel-planner-state-core.ts` reproduces the same invariants and is dispatched from `extensions/drm-copilot/src/lib/validate/orchestration-artifacts.ts` for both new `artifact_type` values. Verified scope: 96 of 96 error strings matched across 43 constructed documents, for JSON-representable values that round-trip through both runtimes' native types. Three divergence classes are known outside that verified scope: (1) **`pythonRepr` quote selection** — `parallel-state-shared.ts:112-132` always single-quotes, while Python's `repr` switches to double quotes when the value contains a single quote (recorded repo-wide at `docs/features/potential/2026-08-07-python-repr-quote-selection-divergence.md`); (2) **integral floats** — `JSON.parse` erases Python's `int`/`float` distinction, so an integral float value produces a different Python-side error count than the TypeScript side; (3) **boolean/integer equality** — `parallel-state-structures.ts:228` uses `===`, so a boolean value is not selected the way Python's `True == 1` equality selects it, producing differing error counts. For ready-gate invariant P10, `extensions/drm-copilot/src/lib/validate/parallel-planner-state-routing.ts` enforces the P10 structural subset (the band enums, the assessment and receipt object checks, `signals_present` shape, `band >= floor` ordering, non-empty `rationale` and `assessed_at`, the band-equality cross-checks, `agent`, and the `fable_policy` enum) with byte-identical error strings; the Python validator is authoritative for floor equality, resolved-model equality, and the disabled clamp, which the TypeScript port does not check.
|
|
687
687
|
- Enforcement is therefore Python validator logic, plus the TypeScript parity port, plus this prose file. It is NEVER an imported JSON Schema. No schema file is read at validation time.
|
|
688
688
|
- The `parallel` route entry lives in `config/orchestration-routing.json` with `requires_pr_gate: false` (there is no run-level pull request to gate; each child's own route checkpoint enforces its per-item pull-request gate) and is mirrored byte-for-byte in `extensions/drm-copilot/resources/config/orchestration-routing.json`.
|
|
689
|
-
- The `PreToolUse` merge gate `.claude/hooks/enforce-epic-merge-gate.ps1` carries a parallel allow-branch that authorizes a per-item `gh pr merge --merge` from the parallel-orchestrator checkpoint when `route_id == "parallel"`, the target item's `merge_status == "ci_green"`, and the command's PR number matches that item's `pr_number`; any other case fails closed with `EPIC_MERGE_GATE_BLOCKED`.
|
|
690
|
-
- The `PreToolUse` worktree-removal gate `.claude/hooks/enforce-epic-worktree-removal-gate.ps1` likewise carries a parallel allow-branch that authorizes a per-item worktree removal from the parallel-orchestrator checkpoint when `route_id == "parallel"` and the `items[]` entry whose `worktree_path` matches the
|
|
689
|
+
- The `PreToolUse` merge gate `.claude/hooks/enforce-epic-merge-gate.ps1` carries a parallel allow-branch that authorizes a per-item `gh pr merge --merge` from the parallel-orchestrator checkpoint when `route_id == "parallel"`, the target item's `merge_status == "ci_green"`, and the command's PR number matches that item's `pr_number`; any other case fails closed with `EPIC_MERGE_GATE_BLOCKED`. After the parallel allow-branch, a standalone green-CI branch (issue #997), implemented in `.claude/hooks/enforce-epic-merge-gate-green-ci.ps1`, allows a run to merge its own pull request only when no epic or parallel checkpoint records the PR number or the item worktree path, the per-feature checkpoint carries no `parallel_mode` or `parallel_slug` marker, and its recorded green S9 gate matches the live PR head. That parallel exclusion is best-effort, because the `Parallel mode: true` marker is not visible in a Bash payload; a parallel child never merges its own pull request.
|
|
690
|
+
- The `PreToolUse` worktree-removal gate `.claude/hooks/enforce-epic-worktree-removal-gate.ps1` likewise carries a parallel allow-branch that authorizes a per-item worktree removal from the parallel-orchestrator checkpoint when `route_id == "parallel"` and the `items[]` entry whose `worktree_path` matches the removal target has `merge_status` in `{merged, worktree_removed}`, where a rooted `worktree_path` is compared to the normalized target, a relative one is joined to the main checkout root and to the root holding the checkpoint, and a blank, `.`, `..`, dot-segment, or `:`-bearing relative value matches nothing, through `Test-WorktreeRunRecordedPathMatchesTarget` in `.claude/lib/worktree-resolution/WorktreeRunRecordPath.psm1` (issue #995); any other case — neither checkpoint present, either checkpoint unparseable, `route_id` absent or not `"parallel"`, no matching `worktree_path`, or a matched entry whose `merge_status` is outside that set or absent — fails closed with `EPIC_WORKTREE_REMOVAL_BLOCKED`. Removal is keyed on the worktree path rather than on `pr_number`, because the command names a path. This gate and the sibling gate `.claude/hooks/enforce-parallel-worktree-removal-gate.ps1`, which owns `PARALLEL_WORKTREE_REMOVAL_BLOCKED`, both fire on the same command, and `PreToolUse` denials are conjunctive, so both must allow for a removal to proceed.
|
|
@@ -32,7 +32,7 @@ Run the toolchain in order: format → analyze → test. Restart from step 1 if
|
|
|
32
32
|
- Avoid `Invoke-Expression`, plaintext secrets, and hard-coded credentials/paths.
|
|
33
33
|
- Use `Write-Error`/`throw` for failures; avoid silent catch-alls.
|
|
34
34
|
- Use approved verbs and descriptive nouns for function names (PSScriptAnalyzer enforces this).
|
|
35
|
-
- Keep scripts cohesive and
|
|
35
|
+
- Keep scripts cohesive and within the tiered file-size policy in `.claude/rules/general-code-change.md` (`## File Size Limit`).
|
|
36
36
|
|
|
37
37
|
## Change Budget
|
|
38
38
|
|
|
@@ -89,7 +89,7 @@ single pass. Do not substitute the VS Code task wrappers for the native command
|
|
|
89
89
|
- Use shfmt default formatting (tab indentation, as in `scripts/bash/coverage_lib.sh`).
|
|
90
90
|
- Quote all expansions; resolve tools with `command -v` (honoring the `SHELL_QC_<TOOL>_BIN`
|
|
91
91
|
override seam).
|
|
92
|
-
-
|
|
92
|
+
- Production, test, and reusable shell files follow the tiered file-size policy in `.claude/rules/general-code-change.md` (`## File Size Limit`).
|
|
93
93
|
- Tests live in `tests/shell/*.bats` and mirror `scripts/bash/`. Tests must not create
|
|
94
94
|
temporary files; use checked-in fixtures under `tests/fixtures/` and checked-in stub
|
|
95
95
|
binaries under `tests/fixtures/shell_qc/stub-bin/` wired through the `SHELL_QC_<TOOL>_BIN`
|
|
@@ -150,6 +150,10 @@
|
|
|
150
150
|
"type": "command",
|
|
151
151
|
"command": "pwsh -NoProfile -File .claude/hooks/enforce-evidence-locations.ps1"
|
|
152
152
|
},
|
|
153
|
+
{
|
|
154
|
+
"type": "command",
|
|
155
|
+
"command": "pwsh -NoProfile -File .claude/hooks/enforce-file-size-limit.ps1"
|
|
156
|
+
},
|
|
153
157
|
{
|
|
154
158
|
"type": "command",
|
|
155
159
|
"command": "pwsh -NoProfile -File .claude/hooks/enforce-orchestration-preimplementation-gate.ps1"
|
|
@@ -571,8 +571,8 @@ and is prohibited by this skill, which is the term the residual rests on.
|
|
|
571
571
|
- `.claude/skills/pr-context-artifacts/SKILL.md` — how the PR-context bundle is collected
|
|
572
572
|
and the base-branch resolution rules.
|
|
573
573
|
- `.claude/rules/shell.md` — the bash toolchain (shfmt/shellcheck/bats/kcov), the
|
|
574
|
-
|
|
575
|
-
convention.
|
|
574
|
+
reference to the tiered file-size policy, the no-temp-files test policy, and the
|
|
575
|
+
`CLEANUP_WT_GIT_BIN` seam convention.
|
|
576
576
|
- `.claude/skills/feature-promotion-lifecycle/SKILL.md` — the potential-entry-to-issue
|
|
577
577
|
promotion path used by the Dirty Worktree Triage Procedure's step 9 for `PRESERVE`
|
|
578
578
|
findings that describe unresolved product scope.
|
|
@@ -122,7 +122,7 @@ These pass every validator rule and are found only by reading the plan against t
|
|
|
122
122
|
- Python: measure a module with `--cov=<dotted.module>`, `--cov-branch`, `--cov-report=term-missing`, and `--cov-report=json:<path>`; read branch figures from the JSON report.
|
|
123
123
|
- TypeScript: a new production file needs its own entry in the `coverageThreshold` map of `extensions/drm-copilot/jest.config.cjs`; Jest runs through `node run-jest.cjs` in `extensions/drm-copilot/`.
|
|
124
124
|
- PowerShell: per-file line coverage is read from `artifacts/pester/powershell-coverage.xml` written by the self-hosted PoshQC module.
|
|
125
|
-
-
|
|
125
|
+
- File size follows three tiers for production, test, and reusable script files, counted in logical lines (a trailing terminator adds no line): at 500 lines look for a logical split (guidance); at 700 lines split more aggressively, and reviewers report a non-blocking Warning (advisory); above 800 lines a split is enforced regardless of logical separability (hard limit; exactly 800 passes). The hard limit is enforced by `tests/scripts/ci/FileSizePolicy.RepoGate.Tests.ps1` in the existing PowerShell shards against `config/file-size-baseline.json`, where legacy files above 800 may shrink (with the entry tightened) or stay equal but may not grow, and by the PreToolUse hook `.claude/hooks/enforce-file-size-limit.ps1` (`.codex/hooks/enforce-file-size-limit.ps1`). New logic in a file near a tier goes into a new module and a new test file. Markdown is exempt.
|
|
126
126
|
|
|
127
127
|
## Test Isolation Checks
|
|
128
128
|
|
|
@@ -47,6 +47,7 @@ When the GitHub issue already exists (it was transferred, filed before orchestra
|
|
|
47
47
|
1. Verify the issue read-only with `gh issue view`, a `gh api` GET request, or a GitHub MCP issue read, and use its number as `${issue-num}`.
|
|
48
48
|
2. Record a top-level `issue_adoption` object in the orchestrator checkpoint (`issue_num`, `issue_url`, `origin`, `verified_via`, `verified_at`, `evidence`, `waived_tools`, and, when a promotion-entry tool is waived, `potential_record`). For that waiver `potential_record` is required when `origin` is `epic_decomposition`, optional when `origin` is `transferred` or `filed_before_orchestration`, and validated whenever it is present. A valid record waives the `potential_to_issue` receipt requirement at completion.
|
|
49
49
|
3. `delegation_receipts.promotion.issue` may be omitted, because no promotion receipt exists for an adopted issue.
|
|
50
|
+
4. When the item's feature folder was already created and committed before this run, do not call `new_active_feature_folder` a second time. Confirm the folder read-only (`git cat-file -t HEAD:<feature-folder>` prints `tree`), list `new_active_feature_folder` in `waived_tools`, and record `issue_adoption.feature_folder_record` with `path` equal to the checkpoint `feature-folder`, `verified_via` (`git_cat_file_head` or `git_ls_tree_head`), `verified_at`, and `evidence`. The Claude stop hook re-checks that `HEAD` holds the folder.
|
|
50
51
|
|
|
51
52
|
The field rules, the closed set of waivable tools, and the fail-closed behavior are defined in `.claude/rules/orchestrator-state.md` under `## Invariants (issue_adoption object)`.
|
|
52
53
|
|
|
@@ -194,7 +194,7 @@ The mandatory sequence is:
|
|
|
194
194
|
|
|
195
195
|
1. The orchestrator first refreshes the PR-context artifact via `mcp__drm-copilot__collect_pr_context` (or the equivalent context-collection mechanism), which writes `artifacts/pr_context.summary.txt`.
|
|
196
196
|
2. The orchestrator runs the orchestrator-state validator (`mcp__drm-copilot__validate_orchestration_artifacts` or the equivalent repository-local validator CLI documented in `.claude/rules/orchestrator-state.md`) against `artifacts/orchestration/orchestrator-state.json --require-pr-creation-ready` and records the pass/fail result under a new `pr_author_preflight` field in the checkpoint, alongside `pr_author_receipt`: `{status ("pass"|"fail"), checked_at (ISO-8601 UTC), checkpoint_path, validator_command, output_summary}`. The orchestrator must not delegate to `Agent(pr-author)` when this preflight fails. The validator's full-lifecycle completion flag (`ci_gate`/`pr_gate`/routing-contract receipts) remains reserved for the post-PR/CI completion context (Step S9 / PR Creation Gate condition 6), not this pre-PR-creation preflight, because those values cannot exist before the first `gh pr create` of a branch.
|
|
197
|
-
3. The orchestrator then delegates PR creation and any PR body edits to `Agent(pr-author)`. The `pr-author` agent runs the `pr-author` skill to author the body, writes the body file `artifacts/pr_body_<N>.md` and the sibling receipt `artifacts/pr_body_<N>.receipt.json` with the shape `{skill, pr_body_path, number, sha256 (lowercase hex of the body bytes), context_summary_path, created_at (ISO-8601 UTC, strictly newer than `artifacts/pr_context.summary.txt` last-write)}`, issues `gh pr create --body-file artifacts/pr_body_<N>.md` (or `gh pr edit --body-file ...`), and reports the resulting PR URL or PR number.
|
|
197
|
+
3. The orchestrator then delegates PR creation and any PR body edits to `Agent(pr-author)`. The `pr-author` agent runs the `pr-author` skill to author the body, writes the body file `artifacts/pr_body_<N>.md` and the sibling receipt `artifacts/pr_body_<N>.receipt.json` with the shape `{skill, pr_body_path, number, sha256 (lowercase hex of the body bytes), context_summary_path, created_at (ISO-8601 UTC, strictly newer than `artifacts/pr_context.summary.txt` last-write)}`, issues `gh pr create --body-file artifacts/pr_body_<N>.md` (or `gh pr edit --body-file ...`), and reports the resulting PR URL or PR number. The delegation prompt MUST carry, on its own line, `Author-asserted autoclose issues: #<issue-num>`, where `<issue-num>` is the checkpoint `issue-num`, so the PR body emits `- Closes #<issue-num>` and the issue closes on merge. The line is omitted only when the checkpoint records no `issue-num`, or when `epic_mode` is true (an epic child whose integration PR owns the closing keyword). In every other case the `enforce-model-routing-receipt.ps1` Agent PreToolUse hook denies the `Agent(pr-author)` delegation with `MODEL_ROUTING_RECEIPT_BLOCKED: AUTHOR_ASSERTED_AUTOCLOSE_MISSING`.
|
|
198
198
|
4. The orchestrator records `pr_author_receipt` in the checkpoint, citing the body-file path and the receipt path that were verified.
|
|
199
199
|
|
|
200
200
|
When the pr-author agent runs outside the item worktree (its process directory is not the worktree that holds the item's `artifacts/` tree), it passes the absolute canonical path `<item worktree>/artifacts/pr_body_<N>.md` to `--body-file`. The `enforce-pr-author-skill.ps1` hook resolves the item worktree from `--head` and reads the PR context summary, the body, and the receipt beneath it; in that topology it denies a relative `--body-file` with `PR_BODY_PATH_NONCANONICAL`, because `gh` would read the relative path from its own process directory.
|
|
@@ -312,6 +312,7 @@ S9 procedure:
|
|
|
312
312
|
4. Poll with a bounded interval and a documented total timeout while `conclusion == "pending"`. When the timeout is exhausted, record the timeout as a finding with `Remediability: awaiting_ci` in `remediation-inputs.<timestamp>.md` and follow the wait branch in Post-Review Outcome Evaluation (`blocked_reason: "awaiting_ci"`, `next_step: "S9_ci_green"`); `step9_status` stays `pending`. A poll timeout does not enter the remediation loop.
|
|
313
313
|
5. Write the `ci_gate` object and `last_verified_ci_sha` to the checkpoint, and set `step9_status` to `passed` only when `ci_gate.conclusion == "success"` AND `ci_gate.head_sha` equals the current PR head SHA.
|
|
314
314
|
6. If the checkpoint's `epic_mode` is `true`, execute `gh pr merge --merge <PR>` merging the feature branch into `epic_context.integration_branch` (already the PR's base branch per the epic-mode `--base` override applied at S8). On success, record `epic_merge: { merge_commit_sha, target_branch, merged_at }` in the checkpoint. On failure due to merge conflict (non-mergeable PR), do not retry blindly: convert the conflict into a synthetic Blocking finding per "Merge-Conflict Remediation" below and re-enter the standard R1–R5 remediation loop; do not proceed to DONE. Merge confirmation (issue #952): a zero exit from `gh pr merge` does not prove a merge when the base branch uses a merge queue, so `epic_merge.merge_commit_sha` is recorded only after the merge is confirmed by polling and ancestry check. Read the pull-request state every 30 seconds, for at most 90 minutes, with the `gh api graphql` query and `.claude/lib/ci-gate/Invoke-MergeConfirmationParser.ps1` exactly as step 3 of `## Per-Item Merge to Main (Merge-on-Green)` in `.claude/skills/parallel-orchestrate/SKILL.md` defines them; on `merged`, run `git fetch origin <integration_branch>` and confirm that `git merge-base --is-ancestor <merge-commit-oid> origin/<integration_branch>` exits 0 before recording `epic_merge`. When the 90 minutes are exhausted, record a finding naming `awaiting_merge_queue` with `Remediability: awaiting_ci` in `remediation-inputs.<timestamp>.md` and follow the wait branch (`blocked_reason: "awaiting_ci"`, `next_step: "S9_ci_green"`). A `not_queued` outcome whose `mergeable` value is `CONFLICTING` follows "Merge-Conflict Remediation"; any other `not_queued` outcome enters `## Remediation Loop — CI-Failure Handling`; a `closed` outcome halts the run with a failure. `gh pr merge` is never re-issued in a loop after `not_queued`.
|
|
315
|
+
7. Standalone merge-on-green (issue #997). This step applies only when the checkpoint's `epic_mode` is absent or `false`, no `Parallel mode: true` marker was supplied in the kickoff, the route requires S9 (the `preparation` route never merges), and the PR base branch is `main`. After step 5 records `step9_status: "passed"` and after every CI-dependent acceptance criterion is checked off and S9 has re-run against the final head, execute `gh pr merge --merge <PR> --match-head-commit <ci_gate.head_sha>`. The merge gate (`.claude/hooks/enforce-epic-merge-gate.ps1`) allows this command through its green-CI branch only when the checkpoint records `pr_gate.pr_number` equal to `<PR>` and the live PR head equals `ci_gate.head_sha`. A zero exit does not prove a merge: confirm it with the step 3 procedure of `## Per-Item Merge to Main (Merge-on-Green)` in `.claude/skills/parallel-orchestrate/SKILL.md` (the `gh api graphql` query and `.claude/lib/ci-gate/Invoke-MergeConfirmationParser.ps1` every 30 seconds for at most 90 minutes; on `merged`, `git fetch origin main` and `git merge-base --is-ancestor <merge-commit-oid> origin/main` exiting 0). Record `pr_merge: { merge_commit_sha, target_branch, merged_at }` in the checkpoint only after that confirmation. Exhausted polling, `not_queued`, and `closed` outcomes are handled as in step 6, and `gh pr merge` is never re-issued in a loop after `not_queued`. A parallel item never merges itself: the parallel parent performs every item merge, and the child records `parallel_mode: true` so the gate declines. The merge gate's parallel exclusion is best-effort, because the `Parallel mode: true` marker is not visible to a Bash hook, so this step relies on the kickoff marker as well as on the gate.
|
|
315
316
|
|
|
316
317
|
CI-dependent acceptance criteria: when the item has acceptance criteria whose verification requires the result of CI on the PR head, the item's own orchestrator run owns their check-off, and a parent or coordinating session never commits them from its own root. After step 5 records `ci_gate.conclusion == "success"`, check off each CI-dependent criterion in the item's own worktree, commit, push to the PR branch, and re-run S9 from step 1 against the new head SHA, so that `ci_gate.head_sha` equals the final PR head before DONE. In epic mode this completes before step 6 merges. A failed re-run enters `## Remediation Loop — CI-Failure Handling` below; the shared `remediation_loop.completed_attempts` count and its halt after three completed attempts are unchanged.
|
|
317
318
|
|
|
@@ -333,6 +334,12 @@ The orchestrator checkpoint (`artifacts/orchestration/orchestrator-state.json`)
|
|
|
333
334
|
- `merge_commit_sha` — the merge commit SHA produced by merging the feature branch into `epic_context.integration_branch`.
|
|
334
335
|
- `target_branch` — the integration branch the feature branch was merged into.
|
|
335
336
|
- `merged_at` — ISO-8601 timestamp of when S9 step 6 recorded the merge.
|
|
337
|
+
- a top-level `pr_merge` object (populated only by a standalone S9 step 7 merge, issue #997) containing:
|
|
338
|
+
- `merge_commit_sha` — the merge commit SHA confirmed on `main`.
|
|
339
|
+
- `target_branch` — the base branch the pull request merged into (`main`).
|
|
340
|
+
- `merged_at` — ISO-8601 timestamp of when S9 step 7 recorded the confirmed merge.
|
|
341
|
+
|
|
342
|
+
Like `epic_merge`, `pr_merge` is not read by any validator; it is an unvalidated record.
|
|
336
343
|
|
|
337
344
|
Illustrative shape:
|
|
338
345
|
|
|
@@ -376,9 +383,9 @@ The orchestrator must not create a PR, push a branch for PR purposes, or report
|
|
|
376
383
|
4. The checkpoint `next_step` is `S8_create_pr` (precondition to entering S9).
|
|
377
384
|
5. PR body produced via the pr-author handoff: `artifacts/pr_body_<N>.md` exists with a matching `artifacts/pr_body_<N>.receipt.json`, created with `--body-file`.
|
|
378
385
|
6. `ci_gate.conclusion == "success"` AND `ci_gate.head_sha == current head SHA of the PR branch`. DONE is not written while either sub-condition is false.
|
|
379
|
-
7. `epic_mode` is `false`, OR (`epic_mode` is `true` AND the integration-branch merge (`gh pr merge --merge`) has completed and `epic_merge.merge_commit_sha` is recorded in the checkpoint). `epic_merge.merge_commit_sha` is recorded only after the merge is confirmed by polling and ancestry check (S9 step 6).
|
|
386
|
+
7. `epic_mode` is `false`, OR (`epic_mode` is `true` AND the integration-branch merge (`gh pr merge --merge`) has completed and `epic_merge.merge_commit_sha` is recorded in the checkpoint). `epic_merge.merge_commit_sha` is recorded only after the merge is confirmed by polling and ancestry check (S9 step 6). In a standalone run whose route requires S9 (issue #997), DONE additionally requires `pr_merge.merge_commit_sha`, recorded only after the S9 step 7 merge is confirmed; the `preparation` route never merges.
|
|
380
387
|
|
|
381
|
-
This gate is non-negotiable. Each condition is independently verified before PR creation proceeds. Conditions 1-4 are unchanged from the prior contract; conditions 5-7 (receipt handoff, CI-green gate, and epic-mode merge-on-green gate) are additive.
|
|
388
|
+
This gate is non-negotiable. Each condition is independently verified before PR creation proceeds. Conditions 1-4 are unchanged from the prior contract; conditions 5-7 (receipt handoff, CI-green gate, and epic-mode merge-on-green gate, with the standalone merge of S9 step 7 for a standalone run) are additive.
|
|
382
389
|
|
|
383
390
|
## Step 6 Delegation — Prohibited Prompt Language
|
|
384
391
|
|
|
@@ -459,6 +466,6 @@ Example:
|
|
|
459
466
|
|
|
460
467
|
The validator counts an MCP receipt as successful only when `tool` is a non-empty string, `ok` is exactly `true`, and `evidence` is a non-empty string. Every `required_mcp_tools` entry must have such a receipt unless a valid `issue_adoption` record waives it.
|
|
461
468
|
|
|
462
|
-
When the GitHub issue already exists before orchestration starts (transferred, filed by hand, or created by epic decomposition) and has been verified read-only (`gh issue view`, a `gh api` GET, or a GitHub MCP issue read), do not call `potential_to_issue`. Record a top-level `issue_adoption` object in the checkpoint instead of a `potential_to_issue` receipt. It may waive only `potential_to_issue
|
|
469
|
+
When the GitHub issue already exists before orchestration starts (transferred, filed by hand, or created by epic decomposition) and has been verified read-only (`gh issue view`, a `gh api` GET, or a GitHub MCP issue read), do not call `potential_to_issue`. Record a top-level `issue_adoption` object in the checkpoint instead of a `potential_to_issue` receipt. It may waive only `potential_to_issue`, the checkpoint's promotion-entry tool (`new_potential_entry`, or `new_potential_bug_entry` for a bug-type checkpoint), and `new_active_feature_folder` with a valid `feature_folder_record` when the feature folder was already created and committed before this run (for example by a preparation child or an earlier session); `potential_to_issue` must always be listed. For a promotion-entry waiver, `potential_record` (a markdown path under `docs/features/potential/`) is required when `origin` is `epic_decomposition`, optional when `origin` is `transferred` or `filed_before_orchestration`, and validated whenever it is present. Every other required tool still needs a successful receipt. Any adoption error waives nothing. The field rules, the closed waivable set, and the error placement are defined in `.claude/rules/orchestrator-state.md` under `## Invariants (issue_adoption object)`.
|
|
463
470
|
|
|
464
471
|
These three receipt arrays, populated with the retained required names of the selected route, are what allow the routing-contract validation under `require_complete: true` to pass.
|
|
@@ -70,7 +70,10 @@ re-derivation is mandatory and is not an optimization to skip when the checkpoin
|
|
|
70
70
|
the issue was transferred), waiving potential_to_issue and the promotion-entry tool for the
|
|
71
71
|
checkpoint's promotion-type. When a lifecycle record for the item exists under
|
|
72
72
|
docs/features/potential/promoted/, cite it as potential_record.` The line is omitted for a
|
|
73
|
-
potential-entry item, whose child promotes it.
|
|
73
|
+
potential-entry item, whose child promotes it. The preparation child creates and commits the
|
|
74
|
+
item's feature folder, so the item's later execution child records `feature_folder_record` for
|
|
75
|
+
that committed folder instead of calling `new_active_feature_folder` again, per
|
|
76
|
+
`/parallel-orchestrate` kickoff element 4.
|
|
74
77
|
|
|
75
78
|
3. **Compute conflict edges over ALL items, including in-flight ones.** Invoke the scheduling
|
|
76
79
|
entry point Get-BlastRadiusConflictEdge from the destination-runtime PowerShell port
|