@danmoisan/drm-copilot-mcp 1.0.26 → 1.0.27

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 (38) hide show
  1. package/out/mcp-server.js +542 -41
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +109 -5
  4. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +390 -0
  5. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
  6. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
  7. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
  8. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
  9. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
  10. package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
  11. package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
  12. package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
  13. package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
  14. package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
  15. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +61 -1
  16. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
  17. package/resources/claude-customizations/.claude/settings.json +5 -0
  18. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
  19. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  20. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
  21. package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
  22. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
  23. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
  24. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
  25. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
  26. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
  27. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
  28. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
  29. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
  30. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
  31. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +8 -7
  32. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +24 -4
  33. package/resources/claude-customizations/config/blast-radius.json +8 -0
  34. package/resources/claude-customizations/pack-manifests/core.json +19 -1
  35. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  36. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  37. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  38. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +16 -1
@@ -0,0 +1,295 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Blast-radius normalization: contract extraction, module resolution, and the
4
+ read-by-mandate exclusion.
5
+
6
+ .DESCRIPTION
7
+ Destination-runtime PowerShell port of
8
+ scripts/dev_tools/_blast_radius_normalization.py, plus the two functions
9
+ relocated here so their source modules stay inside the 500-line limit
10
+ (issue #489): Get-ContractIdentifier from BlastRadiusExtraction.psm1 and
11
+ Resolve-BlastRadiusModule from BlastRadiusConfig.psm1.
12
+
13
+ The Python modules remain the authoritative reference implementation. This
14
+ module is one half of a two-language mirror; it never imports validator
15
+ logic. Every function is pure: no filesystem, subprocess, network, or
16
+ wall-clock access, and no input is mutated.
17
+
18
+ Parity notes for maintainers:
19
+ - Get-ContractIdentifier carries the module-scoped variables it reads
20
+ ($script:HeadingPattern, $script:ContractHeadingKeyword, and
21
+ $script:NoQualifyingHeadingDepth). PowerShell scopes $script: per module,
22
+ so a function-only relocation would resolve them to $null at runtime.
23
+ - Test-MandateRead applies exact ordinal equality first, which is the only
24
+ rule that can settle a glob entry, then glob containment for a concrete
25
+ entry only. A glob entry is never tested for containment in another glob,
26
+ matching matches_mandate_read.
27
+ - An empty mandate-read collection excludes nothing, so a truth table with
28
+ no mandate_reads key reproduces pre-change behaviour exactly.
29
+ - Every returned collection is deduplicated and ordinally sorted.
30
+ #>
31
+
32
+ Set-StrictMode -Version Latest
33
+
34
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusExtraction.psm1') -Force
35
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force
36
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1') -Force
37
+
38
+ # Markdown ATX heading pattern used to locate spec interface sections. Relocated
39
+ # with Get-ContractIdentifier: $script: scope is per module, so the variable must
40
+ # travel with its only consumer.
41
+ $script:HeadingPattern = [regex]::new('^(?<hashes>#{1,6}) (?<title>.+)$')
42
+
43
+ # A spec section qualifies as an interface section when its heading, or the
44
+ # heading of an ancestor section, contains one of these words.
45
+ $script:ContractHeadingKeyword = @('API', 'Interface', 'Contract', 'Surface')
46
+
47
+ # Heading depth sentinel standing in for the Python `qualifying_depth is None`
48
+ # state: markdown heading levels are 1..6, so 0 can never be a real level.
49
+ $script:NoQualifyingHeadingDepth = 0
50
+
51
+ # A contract identifier names something callable or referenceable, so it must
52
+ # carry at least one ASCII letter. Punctuation-only tokens such as -> or a bare
53
+ # digit are notation from an interface example rather than a contract, and
54
+ # admitting them made unrelated specs contend (issue #489).
55
+ $script:ContractLetterPattern = [regex]::new('[A-Za-z]')
56
+
57
+
58
+ function Get-ContractIdentifier {
59
+ <#
60
+ .SYNOPSIS
61
+ Extract contract identifiers from a spec's interface sections.
62
+
63
+ .DESCRIPTION
64
+ Port of extract_contract_identifiers. Implements the contracts level of
65
+ the radius model: exported symbols, schema names, and CLI identifiers
66
+ named in inline code inside sections whose heading, or an ancestor
67
+ heading, contains API, Interface, Contract, or Surface. Markdown sections
68
+ nest, so a heading deeper than the innermost qualifying heading stays
69
+ inside that section and inherits its qualification; a heading at or above
70
+ that level ends the section and is judged on its own title.
71
+
72
+ .PARAMETER SpecText
73
+ Full feature spec.md document text; may be empty.
74
+
75
+ .OUTPUTS
76
+ System.Object[]. Identifiers, deduplicated and ordinally sorted. Tokens
77
+ containing a separator are excluded as path references, as are tokens
78
+ carrying no ASCII letter.
79
+ #>
80
+ [CmdletBinding()]
81
+ [OutputType([System.Object[]])]
82
+ param(
83
+ [Parameter(Mandatory = $true)]
84
+ [AllowEmptyString()]
85
+ [string] $SpecText
86
+ )
87
+
88
+ $identifier = [System.Collections.Generic.List[string]]::new()
89
+ $qualifyingDepth = $script:NoQualifyingHeadingDepth
90
+
91
+ foreach ($line in @(ConvertTo-NormalizedLine -Text $SpecText)) {
92
+ $headingMatch = $script:HeadingPattern.Match($line)
93
+
94
+ # A heading changes the section context and contributes no identifiers of
95
+ # its own, so each heading is handled and the line is then skipped.
96
+ if ($headingMatch.Success) {
97
+ $headingLevel = $headingMatch.Groups['hashes'].Value.Length
98
+ if ($qualifyingDepth -ne $script:NoQualifyingHeadingDepth -and
99
+ $headingLevel -gt $qualifyingDepth) {
100
+ continue
101
+ }
102
+
103
+ $headingTitle = $headingMatch.Groups['title'].Value
104
+ $qualifyingDepth = $script:NoQualifyingHeadingDepth
105
+ foreach ($keyword in $script:ContractHeadingKeyword) {
106
+ if ($headingTitle.IndexOf($keyword, [System.StringComparison]::Ordinal) -ge 0) {
107
+ $qualifyingDepth = $headingLevel
108
+ break
109
+ }
110
+ }
111
+ continue
112
+ }
113
+
114
+ if ($qualifyingDepth -eq $script:NoQualifyingHeadingDepth) {
115
+ continue
116
+ }
117
+
118
+ # Inside a qualifying section an inline-code token without a separator is
119
+ # a contract identifier; a token with one is a path reference and is
120
+ # recorded at the paths level instead. A token carrying no ASCII letter
121
+ # is notation, not an identifier, and is dropped (issue #489).
122
+ foreach ($token in @(Get-InlineCodeToken -Line $line)) {
123
+ if ($token.IndexOf('/') -lt 0 -and
124
+ $script:ContractLetterPattern.IsMatch($token)) {
125
+ $identifier.Add($token)
126
+ }
127
+ }
128
+ }
129
+
130
+ return @(Get-OrdinalSortedEntry -Entry $identifier.ToArray())
131
+ }
132
+
133
+ function Resolve-BlastRadiusModule {
134
+ <#
135
+ .SYNOPSIS
136
+ Resolve path entries to the module names of the truth-table map.
137
+
138
+ .DESCRIPTION
139
+ Port of resolve_modules. A module joins the radius as soon as one of its
140
+ globs covers one entry, so the search stops at the first hit per module.
141
+ A path matching no glob resolves to no module.
142
+
143
+ .PARAMETER PathEntry
144
+ Concrete paths and globs of a radius. An empty collection is accepted.
145
+
146
+ .PARAMETER Config
147
+ Parsed config/blast-radius.json.
148
+
149
+ .OUTPUTS
150
+ System.Object[]. Matched module names, deduplicated and ordinally sorted.
151
+ #>
152
+ [CmdletBinding()]
153
+ [OutputType([System.Object[]])]
154
+ param(
155
+ [Parameter(Mandatory = $true)]
156
+ [AllowEmptyCollection()]
157
+ [AllowEmptyString()]
158
+ [string[]] $PathEntry,
159
+ [Parameter(Mandatory = $true)]
160
+ [AllowNull()]
161
+ [object] $Config
162
+ )
163
+
164
+ $matched = [System.Collections.Generic.List[string]]::new()
165
+ foreach ($pair in @(Get-ConfigModuleEntry -Config $Config)) {
166
+ foreach ($pattern in $pair['globs']) {
167
+ $hit = $false
168
+ foreach ($entry in $PathEntry) {
169
+ if (Test-GlobMatch -Pattern $pattern -Candidate $entry) {
170
+ $matched.Add([string]$pair['name'])
171
+ $hit = $true
172
+ break
173
+ }
174
+ }
175
+ if ($hit) {
176
+ break
177
+ }
178
+ }
179
+ }
180
+
181
+ return @(Get-OrdinalSortedEntry -Entry $matched.ToArray())
182
+ }
183
+
184
+
185
+ function Test-MandateRead {
186
+ <#
187
+ .SYNOPSIS
188
+ Report whether one radius entry is a read-by-mandate citation.
189
+
190
+ .DESCRIPTION
191
+ Port of matches_mandate_read. Exact ordinal equality settles both a
192
+ concrete path listed verbatim and a glob entry that repeats a configured
193
+ glob character for character. Glob containment then covers a concrete
194
+ path falling inside a configured subtree pattern such as artifacts/**. A
195
+ glob entry is deliberately never tested for containment in another glob:
196
+ deciding whether one pattern subsumes another is not a comparison this
197
+ repository's glob vocabulary supports, and guessing would silently drop a
198
+ genuine claim.
199
+
200
+ .PARAMETER Entry
201
+ One harvested or extracted radius entry: a concrete repository-relative
202
+ path or a glob pattern.
203
+
204
+ .PARAMETER MandateRead
205
+ Configured mandate-read patterns from Get-ConfigMandateRead. An empty
206
+ collection matches nothing.
207
+
208
+ .OUTPUTS
209
+ System.Boolean. True when the entry must be excluded from contention.
210
+ #>
211
+ [CmdletBinding()]
212
+ [OutputType([bool])]
213
+ param(
214
+ [Parameter(Mandatory = $true)]
215
+ [AllowEmptyString()]
216
+ [string] $Entry,
217
+ [Parameter(Mandatory = $true)]
218
+ [AllowEmptyCollection()]
219
+ [AllowEmptyString()]
220
+ [string[]] $MandateRead
221
+ )
222
+
223
+ foreach ($pattern in $MandateRead) {
224
+ if ([string]::Equals($Entry, $pattern, [System.StringComparison]::Ordinal)) {
225
+ return $true
226
+ }
227
+ }
228
+
229
+ # A glob entry that did not match exactly is left alone; only a concrete path
230
+ # is tested for containment in a configured subtree pattern.
231
+ if (Test-GlobEntry -Entry $Entry) {
232
+ return $false
233
+ }
234
+
235
+ foreach ($pattern in $MandateRead) {
236
+ if (Test-GlobMatch -Pattern $pattern -Candidate $Entry) {
237
+ return $true
238
+ }
239
+ }
240
+
241
+ return $false
242
+ }
243
+
244
+ function Get-NonMandateReadEntry {
245
+ <#
246
+ .SYNOPSIS
247
+ Drop every read-by-mandate citation from a collection of radius entries.
248
+
249
+ .DESCRIPTION
250
+ Port of exclude_mandate_reads. Every agent is instructed to read the
251
+ policy rules, the tier map, and the process artifacts before doing any
252
+ work, so a citation of one of those paths is evidence that the author
253
+ obeyed the reading order rather than evidence that the change will write
254
+ the file. Counting such citations as contention made thematically
255
+ unrelated work items collide (issue #489).
256
+
257
+ .PARAMETER Entry
258
+ Harvested or extracted radius entries. An empty collection is accepted.
259
+
260
+ .PARAMETER MandateRead
261
+ Configured mandate-read patterns from Get-ConfigMandateRead. An empty
262
+ collection excludes nothing, so the returned content equals the input
263
+ content.
264
+
265
+ .OUTPUTS
266
+ System.Object[]. Surviving entries, deduplicated and ordinally sorted.
267
+ #>
268
+ [CmdletBinding()]
269
+ [OutputType([System.Object[]])]
270
+ param(
271
+ [Parameter(Mandatory = $true)]
272
+ [AllowEmptyCollection()]
273
+ [AllowEmptyString()]
274
+ [string[]] $Entry,
275
+ [Parameter(Mandatory = $true)]
276
+ [AllowEmptyCollection()]
277
+ [AllowEmptyString()]
278
+ [string[]] $MandateRead
279
+ )
280
+
281
+ $survivor = [System.Collections.Generic.List[string]]::new()
282
+ foreach ($candidate in $Entry) {
283
+ if (-not (Test-MandateRead -Entry $candidate -MandateRead $MandateRead)) {
284
+ $survivor.Add($candidate)
285
+ }
286
+ }
287
+
288
+ return @(Get-OrdinalSortedEntry -Entry $survivor.ToArray())
289
+ }
290
+
291
+ Export-ModuleMember -Function `
292
+ Get-ContractIdentifier, `
293
+ Resolve-BlastRadiusModule, `
294
+ Test-MandateRead, `
295
+ Get-NonMandateReadEntry
@@ -37,6 +37,7 @@ Set-StrictMode -Version Latest
37
37
  Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusExtraction.psm1') -Force
38
38
  Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force
39
39
  Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1') -Force
40
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusNormalization.psm1') -Force
40
41
 
41
42
  # Finding vocabulary. These strings are contract literals consumed by the
42
43
  # downstream parallel schema and planner features.
@@ -348,9 +349,14 @@ function Test-BlastRadius {
348
349
  # The root-surface set comes from the same -Config value that V1 and V2 use
349
350
  # below to resolve modules and shared surfaces, and from the same reader
350
351
  # Get-BlastRadius calls. That shared source is what keeps a derived radius
351
- # passing V1 and V2 against its own plan (issue #452).
352
- $planPath = [string[]]@(Get-PlanPaths -PlanText $PlanText `
353
- -RootSurface ([string[]]@(Get-ConfigRootSurface -Config $Config)))
352
+ # passing V1 and V2 against its own plan (issue #452). The mandate-read
353
+ # exclusion is applied here for the same reason: the derivation harvest drops
354
+ # those citations, so V1 and V2 must not then demand that the radius cover
355
+ # them (issue #489).
356
+ $planPath = [string[]]@(Get-NonMandateReadEntry -MandateRead (
357
+ [string[]]@(Get-ConfigMandateRead -Config $Config)) -Entry (
358
+ [string[]]@(Get-PlanPaths -PlanText $PlanText `
359
+ -RootSurface ([string[]]@(Get-ConfigRootSurface -Config $Config)))))
354
360
  $planConcrete = [string[]]@(Get-ConcreteEntry -Entry $planPath)
355
361
 
356
362
  $finding = [System.Collections.Generic.List[hashtable]]::new()