@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.
Files changed (46) hide show
  1. package/out/mcp-server.js +3116 -910
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +15 -2
  4. package/resources/claude-customizations/.claude/agents/parallel-planner.md +3 -0
  5. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +49 -14
  6. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +52 -3
  7. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +7 -1
  8. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +103 -6
  9. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +58 -3
  10. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +37 -5
  11. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +13 -2
  12. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +35 -7
  13. package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +483 -0
  14. package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +483 -0
  15. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +218 -6
  16. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +18 -75
  17. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +290 -0
  18. package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +415 -0
  19. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMerge.psm1 +355 -0
  20. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1 +318 -0
  21. package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 +229 -0
  22. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +66 -2
  23. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +311 -16
  24. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -0
  25. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +3 -1
  26. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +36 -2
  27. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +6 -0
  28. package/resources/claude-customizations/.claude/skills/powershell-orchestration-state-machine/SKILL.md +29 -1
  29. package/resources/claude-customizations/config/blast-radius.json +7 -0
  30. package/resources/claude-customizations/pack-manifests/core.json +7 -0
  31. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +41 -0
  32. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -0
  33. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +26 -0
  34. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  35. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +49 -3
  36. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +57 -12
  37. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +34 -8
  38. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -1
  39. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +34 -7
  40. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +483 -0
  41. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +483 -0
  42. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +130 -2
  43. package/resources/codex-and-agents-customizations/pack-manifests/core.json +6 -1
  44. package/resources/config/orchestration-handoff-registry.json +138 -0
  45. package/resources/config/orchestration-handoff.schema.json +472 -0
  46. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +38 -0
@@ -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