@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
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Quote-aware single-line scanner for the structural Mermaid validator.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Pure string analysis for one line of Mermaid source. Every rule here exists to
|
|
7
|
+
avoid a specific false positive catalogued in the feature research:
|
|
8
|
+
|
|
9
|
+
- Characters inside a double-quoted span never participate in bracket
|
|
10
|
+
balance, arrow scanning, or comment detection, so `A["foo[bar](baz)"]`
|
|
11
|
+
and `A["50%% off"]` are accepted.
|
|
12
|
+
- A backslash is an ordinary character. Mermaid has no escape system; the
|
|
13
|
+
documented mechanism is the `#quot;` entity. Treating `\"` as an escape
|
|
14
|
+
would turn a valid label into a spurious unterminated-quote finding.
|
|
15
|
+
- Angle brackets are never structural, so `<br/>` in a label is inert and
|
|
16
|
+
`>` participates only as part of an arrow token.
|
|
17
|
+
- A `%%{...}%%` directive is recognized before comment stripping, so a
|
|
18
|
+
directive is never deleted as a comment.
|
|
19
|
+
- Arrow tokens are masked out before bracket counting, because `{`, `}`, and
|
|
20
|
+
`|` appear inside ER cardinality tokens (`||--o{`) and class relations
|
|
21
|
+
(`<|--`) where they are not brackets at all.
|
|
22
|
+
- Bracket-label contents are masked before arrow scanning, so arrow-like
|
|
23
|
+
text inside a node label is not read as an edge token.
|
|
24
|
+
- A single-character arrow core is kept only when an affix was applied, so
|
|
25
|
+
the tilde delimiters of a class generic (`List~int~`) are not arrow
|
|
26
|
+
tokens while the one-dash sequence arrows (`->`, `-x`, `-)`) still are.
|
|
27
|
+
- The letter-shaped affixes `o` and `x` are absorbed only across a
|
|
28
|
+
non-word boundary, so the `x` in `Box--Bar` never extends the `--` core.
|
|
29
|
+
|
|
30
|
+
Pinned to Mermaid 11.17.0 through MermaidGrammar.psm1. Every function is
|
|
31
|
+
pure: no filesystem, subprocess, network, or wall-clock access, and no input
|
|
32
|
+
is mutated.
|
|
33
|
+
#>
|
|
34
|
+
|
|
35
|
+
Set-StrictMode -Version Latest
|
|
36
|
+
|
|
37
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'MermaidGrammar.psm1') -Force
|
|
38
|
+
|
|
39
|
+
# Arrow-token affix tables. An arrow candidate is a run of `-`, `=`, `.`, or `~`
|
|
40
|
+
# (the "core") optionally extended by one of these affixes on either side. The
|
|
41
|
+
# two-character forms are tried before the one-character forms so `<<-->>` and
|
|
42
|
+
# `||--o{` tokenize whole.
|
|
43
|
+
$script:ArrowLeftAffixTwo = @('<<', '<|', '}o', '}|', '|o', '||')
|
|
44
|
+
$script:ArrowLeftAffixOne = @('<', 'o', 'x', '*')
|
|
45
|
+
$script:ArrowRightAffixTwo = @('>>', '|>', 'o|', 'o{', '||', '|{')
|
|
46
|
+
$script:ArrowRightAffixOne = @('>', 'o', 'x', ')', '*', '\', '/')
|
|
47
|
+
|
|
48
|
+
function Test-MermaidWordCharacter {
|
|
49
|
+
<#
|
|
50
|
+
.SYNOPSIS
|
|
51
|
+
Returns $true when the character is a letter, digit, or underscore.
|
|
52
|
+
#>
|
|
53
|
+
[CmdletBinding()]
|
|
54
|
+
[OutputType([bool])]
|
|
55
|
+
param(
|
|
56
|
+
[Parameter(Mandatory)]
|
|
57
|
+
[char] $Character
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if ($Character -eq '_') {
|
|
61
|
+
return $true
|
|
62
|
+
}
|
|
63
|
+
return [char]::IsLetterOrDigit($Character)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Test-MermaidDirectiveLine {
|
|
67
|
+
<#
|
|
68
|
+
.SYNOPSIS
|
|
69
|
+
Returns $true when the line is a `%%{...}%%` init directive.
|
|
70
|
+
#>
|
|
71
|
+
[CmdletBinding()]
|
|
72
|
+
[OutputType([bool])]
|
|
73
|
+
param(
|
|
74
|
+
[AllowEmptyString()]
|
|
75
|
+
[string] $Line
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if ([string]::IsNullOrWhiteSpace($Line)) {
|
|
79
|
+
return $false
|
|
80
|
+
}
|
|
81
|
+
return $Line.TrimStart().StartsWith('%%{', [System.StringComparison]::Ordinal)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function Test-MermaidCommentLine {
|
|
85
|
+
<#
|
|
86
|
+
.SYNOPSIS
|
|
87
|
+
Returns $true when the whole line is a `%%` comment and not a directive.
|
|
88
|
+
#>
|
|
89
|
+
[CmdletBinding()]
|
|
90
|
+
[OutputType([bool])]
|
|
91
|
+
param(
|
|
92
|
+
[AllowEmptyString()]
|
|
93
|
+
[string] $Line
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if ([string]::IsNullOrWhiteSpace($Line)) {
|
|
97
|
+
return $false
|
|
98
|
+
}
|
|
99
|
+
if (Test-MermaidDirectiveLine -Line $Line) {
|
|
100
|
+
return $false
|
|
101
|
+
}
|
|
102
|
+
return $Line.TrimStart().StartsWith('%%', [System.StringComparison]::Ordinal)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function Get-MermaidQuoteMaskedText {
|
|
106
|
+
<#
|
|
107
|
+
.SYNOPSIS
|
|
108
|
+
Masks quoted spans and strips a trailing `%%` comment from one line.
|
|
109
|
+
.DESCRIPTION
|
|
110
|
+
Returns Text (every character of every double-quoted span, including the
|
|
111
|
+
quote marks, replaced by a space, truncated at the first `%%` outside a
|
|
112
|
+
quoted span; indices align one-for-one with the input up to that point),
|
|
113
|
+
HasUnterminatedQuote, and ColonIndex (index of the first `:` outside a
|
|
114
|
+
quoted span, or -1).
|
|
115
|
+
#>
|
|
116
|
+
[CmdletBinding()]
|
|
117
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
118
|
+
param(
|
|
119
|
+
[AllowEmptyString()]
|
|
120
|
+
[string] $Line
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
$builder = [System.Text.StringBuilder]::new()
|
|
124
|
+
$inQuote = $false
|
|
125
|
+
$colonIndex = -1
|
|
126
|
+
$index = 0
|
|
127
|
+
|
|
128
|
+
while ($index -lt $Line.Length) {
|
|
129
|
+
$character = $Line[$index]
|
|
130
|
+
|
|
131
|
+
if ($inQuote) {
|
|
132
|
+
if ($character -eq '"') {
|
|
133
|
+
$inQuote = $false
|
|
134
|
+
}
|
|
135
|
+
[void]$builder.Append(' ')
|
|
136
|
+
$index++
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if ($character -eq '"') {
|
|
141
|
+
$inQuote = $true
|
|
142
|
+
[void]$builder.Append(' ')
|
|
143
|
+
$index++
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# A comment runs to end of line. The directive case is recognized by the
|
|
148
|
+
# caller before this function is reached, so it is never stripped here.
|
|
149
|
+
if ($character -eq '%' -and ($index + 1) -lt $Line.Length -and $Line[$index + 1] -eq '%') {
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if ($character -eq ':' -and $colonIndex -lt 0) {
|
|
154
|
+
$colonIndex = $index
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
[void]$builder.Append($character)
|
|
158
|
+
$index++
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return [ordered]@{
|
|
162
|
+
Text = $builder.ToString()
|
|
163
|
+
HasUnterminatedQuote = $inQuote
|
|
164
|
+
ColonIndex = $colonIndex
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function Get-MermaidArrowCandidate {
|
|
169
|
+
<#
|
|
170
|
+
.SYNOPSIS
|
|
171
|
+
Extracts the candidate arrow tokens from already-masked line text.
|
|
172
|
+
.PARAMETER Text
|
|
173
|
+
Masked line text, normally the ArrowText of Get-MermaidLineScan.
|
|
174
|
+
#>
|
|
175
|
+
[CmdletBinding()]
|
|
176
|
+
[OutputType([string[]])]
|
|
177
|
+
param(
|
|
178
|
+
[AllowEmptyString()]
|
|
179
|
+
[string] $Text
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
$candidates = [System.Collections.Generic.List[string]]::new()
|
|
183
|
+
if ([string]::IsNullOrEmpty($Text)) {
|
|
184
|
+
return [string[]]@()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
foreach ($match in [regex]::Matches($Text, '[-=.~]+')) {
|
|
188
|
+
$start = $match.Index
|
|
189
|
+
$length = $match.Length
|
|
190
|
+
|
|
191
|
+
$leftLength = 0
|
|
192
|
+
if ($start -ge 2 -and $script:ArrowLeftAffixTwo -contains $Text.Substring($start - 2, 2)) {
|
|
193
|
+
$leftLength = 2
|
|
194
|
+
} elseif ($start -ge 1) {
|
|
195
|
+
$affix = [string]$Text[$start - 1]
|
|
196
|
+
if ($script:ArrowLeftAffixOne -contains $affix) {
|
|
197
|
+
if ($affix -eq 'o' -or $affix -eq 'x') {
|
|
198
|
+
if ($start -lt 2 -or -not (Test-MermaidWordCharacter -Character $Text[$start - 2])) {
|
|
199
|
+
$leftLength = 1
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
$leftLength = 1
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
$end = $start + $length
|
|
208
|
+
$rightLength = 0
|
|
209
|
+
if (($end + 2) -le $Text.Length -and $script:ArrowRightAffixTwo -contains $Text.Substring($end, 2)) {
|
|
210
|
+
$rightLength = 2
|
|
211
|
+
} elseif ($end -lt $Text.Length) {
|
|
212
|
+
$affix = [string]$Text[$end]
|
|
213
|
+
if ($script:ArrowRightAffixOne -contains $affix) {
|
|
214
|
+
if ($affix -eq 'o' -or $affix -eq 'x') {
|
|
215
|
+
if (($end + 1) -ge $Text.Length -or -not (Test-MermaidWordCharacter -Character $Text[$end + 1])) {
|
|
216
|
+
$rightLength = 1
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
$rightLength = 1
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if ($length -lt 2 -and $leftLength -eq 0 -and $rightLength -eq 0) {
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
$candidates.Add($Text.Substring($start - $leftLength, $leftLength + $length + $rightLength))
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return [string[]]@($candidates.ToArray())
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function Get-MermaidLabelMaskedText {
|
|
235
|
+
<#
|
|
236
|
+
.SYNOPSIS
|
|
237
|
+
Masks the contents of bracket-delimited label spans, keeping the brackets.
|
|
238
|
+
.DESCRIPTION
|
|
239
|
+
Indices align one-for-one with the input. Closers are clamped at depth
|
|
240
|
+
zero so a sequence async arrow (`-)`) is preserved.
|
|
241
|
+
#>
|
|
242
|
+
[CmdletBinding()]
|
|
243
|
+
[OutputType([string])]
|
|
244
|
+
param(
|
|
245
|
+
[AllowEmptyString()]
|
|
246
|
+
[string] $Text
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
$builder = [System.Text.StringBuilder]::new()
|
|
250
|
+
$depth = 0
|
|
251
|
+
|
|
252
|
+
foreach ($character in $Text.ToCharArray()) {
|
|
253
|
+
if ($character -eq '[' -or $character -eq '(' -or $character -eq '{') {
|
|
254
|
+
$depth++
|
|
255
|
+
[void]$builder.Append($character)
|
|
256
|
+
} elseif ($character -eq ']' -or $character -eq ')' -or $character -eq '}') {
|
|
257
|
+
if ($depth -gt 0) {
|
|
258
|
+
$depth--
|
|
259
|
+
}
|
|
260
|
+
[void]$builder.Append($character)
|
|
261
|
+
} elseif ($depth -gt 0) {
|
|
262
|
+
[void]$builder.Append(' ')
|
|
263
|
+
} else {
|
|
264
|
+
[void]$builder.Append($character)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return $builder.ToString()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function Get-MermaidArrowMaskedText {
|
|
272
|
+
<#
|
|
273
|
+
.SYNOPSIS
|
|
274
|
+
Blanks every arrow candidate so bracket counting ignores arrow tokens.
|
|
275
|
+
#>
|
|
276
|
+
[CmdletBinding()]
|
|
277
|
+
[OutputType([string])]
|
|
278
|
+
param(
|
|
279
|
+
[AllowEmptyString()]
|
|
280
|
+
[string] $Text
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if ([string]::IsNullOrEmpty($Text)) {
|
|
284
|
+
return $Text
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
$characters = $Text.ToCharArray()
|
|
288
|
+
$offset = 0
|
|
289
|
+
foreach ($candidate in @(Get-MermaidArrowCandidate -Text $Text)) {
|
|
290
|
+
$position = $Text.IndexOf($candidate, $offset, [System.StringComparison]::Ordinal)
|
|
291
|
+
if ($position -lt 0) {
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
for ($index = $position; $index -lt ($position + $candidate.Length); $index++) {
|
|
295
|
+
$characters[$index] = ' '
|
|
296
|
+
}
|
|
297
|
+
$offset = $position + $candidate.Length
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return [string]::new($characters)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function Get-MermaidBracketDelta {
|
|
304
|
+
<#
|
|
305
|
+
.SYNOPSIS
|
|
306
|
+
Counts the net square, round, and curly bracket delta of masked text.
|
|
307
|
+
#>
|
|
308
|
+
[CmdletBinding()]
|
|
309
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
310
|
+
param(
|
|
311
|
+
[AllowEmptyString()]
|
|
312
|
+
[string] $Text
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
$square = 0
|
|
316
|
+
$round = 0
|
|
317
|
+
$curly = 0
|
|
318
|
+
|
|
319
|
+
foreach ($character in $Text.ToCharArray()) {
|
|
320
|
+
switch ($character) {
|
|
321
|
+
'[' { $square++ }
|
|
322
|
+
']' { $square-- }
|
|
323
|
+
'(' { $round++ }
|
|
324
|
+
')' { $round-- }
|
|
325
|
+
'{' { $curly++ }
|
|
326
|
+
'}' { $curly-- }
|
|
327
|
+
default { }
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return [ordered]@{ Square = $square; Round = $round; Curly = $curly }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function Get-MermaidLineScan {
|
|
335
|
+
<#
|
|
336
|
+
.SYNOPSIS
|
|
337
|
+
Produces the full scan result for one line of Mermaid source.
|
|
338
|
+
.DESCRIPTION
|
|
339
|
+
Returns Raw, IsBlank, IsDirective, IsComment, Structural (quote-masked and
|
|
340
|
+
comment-stripped), ArrowText (Structural with bracket-label contents
|
|
341
|
+
masked), BracketText (Structural with arrow tokens masked),
|
|
342
|
+
HasUnterminatedQuote, ColonIndex, FirstToken, Class (Blank | Directive |
|
|
343
|
+
Comment | StatementKeyword | Edge | Unclassifiable), and BracketDelta.
|
|
344
|
+
#>
|
|
345
|
+
[CmdletBinding()]
|
|
346
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
347
|
+
param(
|
|
348
|
+
[AllowEmptyString()]
|
|
349
|
+
[string] $Line
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
$isDirective = Test-MermaidDirectiveLine -Line $Line
|
|
353
|
+
$isComment = Test-MermaidCommentLine -Line $Line
|
|
354
|
+
$isBlank = [string]::IsNullOrWhiteSpace($Line)
|
|
355
|
+
|
|
356
|
+
if ($isDirective -or $isComment -or $isBlank) {
|
|
357
|
+
$inertClass = if ($isBlank) { 'Blank' } elseif ($isDirective) { 'Directive' } else { 'Comment' }
|
|
358
|
+
return [ordered]@{
|
|
359
|
+
Raw = $Line
|
|
360
|
+
IsBlank = $isBlank
|
|
361
|
+
IsDirective = $isDirective
|
|
362
|
+
IsComment = $isComment
|
|
363
|
+
Structural = ''
|
|
364
|
+
ArrowText = ''
|
|
365
|
+
BracketText = ''
|
|
366
|
+
HasUnterminatedQuote = $false
|
|
367
|
+
ColonIndex = -1
|
|
368
|
+
FirstToken = ''
|
|
369
|
+
Class = $inertClass
|
|
370
|
+
BracketDelta = [ordered]@{ Square = 0; Round = 0; Curly = 0 }
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
$masked = Get-MermaidQuoteMaskedText -Line $Line
|
|
375
|
+
$structural = $masked.Text
|
|
376
|
+
$arrowText = Get-MermaidLabelMaskedText -Text $structural
|
|
377
|
+
$bracketText = Get-MermaidArrowMaskedText -Text $structural
|
|
378
|
+
|
|
379
|
+
# A trailing colon or semicolon is stripped from the first token because the
|
|
380
|
+
# accessibility statements are written `accTitle: text` and `accDescr: text`.
|
|
381
|
+
# Without this normalization those lines would miss the statement-keyword
|
|
382
|
+
# exemption and their free text would be arrow-checked.
|
|
383
|
+
$firstToken = ''
|
|
384
|
+
if (-not [string]::IsNullOrWhiteSpace($structural)) {
|
|
385
|
+
$firstToken = ($structural.Trim() -split '\s+', 2)[0] -replace '[:;]+$', ''
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
$class = 'Unclassifiable'
|
|
389
|
+
if (Test-MermaidStatementKeyword -Token $firstToken) {
|
|
390
|
+
$class = 'StatementKeyword'
|
|
391
|
+
} elseif (@(Get-MermaidArrowCandidate -Text $arrowText).Count -gt 0) {
|
|
392
|
+
$class = 'Edge'
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return [ordered]@{
|
|
396
|
+
Raw = $Line
|
|
397
|
+
IsBlank = $false
|
|
398
|
+
IsDirective = $false
|
|
399
|
+
IsComment = $false
|
|
400
|
+
Structural = $structural
|
|
401
|
+
ArrowText = $arrowText
|
|
402
|
+
BracketText = $bracketText
|
|
403
|
+
HasUnterminatedQuote = $masked.HasUnterminatedQuote
|
|
404
|
+
ColonIndex = $masked.ColonIndex
|
|
405
|
+
FirstToken = $firstToken
|
|
406
|
+
Class = $class
|
|
407
|
+
BracketDelta = (Get-MermaidBracketDelta -Text $bracketText)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function Get-MermaidLineClass {
|
|
412
|
+
<#
|
|
413
|
+
.SYNOPSIS
|
|
414
|
+
Returns the classification of one line of Mermaid source.
|
|
415
|
+
#>
|
|
416
|
+
[CmdletBinding()]
|
|
417
|
+
[OutputType([string])]
|
|
418
|
+
param(
|
|
419
|
+
[AllowEmptyString()]
|
|
420
|
+
[string] $Line
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
return (Get-MermaidLineScan -Line $Line).Class
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function Test-MermaidLineBracketBalanced {
|
|
427
|
+
<#
|
|
428
|
+
.SYNOPSIS
|
|
429
|
+
Returns $true when a line's own bracket deltas are all zero.
|
|
430
|
+
.DESCRIPTION
|
|
431
|
+
Per-line balance is a diagnostic, not the validator's verdict. Legal
|
|
432
|
+
Mermaid opens a brace block on one line and closes it on another, so the
|
|
433
|
+
validator aggregates deltas across the diagram body instead.
|
|
434
|
+
#>
|
|
435
|
+
[CmdletBinding()]
|
|
436
|
+
[OutputType([bool])]
|
|
437
|
+
param(
|
|
438
|
+
[AllowEmptyString()]
|
|
439
|
+
[string] $Line
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
$delta = (Get-MermaidLineScan -Line $Line).BracketDelta
|
|
443
|
+
return ($delta.Square -eq 0 -and $delta.Round -eq 0 -and $delta.Curly -eq 0)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function Split-MermaidStatementLabel {
|
|
447
|
+
<#
|
|
448
|
+
.SYNOPSIS
|
|
449
|
+
Splits a line at its first unquoted colon into statement and label parts.
|
|
450
|
+
.DESCRIPTION
|
|
451
|
+
Mermaid puts free text after the first colon on sequence, class, state, and
|
|
452
|
+
ER statement lines, so arrow and quote judgement must stop at the colon.
|
|
453
|
+
Returns Statement, Label, and HasLabel. With no unquoted colon, Statement
|
|
454
|
+
is the whole line and Label is empty.
|
|
455
|
+
#>
|
|
456
|
+
[CmdletBinding()]
|
|
457
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
458
|
+
param(
|
|
459
|
+
[AllowEmptyString()]
|
|
460
|
+
[string] $Line
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
$scan = Get-MermaidLineScan -Line $Line
|
|
464
|
+
if ($scan.ColonIndex -lt 0) {
|
|
465
|
+
return [ordered]@{ Statement = $Line; Label = ''; HasLabel = $false }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
$index = [Math]::Min($scan.ColonIndex, $Line.Length)
|
|
469
|
+
return [ordered]@{
|
|
470
|
+
Statement = $Line.Substring(0, $index)
|
|
471
|
+
Label = $Line.Substring([Math]::Min($index + 1, $Line.Length))
|
|
472
|
+
HasLabel = $true
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
Export-ModuleMember -Function `
|
|
477
|
+
Test-MermaidWordCharacter, `
|
|
478
|
+
Test-MermaidDirectiveLine, `
|
|
479
|
+
Test-MermaidCommentLine, `
|
|
480
|
+
Get-MermaidQuoteMaskedText, `
|
|
481
|
+
Get-MermaidArrowCandidate, `
|
|
482
|
+
Get-MermaidLabelMaskedText, `
|
|
483
|
+
Get-MermaidArrowMaskedText, `
|
|
484
|
+
Get-MermaidBracketDelta, `
|
|
485
|
+
Get-MermaidLineScan, `
|
|
486
|
+
Get-MermaidLineClass, `
|
|
487
|
+
Test-MermaidLineBracketBalanced, `
|
|
488
|
+
Split-MermaidStatementLabel
|