@danmoisan/drm-copilot-mcp 1.1.10 → 1.1.11
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 +3116 -910
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +15 -2
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +3 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +49 -14
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +52 -3
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +103 -6
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +58 -3
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +37 -5
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +13 -2
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +35 -7
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +483 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +483 -0
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +218 -6
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +18 -75
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +290 -0
- package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +415 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMerge.psm1 +355 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1 +318 -0
- package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 +229 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +66 -2
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +311 -16
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -0
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +36 -2
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/powershell-orchestration-state-machine/SKILL.md +29 -1
- package/resources/claude-customizations/config/blast-radius.json +7 -0
- package/resources/claude-customizations/pack-manifests/core.json +7 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +41 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -0
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +26 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +49 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +57 -12
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +34 -8
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +34 -7
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +483 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +483 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +130 -2
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +6 -1
- package/resources/config/orchestration-handoff-registry.json +138 -0
- package/resources/config/orchestration-handoff.schema.json +472 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +38 -0
package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Sanctioned-removal manifest reader for the two worktree-removal gate hooks
|
|
4
|
+
(issue #635).
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
The cleanup-merged-worktrees skill records each per-worktree removal it has
|
|
8
|
+
triaged, together with the verdict and the evidence behind that verdict, in a
|
|
9
|
+
manifest document. This module owns reading that document and deciding whether
|
|
10
|
+
a given removal target is authorized by it. Both PreToolUse gate hooks consume
|
|
11
|
+
the decision; neither re-implements the parse.
|
|
12
|
+
|
|
13
|
+
Vocabulary. Two script-scope constants carry the narrow allow-sets the
|
|
14
|
+
predicate is written against, following the $script:AllowedMergeStatuses
|
|
15
|
+
precedent the two gate hooks already use. The removal-disposition set holds
|
|
16
|
+
exactly SAFE_TO_DELETE. The authorized-branch-state set holds exactly
|
|
17
|
+
NOT_MERGED and HAS_UNIQUE_RESIDUALS, which are the two states the skill
|
|
18
|
+
permanently forbids adding to the cleanup script's apply-mode allowlist and are
|
|
19
|
+
therefore the durable residual this manifest exists to serve.
|
|
20
|
+
|
|
21
|
+
Fail-closed posture. Every malformation resolves to a null parsed object or a
|
|
22
|
+
false predicate result. The module raises nothing on a malformed manifest, so a
|
|
23
|
+
hook consuming it always reaches its own unchanged deny path rather than
|
|
24
|
+
throwing.
|
|
25
|
+
|
|
26
|
+
.NOTES
|
|
27
|
+
Compatible with PowerShell 7+. No external module dependencies, no subprocess,
|
|
28
|
+
and no network. The single filesystem read and the single wall-clock read each
|
|
29
|
+
sit behind an injectable seam so tests drive them without writing a temporary
|
|
30
|
+
file. Mirrored byte-identically under
|
|
31
|
+
extensions/drm-copilot/resources/claude-customizations/.
|
|
32
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
33
|
+
#>
|
|
34
|
+
|
|
35
|
+
Set-StrictMode -Version Latest
|
|
36
|
+
$ErrorActionPreference = 'Stop'
|
|
37
|
+
|
|
38
|
+
# Repo-relative location of the sanctioned-removal manifest. Kept as a script-scope
|
|
39
|
+
# constant so the read seam is the only function that names the path.
|
|
40
|
+
$script:CleanupWorktreeManifestPath = 'artifacts/orchestration/cleanup-worktrees-manifest.json'
|
|
41
|
+
|
|
42
|
+
# The only removal disposition that authorizes anything. Deliberately a
|
|
43
|
+
# single-member set rather than a bare string comparison, so a widening of the set
|
|
44
|
+
# is a visible edit to a named constant that a test pins.
|
|
45
|
+
$script:AllowedRemovalDispositions = @('SAFE_TO_DELETE')
|
|
46
|
+
|
|
47
|
+
# The branch states a manifest record may authorize a removal for. PROTECTED_CURRENT
|
|
48
|
+
# is never authorized, and the three merged states are excluded because the cleanup
|
|
49
|
+
# script's deterministic path owns them.
|
|
50
|
+
$script:AuthorizedBranchStates = @('NOT_MERGED', 'HAS_UNIQUE_RESIDUALS')
|
|
51
|
+
|
|
52
|
+
# The step-5 verdicts that may authorize a removal. Derived from the skill's
|
|
53
|
+
# five-member verdict vocabulary -- DEAD_ONE_OFF, ALREADY_SOLVED_ELSEWHERE,
|
|
54
|
+
# STALE_OR_CONTRADICTED, GENUINELY_NEW, STILL_RELEVANT -- with the two
|
|
55
|
+
# preserve-implying members removed, because the skill classifies both as content
|
|
56
|
+
# that must be preserved before the worktree is deleted. Recorded as the authorized
|
|
57
|
+
# subset rather than as the vocabulary plus an exclusion list, so a verdict added to
|
|
58
|
+
# the vocabulary later is not authorized by silence.
|
|
59
|
+
$script:AuthorizedRemovalVerdicts = @('DEAD_ONE_OFF', 'ALREADY_SOLVED_ELSEWHERE', 'STALE_OR_CONTRADICTED')
|
|
60
|
+
|
|
61
|
+
# The self-identifying discriminator and the contract version this module reads.
|
|
62
|
+
$script:ExpectedManifestTool = 'cleanup-merged-worktrees'
|
|
63
|
+
$script:ExpectedManifestSchemaVersion = 1
|
|
64
|
+
|
|
65
|
+
# Freshness bound. artifacts/ is gitignored, so a stale manifest persists after its
|
|
66
|
+
# run ends, and cleanup targets are ordinary long-lived worktree paths rather than
|
|
67
|
+
# session-stamped ones, which makes a stale collision materially re-matchable.
|
|
68
|
+
$script:ManifestFreshnessBound = [timespan]::FromHours(24)
|
|
69
|
+
|
|
70
|
+
function Get-CleanupWorktreeManifestContent {
|
|
71
|
+
<#
|
|
72
|
+
.SYNOPSIS
|
|
73
|
+
Read the raw JSON text of the sanctioned-removal manifest. Tests mock this
|
|
74
|
+
function (read seam).
|
|
75
|
+
.DESCRIPTION
|
|
76
|
+
The only filesystem read in this module. An absent manifest is an ordinary
|
|
77
|
+
state rather than an error, so the function returns $null instead of
|
|
78
|
+
throwing and the caller resolves it to a non-authorizing parse.
|
|
79
|
+
.OUTPUTS
|
|
80
|
+
System.String or $null
|
|
81
|
+
#>
|
|
82
|
+
[CmdletBinding()]
|
|
83
|
+
[OutputType([string])]
|
|
84
|
+
param()
|
|
85
|
+
|
|
86
|
+
if (-not (Test-Path -LiteralPath $script:CleanupWorktreeManifestPath -PathType Leaf)) {
|
|
87
|
+
return $null
|
|
88
|
+
}
|
|
89
|
+
return (Get-Content -LiteralPath $script:CleanupWorktreeManifestPath -Raw)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function Get-CleanupWorktreeManifestUtcNow {
|
|
93
|
+
<#
|
|
94
|
+
.SYNOPSIS
|
|
95
|
+
Read the current UTC time. Tests mock this function (clock seam).
|
|
96
|
+
.DESCRIPTION
|
|
97
|
+
The only wall-clock read in this module. Freshness evaluation calls it once
|
|
98
|
+
per predicate evaluation, so a mocked value governs the whole comparison and
|
|
99
|
+
no test depends on real elapsed time.
|
|
100
|
+
.OUTPUTS
|
|
101
|
+
System.DateTime
|
|
102
|
+
#>
|
|
103
|
+
[CmdletBinding()]
|
|
104
|
+
[OutputType([datetime])]
|
|
105
|
+
param()
|
|
106
|
+
|
|
107
|
+
return [datetime]::UtcNow
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ConvertTo-CleanupWorktreeManifestNormalizedPath {
|
|
111
|
+
<#
|
|
112
|
+
.SYNOPSIS
|
|
113
|
+
Normalize a worktree path so both sides of a comparison share one spelling.
|
|
114
|
+
.DESCRIPTION
|
|
115
|
+
Replaces backslashes with forward slashes, then trims a single trailing
|
|
116
|
+
slash. Applied to the command target and to every recorded worktree_path,
|
|
117
|
+
so a trailing-slash target, a Windows-separator target, and a recorded
|
|
118
|
+
POSIX path all compare equal.
|
|
119
|
+
.PARAMETER Path
|
|
120
|
+
The raw path to normalize, or $null.
|
|
121
|
+
.OUTPUTS
|
|
122
|
+
System.String
|
|
123
|
+
#>
|
|
124
|
+
[CmdletBinding()]
|
|
125
|
+
[OutputType([string])]
|
|
126
|
+
param(
|
|
127
|
+
[AllowNull()]
|
|
128
|
+
[AllowEmptyString()]
|
|
129
|
+
[string] $Path
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if ([string]::IsNullOrWhiteSpace($Path)) {
|
|
133
|
+
return ''
|
|
134
|
+
}
|
|
135
|
+
return ($Path -replace '\\', '/').TrimEnd('/')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function Find-CleanupWorktreeManifestRemovalRecord {
|
|
139
|
+
<#
|
|
140
|
+
.SYNOPSIS
|
|
141
|
+
Locate the removals[] record whose worktree_path matches the removal target.
|
|
142
|
+
.DESCRIPTION
|
|
143
|
+
Parses the manifest text behind a fail-closed try/catch: unparseable text
|
|
144
|
+
yields $null rather than a throw, matching the gate hooks' conventions.
|
|
145
|
+
Scanning stops at the FIRST normalized path match, so two records sharing a
|
|
146
|
+
path resolve on the first, following the parallel-branch precedent in the
|
|
147
|
+
epic gate. A record with no worktree_path key is skipped and the scan
|
|
148
|
+
continues.
|
|
149
|
+
.PARAMETER Raw
|
|
150
|
+
Raw manifest text, or $null when the manifest does not exist.
|
|
151
|
+
.PARAMETER WorktreePath
|
|
152
|
+
The removal target extracted from the command text.
|
|
153
|
+
.OUTPUTS
|
|
154
|
+
System.Object or $null
|
|
155
|
+
#>
|
|
156
|
+
[CmdletBinding()]
|
|
157
|
+
param(
|
|
158
|
+
[AllowNull()]
|
|
159
|
+
[AllowEmptyString()]
|
|
160
|
+
[string] $Raw,
|
|
161
|
+
|
|
162
|
+
[AllowNull()]
|
|
163
|
+
[AllowEmptyString()]
|
|
164
|
+
[string] $WorktreePath
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if ([string]::IsNullOrWhiteSpace($Raw) -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
168
|
+
return $null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
$manifest = $Raw | ConvertFrom-Json
|
|
173
|
+
} catch {
|
|
174
|
+
$manifest = $null
|
|
175
|
+
}
|
|
176
|
+
if ($null -eq $manifest) {
|
|
177
|
+
return $null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
$manifestProperties = @($manifest.PSObject.Properties.Name)
|
|
181
|
+
if ($manifestProperties -notcontains 'removals') {
|
|
182
|
+
return $null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
$normalizedTarget = ConvertTo-CleanupWorktreeManifestNormalizedPath -Path $WorktreePath
|
|
186
|
+
|
|
187
|
+
foreach ($record in @($manifest.removals)) {
|
|
188
|
+
if ($null -eq $record) {
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
$recordProperties = @($record.PSObject.Properties.Name)
|
|
192
|
+
if ($recordProperties -notcontains 'worktree_path') {
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
$normalizedRecordPath = ConvertTo-CleanupWorktreeManifestNormalizedPath -Path ([string]$record.worktree_path)
|
|
196
|
+
if ($normalizedRecordPath -eq $normalizedTarget) {
|
|
197
|
+
return $record
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return $null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function Test-CleanupWorktreeManifestAuthorizesRemoval {
|
|
204
|
+
<#
|
|
205
|
+
.SYNOPSIS
|
|
206
|
+
Decide whether the sanctioned-removal manifest authorizes removing a
|
|
207
|
+
worktree.
|
|
208
|
+
.DESCRIPTION
|
|
209
|
+
Evaluates conditions 1 through 9 of the specification's allow predicate in
|
|
210
|
+
order: parse, tool and schema version, freshness against the injected
|
|
211
|
+
clock, removals present and non-empty, first normalized path match, removal
|
|
212
|
+
disposition, evidence, verdict, and branch state. Conditions 1 through 4
|
|
213
|
+
are evaluated before the path scan so an absent or malformed manifest costs
|
|
214
|
+
one existence test and returns to the caller's deny path.
|
|
215
|
+
|
|
216
|
+
The function returns a boolean and raises nothing: every malformation
|
|
217
|
+
resolves to $false. It never reads the preserved_files array, which belongs
|
|
218
|
+
to a different consumer and must not influence any gate decision.
|
|
219
|
+
|
|
220
|
+
Condition 10, checkpoint exclusion, is NOT evaluated here. It is the
|
|
221
|
+
caller's obligation, because the two gate hooks own the checkpoint seams.
|
|
222
|
+
.PARAMETER WorktreePath
|
|
223
|
+
The removal target extracted from the command text.
|
|
224
|
+
.OUTPUTS
|
|
225
|
+
System.Boolean
|
|
226
|
+
#>
|
|
227
|
+
[CmdletBinding()]
|
|
228
|
+
[OutputType([bool])]
|
|
229
|
+
param(
|
|
230
|
+
[AllowNull()]
|
|
231
|
+
[AllowEmptyString()]
|
|
232
|
+
[string] $WorktreePath
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if ([string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
236
|
+
return $false
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Condition 1 -- the manifest exists, is readable, and parses as JSON.
|
|
240
|
+
$raw = Get-CleanupWorktreeManifestContent
|
|
241
|
+
if ([string]::IsNullOrWhiteSpace($raw)) {
|
|
242
|
+
return $false
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
$manifest = $raw | ConvertFrom-Json
|
|
246
|
+
} catch {
|
|
247
|
+
$manifest = $null
|
|
248
|
+
}
|
|
249
|
+
if ($null -eq $manifest) {
|
|
250
|
+
return $false
|
|
251
|
+
}
|
|
252
|
+
$manifestProperties = @($manifest.PSObject.Properties.Name)
|
|
253
|
+
|
|
254
|
+
# Condition 2 -- the self-identifying discriminator and the contract version.
|
|
255
|
+
if ($manifestProperties -notcontains 'tool' -or $manifestProperties -notcontains 'schema_version') {
|
|
256
|
+
return $false
|
|
257
|
+
}
|
|
258
|
+
if ($manifest.tool -isnot [string] -or $manifest.tool -cne $script:ExpectedManifestTool) {
|
|
259
|
+
return $false
|
|
260
|
+
}
|
|
261
|
+
$schemaVersion = $manifest.schema_version
|
|
262
|
+
if ($schemaVersion -isnot [int] -and $schemaVersion -isnot [long]) {
|
|
263
|
+
return $false
|
|
264
|
+
}
|
|
265
|
+
if ([long]$schemaVersion -ne [long]$script:ExpectedManifestSchemaVersion) {
|
|
266
|
+
return $false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Condition 3 -- freshness, measured against the injected clock only.
|
|
270
|
+
if ($manifestProperties -notcontains 'generated_at') {
|
|
271
|
+
return $false
|
|
272
|
+
}
|
|
273
|
+
$generatedAt = [datetime]::MinValue
|
|
274
|
+
$parseStyles = [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal
|
|
275
|
+
$isTimestampParsed = [datetime]::TryParse(
|
|
276
|
+
[string]$manifest.generated_at,
|
|
277
|
+
[cultureinfo]::InvariantCulture,
|
|
278
|
+
$parseStyles,
|
|
279
|
+
[ref] $generatedAt)
|
|
280
|
+
if (-not $isTimestampParsed) {
|
|
281
|
+
return $false
|
|
282
|
+
}
|
|
283
|
+
$utcNow = Get-CleanupWorktreeManifestUtcNow
|
|
284
|
+
if ($generatedAt -gt $utcNow) {
|
|
285
|
+
return $false
|
|
286
|
+
}
|
|
287
|
+
if (($utcNow - $generatedAt) -gt $script:ManifestFreshnessBound) {
|
|
288
|
+
return $false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# Condition 4 -- removals is present, is an array, and is non-empty.
|
|
292
|
+
if ($manifestProperties -notcontains 'removals') {
|
|
293
|
+
return $false
|
|
294
|
+
}
|
|
295
|
+
$removals = $manifest.removals
|
|
296
|
+
if ($null -eq $removals -or $removals -isnot [System.Collections.IList]) {
|
|
297
|
+
return $false
|
|
298
|
+
}
|
|
299
|
+
if (@($removals).Count -lt 1) {
|
|
300
|
+
return $false
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Condition 5 -- the first record whose normalized path matches the target.
|
|
304
|
+
$record = Find-CleanupWorktreeManifestRemovalRecord -Raw $raw -WorktreePath $WorktreePath
|
|
305
|
+
if ($null -eq $record) {
|
|
306
|
+
return $false
|
|
307
|
+
}
|
|
308
|
+
$recordProperties = @($record.PSObject.Properties.Name)
|
|
309
|
+
|
|
310
|
+
# Condition 6 -- the removal disposition is in the single-member allowed set.
|
|
311
|
+
if ($recordProperties -notcontains 'removal_disposition') {
|
|
312
|
+
return $false
|
|
313
|
+
}
|
|
314
|
+
if ($script:AllowedRemovalDispositions -cnotcontains ([string]$record.removal_disposition)) {
|
|
315
|
+
return $false
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Condition 7 -- the evidence justification is a present, non-empty string.
|
|
319
|
+
if ($recordProperties -notcontains 'evidence') {
|
|
320
|
+
return $false
|
|
321
|
+
}
|
|
322
|
+
if ($record.evidence -isnot [string] -or [string]::IsNullOrWhiteSpace($record.evidence)) {
|
|
323
|
+
return $false
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# Condition 8 -- the verdict is one the skill does not classify as preserve.
|
|
327
|
+
if ($recordProperties -notcontains 'verdict') {
|
|
328
|
+
return $false
|
|
329
|
+
}
|
|
330
|
+
if ($script:AuthorizedRemovalVerdicts -cnotcontains ([string]$record.verdict)) {
|
|
331
|
+
return $false
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# Condition 9 -- the branch state is one of the two durable-residual states.
|
|
335
|
+
if ($recordProperties -notcontains 'branch_state') {
|
|
336
|
+
return $false
|
|
337
|
+
}
|
|
338
|
+
if ($script:AuthorizedBranchStates -cnotcontains ([string]$record.branch_state)) {
|
|
339
|
+
return $false
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return $true
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function Test-CleanupManifestCheckpointCoversPath {
|
|
346
|
+
<#
|
|
347
|
+
.SYNOPSIS
|
|
348
|
+
Report whether an orchestration checkpoint records the removal target at
|
|
349
|
+
all.
|
|
350
|
+
.DESCRIPTION
|
|
351
|
+
Condition 10 of the specification's allow predicate. This is a PRESENCE
|
|
352
|
+
test, deliberately not an authorization test: a record matching the target
|
|
353
|
+
makes the manifest branch inapplicable regardless of that record's
|
|
354
|
+
merge_status, so a removal the checkpoint does not authorize still reaches
|
|
355
|
+
the gate's existing deny. Reusing the gates' merge_status predicate here
|
|
356
|
+
would reopen the one path by which the manifest could widen what the gates
|
|
357
|
+
protect.
|
|
358
|
+
.PARAMETER Checkpoint
|
|
359
|
+
Parsed checkpoint object, or $null when absent or unreadable.
|
|
360
|
+
.PARAMETER RecordArrayName
|
|
361
|
+
Name of the record array to scan: features for the epic checkpoint, items
|
|
362
|
+
for the parallel checkpoint.
|
|
363
|
+
.PARAMETER WorktreePath
|
|
364
|
+
The removal target extracted from the command text.
|
|
365
|
+
.OUTPUTS
|
|
366
|
+
System.Boolean
|
|
367
|
+
#>
|
|
368
|
+
[CmdletBinding()]
|
|
369
|
+
[OutputType([bool])]
|
|
370
|
+
param(
|
|
371
|
+
[AllowNull()]
|
|
372
|
+
$Checkpoint,
|
|
373
|
+
|
|
374
|
+
[Parameter(Mandatory = $true)]
|
|
375
|
+
[ValidateNotNullOrEmpty()]
|
|
376
|
+
[string] $RecordArrayName,
|
|
377
|
+
|
|
378
|
+
[AllowNull()]
|
|
379
|
+
[AllowEmptyString()]
|
|
380
|
+
[string] $WorktreePath
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
if ($null -eq $Checkpoint -or [string]::IsNullOrWhiteSpace($WorktreePath)) {
|
|
384
|
+
return $false
|
|
385
|
+
}
|
|
386
|
+
$checkpointProperties = @($Checkpoint.PSObject.Properties.Name)
|
|
387
|
+
if ($checkpointProperties -notcontains $RecordArrayName) {
|
|
388
|
+
return $false
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
$normalizedTarget = ConvertTo-CleanupWorktreeManifestNormalizedPath -Path $WorktreePath
|
|
392
|
+
|
|
393
|
+
foreach ($record in @($Checkpoint.PSObject.Properties[$RecordArrayName].Value)) {
|
|
394
|
+
if ($null -eq $record) {
|
|
395
|
+
continue
|
|
396
|
+
}
|
|
397
|
+
$recordProperties = @($record.PSObject.Properties.Name)
|
|
398
|
+
if ($recordProperties -notcontains 'worktree_path') {
|
|
399
|
+
continue
|
|
400
|
+
}
|
|
401
|
+
$normalizedRecordPath = ConvertTo-CleanupWorktreeManifestNormalizedPath -Path ([string]$record.worktree_path)
|
|
402
|
+
if ($normalizedRecordPath -eq $normalizedTarget) {
|
|
403
|
+
return $true
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return $false
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
Export-ModuleMember -Function `
|
|
410
|
+
Get-CleanupWorktreeManifestContent, `
|
|
411
|
+
Get-CleanupWorktreeManifestUtcNow, `
|
|
412
|
+
ConvertTo-CleanupWorktreeManifestNormalizedPath, `
|
|
413
|
+
Find-CleanupWorktreeManifestRemovalRecord, `
|
|
414
|
+
Test-CleanupWorktreeManifestAuthorizesRemoval, `
|
|
415
|
+
Test-CleanupManifestCheckpointCoversPath
|