@danmoisan/drm-copilot-mcp 1.1.25 → 1.1.26
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/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +7 -3
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +6 -2
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +41 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +3 -1
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +69 -3
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +2 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.cleanup.ps1 +246 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +1 -0
- package/resources/claude-customizations/.claude/hooks/hook-cleanup-context.ps1 +343 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/Invoke-ParallelAdmission.ps1 +626 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmission.psm1 +508 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionJson.psm1 +141 -0
- package/resources/claude-customizations/.claude/lib/parallel-admission/ParallelAdmissionPrecedence.psm1 +508 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +17 -1
- package/resources/claude-customizations/.claude/settings.json +1 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +93 -14
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +23 -10
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +54 -46
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_merge_lib.sh +234 -0
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +15 -5
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +71 -7
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +26 -2
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +82 -12
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +15 -5
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-cleanup.ps1 +481 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +4 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-cleanup-context.ps1 +343 -0
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +15 -5
- package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +3 -1
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -10
package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
Dot-sourced record-resolution and barrier helpers for enforce-parallel-cohort-barrier.ps1.
|
|
4
4
|
|
|
5
5
|
.DESCRIPTION
|
|
6
|
-
Holds the
|
|
6
|
+
Holds the six resolution helpers the Layer 1 parallel cohort barrier uses once a
|
|
7
7
|
delegation has been identified as a parallel-mode orchestrator call:
|
|
8
8
|
|
|
9
9
|
- Find-ParallelCohortBarrierItemRecord: resolves the items[] record for a folder.
|
|
10
10
|
- Find-ParallelCohortBarrierItemByKey: resolves an items[] record by issue_num.
|
|
11
11
|
- Find-ParallelCohortBarrierCohortIndex: projects the current-generation coloring.
|
|
12
12
|
- Get-ParallelCohortBarrierConflictNeighborList: collects conflict-edge neighbours.
|
|
13
|
-
-
|
|
13
|
+
- Get-ParallelCohortBarrierPrecedencePredecessorList: collects precedence[] predecessors (issue #1047).
|
|
14
|
+
- Test-ParallelCohortBarrierClear: applies the per-edge barrier rule and the precedence rule.
|
|
14
15
|
|
|
15
16
|
They are split out of the parent hook, .claude/hooks/enforce-parallel-cohort-barrier.ps1,
|
|
16
17
|
because that file stood at 499 lines -- one below the 500-line limit -- with no room
|
|
@@ -215,11 +216,60 @@ function Get-ParallelCohortBarrierConflictNeighborList {
|
|
|
215
216
|
return $neighborList
|
|
216
217
|
}
|
|
217
218
|
|
|
219
|
+
function Get-ParallelCohortBarrierPrecedencePredecessorList {
|
|
220
|
+
<#
|
|
221
|
+
.SYNOPSIS
|
|
222
|
+
Return the before keys of every precedence[] entry whose after equals the supplied item key.
|
|
223
|
+
.DESCRIPTION
|
|
224
|
+
precedence[] is the top-level operator ordering input of issue #1047
|
|
225
|
+
({before, after, source, recorded_at}); it is a separating input, not a
|
|
226
|
+
depends_on key. An absent precedence key yields an empty list.
|
|
227
|
+
.PARAMETER Checkpoint
|
|
228
|
+
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
229
|
+
.PARAMETER IssueKey
|
|
230
|
+
The item primary key (issue_num) rendered as a string.
|
|
231
|
+
.OUTPUTS
|
|
232
|
+
System.Object[] holding each predecessor item key rendered as a string.
|
|
233
|
+
#>
|
|
234
|
+
[CmdletBinding()]
|
|
235
|
+
[OutputType([object[]])]
|
|
236
|
+
param(
|
|
237
|
+
[AllowNull()]
|
|
238
|
+
$Checkpoint,
|
|
239
|
+
|
|
240
|
+
[AllowNull()]
|
|
241
|
+
[string] $IssueKey
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
$predecessorList = @()
|
|
245
|
+
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($IssueKey)) {
|
|
246
|
+
return $predecessorList
|
|
247
|
+
}
|
|
248
|
+
if (@($Checkpoint.PSObject.Properties.Name) -notcontains 'precedence') {
|
|
249
|
+
return $predecessorList
|
|
250
|
+
}
|
|
251
|
+
foreach ($entry in @($Checkpoint.precedence)) {
|
|
252
|
+
if ($null -eq $entry) {
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
$entryProps = @($entry.PSObject.Properties.Name)
|
|
256
|
+
if ($entryProps -notcontains 'before' -or $entryProps -notcontains 'after') {
|
|
257
|
+
continue
|
|
258
|
+
}
|
|
259
|
+
if (([string]$entry.after) -eq $IssueKey) {
|
|
260
|
+
$predecessorList += [string]$entry.before
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return $predecessorList
|
|
264
|
+
}
|
|
265
|
+
|
|
218
266
|
function Test-ParallelCohortBarrierClear {
|
|
219
267
|
<#
|
|
220
268
|
.SYNOPSIS
|
|
221
269
|
Decision logic: true only when every conflicting neighbor in a strictly prior
|
|
222
|
-
current-generation cohort has merge_status merged or worktree_removed
|
|
270
|
+
current-generation cohort has merge_status merged or worktree_removed, and every
|
|
271
|
+
precedence[] predecessor has merge_status merged or worktree_removed or state
|
|
272
|
+
withdrawn (a blocked predecessor holds; a missing predecessor record fails closed).
|
|
223
273
|
.PARAMETER Checkpoint
|
|
224
274
|
Parsed parallel checkpoint, or $null when absent/unreadable.
|
|
225
275
|
.PARAMETER ItemRecord
|
|
@@ -274,5 +324,21 @@ function Test-ParallelCohortBarrierClear {
|
|
|
274
324
|
return $false
|
|
275
325
|
}
|
|
276
326
|
}
|
|
327
|
+
|
|
328
|
+
# Precedence (issue #1047): the target starts only after each predecessor has merged or
|
|
329
|
+
# been withdrawn, whatever its cohort. An absent precedence key leaves the decision unchanged.
|
|
330
|
+
foreach ($predecessorKey in @(Get-ParallelCohortBarrierPrecedencePredecessorList -Checkpoint $Checkpoint -IssueKey $targetKey)) {
|
|
331
|
+
$predecessorRecord = Find-ParallelCohortBarrierItemByKey -Checkpoint $Checkpoint -IssueKey $predecessorKey
|
|
332
|
+
if ($null -eq $predecessorRecord) {
|
|
333
|
+
return $false
|
|
334
|
+
}
|
|
335
|
+
$predecessorProps = @($predecessorRecord.PSObject.Properties.Name)
|
|
336
|
+
if ($predecessorProps -contains 'state' -and ([string]$predecessorRecord.state) -eq 'withdrawn') {
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
if ($predecessorProps -notcontains 'merge_status' -or $script:AllowedMergeStatuses -notcontains ([string]$predecessorRecord.merge_status)) {
|
|
340
|
+
return $false
|
|
341
|
+
}
|
|
342
|
+
}
|
|
277
343
|
return $true
|
|
278
344
|
}
|
|
@@ -374,7 +374,7 @@ function Get-PrAuthorBypassReason {
|
|
|
374
374
|
is resolved once (issue #850), and the deny precedence is: the resolution reason
|
|
375
375
|
(no-target or ambiguity code, or an underivable epic worktree), PR_CONTEXT_MISSING when the
|
|
376
376
|
context summary is absent beneath the resolved worktree, ORCHESTRATOR_STATE_PREFLIGHT_FAILED
|
|
377
|
-
unless Get-
|
|
377
|
+
unless Get-PrAuthorPreflightExemption exempts a docs-only gh pr create (issue #901), then
|
|
378
378
|
receipt checks 1-6 via Test-PrAuthorReceiptVerification. Returns $null for all allowed
|
|
379
379
|
patterns.
|
|
380
380
|
.PARAMETER CommandText
|
|
@@ -482,7 +482,7 @@ function Get-PrAuthorBypassReason {
|
|
|
482
482
|
} else {
|
|
483
483
|
$preflightResult.ErrorText
|
|
484
484
|
}
|
|
485
|
-
$exemption = Get-
|
|
485
|
+
$exemption = Get-PrAuthorPreflightExemption -CommandText $CommandText -WorktreeRoot $artifact.ArtifactRoot -CheckpointPath $checkpointPath
|
|
486
486
|
if (-not $exemption.Exempt) {
|
|
487
487
|
return "ORCHESTRATOR_STATE_PREFLIGHT_FAILED: $preflightSummary (docs-only exemption not applied: $($exemption.Reason))"
|
|
488
488
|
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Cleanup exemption from the checkpoint preflight of enforce-pr-author-skill.ps1 (issue #1026).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by enforce-pr-author-skill.docs-only.ps1. Get-PrAuthorBypassReason calls
|
|
7
|
+
Get-PrAuthorPreflightExemption, which has the parameters and the return shape of
|
|
8
|
+
Get-PrAuthorDocsOnlyExemption, only in its non-epic branch and only after
|
|
9
|
+
Invoke-OrchestratorStatePreflight reports errors. The wrapper evaluates the docs-only
|
|
10
|
+
exemption first and returns its result unchanged when it is exempt, or when the matched
|
|
11
|
+
gh pr create or gh pr edit segment names no consolidation branch: no token equals
|
|
12
|
+
documentationandmemories or ends with '=documentationandmemories' or
|
|
13
|
+
':documentationandmemories' (ordinal). Inside that cleanup context the cleanup exemption is
|
|
14
|
+
evaluated, and the first failed condition names the sub-code appended to the docs-only
|
|
15
|
+
reason after '; cleanup exemption not applied: '.
|
|
16
|
+
|
|
17
|
+
K1 CLEANUP_EXEMPTION_NOT_CREATE - the command is a structural gh pr create.
|
|
18
|
+
K2 CLEANUP_EXEMPTION_CHECKPOINT_PRESENT - no checkpoint file exists at the resolved path.
|
|
19
|
+
K3 CLEANUP_EXEMPTION_HEAD_UNRESOLVED - exactly one --head token, its value ordinal-equal
|
|
20
|
+
to documentationandmemories, with no --head= form
|
|
21
|
+
and no single-dash token containing H.
|
|
22
|
+
K4 CLEANUP_EXEMPTION_BASE_UNRESOLVED - Get-PrAuthorDocsOnlyBaseName returns exactly main.
|
|
23
|
+
K5 CLEANUP_EXEMPTION_MANIFEST_INVALID - the session-root manifest read through
|
|
24
|
+
Get-CleanupWorktreeManifestContent passes
|
|
25
|
+
Test-CleanupManifestText with the
|
|
26
|
+
Get-CleanupWorktreeManifestUtcNow clock.
|
|
27
|
+
K6 CLEANUP_EXEMPTION_REMOTE_HEAD_MISMATCH - refs/remotes/origin/documentationandmemories and
|
|
28
|
+
HEAD resolve to the same commit
|
|
29
|
+
(Get-PrAuthorCleanupPublishedHead), so the push
|
|
30
|
+
precedes gh pr create.
|
|
31
|
+
CLEANUP_EXEMPTION_DIFF_FAILED - refs/remotes/origin/main resolves to a commit and
|
|
32
|
+
git diff <commit>...HEAD exits 0 with output that
|
|
33
|
+
ends with NUL when non-empty.
|
|
34
|
+
CLEANUP_EXEMPTION_EMPTY_DIFF - the diff lists at least one path.
|
|
35
|
+
CLEANUP_EXEMPTION_NON_ALLOWLISTED_PATH - every path is under docs/ or .claude/agent-memory/
|
|
36
|
+
(Test-CleanupConsolidationRelativePath).
|
|
37
|
+
|
|
38
|
+
The receipt checks run unchanged after the exemption. A pull request that names no
|
|
39
|
+
consolidation branch receives the docs-only result unchanged, so its denial text is the
|
|
40
|
+
text it is today.
|
|
41
|
+
|
|
42
|
+
.NOTES
|
|
43
|
+
PowerShell 7+. Call-time dependencies from the docs-only sibling and the parsers the helpers
|
|
44
|
+
load: Get-PrAuthorDocsOnlyExemption, Test-PrAuthorDocsOnlyCheckpointPresence,
|
|
45
|
+
Get-PrAuthorDocsOnlyBaseName, Invoke-PrAuthorDocsOnlyGitExe, ConvertFrom-DocsOnlyNameList,
|
|
46
|
+
Test-CommandLineInvocation, and Get-CommandLineStructuralMatch. git runs only through
|
|
47
|
+
Invoke-PrAuthorDocsOnlyGitExe, and every git call is wrapped so a throwing seam denies.
|
|
48
|
+
Mirrored byte-identically under extensions/drm-copilot/resources/claude-customizations/.
|
|
49
|
+
#>
|
|
50
|
+
[CmdletBinding()]
|
|
51
|
+
param()
|
|
52
|
+
try { . (Join-Path $PSScriptRoot 'hook-cleanup-context.ps1') } catch { Add-HookDependencyFailure -Name 'hook-cleanup-context.ps1' -ErrorRecord $_ }
|
|
53
|
+
try { Import-Module (Join-Path $PSScriptRoot '../lib/cleanup-manifest/CleanupWorktreeManifest.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'CleanupWorktreeManifest.psm1' -ErrorRecord $_ }
|
|
54
|
+
|
|
55
|
+
function Get-PrAuthorCleanupSegmentToken {
|
|
56
|
+
<#
|
|
57
|
+
.SYNOPSIS
|
|
58
|
+
Return the tokens of the matched gh pr create segment, else of gh pr edit, or an empty array.
|
|
59
|
+
.OUTPUTS
|
|
60
|
+
System.String[]
|
|
61
|
+
#>
|
|
62
|
+
[CmdletBinding()]
|
|
63
|
+
[OutputType([string[]])]
|
|
64
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $CommandText)
|
|
65
|
+
|
|
66
|
+
foreach ($subcommand in @('create', 'edit')) {
|
|
67
|
+
$match = Get-CommandLineStructuralMatch -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', $subcommand)
|
|
68
|
+
if ($null -ne $match) {
|
|
69
|
+
return [string[]]@($match.Segment.Tokens | ForEach-Object { [string]$_ })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return [string[]]@()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Test-PrAuthorCleanupContextCommand {
|
|
76
|
+
<#
|
|
77
|
+
.SYNOPSIS
|
|
78
|
+
Return $true when the matched gh pr segment names the consolidation branch.
|
|
79
|
+
.OUTPUTS
|
|
80
|
+
System.Boolean
|
|
81
|
+
#>
|
|
82
|
+
[CmdletBinding()]
|
|
83
|
+
[OutputType([bool])]
|
|
84
|
+
param([Parameter(Mandatory)][AllowEmptyString()][string] $CommandText)
|
|
85
|
+
|
|
86
|
+
$branch = $script:CleanupContextBranch
|
|
87
|
+
foreach ($token in @(Get-PrAuthorCleanupSegmentToken -CommandText $CommandText)) {
|
|
88
|
+
if ($token -ceq $branch -or $token.EndsWith("=$branch", [System.StringComparison]::Ordinal) -or $token.EndsWith(":$branch", [System.StringComparison]::Ordinal)) {
|
|
89
|
+
return $true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return $false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Get-PrAuthorCleanupPublishedHead {
|
|
96
|
+
<#
|
|
97
|
+
.SYNOPSIS
|
|
98
|
+
Resolve the published consolidation branch and HEAD of a worktree (test seam).
|
|
99
|
+
.DESCRIPTION
|
|
100
|
+
Runs git rev-parse --verify --quiet <ref>^{commit} through Invoke-PrAuthorDocsOnlyGitExe
|
|
101
|
+
for refs/remotes/origin/<consolidation branch> and for HEAD. Each value is the printed
|
|
102
|
+
commit, or an empty string when the call throws, exits non-zero, or prints a value that
|
|
103
|
+
is not a 40- or 64-digit lowercase hex commit. Never throws. Tests mock this function.
|
|
104
|
+
.PARAMETER WorktreeRoot
|
|
105
|
+
The consolidation worktree root.
|
|
106
|
+
.OUTPUTS
|
|
107
|
+
System.Management.Automation.PSCustomObject with Remote and Head.
|
|
108
|
+
#>
|
|
109
|
+
[CmdletBinding()]
|
|
110
|
+
[OutputType([pscustomobject])]
|
|
111
|
+
param([AllowNull()][AllowEmptyString()][string] $WorktreeRoot)
|
|
112
|
+
|
|
113
|
+
$resolved = foreach ($ref in @("refs/remotes/origin/$($script:CleanupContextBranch)", 'HEAD')) {
|
|
114
|
+
try {
|
|
115
|
+
$result = Invoke-PrAuthorDocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('rev-parse', '--verify', '--quiet', ($ref + '^{commit}'))
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
$result = $null
|
|
119
|
+
}
|
|
120
|
+
$value = if ($null -ne $result -and $result.ExitCode -eq 0) { ([string]$result.Output).Trim() } else { '' }
|
|
121
|
+
if ($value -cmatch '^[0-9a-f]{40}([0-9a-f]{24})?$') { $value } else { '' }
|
|
122
|
+
}
|
|
123
|
+
return [pscustomobject]@{ Remote = [string]$resolved[0]; Head = [string]$resolved[1] }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function Get-PrAuthorCleanupExemptionFailure {
|
|
127
|
+
<#
|
|
128
|
+
.SYNOPSIS
|
|
129
|
+
Evaluate K1 to K6 of the file header and return the first failure text, or $null.
|
|
130
|
+
.OUTPUTS
|
|
131
|
+
System.String or $null
|
|
132
|
+
#>
|
|
133
|
+
[CmdletBinding()]
|
|
134
|
+
[OutputType([string])]
|
|
135
|
+
param(
|
|
136
|
+
[Parameter(Mandatory)][string] $CommandText,
|
|
137
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot,
|
|
138
|
+
[AllowNull()][AllowEmptyString()][string] $CheckpointPath
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
$branch = $script:CleanupContextBranch
|
|
142
|
+
if (-not (Test-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'create'))) {
|
|
143
|
+
return 'CLEANUP_EXEMPTION_NOT_CREATE: only gh pr create is eligible.'
|
|
144
|
+
}
|
|
145
|
+
if (Test-PrAuthorDocsOnlyCheckpointPresence -CheckpointPath $CheckpointPath) {
|
|
146
|
+
return "CLEANUP_EXEMPTION_CHECKPOINT_PRESENT: a checkpoint file exists at '$CheckpointPath', so its preflight governs."
|
|
147
|
+
}
|
|
148
|
+
$tokens = @(Get-PrAuthorCleanupSegmentToken -CommandText $CommandText)
|
|
149
|
+
$headIndex = @(for ($index = 0; $index -lt $tokens.Count; $index++) { if ($tokens[$index] -ceq '--head') { $index } })
|
|
150
|
+
$headVariant = @($tokens | Where-Object { $_.StartsWith('--head=', [System.StringComparison]::Ordinal) -or ($_ -cmatch '^-[^-]' -and $_.Substring(1).Contains('H')) })
|
|
151
|
+
if ($headIndex.Count -ne 1 -or $headVariant.Count -gt 0 -or $headIndex[0] + 1 -ge $tokens.Count -or $tokens[$headIndex[0] + 1] -cne $branch) {
|
|
152
|
+
return "CLEANUP_EXEMPTION_HEAD_UNRESOLVED: pass exactly one explicit --head $branch; -H, --head=, and a repeated --head are not accepted."
|
|
153
|
+
}
|
|
154
|
+
if ((Get-PrAuthorDocsOnlyBaseName -CommandText $CommandText) -cne 'main') {
|
|
155
|
+
return 'CLEANUP_EXEMPTION_BASE_UNRESOLVED: pass exactly one explicit --base main and no base or repository override.'
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
$manifestValid = Test-CleanupManifestText -Raw (Get-CleanupWorktreeManifestContent) -UtcNow (Get-CleanupWorktreeManifestUtcNow)
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
$manifestValid = $false
|
|
162
|
+
}
|
|
163
|
+
if (-not $manifestValid) {
|
|
164
|
+
return "CLEANUP_EXEMPTION_MANIFEST_INVALID: the session-root manifest $($script:CleanupContextManifestRelativePath) is absent, invalid, older than $($script:CleanupContextFreshnessHours) hours, in the future, or has no run_id."
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
$published = Get-PrAuthorCleanupPublishedHead -WorktreeRoot $WorktreeRoot
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
$published = $null
|
|
172
|
+
}
|
|
173
|
+
if ($null -eq $published -or [string]::IsNullOrEmpty([string]$published.Remote)) {
|
|
174
|
+
return "CLEANUP_EXEMPTION_REMOTE_HEAD_MISMATCH: refs/remotes/origin/$branch does not resolve to a commit in the consolidation worktree; push the branch before gh pr create."
|
|
175
|
+
}
|
|
176
|
+
if ([string]::IsNullOrEmpty([string]$published.Head) -or [string]$published.Remote -cne [string]$published.Head) {
|
|
177
|
+
return "CLEANUP_EXEMPTION_REMOTE_HEAD_MISMATCH: refs/remotes/origin/$branch differs from the consolidation worktree HEAD; push the branch again before gh pr create."
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
$revParse = Invoke-PrAuthorDocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('rev-parse', '--verify', '--quiet', 'refs/remotes/origin/main^{commit}')
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
$revParse = $null
|
|
185
|
+
}
|
|
186
|
+
$commit = if ($null -ne $revParse) { ([string]$revParse.Output).Trim() } else { '' }
|
|
187
|
+
if ($null -eq $revParse -or $revParse.ExitCode -ne 0 -or $commit -cnotmatch '^[0-9a-f]{40}([0-9a-f]{24})?$') {
|
|
188
|
+
return 'CLEANUP_EXEMPTION_DIFF_FAILED: refs/remotes/origin/main does not resolve to a commit in the consolidation worktree.'
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
$diff = Invoke-PrAuthorDocsOnlyGitExe -WorktreeRoot $WorktreeRoot -GitArgs @('diff', '--name-only', '-z', '--no-renames', '--no-color', '--no-ext-diff', '--no-relative', ($commit + '...HEAD'))
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
$diff = $null
|
|
195
|
+
}
|
|
196
|
+
$text = if ($null -ne $diff) { [string]$diff.Output } else { '' }
|
|
197
|
+
if ($null -eq $diff -or $diff.ExitCode -ne 0 -or ($text.Length -gt 0 -and -not $text.EndsWith([char]0))) {
|
|
198
|
+
return 'CLEANUP_EXEMPTION_DIFF_FAILED: git diff against origin/main could not be computed or did not return NUL-terminated output.'
|
|
199
|
+
}
|
|
200
|
+
$paths = @(ConvertFrom-DocsOnlyNameList -Text $text)
|
|
201
|
+
if ($paths.Count -eq 0) {
|
|
202
|
+
return 'CLEANUP_EXEMPTION_EMPTY_DIFF: the branch diff lists no path.'
|
|
203
|
+
}
|
|
204
|
+
foreach ($path in $paths) {
|
|
205
|
+
if (-not (Test-CleanupConsolidationRelativePath -RelativePath $path)) {
|
|
206
|
+
return "CLEANUP_EXEMPTION_NON_ALLOWLISTED_PATH: '$path' is outside docs/ and .claude/agent-memory/."
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return $null
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function Get-PrAuthorPreflightExemption {
|
|
213
|
+
<#
|
|
214
|
+
.SYNOPSIS
|
|
215
|
+
Decide whether a failed preflight is exempt as docs-only or as a cleanup consolidation PR.
|
|
216
|
+
.DESCRIPTION
|
|
217
|
+
Returns the docs-only result unchanged when it is exempt or when the command names no
|
|
218
|
+
consolidation branch. Otherwise evaluates the cleanup exemption and, on failure, returns
|
|
219
|
+
the docs-only reason followed by '; cleanup exemption not applied: ' and the sub-code.
|
|
220
|
+
.PARAMETER CommandText
|
|
221
|
+
The Bash command text under evaluation.
|
|
222
|
+
.PARAMETER WorktreeRoot
|
|
223
|
+
The resolved worktree root the diff is computed in.
|
|
224
|
+
.PARAMETER CheckpointPath
|
|
225
|
+
The resolved checkpoint path whose absence both exemptions require.
|
|
226
|
+
.OUTPUTS
|
|
227
|
+
System.Collections.Specialized.OrderedDictionary with Exempt and Reason.
|
|
228
|
+
#>
|
|
229
|
+
[CmdletBinding()]
|
|
230
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
231
|
+
param(
|
|
232
|
+
[Parameter(Mandatory)][string] $CommandText,
|
|
233
|
+
[AllowNull()][AllowEmptyString()][string] $WorktreeRoot,
|
|
234
|
+
[AllowNull()][AllowEmptyString()][string] $CheckpointPath
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
$docsOnly = Get-PrAuthorDocsOnlyExemption -CommandText $CommandText -WorktreeRoot $WorktreeRoot -CheckpointPath $CheckpointPath
|
|
238
|
+
if ($docsOnly.Exempt -or -not (Test-PrAuthorCleanupContextCommand -CommandText $CommandText)) {
|
|
239
|
+
return $docsOnly
|
|
240
|
+
}
|
|
241
|
+
$failure = Get-PrAuthorCleanupExemptionFailure -CommandText $CommandText -WorktreeRoot $WorktreeRoot -CheckpointPath $CheckpointPath
|
|
242
|
+
if ($null -eq $failure) {
|
|
243
|
+
return [ordered]@{ Exempt = $true; Reason = $null }
|
|
244
|
+
}
|
|
245
|
+
return [ordered]@{ Exempt = $false; Reason = "$($docsOnly.Reason); cleanup exemption not applied: $failure" }
|
|
246
|
+
}
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
[CmdletBinding()]
|
|
43
43
|
param()
|
|
44
44
|
. (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1')
|
|
45
|
+
try { . (Join-Path $PSScriptRoot 'enforce-pr-author-skill.cleanup.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-pr-author-skill.cleanup.ps1' -ErrorRecord $_ }
|
|
45
46
|
|
|
46
47
|
function Invoke-PrAuthorDocsOnlyGitExe {
|
|
47
48
|
<#
|