@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/hooks/enforce-parallel-worktree-removal-gate.ps1
CHANGED
|
@@ -46,6 +46,8 @@ try { Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWor
|
|
|
46
46
|
try { . (Join-Path $PSScriptRoot 'hook-command-scanner.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-scanner.ps1' -ErrorRecord $_ }
|
|
47
47
|
try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-HookDependencyFailure -Name 'hook-command-invocation.ps1' -ErrorRecord $_ }
|
|
48
48
|
|
|
49
|
+
# Shared recorded-path matcher and main-checkout-root seam (issue #995).
|
|
50
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunRecordPath.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunRecordPath.psm1' -ErrorRecord $_ }
|
|
49
51
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
|
|
50
52
|
|
|
51
53
|
$script:AllowedMergeStatuses = @('merged', 'worktree_removed')
|
|
@@ -170,7 +172,10 @@ function Find-ParallelWorktreeItemRecord {
|
|
|
170
172
|
Name of the record array to scan. Defaults to 'items', the parallel
|
|
171
173
|
checkpoint's array, so every existing caller is unchanged; the epic branch
|
|
172
174
|
added for issue #688 passes 'features'. Parameterized rather than duplicated
|
|
173
|
-
so both topologies share one path-
|
|
175
|
+
so both topologies share one path-comparison implementation.
|
|
176
|
+
.PARAMETER BaseRoot
|
|
177
|
+
Optional. The roots a relative recorded worktree_path is joined to (issue #995).
|
|
178
|
+
With none, a relative record matches nothing.
|
|
174
179
|
.OUTPUTS
|
|
175
180
|
System.Object or $null
|
|
176
181
|
#>
|
|
@@ -183,7 +188,11 @@ function Find-ParallelWorktreeItemRecord {
|
|
|
183
188
|
[string] $WorktreePath,
|
|
184
189
|
|
|
185
190
|
[ValidateNotNullOrEmpty()]
|
|
186
|
-
[string] $RecordArrayName = 'items'
|
|
191
|
+
[string] $RecordArrayName = 'items',
|
|
192
|
+
|
|
193
|
+
[AllowNull()]
|
|
194
|
+
[AllowEmptyCollection()]
|
|
195
|
+
[string[]] $BaseRoot = @()
|
|
187
196
|
)
|
|
188
197
|
|
|
189
198
|
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
@@ -194,17 +203,14 @@ function Find-ParallelWorktreeItemRecord {
|
|
|
194
203
|
return $null
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
# Scan every recorded item for a worktree_path that matches the removal target;
|
|
200
|
-
# path separators are normalized so Windows- and POSIX-style paths compare equal.
|
|
206
|
+
# Scan every recorded item for a worktree_path that names the removal target through
|
|
207
|
+
# the shared matcher (separator style, trailing slash, case rule, relative join).
|
|
201
208
|
foreach ($item in @($Checkpoint.$RecordArrayName)) {
|
|
202
209
|
$itemProps = @($item.PSObject.Properties.Name)
|
|
203
210
|
if ($itemProps -notcontains 'worktree_path') {
|
|
204
211
|
continue
|
|
205
212
|
}
|
|
206
|
-
|
|
207
|
-
if ($normalizedItemPath -eq $normalizedTarget) {
|
|
213
|
+
if (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath ([string]$item.worktree_path) -TargetPath $WorktreePath -BaseRoot $BaseRoot) {
|
|
208
214
|
return $item
|
|
209
215
|
}
|
|
210
216
|
}
|
|
@@ -353,8 +359,13 @@ function Get-ParallelWorktreeRemovalTargetDenial {
|
|
|
353
359
|
}
|
|
354
360
|
}
|
|
355
361
|
$checkpoint = $parallelRead.Checkpoint
|
|
362
|
+
# A relative record is joined to the main checkout root and to the root holding the
|
|
363
|
+
# checkpoint read (issue #995); the main root is derived once per target.
|
|
364
|
+
$mainRoot = Get-WorktreeRunMainCheckoutRoot -SessionRoot (Get-Location).Path
|
|
365
|
+
$parallelBaseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $mainRoot -Read $parallelRead
|
|
366
|
+
$epicBaseRoot = Get-WorktreeRunRecordBaseRoot -MainRoot $mainRoot -Read $epicRead
|
|
356
367
|
|
|
357
|
-
$itemRecord = Find-ParallelWorktreeItemRecord -Checkpoint $checkpoint -WorktreePath $worktreePath
|
|
368
|
+
$itemRecord = Find-ParallelWorktreeItemRecord -Checkpoint $checkpoint -WorktreePath $worktreePath -BaseRoot $parallelBaseRoot
|
|
358
369
|
if (Test-ParallelWorktreeRemovalAllowed -ItemRecord $itemRecord) {
|
|
359
370
|
return $null
|
|
360
371
|
}
|
|
@@ -371,7 +382,7 @@ function Get-ParallelWorktreeRemovalTargetDenial {
|
|
|
371
382
|
# $null, which yields no record, which is not allowed.
|
|
372
383
|
$epicCheckpoint = $epicRead.Checkpoint
|
|
373
384
|
|
|
374
|
-
$featureRecord = Find-ParallelWorktreeItemRecord -Checkpoint $epicCheckpoint -WorktreePath $worktreePath -RecordArrayName 'features'
|
|
385
|
+
$featureRecord = Find-ParallelWorktreeItemRecord -Checkpoint $epicCheckpoint -WorktreePath $worktreePath -RecordArrayName 'features' -BaseRoot $epicBaseRoot
|
|
375
386
|
if (Test-ParallelWorktreeRemovalAllowed -ItemRecord $featureRecord) {
|
|
376
387
|
return $null
|
|
377
388
|
}
|
|
@@ -386,7 +397,7 @@ function Get-ParallelWorktreeRemovalTargetDenial {
|
|
|
386
397
|
# reaches the unchanged deny below. This gate defines only the parallel checkpoint
|
|
387
398
|
# seam and has no epic-checkpoint seam, so its exclusion covers the items array
|
|
388
399
|
# only; the epic gate covers the features array with its own exclusion.
|
|
389
|
-
if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'items' -WorktreePath $worktreePath) -and
|
|
400
|
+
if (-not (Test-CleanupManifestCheckpointCoversPath -Checkpoint $checkpoint -RecordArrayName 'items' -WorktreePath $worktreePath -BaseRoot $parallelBaseRoot) -and
|
|
390
401
|
(Test-CleanupWorktreeManifestAuthorizesRemoval -WorktreePath $worktreePath)) {
|
|
391
402
|
return $null
|
|
392
403
|
}
|
package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
- git log ... and git rev-parse ... (no git global option)
|
|
13
13
|
- gh pr create ... and gh pr edit ... (no gh global option)
|
|
14
|
+
- gh pr view <N> --json closingIssuesReferences,body (exactly these six tokens, issue #989)
|
|
14
15
|
- sha256sum artifacts/pr_body_<N>.md (only as the whole command)
|
|
15
16
|
- date -u [+<format>] (only as the whole command)
|
|
16
17
|
|
|
@@ -41,7 +42,7 @@ $script:PrAuthorAllowlistWrapperWords = @(
|
|
|
41
42
|
'sh', 'bash', 'zsh', 'dash', 'ksh', 'pwsh', 'powershell', 'eval', 'xargs',
|
|
42
43
|
'env', 'timeout', 'time', 'command', 'nohup'
|
|
43
44
|
)
|
|
44
|
-
$script:PrAuthorAllowlistFormText = 'git log, git rev-parse, gh pr create, gh pr edit, sha256sum artifacts/pr_body_<N>.md, date -u [+<format>]'
|
|
45
|
+
$script:PrAuthorAllowlistFormText = 'git log, git rev-parse, gh pr create, gh pr edit, gh pr view <N> --json closingIssuesReferences,body, sha256sum artifacts/pr_body_<N>.md, date -u [+<format>]'
|
|
45
46
|
|
|
46
47
|
function Test-PrAuthorAllowlistForm {
|
|
47
48
|
<#
|
|
@@ -69,7 +70,12 @@ function Test-PrAuthorAllowlistForm {
|
|
|
69
70
|
return ($Token.Count -ge 2 -and @('log', 'rev-parse') -ccontains $Token[1])
|
|
70
71
|
}
|
|
71
72
|
'gh' {
|
|
72
|
-
|
|
73
|
+
if ($Token.Count -ge 3 -and $Token[1] -ceq 'pr' -and @('create', 'edit') -ccontains $Token[2]) {
|
|
74
|
+
return $true
|
|
75
|
+
}
|
|
76
|
+
# The read-only closing-reference verification form, matched exactly (issue #989).
|
|
77
|
+
return ($Token.Count -eq 6 -and $Token[1] -ceq 'pr' -and $Token[2] -ceq 'view' -and $Token[3] -cmatch '^\d+$' -and
|
|
78
|
+
$Token[4] -ceq '--json' -and $Token[5] -ceq 'closingIssuesReferences,body')
|
|
73
79
|
}
|
|
74
80
|
'sha256sum' {
|
|
75
81
|
return ($IsWholeCommand -and $Token.Count -eq 2 -and $Token[1] -cmatch '^artifacts/pr_body_\d+\.md$')
|
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
|
|
28
28
|
.NOTES
|
|
29
29
|
Compatible with PowerShell 7+. Dependencies are in-repository only: the command parsers
|
|
30
|
-
and worktree-resolution modules loaded below, and the artifact-root
|
|
31
|
-
No PowerShell Gallery module is required.
|
|
32
|
-
The docs-only sibling runs the git executable at call time (issue #901)
|
|
30
|
+
and worktree-resolution modules loaded below, and the artifact-root, docs-only, and
|
|
31
|
+
current-branch siblings. No PowerShell Gallery module is required.
|
|
32
|
+
The docs-only sibling runs the git executable at call time (issue #901), and so does the
|
|
33
|
+
current-branch sibling when a call carries no target signal (issue #989).
|
|
33
34
|
#>
|
|
34
35
|
[CmdletBinding()]
|
|
35
36
|
param()
|
|
@@ -56,6 +57,8 @@ Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/EpicScopeRead
|
|
|
56
57
|
. (Join-Path $PSScriptRoot 'enforce-pr-author-skill.artifact-root.ps1')
|
|
57
58
|
# Docs-only exemption from the orchestrator-state preflight (issue #901).
|
|
58
59
|
. (Join-Path $PSScriptRoot 'enforce-pr-author-skill.docs-only.ps1')
|
|
60
|
+
# Current-branch target derivation and the no-target guidance text (issue #989).
|
|
61
|
+
. (Join-Path $PSScriptRoot 'enforce-pr-author-skill.current-branch.ps1')
|
|
59
62
|
|
|
60
63
|
function Resolve-PrAuthorWorktreeTarget {
|
|
61
64
|
<#
|
|
@@ -67,7 +70,8 @@ function Resolve-PrAuthorWorktreeTarget {
|
|
|
67
70
|
directly would depend on the machine's live worktrees and would pass or fail
|
|
68
71
|
according to what other sessions happen to have checked out, which the repository's
|
|
69
72
|
determinism policy forbids. Mocking here lets a test state the resolution outcome it
|
|
70
|
-
is exercising and nothing else.
|
|
73
|
+
is exercising and nothing else. A NoTarget result for a command with no signal passes
|
|
74
|
+
through Resolve-PrAuthorCurrentBranchTarget, which re-resolves the current branch (#989).
|
|
71
75
|
.OUTPUTS
|
|
72
76
|
The target result object from Resolve-WorktreeItemTarget.
|
|
73
77
|
#>
|
|
@@ -77,7 +81,8 @@ function Resolve-PrAuthorWorktreeTarget {
|
|
|
77
81
|
[string] $CommandText
|
|
78
82
|
)
|
|
79
83
|
|
|
80
|
-
|
|
84
|
+
$target = Resolve-WorktreeItemTarget -Text $CommandText -SessionRoot (Get-Location).Path
|
|
85
|
+
return (Resolve-PrAuthorCurrentBranchTarget -CommandText $CommandText -Target $target)
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
function Get-PrAuthorTargetCheckpointResolution {
|
|
@@ -91,10 +96,10 @@ function Get-PrAuthorTargetCheckpointResolution {
|
|
|
91
96
|
SessionRoot -> that worktree's own checkpoint, composed absolutely.
|
|
92
97
|
OtherWorktree -> that worktree's own checkpoint, composed the same way.
|
|
93
98
|
NoTarget -> deny, carrying the code Get-WorktreeResolutionNoTargetReasonCode
|
|
94
|
-
returns.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
session root.
|
|
99
|
+
returns. The call named no target and no branch could be derived
|
|
100
|
+
from the current checkout; the remedy text from
|
|
101
|
+
Get-PrAuthorTargetGuidanceText names the accepted forms, and the
|
|
102
|
+
gate never falls back to the session root's checkpoint.
|
|
98
103
|
Ambiguous -> deny, carrying the code
|
|
99
104
|
Get-WorktreeResolutionAmbiguityReasonCode returns.
|
|
100
105
|
|
|
@@ -125,7 +130,7 @@ function Get-PrAuthorTargetCheckpointResolution {
|
|
|
125
130
|
return [ordered]@{ CheckpointPath = (Get-WorktreeItemCheckpointPath -WorktreeRoot $target.WorktreeRoot); Reason = $null; WorktreeRoot = $target.WorktreeRoot; Status = $target.Status }
|
|
126
131
|
}
|
|
127
132
|
default {
|
|
128
|
-
$reason = "$($target.ReasonCode): $($target.Detail) The pr-author gate will not validate this call against the session root's checkpoint, because that checkpoint may belong to a different item.
|
|
133
|
+
$reason = "$($target.ReasonCode): $($target.Detail) The pr-author gate will not validate this call against the session root's checkpoint, because that checkpoint may belong to a different item. $(Get-PrAuthorTargetGuidanceText)"
|
|
129
134
|
return [ordered]@{ CheckpointPath = $null; Reason = $reason; WorktreeRoot = $null; Status = $target.Status }
|
|
130
135
|
}
|
|
131
136
|
}
|
package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.current-branch.ps1
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Current-branch target derivation for enforce-pr-author-skill.ps1 (issue #989).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by enforce-pr-author-skill-helpers.ps1. Resolve-PrAuthorWorktreeTarget passes its
|
|
7
|
+
command-text resolution through Resolve-PrAuthorCurrentBranchTarget, which applies one
|
|
8
|
+
fallback: when the command carries no branch signal (--head, --branch, or branch:) and no
|
|
9
|
+
canonical issue line, and resolution returned NoTarget, the branch checked out in the hook
|
|
10
|
+
process's working directory is read and re-resolved through the existing resolver as a
|
|
11
|
+
'branch: <name>' signal. gh pr edit has no --head flag, so this is how an edit run from the
|
|
12
|
+
item worktree names its own target.
|
|
13
|
+
|
|
14
|
+
Derivation order: explicit signal in the command, else the current branch, else the
|
|
15
|
+
original NoTarget result. A signal that matches no live worktree does not trigger the
|
|
16
|
+
fallback. A detached HEAD (git prints the literal HEAD), a non-zero git exit, or empty
|
|
17
|
+
output keeps the original NoTarget result and its reason code.
|
|
18
|
+
|
|
19
|
+
Get-PrAuthorTargetGuidanceText supplies the remedy sentence that follows the reason code and
|
|
20
|
+
detail in the TARGET_WORKTREE_NOT_DERIVABLE denial.
|
|
21
|
+
|
|
22
|
+
.NOTES
|
|
23
|
+
PowerShell 7+. Depends, at call time, on Find-WorktreeResolutionBranchSignal,
|
|
24
|
+
Find-WorktreeItemIssueSignal, and Resolve-WorktreeItemTarget from the worktree-resolution
|
|
25
|
+
modules the helpers file imports. It reads no file content. The git executable runs only
|
|
26
|
+
through Invoke-PrAuthorCurrentBranchGitExe, the single executable seam, which never throws.
|
|
27
|
+
Mirrored byte-identically under extensions/drm-copilot/resources/claude-customizations/.
|
|
28
|
+
#>
|
|
29
|
+
[CmdletBinding()]
|
|
30
|
+
param()
|
|
31
|
+
|
|
32
|
+
function Invoke-PrAuthorCurrentBranchGitExe {
|
|
33
|
+
<#
|
|
34
|
+
.SYNOPSIS
|
|
35
|
+
Run git with the given arguments in the current directory. The single executable seam.
|
|
36
|
+
.DESCRIPTION
|
|
37
|
+
Never throws: an uncaught error ends the hook with exit 1, which PreToolUse treats as
|
|
38
|
+
non-blocking, so a throw here would allow the call. A git that cannot start returns
|
|
39
|
+
ExitCode -1. Standard error is discarded so that it is never read as a branch name.
|
|
40
|
+
.PARAMETER GitArgs
|
|
41
|
+
The git arguments.
|
|
42
|
+
.PARAMETER GitInvoker
|
|
43
|
+
The scriptblock that runs git; a unit test passes a test-local stand-in so that this
|
|
44
|
+
body runs without starting a process.
|
|
45
|
+
.OUTPUTS
|
|
46
|
+
System.Management.Automation.PSCustomObject with ExitCode and Output (trimmed).
|
|
47
|
+
#>
|
|
48
|
+
[CmdletBinding()]
|
|
49
|
+
[OutputType([pscustomobject])]
|
|
50
|
+
param(
|
|
51
|
+
[Parameter(Mandatory)][string[]] $GitArgs,
|
|
52
|
+
[ValidateNotNull()][scriptblock] $GitInvoker = { git @args 2>$null }
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
$lines = @(& $GitInvoker @GitArgs 2>$null)
|
|
57
|
+
$code = $LASTEXITCODE
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [pscustomobject]@{ ExitCode = -1; Output = '' }
|
|
61
|
+
}
|
|
62
|
+
return [pscustomobject]@{ ExitCode = [int]$code; Output = ($lines -join "`n").Trim() }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function Get-PrAuthorCurrentBranch {
|
|
66
|
+
<#
|
|
67
|
+
.SYNOPSIS
|
|
68
|
+
Return the branch checked out in the current directory, or $null.
|
|
69
|
+
.DESCRIPTION
|
|
70
|
+
Runs git rev-parse --abbrev-ref HEAD through Invoke-PrAuthorCurrentBranchGitExe. Returns
|
|
71
|
+
$null for a non-zero exit, for empty output, for output containing whitespace, and for
|
|
72
|
+
the literal HEAD that git prints on a detached checkout.
|
|
73
|
+
.PARAMETER GitInvoker
|
|
74
|
+
Optional. Forwarded to the git seam; omitted, the seam's default invoker runs git.
|
|
75
|
+
.OUTPUTS
|
|
76
|
+
System.String or $null
|
|
77
|
+
#>
|
|
78
|
+
[CmdletBinding()]
|
|
79
|
+
[OutputType([string])]
|
|
80
|
+
param(
|
|
81
|
+
[scriptblock] $GitInvoker
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
$seamArgs = @{ GitArgs = @('rev-parse', '--abbrev-ref', 'HEAD') }
|
|
85
|
+
if ($null -ne $GitInvoker) { $seamArgs['GitInvoker'] = $GitInvoker }
|
|
86
|
+
$result = Invoke-PrAuthorCurrentBranchGitExe @seamArgs
|
|
87
|
+
$name = [string]$result.Output
|
|
88
|
+
if ($result.ExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($name) -or $name -match '\s' -or $name -ceq 'HEAD') {
|
|
89
|
+
return $null
|
|
90
|
+
}
|
|
91
|
+
return $name
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function Resolve-PrAuthorCurrentBranchTarget {
|
|
95
|
+
<#
|
|
96
|
+
.SYNOPSIS
|
|
97
|
+
Apply the current-branch fallback to a command-text resolution result.
|
|
98
|
+
.DESCRIPTION
|
|
99
|
+
Returns Target unchanged unless its Status is NoTarget and the command text carries
|
|
100
|
+
neither a branch signal nor a canonical issue line. In that case the current branch is
|
|
101
|
+
read; a $null branch returns Target unchanged, and any other branch is re-resolved with
|
|
102
|
+
Resolve-WorktreeItemTarget as the text 'branch: <name>', so ambiguity handling and the
|
|
103
|
+
SessionRoot or OtherWorktree labelling stay with the existing resolver.
|
|
104
|
+
.PARAMETER CommandText
|
|
105
|
+
The Bash command text under evaluation.
|
|
106
|
+
.PARAMETER Target
|
|
107
|
+
The result Resolve-WorktreeItemTarget returned for the command text.
|
|
108
|
+
.OUTPUTS
|
|
109
|
+
The target result object from Resolve-WorktreeItemTarget.
|
|
110
|
+
#>
|
|
111
|
+
[CmdletBinding()]
|
|
112
|
+
[OutputType([pscustomobject])]
|
|
113
|
+
param(
|
|
114
|
+
[Parameter(Mandatory)][AllowEmptyString()][string] $CommandText,
|
|
115
|
+
[Parameter(Mandatory)][AllowNull()][object] $Target
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if ($null -eq $Target -or $Target.Status -ne 'NoTarget') {
|
|
119
|
+
return $Target
|
|
120
|
+
}
|
|
121
|
+
# Find-WorktreeItemIssueSignal emits its array as one object, so it is assigned before counting.
|
|
122
|
+
$issueSignal = Find-WorktreeItemIssueSignal -Text $CommandText
|
|
123
|
+
if ($null -ne (Find-WorktreeResolutionBranchSignal -Text $CommandText) -or $issueSignal.Count -gt 0) {
|
|
124
|
+
return $Target
|
|
125
|
+
}
|
|
126
|
+
$branch = Get-PrAuthorCurrentBranch
|
|
127
|
+
if ($null -eq $branch) {
|
|
128
|
+
return $Target
|
|
129
|
+
}
|
|
130
|
+
return (Resolve-WorktreeItemTarget -Text "branch: $branch" -SessionRoot (Get-Location).Path)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function Get-PrAuthorTargetGuidanceText {
|
|
134
|
+
<#
|
|
135
|
+
.SYNOPSIS
|
|
136
|
+
Return the remedy sentence of the TARGET_WORKTREE_NOT_DERIVABLE denial.
|
|
137
|
+
.DESCRIPTION
|
|
138
|
+
Names the accepted target forms: --head <branch> on gh pr create, and for gh pr edit
|
|
139
|
+
either running the command from the item worktree (the branch is derived) or appending
|
|
140
|
+
the shell comment # branch: <branch>. States that --head is not valid for gh pr edit.
|
|
141
|
+
.OUTPUTS
|
|
142
|
+
System.String
|
|
143
|
+
#>
|
|
144
|
+
[CmdletBinding()]
|
|
145
|
+
[OutputType([string])]
|
|
146
|
+
param()
|
|
147
|
+
|
|
148
|
+
return 'Name the target: for gh pr create, pass --head <branch> (gh pr create --head <branch> ...). For gh pr edit, run the command from the item worktree so the gate derives the branch from its checkout, or append the shell comment # branch: <branch> to the command; --head is not valid for gh pr edit.'
|
|
149
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Adopted feature-folder probe for the orchestrator SubagentStop hook validate-orchestrator-output.ps1 (issue #992).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by validate-orchestrator-output.ps1 through a guarded load whose catch records the
|
|
7
|
+
failure with Add-HookDependencyFailure, so a missing or broken copy of this file blocks the hook
|
|
8
|
+
instead of failing open. It defines Invoke-FeatureFolderGitExe, the single git seam, and
|
|
9
|
+
Get-OrchestratorOutputFeatureFolderDecision.
|
|
10
|
+
|
|
11
|
+
When an orchestrator-state checkpoint waives new_active_feature_folder through
|
|
12
|
+
issue_adoption.waived_tools, the validators accept a declared feature_folder_record but cannot
|
|
13
|
+
read the repository. This probe confirms that HEAD of the run worktree holds a tree at the
|
|
14
|
+
checkpoint feature-folder, so a staged-only, untracked, or absent folder blocks. It proves only
|
|
15
|
+
that a tree exists at that path at stop time; it does not prove which tool created the folder
|
|
16
|
+
or that its contents are correct.
|
|
17
|
+
|
|
18
|
+
The file imports nothing. Test-OrchestratorStateFeatureFolderPath comes from
|
|
19
|
+
OrchestratorStateFeatureFolderRecord.psm1, which the main hook imports through its own guarded
|
|
20
|
+
load. The file invokes no Python.
|
|
21
|
+
#>
|
|
22
|
+
|
|
23
|
+
$script:FeatureFolderProbeTool = 'new_active_feature_folder'
|
|
24
|
+
|
|
25
|
+
function Invoke-FeatureFolderGitExe {
|
|
26
|
+
<#
|
|
27
|
+
.SYNOPSIS
|
|
28
|
+
Run git with the given arguments. The single executable seam of the feature-folder probe.
|
|
29
|
+
.DESCRIPTION
|
|
30
|
+
Never throws: a git that cannot start, or an invoker that throws, returns ExitCode -1 so the
|
|
31
|
+
caller blocks instead of failing open. The default invoker merges standard error into Output;
|
|
32
|
+
success requires the exact output tree, so error text is never read as success.
|
|
33
|
+
.PARAMETER GitArgs
|
|
34
|
+
The git arguments.
|
|
35
|
+
.PARAMETER GitInvoker
|
|
36
|
+
The scriptblock that runs git; a unit test passes a test-local stand-in that sets
|
|
37
|
+
$global:LASTEXITCODE, so this body runs without starting a process.
|
|
38
|
+
.OUTPUTS
|
|
39
|
+
System.Collections.Hashtable with keys ExitCode (int) and Output (string, trimmed).
|
|
40
|
+
#>
|
|
41
|
+
[CmdletBinding()]
|
|
42
|
+
[OutputType([hashtable])]
|
|
43
|
+
param(
|
|
44
|
+
[Parameter(Mandatory = $true)]
|
|
45
|
+
[string[]] $GitArgs,
|
|
46
|
+
|
|
47
|
+
[Parameter(Mandatory = $false)]
|
|
48
|
+
[ValidateNotNull()]
|
|
49
|
+
[scriptblock] $GitInvoker = { git @args 2>&1 }
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
$lines = @(& $GitInvoker @GitArgs)
|
|
54
|
+
$code = $LASTEXITCODE
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return @{ ExitCode = -1; Output = '' }
|
|
58
|
+
}
|
|
59
|
+
if ($null -eq $code) { $code = -1 }
|
|
60
|
+
return @{ ExitCode = [int]$code; Output = ((@($lines | ForEach-Object { [string]$_ }) -join "`n").Trim()) }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Test-FeatureFolderProbeWaived {
|
|
64
|
+
<#
|
|
65
|
+
.SYNOPSIS
|
|
66
|
+
Report whether the checkpoint's issue_adoption.waived_tools lists new_active_feature_folder.
|
|
67
|
+
.PARAMETER Checkpoint
|
|
68
|
+
The parsed checkpoint object. May be $null.
|
|
69
|
+
.OUTPUTS
|
|
70
|
+
System.Boolean
|
|
71
|
+
#>
|
|
72
|
+
[CmdletBinding()]
|
|
73
|
+
[OutputType([bool])]
|
|
74
|
+
param(
|
|
75
|
+
[Parameter(Mandatory = $true)]
|
|
76
|
+
[AllowNull()]
|
|
77
|
+
[object] $Checkpoint
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if ($Checkpoint -isnot [System.Management.Automation.PSCustomObject]) { return $false }
|
|
81
|
+
$adoptionProperty = $Checkpoint.PSObject.Properties['issue_adoption']
|
|
82
|
+
if ($null -eq $adoptionProperty -or $adoptionProperty.Value -isnot [System.Management.Automation.PSCustomObject]) { return $false }
|
|
83
|
+
$waivedProperty = $adoptionProperty.Value.PSObject.Properties['waived_tools']
|
|
84
|
+
if ($null -eq $waivedProperty -or $waivedProperty.Value -isnot [System.Array]) { return $false }
|
|
85
|
+
return (@($waivedProperty.Value | Where-Object { $_ -is [string] }) -ccontains $script:FeatureFolderProbeTool)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Get-OrchestratorOutputFeatureFolderDecision {
|
|
89
|
+
<#
|
|
90
|
+
.SYNOPSIS
|
|
91
|
+
Confirm that HEAD of the run worktree holds a tree at the adopted feature-folder.
|
|
92
|
+
.DESCRIPTION
|
|
93
|
+
Returns @{ Ok = $true; Message = $null } when the checkpoint does not waive
|
|
94
|
+
new_active_feature_folder. Otherwise the checkpoint feature-folder is re-checked against the
|
|
95
|
+
shared path pattern before any git call (a failing path never reaches git), and
|
|
96
|
+
git -C <WorktreeRoot> cat-file -t HEAD:<feature-folder> must exit 0 and print exactly tree.
|
|
97
|
+
Every other outcome (non-zero exit, -1, empty output, other output) blocks with a repairable
|
|
98
|
+
ROUTING_CONTRACT_BLOCKED message naming the path.
|
|
99
|
+
.PARAMETER Checkpoint
|
|
100
|
+
The parsed checkpoint object. May be $null.
|
|
101
|
+
.PARAMETER WorktreeRoot
|
|
102
|
+
The root of the worktree whose run stopped.
|
|
103
|
+
.PARAMETER GitInvoker
|
|
104
|
+
Optional. Forwarded to Invoke-FeatureFolderGitExe; omitted, the seam's default runs git.
|
|
105
|
+
.OUTPUTS
|
|
106
|
+
System.Collections.Hashtable with keys Ok, Message, and (on a block) Repairable.
|
|
107
|
+
#>
|
|
108
|
+
[CmdletBinding()]
|
|
109
|
+
[OutputType([hashtable])]
|
|
110
|
+
param(
|
|
111
|
+
[Parameter(Mandatory = $true)]
|
|
112
|
+
[AllowNull()]
|
|
113
|
+
[object] $Checkpoint,
|
|
114
|
+
|
|
115
|
+
[Parameter(Mandatory = $true)]
|
|
116
|
+
[string] $WorktreeRoot,
|
|
117
|
+
|
|
118
|
+
[Parameter(Mandatory = $false)]
|
|
119
|
+
[scriptblock] $GitInvoker
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if (-not (Test-FeatureFolderProbeWaived -Checkpoint $Checkpoint)) {
|
|
123
|
+
return @{ Ok = $true; Message = $null }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
$folderProperty = $Checkpoint.PSObject.Properties['feature-folder']
|
|
127
|
+
$folder = if ($null -ne $folderProperty) { $folderProperty.Value } else { $null }
|
|
128
|
+
$blocked = @{ Ok = $false; Repairable = $true; Message = ('ROUTING_CONTRACT_BLOCKED: Checkpoint feature-folder is not committed in HEAD: {0}.' -f [string]$folder) }
|
|
129
|
+
# Defense in depth: the validators already require the record path to match the pattern and to
|
|
130
|
+
# equal feature-folder, but a value that fails the pattern must never become a git argument.
|
|
131
|
+
if (-not (Test-OrchestratorStateFeatureFolderPath -Value $folder)) {
|
|
132
|
+
return $blocked
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
$gitArgs = [string[]]@('-C', $WorktreeRoot, 'cat-file', '-t', ('HEAD:' + [string]$folder))
|
|
136
|
+
$probe = if ($PSBoundParameters.ContainsKey('GitInvoker') -and $null -ne $GitInvoker) {
|
|
137
|
+
Invoke-FeatureFolderGitExe -GitArgs $gitArgs -GitInvoker $GitInvoker
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
Invoke-FeatureFolderGitExe -GitArgs $gitArgs
|
|
141
|
+
}
|
|
142
|
+
if (($probe.ExitCode -eq 0) -and [string]::Equals([string]$probe.Output, 'tree', [System.StringComparison]::Ordinal)) {
|
|
143
|
+
return @{ Ok = $true; Message = $null }
|
|
144
|
+
}
|
|
145
|
+
return $blocked
|
|
146
|
+
}
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
ambiguous, or mismatched target blocks with ORCHESTRATOR_CHECKPOINT_UNRESOLVED:
|
|
26
26
|
before any checkpoint is read. For epic-orchestrator-state, the Layer 2 wave-barrier
|
|
27
27
|
ordering check (OrchestratorStateEpicWaveBarrier.psm1, issue #840) runs after the
|
|
28
|
-
routing dispatch passes.
|
|
28
|
+
routing dispatch passes. For orchestrator-state, the adopted feature-folder probe
|
|
29
|
+
(validate-orchestrator-output-feature-folder.ps1, issue #992) runs after the routing
|
|
30
|
+
dispatch passes, and only when the checkpoint waives new_active_feature_folder.
|
|
29
31
|
|
|
30
32
|
.NOTES
|
|
31
33
|
Reads the SubagentStop envelope from stdin through Read-SubagentStopEnvelope
|
|
@@ -61,6 +63,10 @@ try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/Orchestr
|
|
|
61
63
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorStateCompletion.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorStateCompletion.psm1' -ErrorRecord $_ }
|
|
62
64
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorStateUnconditional.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorStateUnconditional.psm1' -ErrorRecord $_ }
|
|
63
65
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorState.psm1') -Force -Global -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorState.psm1' -ErrorRecord $_ }
|
|
66
|
+
# Imported after the completion modules without -Force, so the instance they already loaded through
|
|
67
|
+
# the adoption module stays the one in use (issue #992).
|
|
68
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorStateFeatureFolderRecord.psm1') -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorStateFeatureFolderRecord.psm1' -ErrorRecord $_ }
|
|
69
|
+
try { . (Join-Path $PSScriptRoot 'validate-orchestrator-output-feature-folder.ps1') } catch { Add-HookDependencyFailure -Name 'validate-orchestrator-output-feature-folder.ps1' -ErrorRecord $_ }
|
|
64
70
|
try { . (Join-Path $PSScriptRoot 'subagentstop-envelope.ps1') } catch { Add-HookDependencyFailure -Name 'subagentstop-envelope.ps1' -ErrorRecord $_ }
|
|
65
71
|
$ErrorActionPreference = 'Stop'
|
|
66
72
|
$script:SubagentStopReportAndHaltLine = 'This condition is not repairable by the subagent. Report it to the operator and halt.'
|
|
@@ -194,7 +200,10 @@ function Invoke-OrchestratorOutputValidation {
|
|
|
194
200
|
[string] $SessionRoot = (Get-Location).Path,
|
|
195
201
|
|
|
196
202
|
[Parameter(Mandatory = $false)]
|
|
197
|
-
[scriptblock] $RoutingInvoker
|
|
203
|
+
[scriptblock] $RoutingInvoker,
|
|
204
|
+
|
|
205
|
+
[Parameter(Mandatory = $false)]
|
|
206
|
+
[scriptblock] $FeatureFolderGitInvoker
|
|
198
207
|
)
|
|
199
208
|
$dependencyDecision = Get-HookDependencyFailureDecision -HookEvent SubagentStop -ReasonPrefix 'ORCHESTRATOR_CHECKPOINT_UNRESOLVED:'
|
|
200
209
|
if ($null -ne $dependencyDecision) { return $dependencyDecision }
|
|
@@ -205,6 +214,7 @@ function Invoke-OrchestratorOutputValidation {
|
|
|
205
214
|
$agentOutput = $envelope.AssistantText
|
|
206
215
|
$blockArguments = @{ AgentOutput = ([string]$agentOutput); CheckpointPath = $CheckpointPath; ArtifactType = $ArtifactType; SessionRoot = $SessionRoot }
|
|
207
216
|
if ($PSBoundParameters.ContainsKey('RoutingInvoker')) { $blockArguments['RoutingInvoker'] = $RoutingInvoker }
|
|
217
|
+
if ($PSBoundParameters.ContainsKey('FeatureFolderGitInvoker')) { $blockArguments['FeatureFolderGitInvoker'] = $FeatureFolderGitInvoker }
|
|
208
218
|
$decision = Get-OrchestratorOutputBlockDecision @blockArguments
|
|
209
219
|
}
|
|
210
220
|
if ($decision.Ok) { return @{ Ok = $true; Message = $null } }
|
|
@@ -235,7 +245,10 @@ function Get-OrchestratorOutputBlockDecision {
|
|
|
235
245
|
[string] $SessionRoot = (Get-Location).Path,
|
|
236
246
|
|
|
237
247
|
[Parameter(Mandatory = $false)]
|
|
238
|
-
[scriptblock] $RoutingInvoker
|
|
248
|
+
[scriptblock] $RoutingInvoker,
|
|
249
|
+
|
|
250
|
+
[Parameter(Mandatory = $false)]
|
|
251
|
+
[scriptblock] $FeatureFolderGitInvoker
|
|
239
252
|
)
|
|
240
253
|
|
|
241
254
|
if ([string]::IsNullOrWhiteSpace($agentOutput)) {
|
|
@@ -322,6 +335,15 @@ function Get-OrchestratorOutputBlockDecision {
|
|
|
322
335
|
return @{ Ok = $false; Repairable = $true; Message = "ROUTING_CONTRACT_BLOCKED: $($routingResult.ErrorText)" }
|
|
323
336
|
}
|
|
324
337
|
|
|
338
|
+
# Issue #992: a waived new_active_feature_folder must name a folder committed in HEAD of the run
|
|
339
|
+
# worktree. The validators only see the declared record, so the hook probes git once.
|
|
340
|
+
if ($ArtifactType -eq 'orchestrator-state') {
|
|
341
|
+
$folderArgs = @{ Checkpoint = $checkpoint; WorktreeRoot = [string]$resolution.WorktreeRoot }
|
|
342
|
+
if ($PSBoundParameters.ContainsKey('FeatureFolderGitInvoker') -and $null -ne $FeatureFolderGitInvoker) { $folderArgs['GitInvoker'] = $FeatureFolderGitInvoker }
|
|
343
|
+
$folderDecision = Get-OrchestratorOutputFeatureFolderDecision @folderArgs
|
|
344
|
+
if (-not $folderDecision.Ok) { return @{ Ok = $false; Repairable = $folderDecision.Repairable; Message = $folderDecision.Message } }
|
|
345
|
+
}
|
|
346
|
+
|
|
325
347
|
# Layer 2 (issue #840): the epic checkpoint only, after the routing dispatch passes,
|
|
326
348
|
# on the checkpoint text already read.
|
|
327
349
|
if ($ArtifactType -eq 'epic-orchestrator-state') {
|
package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1
CHANGED
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
throwing.
|
|
25
25
|
|
|
26
26
|
.NOTES
|
|
27
|
-
Compatible with PowerShell 7+.
|
|
28
|
-
and no network. The single filesystem read and the single wall-clock read each
|
|
27
|
+
Compatible with PowerShell 7+. Imports only the in-repository sibling
|
|
28
|
+
WorktreeRunRecordPath.psm1 (issue #995); no subprocess and no network. The single filesystem read and the single wall-clock read each
|
|
29
29
|
sit behind an injectable seam so tests drive them without writing a temporary
|
|
30
30
|
file. Mirrored byte-identically under
|
|
31
31
|
extensions/drm-copilot/resources/claude-customizations/.
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
Set-StrictMode -Version Latest
|
|
36
36
|
$ErrorActionPreference = 'Stop'
|
|
37
37
|
|
|
38
|
+
# The shared recorded-path matcher (issue #995), used by the checkpoint-coverage test.
|
|
39
|
+
Import-Module (Join-Path $PSScriptRoot '../worktree-resolution/WorktreeRunRecordPath.psm1') -ErrorAction Stop
|
|
40
|
+
|
|
38
41
|
# Repo-relative location of the sanctioned-removal manifest. Kept as a script-scope
|
|
39
42
|
# constant so the read seam is the only function that names the path.
|
|
40
43
|
$script:CleanupWorktreeManifestPath = 'artifacts/orchestration/cleanup-worktrees-manifest.json'
|
|
@@ -355,6 +358,14 @@ function Test-CleanupManifestCheckpointCoversPath {
|
|
|
355
358
|
the gate's existing deny. Reusing the gates' merge_status predicate here
|
|
356
359
|
would reopen the one path by which the manifest could widen what the gates
|
|
357
360
|
protect.
|
|
361
|
+
|
|
362
|
+
Case rule: a record covers the target when the shared matcher holds on the
|
|
363
|
+
inputs as given, or holds on the same inputs lower-cased with the invariant
|
|
364
|
+
culture. This keeps the exclusion at parity with
|
|
365
|
+
Find-CleanupWorktreeManifestRemovalRecord, which ignores case for every path,
|
|
366
|
+
so a record that differs from the target only by case still blocks the
|
|
367
|
+
manifest branch. The shared matcher itself stays ordinal for single-slash
|
|
368
|
+
POSIX paths because the gates' allow lookups must remain fail-closed.
|
|
358
369
|
.PARAMETER Checkpoint
|
|
359
370
|
Parsed checkpoint object, or $null when absent or unreadable.
|
|
360
371
|
.PARAMETER RecordArrayName
|
|
@@ -362,6 +373,10 @@ function Test-CleanupManifestCheckpointCoversPath {
|
|
|
362
373
|
for the parallel checkpoint.
|
|
363
374
|
.PARAMETER WorktreePath
|
|
364
375
|
The removal target extracted from the command text.
|
|
376
|
+
.PARAMETER BaseRoot
|
|
377
|
+
Optional. The roots a relative recorded worktree_path is joined to: the main
|
|
378
|
+
checkout root and the root that holds the checkpoint (issue #995). With none, a
|
|
379
|
+
relative record covers nothing and a rooted record compares as before.
|
|
365
380
|
.OUTPUTS
|
|
366
381
|
System.Boolean
|
|
367
382
|
#>
|
|
@@ -377,7 +392,11 @@ function Test-CleanupManifestCheckpointCoversPath {
|
|
|
377
392
|
|
|
378
393
|
[AllowNull()]
|
|
379
394
|
[AllowEmptyString()]
|
|
380
|
-
[string] $WorktreePath
|
|
395
|
+
[string] $WorktreePath,
|
|
396
|
+
|
|
397
|
+
[AllowNull()]
|
|
398
|
+
[AllowEmptyCollection()]
|
|
399
|
+
[string[]] $BaseRoot = @()
|
|
381
400
|
)
|
|
382
401
|
|
|
383
402
|
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
@@ -388,8 +407,10 @@ function Test-CleanupManifestCheckpointCoversPath {
|
|
|
388
407
|
return $false
|
|
389
408
|
}
|
|
390
409
|
|
|
391
|
-
$
|
|
410
|
+
$lowerBaseRoot = [string[]] @(@($BaseRoot) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.ToLowerInvariant() })
|
|
392
411
|
|
|
412
|
+
# Every record is compared through the shared matcher, so a relative record covers
|
|
413
|
+
# the absolute target it names under a base root, and an unsafe one covers nothing.
|
|
393
414
|
foreach ($record in @($Checkpoint.PSObject.Properties[$RecordArrayName].Value)) {
|
|
394
415
|
if ($null -eq $record) {
|
|
395
416
|
continue
|
|
@@ -398,8 +419,14 @@ function Test-CleanupManifestCheckpointCoversPath {
|
|
|
398
419
|
if ($recordProperties -notcontains 'worktree_path') {
|
|
399
420
|
continue
|
|
400
421
|
}
|
|
401
|
-
$
|
|
402
|
-
if ($
|
|
422
|
+
$recordedPath = [string]$record.worktree_path
|
|
423
|
+
if (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath $recordedPath -TargetPath $WorktreePath -BaseRoot $BaseRoot) {
|
|
424
|
+
return $true
|
|
425
|
+
}
|
|
426
|
+
# Case-insensitive parity with Find-CleanupWorktreeManifestRemovalRecord: the same
|
|
427
|
+
# inputs lower-cased with the invariant culture, so the exclusion is never narrower
|
|
428
|
+
# than the manifest lookup it guards.
|
|
429
|
+
if (Test-WorktreeRunRecordedPathMatchesTarget -RecordedPath $recordedPath.ToLowerInvariant() -TargetPath $WorktreePath.ToLowerInvariant() -BaseRoot $lowerBaseRoot) {
|
|
403
430
|
return $true
|
|
404
431
|
}
|
|
405
432
|
}
|