@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.
- package/out/mcp-server.js +542 -41
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +109 -5
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +390 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
- package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +61 -1
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
- package/resources/claude-customizations/.claude/settings.json +5 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +8 -7
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +24 -4
- package/resources/claude-customizations/config/blast-radius.json +8 -0
- package/resources/claude-customizations/pack-manifests/core.json +19 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +16 -1
package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1
ADDED
|
@@ -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
|
-
|
|
353
|
-
|
|
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()
|