@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.
Files changed (85) hide show
  1. package/out/mcp-server.js +2568 -1261
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agent-memory/orchestrator/MEMORY.md +1 -1
  4. package/resources/claude-customizations/.claude/agent-memory/orchestrator/feedback_test_files_count_against_500_cap.md +4 -4
  5. package/resources/claude-customizations/.claude/agents/csharp-typed-engineer.md +1 -1
  6. package/resources/claude-customizations/.claude/agents/feature-review.md +8 -0
  7. package/resources/claude-customizations/.claude/agents/orchestrator.md +1 -1
  8. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +46 -6
  9. package/resources/claude-customizations/.claude/agents/powershell-typed-engineer.md +1 -1
  10. package/resources/claude-customizations/.claude/agents/pr-author.md +32 -7
  11. package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +1 -1
  12. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-green-ci.ps1 +325 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +22 -6
  14. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +23 -10
  15. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +29 -21
  16. package/resources/claude-customizations/.claude/hooks/enforce-file-size-limit.ps1 +244 -0
  17. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +48 -0
  18. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +22 -11
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +8 -2
  20. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +15 -10
  21. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.current-branch.ps1 +149 -0
  22. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-feature-folder.ps1 +146 -0
  23. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +25 -3
  24. package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +33 -6
  25. package/resources/claude-customizations/.claude/lib/file-size-policy/FileSizePolicy.psm1 +434 -0
  26. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateFeatureFolderRecord.psm1 +166 -0
  27. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +8 -3
  28. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunRecordPath.psm1 +185 -0
  29. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +11 -21
  30. package/resources/claude-customizations/.claude/rules/general-code-change.md +4 -1
  31. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +28 -5
  32. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +2 -2
  33. package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
  34. package/resources/claude-customizations/.claude/rules/shell.md +1 -1
  35. package/resources/claude-customizations/.claude/settings.json +4 -0
  36. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +2 -2
  37. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +1 -1
  38. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -0
  39. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +11 -4
  40. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +4 -1
  41. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +90 -14
  42. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +3 -1
  43. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +39 -9
  44. package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +1 -1
  45. package/resources/claude-customizations/pack-manifests/core.json +7 -0
  46. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +1 -1
  47. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  48. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +8 -0
  49. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +4 -1
  50. package/resources/codex-and-agents-customizations/.agents/skills/powershell/SKILL.md +1 -1
  51. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +1 -1
  52. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +1 -1
  53. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +1 -1
  54. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +1 -1
  55. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +1 -1
  56. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +1 -1
  57. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +8 -0
  58. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +1 -1
  59. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +1 -1
  60. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +1 -1
  61. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +1 -1
  62. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +1 -1
  63. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +1 -1
  64. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +1 -1
  65. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +1 -1
  66. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +1 -1
  67. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +1 -1
  68. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +1 -1
  69. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +1 -1
  70. package/resources/codex-and-agents-customizations/.codex/config.toml +7 -1
  71. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-file-size-limit.ps1 +282 -0
  72. package/resources/codex-and-agents-customizations/.codex/hooks/file-size-policy.ps1 +421 -0
  73. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +1 -1
  74. package/resources/codex-and-agents-customizations/AGENTS.md +4 -1
  75. package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
  76. package/resources/customizations/.github/agents/commentary-remediation.agent.md +1 -1
  77. package/resources/customizations/.github/agents/python-execution-only-typed.agent.md +1 -1
  78. package/resources/customizations/.github/agents/python-typed-engineer.agent.md +1 -1
  79. package/resources/customizations/.github/instructions/general-code-change.instructions.md +5 -2
  80. package/resources/customizations/.github/instructions/powershell-code-change.instructions.md +1 -1
  81. package/resources/customizations/.github/prompts/remediate-comments.prompt.md +1 -1
  82. package/resources/customizations/.github/skills/execution-environment/SKILL.md +1 -1
  83. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +8 -0
  84. package/resources/templates/policy_audit/code-review.yyyy-MM-ddTHH-mm.md +4 -4
  85. package/resources/templates/policy_audit/policy-audit.yyyy-MM-ddTHH-mm.md +4 -4
@@ -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
@@ -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-WorktreeRunPathEqual -Left ([string] $recorded) -Right $Value) { return $true }
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
- if (Test-WorktreeRunCheckpointRecord -Checkpoint $checkpoint -Kind $Kind -RecordField $RecordField -Value $needle) { $matched.Add($root) }
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
- - No production code, test code, or reusable script file may exceed **500 lines**.
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 `new_potential_bug_entry`. `new_active_feature_folder`, `collect_pr_context`, `validate_orchestration_artifacts`, and every other tool can never be waived. All comparisons are ordinal and case-sensitive.
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 normalized removal target has `merge_status` in `{merged, worktree_removed}`; 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.
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 under 500 lines.
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
- - No production, test, or reusable shell file may exceed 500 lines.
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
- 500-line cap, the no-temp-files test policy, and the `CLEANUP_WT_GIT_BIN` seam
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
- - No production, test, or reusable script file exceeds 500 physical lines; Markdown is exempt. Files at or near the limit are not extended; new logic goes into a new module and a new test file.
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` and the checkpoint's promotion-entry tool (`new_potential_entry`, or `new_potential_bug_entry` for a bug-type checkpoint); `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)`.
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