@danmoisan/drm-copilot-mcp 1.0.21 → 1.0.22

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 (31) hide show
  1. package/out/mcp-server.js +1624 -190
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/MEMORY.md +5 -1
  4. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_commit_push_memory_before_pr.md +48 -2
  5. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_no_sendmessage_tool.md +35 -0
  6. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_worktree_isolation_branches_from_main.md +45 -0
  7. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +238 -0
  8. package/resources/claude-customizations/.claude/agents/parallel-planner.md +149 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +23 -11
  10. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +259 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +499 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate-helpers.ps1 +302 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +359 -0
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +244 -0
  15. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +379 -0
  16. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +491 -0
  17. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +490 -0
  18. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +429 -0
  19. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +366 -0
  20. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +184 -0
  21. package/resources/claude-customizations/.claude/settings.json +25 -0
  22. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +148 -0
  23. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +93 -0
  24. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +960 -0
  25. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +420 -0
  26. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +176 -0
  27. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +56 -0
  28. package/resources/claude-customizations/pack-manifests/core.json +19 -1
  29. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  30. package/resources/config/orchestration-routing.json +22 -0
  31. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +29 -0
@@ -0,0 +1,491 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Blast-radius input guards and truth-table resolution.
4
+
5
+ .DESCRIPTION
6
+ Destination-runtime PowerShell port of the guard and truth-table half of
7
+ scripts/dev_tools/_blast_radius_validation.py (require_text,
8
+ require_str_tuple, require_mapping, config_string_list, config_modules,
9
+ config_over_breadth_fraction, resolve_modules, resolve_shared_surfaces). The
10
+ construction-time invariants of the BlastRadius dataclass are ported in the
11
+ sibling module BlastRadiusValidation.psm1, which keeps every file inside the
12
+ 500-line limit.
13
+
14
+ The Python modules remain the authoritative reference implementation. This
15
+ module is one half of a two-language mirror; it never imports validator
16
+ logic. Every function is pure: no filesystem, subprocess, network, or
17
+ wall-clock access, and no input is mutated.
18
+
19
+ Parity notes for maintainers:
20
+ - The Python guards raise TypeError or ValueError; this port throws, which
21
+ is the PowerShell analog of a fail-fast contract violation.
22
+ - require_str_tuple rejects a bare string so Python cannot silently split
23
+ it into characters. The port keeps that rejection even though PowerShell
24
+ would not split, because the contract is "throw on malformed input" and
25
+ the two languages must agree on which inputs are malformed.
26
+ - A parsed truth table must be a dictionary. ConvertFrom-Json produces a
27
+ PSCustomObject by default, so callers should either pass -AsHashtable or
28
+ rely on the PSCustomObject conversion this module performs; no other
29
+ shape is accepted.
30
+ - Every returned collection is deduplicated and ordinally sorted.
31
+ #>
32
+
33
+ Set-StrictMode -Version Latest
34
+
35
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusExtraction.psm1') -Force
36
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force
37
+
38
+ # Keys read from the parsed config/blast-radius.json truth table.
39
+ $script:ConfigSharedSurfaceKey = 'shared_surfaces'
40
+ $script:ConfigSharedSurfaceGlobKey = 'shared_surface_globs'
41
+ $script:ConfigModuleKey = 'modules'
42
+ $script:ConfigOverBreadthKey = 'over_breadth_fraction'
43
+
44
+ # Numeric types a JSON or literal truth table may carry for the V3 threshold.
45
+ # Booleans are excluded explicitly because Python treats them as integers and the
46
+ # reference guard rejects them.
47
+ $script:NumericTypeName = @('System.Int32', 'System.Int64', 'System.Double',
48
+ 'System.Single', 'System.Decimal', 'System.Int16', 'System.Byte')
49
+
50
+
51
+ function Get-RequiredText {
52
+ <#
53
+ .SYNOPSIS
54
+ Guard a caller-supplied value that must be a string.
55
+
56
+ .DESCRIPTION
57
+ Port of require_text. Returns the validated value unchanged so call sites
58
+ can guard and assign in one expression.
59
+
60
+ .PARAMETER Value
61
+ Value of unknown runtime type.
62
+
63
+ .PARAMETER FieldName
64
+ Name used in the error message.
65
+
66
+ .PARAMETER AllowEmpty
67
+ When set, a blank string is accepted.
68
+
69
+ .OUTPUTS
70
+ System.String. The validated value, unchanged.
71
+ #>
72
+ [CmdletBinding()]
73
+ [OutputType([string])]
74
+ param(
75
+ [Parameter(Mandatory = $true)]
76
+ [AllowNull()]
77
+ [AllowEmptyString()]
78
+ [object] $Value,
79
+ [Parameter(Mandatory = $true)]
80
+ [string] $FieldName,
81
+ [switch] $AllowEmpty
82
+ )
83
+
84
+ if ($Value -isnot [string]) {
85
+ $actual = if ($null -eq $Value) { 'null' } else { $Value.GetType().Name }
86
+ throw "$FieldName must be a string, got $actual."
87
+ }
88
+ if (-not $AllowEmpty -and [string]::IsNullOrWhiteSpace($Value)) {
89
+ throw "$FieldName must not be empty."
90
+ }
91
+
92
+ return [string]$Value
93
+ }
94
+
95
+ function Get-RequiredStringList {
96
+ <#
97
+ .SYNOPSIS
98
+ Guard a caller-supplied string collection and normalize its order.
99
+
100
+ .DESCRIPTION
101
+ Port of require_str_tuple. A bare string is rejected because the Python
102
+ reference rejects it; accepting it here would make the two languages
103
+ disagree about which inputs are malformed. Every entry is validated
104
+ before sorting so an error names the offending value rather than a
105
+ position in an already-reordered collection.
106
+
107
+ .PARAMETER Value
108
+ A collection of non-blank strings.
109
+
110
+ .PARAMETER FieldName
111
+ Name used in the error message.
112
+
113
+ .OUTPUTS
114
+ System.Object[]. Entries deduplicated and ordinally sorted for parity.
115
+ #>
116
+ [CmdletBinding()]
117
+ [OutputType([System.Object[]])]
118
+ param(
119
+ [Parameter(Mandatory = $true)]
120
+ [AllowNull()]
121
+ [object] $Value,
122
+ [Parameter(Mandatory = $true)]
123
+ [string] $FieldName
124
+ )
125
+
126
+ if ($Value -is [string] -or $Value -isnot [System.Collections.IEnumerable]) {
127
+ $actual = if ($null -eq $Value) { 'null' } else { $Value.GetType().Name }
128
+ throw "$FieldName must be a list or tuple, got $actual."
129
+ }
130
+
131
+ $entry = [System.Collections.Generic.List[string]]::new()
132
+ foreach ($item in $Value) {
133
+ $entry.Add((Get-RequiredText -Value $item -FieldName "$FieldName entry"))
134
+ }
135
+
136
+ return @(Get-OrdinalSortedEntry -Entry $entry.ToArray())
137
+ }
138
+
139
+ function Get-RequiredMapping {
140
+ <#
141
+ .SYNOPSIS
142
+ Guard a caller-supplied mapping such as the parsed truth table.
143
+
144
+ .DESCRIPTION
145
+ Port of require_mapping. A dictionary is returned as a hashtable view; a
146
+ PSCustomObject, which is what ConvertFrom-Json yields without
147
+ -AsHashtable, is converted from its properties. The input is read only
148
+ and never mutated.
149
+
150
+ .PARAMETER Value
151
+ Value of unknown runtime type.
152
+
153
+ .PARAMETER FieldName
154
+ Name used in the error message.
155
+
156
+ .OUTPUTS
157
+ System.Collections.Hashtable. The validated mapping.
158
+ #>
159
+ [CmdletBinding()]
160
+ [OutputType([hashtable])]
161
+ param(
162
+ [Parameter(Mandatory = $true)]
163
+ [AllowNull()]
164
+ [object] $Value,
165
+ [Parameter(Mandatory = $true)]
166
+ [string] $FieldName
167
+ )
168
+
169
+ if ($Value -is [hashtable]) {
170
+ return $Value
171
+ }
172
+
173
+ # The two remaining accepted shapes are any other IDictionary (for example an
174
+ # ordered dictionary) and the PSCustomObject ConvertFrom-Json produces; both
175
+ # are copied key by key so the caller's object is never mutated.
176
+ $copy = @{}
177
+ if ($Value -is [System.Collections.IDictionary]) {
178
+ foreach ($key in $Value.Keys) {
179
+ $copy[$key] = $Value[$key]
180
+ }
181
+ return $copy
182
+ }
183
+ if ($Value -is [System.Management.Automation.PSCustomObject]) {
184
+ foreach ($property in $Value.PSObject.Properties) {
185
+ $copy[$property.Name] = $property.Value
186
+ }
187
+ return $copy
188
+ }
189
+
190
+ $actual = if ($null -eq $Value) { 'null' } else { $Value.GetType().Name }
191
+ throw "$FieldName must be a mapping, got $actual."
192
+ }
193
+
194
+ function Get-ConfigStringList {
195
+ <#
196
+ .SYNOPSIS
197
+ Read an optional list-of-strings entry from the truth table.
198
+
199
+ .DESCRIPTION
200
+ Port of config_string_list. An absent or null key yields an empty list so
201
+ a minimal truth table stays usable.
202
+
203
+ .PARAMETER Config
204
+ Parsed config/blast-radius.json.
205
+
206
+ .PARAMETER Key
207
+ Truth-table key to read.
208
+
209
+ .OUTPUTS
210
+ System.Object[]. Entries sorted and deduplicated.
211
+ #>
212
+ [CmdletBinding()]
213
+ [OutputType([System.Object[]])]
214
+ param(
215
+ [Parameter(Mandatory = $true)]
216
+ [AllowNull()]
217
+ [object] $Config,
218
+ [Parameter(Mandatory = $true)]
219
+ [string] $Key
220
+ )
221
+
222
+ $mapping = Get-RequiredMapping -Value $Config -FieldName 'config'
223
+ if (-not $mapping.ContainsKey($Key) -or $null -eq $mapping[$Key]) {
224
+ return @()
225
+ }
226
+
227
+ return @(Get-RequiredStringList -Value $mapping[$Key] -FieldName "config[""$Key""]")
228
+ }
229
+
230
+ function Get-ConfigRootSurface {
231
+ <#
232
+ .SYNOPSIS
233
+ Read the separator-free subset of the configured shared surfaces.
234
+
235
+ .DESCRIPTION
236
+ Port of config_root_surfaces. This is the sole source of separator-free
237
+ path acceptance (issue #452). The extraction module has no access to the
238
+ truth table, so both entry points that must agree, Get-BlastRadius and
239
+ Test-BlastRadius, call this reader on the same -Config value and forward
240
+ the result as -RootSurface. Deriving the set from the shared_surfaces
241
+ list rather than a second hardcoded list is what keeps extraction and
242
+ surface resolution from desynchronizing.
243
+
244
+ shared_surface_globs is deliberately not a source: a glob can never be an
245
+ exact token match, and admitting one would classify as a glob rather than
246
+ a concrete path.
247
+
248
+ .PARAMETER Config
249
+ Parsed config/blast-radius.json. Only the shared_surfaces key is read.
250
+
251
+ .OUTPUTS
252
+ System.Object[]. The shared_surfaces entries carrying no '/', ordinally
253
+ sorted and deduplicated by the underlying reader. A config with no
254
+ shared_surfaces key yields an empty array, which reproduces pre-change
255
+ behavior.
256
+ #>
257
+ [CmdletBinding()]
258
+ [OutputType([System.Object[]])]
259
+ param(
260
+ [Parameter(Mandatory = $true)]
261
+ [AllowNull()]
262
+ [object] $Config
263
+ )
264
+
265
+ $listed = @(Get-ConfigStringList -Config $Config -Key $script:ConfigSharedSurfaceKey)
266
+
267
+ # Keep only the entries a bare inline-code token could match exactly. A
268
+ # surface carrying a separator is already reachable through the ordinary
269
+ # path-shape rules, so admitting it here would widen nothing; a
270
+ # separator-free surface is the only kind the classifier's separator test
271
+ # made unreachable.
272
+ $rootSurface = [System.Collections.Generic.List[string]]::new()
273
+ foreach ($surface in $listed) {
274
+ if (-not $surface.Contains('/')) {
275
+ $rootSurface.Add($surface)
276
+ }
277
+ }
278
+
279
+ return @($rootSurface.ToArray())
280
+ }
281
+
282
+ function Get-ConfigModuleEntry {
283
+ <#
284
+ .SYNOPSIS
285
+ Read the module map from the truth table as ordered name/glob pairs.
286
+
287
+ .DESCRIPTION
288
+ Port of config_modules. Each module is validated as it is read so a
289
+ malformed truth table fails at the first offending module rather than
290
+ producing a partial resolution. Pairs are returned ordered by module name
291
+ so resolution is deterministic in both languages.
292
+
293
+ .PARAMETER Config
294
+ Parsed config/blast-radius.json.
295
+
296
+ .OUTPUTS
297
+ System.Object[]. Hashtables with keys name and globs, ordered by name.
298
+ #>
299
+ [CmdletBinding()]
300
+ [OutputType([System.Object[]])]
301
+ param(
302
+ [Parameter(Mandatory = $true)]
303
+ [AllowNull()]
304
+ [object] $Config
305
+ )
306
+
307
+ $mapping = Get-RequiredMapping -Value $Config -FieldName 'config'
308
+ if (-not $mapping.ContainsKey($script:ConfigModuleKey) -or
309
+ $null -eq $mapping[$script:ConfigModuleKey]) {
310
+ return @()
311
+ }
312
+
313
+ $moduleMap = Get-RequiredMapping -Value $mapping[$script:ConfigModuleKey] `
314
+ -FieldName "config[""$script:ConfigModuleKey""]"
315
+
316
+ # Validate every key before ordering so the error names the offending module
317
+ # rather than a position in an already-sorted collection.
318
+ $name = [System.Collections.Generic.List[string]]::new()
319
+ foreach ($key in $moduleMap.Keys) {
320
+ $name.Add((Get-RequiredText -Value $key -FieldName "config[""$script:ConfigModuleKey""] key"))
321
+ }
322
+
323
+ $pair = [System.Collections.Generic.List[hashtable]]::new()
324
+ foreach ($module in @(Get-OrdinalSortedEntry -Entry $name.ToArray())) {
325
+ $globs = @(Get-RequiredStringList -Value $moduleMap[$module] `
326
+ -FieldName "config[""$script:ConfigModuleKey""][$module]")
327
+ $pair.Add(@{ name = $module; globs = $globs })
328
+ }
329
+
330
+ return @($pair.ToArray())
331
+ }
332
+
333
+ function Get-ConfigOverBreadthFraction {
334
+ <#
335
+ .SYNOPSIS
336
+ Read the V3 over-breadth threshold from the truth table.
337
+
338
+ .DESCRIPTION
339
+ Port of config_over_breadth_fraction. Booleans are rejected explicitly
340
+ because Python treats them as integers, and the value must lie within
341
+ (0, 1].
342
+
343
+ .PARAMETER Config
344
+ Parsed config/blast-radius.json.
345
+
346
+ .OUTPUTS
347
+ System.Double. The fraction of tracked files above which a radius is
348
+ over-broad.
349
+ #>
350
+ [CmdletBinding()]
351
+ [OutputType([double])]
352
+ param(
353
+ [Parameter(Mandatory = $true)]
354
+ [AllowNull()]
355
+ [object] $Config
356
+ )
357
+
358
+ $mapping = Get-RequiredMapping -Value $Config -FieldName 'config'
359
+ $value = $null
360
+ if ($mapping.ContainsKey($script:ConfigOverBreadthKey)) {
361
+ $value = $mapping[$script:ConfigOverBreadthKey]
362
+ }
363
+
364
+ if ($value -is [bool] -or $null -eq $value -or
365
+ $script:NumericTypeName -notcontains $value.GetType().FullName) {
366
+ throw "config[""$script:ConfigOverBreadthKey""] must be a number in (0, 1]."
367
+ }
368
+
369
+ $fraction = [double]$value
370
+ if (-not ($fraction -gt 0 -and $fraction -le 1)) {
371
+ throw "config[""$script:ConfigOverBreadthKey""] must be within (0, 1]."
372
+ }
373
+
374
+ return $fraction
375
+ }
376
+
377
+ function Resolve-BlastRadiusModule {
378
+ <#
379
+ .SYNOPSIS
380
+ Resolve path entries to the module names of the truth-table map.
381
+
382
+ .DESCRIPTION
383
+ Port of resolve_modules. A module joins the radius as soon as one of its
384
+ globs covers one entry, so the search stops at the first hit per module.
385
+ A path matching no glob resolves to no module.
386
+
387
+ .PARAMETER PathEntry
388
+ Concrete paths and globs of a radius. An empty collection is accepted.
389
+
390
+ .PARAMETER Config
391
+ Parsed config/blast-radius.json.
392
+
393
+ .OUTPUTS
394
+ System.Object[]. Matched module names, deduplicated and ordinally sorted.
395
+ #>
396
+ [CmdletBinding()]
397
+ [OutputType([System.Object[]])]
398
+ param(
399
+ [Parameter(Mandatory = $true)]
400
+ [AllowEmptyCollection()]
401
+ [AllowEmptyString()]
402
+ [string[]] $PathEntry,
403
+ [Parameter(Mandatory = $true)]
404
+ [AllowNull()]
405
+ [object] $Config
406
+ )
407
+
408
+ $matched = [System.Collections.Generic.List[string]]::new()
409
+ foreach ($pair in @(Get-ConfigModuleEntry -Config $Config)) {
410
+ foreach ($pattern in $pair['globs']) {
411
+ $hit = $false
412
+ foreach ($entry in $PathEntry) {
413
+ if (Test-GlobMatch -Pattern $pattern -Candidate $entry) {
414
+ $matched.Add([string]$pair['name'])
415
+ $hit = $true
416
+ break
417
+ }
418
+ }
419
+ if ($hit) {
420
+ break
421
+ }
422
+ }
423
+ }
424
+
425
+ return @(Get-OrdinalSortedEntry -Entry $matched.ToArray())
426
+ }
427
+
428
+ function Resolve-BlastRadiusSharedSurface {
429
+ <#
430
+ .SYNOPSIS
431
+ Select the concrete paths that are shared surfaces.
432
+
433
+ .DESCRIPTION
434
+ Port of resolve_shared_surfaces. Membership has two independent sources,
435
+ the literal truth-table list and the membership globs. A glob hit counts
436
+ even when the path is absent from the literal list, which is the
437
+ fail-closed direction.
438
+
439
+ .PARAMETER ConcretePath
440
+ Wildcard-free paths of a radius or plan. An empty collection is accepted.
441
+
442
+ .PARAMETER Config
443
+ Parsed config/blast-radius.json.
444
+
445
+ .OUTPUTS
446
+ System.Object[]. Touched surfaces, deduplicated and ordinally sorted.
447
+ #>
448
+ [CmdletBinding()]
449
+ [OutputType([System.Object[]])]
450
+ param(
451
+ [Parameter(Mandatory = $true)]
452
+ [AllowEmptyCollection()]
453
+ [AllowEmptyString()]
454
+ [string[]] $ConcretePath,
455
+ [Parameter(Mandatory = $true)]
456
+ [AllowNull()]
457
+ [object] $Config
458
+ )
459
+
460
+ $listed = [System.Collections.Generic.HashSet[string]]::new(
461
+ [string[]]@(Get-ConfigStringList -Config $Config -Key $script:ConfigSharedSurfaceKey),
462
+ [StringComparer]::Ordinal)
463
+ $surfaceGlob = [string[]]@(Get-ConfigStringList -Config $Config -Key $script:ConfigSharedSurfaceGlobKey)
464
+
465
+ $touched = [System.Collections.Generic.List[string]]::new()
466
+ foreach ($path in $ConcretePath) {
467
+ if ($listed.Contains($path)) {
468
+ $touched.Add($path)
469
+ continue
470
+ }
471
+ foreach ($pattern in $surfaceGlob) {
472
+ if (Test-GlobMatch -Pattern $pattern -Candidate $path) {
473
+ $touched.Add($path)
474
+ break
475
+ }
476
+ }
477
+ }
478
+
479
+ return @(Get-OrdinalSortedEntry -Entry $touched.ToArray())
480
+ }
481
+
482
+ Export-ModuleMember -Function `
483
+ Get-RequiredText, `
484
+ Get-RequiredStringList, `
485
+ Get-RequiredMapping, `
486
+ Get-ConfigStringList, `
487
+ Get-ConfigRootSurface, `
488
+ Get-ConfigModuleEntry, `
489
+ Get-ConfigOverBreadthFraction, `
490
+ Resolve-BlastRadiusModule, `
491
+ Resolve-BlastRadiusSharedSurface