@danmoisan/drm-copilot-mcp 1.0.26 → 1.1.0

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 (63) hide show
  1. package/out/mcp-server.js +542 -42
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +17 -21
  4. package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +17 -19
  5. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +17 -19
  6. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +18 -19
  7. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +18 -19
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +54 -32
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +172 -24
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +51 -22
  11. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +56 -19
  12. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +71 -28
  13. package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +68 -23
  14. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +402 -0
  15. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +20 -22
  16. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +59 -14
  17. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +17 -20
  18. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +278 -0
  19. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +55 -271
  20. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +57 -22
  21. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +56 -19
  22. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +70 -27
  23. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +228 -0
  24. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +68 -225
  25. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +161 -34
  26. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +59 -22
  27. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +70 -27
  28. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +32 -18
  29. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
  30. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
  31. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
  32. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
  33. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
  34. package/resources/claude-customizations/.claude/lib/hook-payload/HookPayload.psm1 +494 -0
  35. package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
  36. package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
  37. package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
  38. package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
  39. package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
  40. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +124 -1
  41. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
  42. package/resources/claude-customizations/.claude/settings.json +5 -0
  43. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
  44. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  45. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
  46. package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
  47. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
  48. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
  49. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
  50. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
  51. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
  52. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
  53. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
  54. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
  55. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
  56. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +8 -7
  57. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +24 -4
  58. package/resources/claude-customizations/config/blast-radius.json +16 -2
  59. package/resources/claude-customizations/pack-manifests/core.json +22 -1
  60. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  61. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  62. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  63. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +31 -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