zombie-mermaid 2.2.1 → 2.2.2
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/LICENSE +1 -0
- package/README.md +7 -2
- package/dist/ascii.cjs +9 -9
- package/dist/ascii.cjs.map +1 -1
- package/dist/ascii.js +4069 -3969
- package/dist/ascii.js.map +1 -1
- package/dist/cli.js +7605 -7381
- package/dist/index.cjs +52 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7613 -7459
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +14142 -13717
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +14142 -13717
- package/dist/mcp.js.map +1 -1
- package/package.json +27 -5
- package/packages/ascii-renderer/LICENSE +22 -0
- package/{src/ascii → packages/ascii-renderer/src}/class-diagram.ts +277 -57
- package/{src/ascii → packages/ascii-renderer/src}/er-diagram.ts +2 -2
- package/packages/ascii-renderer/src/flowchart.ts +94 -0
- package/{src/ascii → packages/ascii-renderer/src}/grid.ts +23 -0
- package/{src/ascii → packages/ascii-renderer/src}/index.ts +13 -84
- package/{src/ascii → packages/ascii-renderer/src}/registry.ts +23 -12
- package/{src/ascii → packages/ascii-renderer/src}/sequence.ts +55 -8
- package/{src/ascii → packages/ascii-renderer/src}/xychart.ts +6 -3
- package/packages/core/LICENSE +22 -0
- package/packages/core/src/click-directive.ts +106 -0
- package/packages/core/src/color-utils.ts +205 -0
- package/packages/core/src/diagram-type.ts +35 -0
- package/packages/core/src/direction-override.ts +46 -0
- package/packages/core/src/direction.ts +56 -0
- package/packages/core/src/index.ts +34 -0
- package/packages/core/src/init-directive.ts +248 -0
- package/packages/core/src/multiline-utils.ts +275 -0
- package/packages/core/src/statements.ts +197 -0
- package/packages/core/src/style-directives.ts +214 -0
- package/packages/core/src/text-metrics.ts +498 -0
- package/packages/core/src/theme.ts +696 -0
- package/packages/core/src/types.ts +670 -0
- package/packages/mcp/LICENSE +22 -0
- package/{src/mcp → packages/mcp/src}/server.ts +34 -2
- package/{src/mcp → packages/mcp/src}/tools/check-sequence-activations.ts +8 -5
- package/packages/mcp/src/tools/fix-sequence-activations.ts +77 -0
- package/{src/mcp → packages/mcp/src}/tools/render-ascii.ts +2 -2
- package/{src/mcp → packages/mcp/src}/tools/render-svg.ts +1 -1
- package/packages/mermaid-parser/LICENSE +22 -0
- package/{src → packages/mermaid-parser/src}/class/parser.ts +86 -8
- package/{src → packages/mermaid-parser/src}/er/parser.ts +59 -26
- package/packages/mermaid-parser/src/index.ts +48 -0
- package/{src → packages/mermaid-parser/src}/sequence/activation-check.ts +4 -3
- package/packages/mermaid-parser/src/sequence/activation-fix.ts +112 -0
- package/{src → packages/mermaid-parser/src}/sequence/parser.ts +74 -16
- package/{src → packages/mermaid-parser/src}/xychart/parser.ts +84 -6
- package/packages/svg-renderer/LICENSE +22 -0
- package/{src → packages/svg-renderer/src}/class/layout.ts +8 -4
- package/{src → packages/svg-renderer/src}/class/renderer.ts +7 -6
- package/packages/svg-renderer/src/edge-curves.ts +204 -0
- package/packages/svg-renderer/src/elk-instance.ts +292 -0
- package/{src → packages/svg-renderer/src}/er/layout.ts +6 -4
- package/{src → packages/svg-renderer/src}/er/renderer.ts +4 -4
- package/packages/svg-renderer/src/index.ts +58 -0
- package/packages/svg-renderer/src/layout-engine/constants.ts +19 -0
- package/packages/svg-renderer/src/layout-engine/edge-bundling.ts +379 -0
- package/packages/svg-renderer/src/layout-engine/elk-adapter-utils.ts +81 -0
- package/packages/svg-renderer/src/layout-engine/elk-graph-builder.ts +240 -0
- package/packages/svg-renderer/src/layout-engine/from-elk.ts +685 -0
- package/packages/svg-renderer/src/layout-engine/layer-alignment.ts +174 -0
- package/packages/svg-renderer/src/layout-engine/to-elk.ts +695 -0
- package/packages/svg-renderer/src/layout-engine.ts +74 -0
- package/packages/svg-renderer/src/layout.ts +8 -0
- package/packages/svg-renderer/src/renderer.ts +1485 -0
- package/packages/svg-renderer/src/resolve-colors.ts +339 -0
- package/{src → packages/svg-renderer/src}/sequence/layout.ts +2 -6
- package/{src → packages/svg-renderer/src}/sequence/renderer.ts +4 -4
- package/packages/svg-renderer/src/shape-clipping.ts +197 -0
- package/packages/svg-renderer/src/styles.ts +118 -0
- package/{src → packages/svg-renderer/src}/xychart/layout.ts +2 -2
- package/{src → packages/svg-renderer/src}/xychart/renderer.ts +6 -6
- package/src/__tests__/ascii-ansi.test.ts +6 -2
- package/src/__tests__/ascii-canvas-write.test.ts +2 -2
- package/src/__tests__/ascii-charset-border-junctions.test.ts +1 -1
- package/src/__tests__/ascii-cjk-width.test.ts +1 -1
- package/src/__tests__/ascii-class-box-occupancy.test.ts +147 -45
- package/src/__tests__/ascii-class-column-width-488-489.test.ts +1 -1
- package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
- package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +1 -1
- package/src/__tests__/ascii-class-diagram-compartments.test.ts +1 -1
- package/src/__tests__/ascii-class-label-box-collision.test.ts +1 -1
- package/src/__tests__/ascii-class-label-row-collision-531.test.ts +1 -1
- package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +1 -1
- package/src/__tests__/ascii-class-padding.test.ts +1 -1
- package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +86 -1
- package/src/__tests__/ascii-combining-mark-width.test.ts +2 -2
- package/src/__tests__/ascii-coords-overlay.test.ts +2 -2
- package/src/__tests__/ascii-decision-lr-box-start.test.ts +2 -2
- package/src/__tests__/ascii-display-width-unit.test.ts +1 -1
- package/src/__tests__/ascii-draw-arrows-coverage.test.ts +1 -1
- package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +12 -5
- package/src/__tests__/ascii-draw-bundles.test.ts +5 -5
- package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +1 -1
- package/src/__tests__/ascii-edge-bundling-routing.test.ts +9 -5
- package/src/__tests__/ascii-edge-cross-style-overlap.test.ts +10 -6
- package/src/__tests__/ascii-edge-ending-glyphs.test.ts +1 -1
- package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +2 -2
- package/src/__tests__/ascii-edge-routing-fixes.test.ts +1 -1
- package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +8 -4
- package/src/__tests__/ascii-edge-styles.test.ts +1 -1
- package/src/__tests__/ascii-emoji-cluster-width.test.ts +2 -2
- package/src/__tests__/ascii-er-box-occupancy.test.ts +1 -1
- package/src/__tests__/ascii-er-cardinality.test.ts +1 -1
- package/src/__tests__/ascii-er-corner-glyphs.test.ts +1 -1
- package/src/__tests__/ascii-er-jog-stray-line.test.ts +1 -1
- package/src/__tests__/ascii-er-label-padding.test.ts +1 -1
- package/src/__tests__/ascii-er-padding.test.ts +1 -1
- package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +1 -1
- package/src/__tests__/ascii-er-relationship-overwrite.test.ts +1 -1
- package/src/__tests__/ascii-er-stray-connectors.test.ts +7 -4
- package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +1 -1
- package/src/__tests__/ascii-er-vertical-one-marker.test.ts +1 -1
- package/src/__tests__/ascii-grid-deep-chain.test.ts +4 -4
- package/src/__tests__/ascii-hyperlinks.test.ts +9 -6
- package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +8 -4
- package/src/__tests__/ascii-lane-search.test.ts +1 -1
- package/src/__tests__/ascii-multibox-cjk-width.test.ts +1 -1
- package/src/__tests__/ascii-multiline.test.ts +6 -3
- package/src/__tests__/ascii-nested-subgraph-sibling-order-444.test.ts +8 -5
- package/src/__tests__/ascii-package-boundary.test.ts +47 -37
- package/src/__tests__/ascii-padding-edge-cases.test.ts +1 -1
- package/src/__tests__/ascii-parallel-edges-329.test.ts +8 -5
- package/src/__tests__/ascii-pathfinder-route-edge.test.ts +14 -4
- package/src/__tests__/ascii-reverse-direction-edges-629.test.ts +9 -5
- package/src/__tests__/ascii-sequence-alt-else-label.test.ts +1 -1
- package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +1 -1
- package/src/__tests__/ascii-sequence-box-group.test.ts +1 -1
- package/src/__tests__/ascii-sequence-cjk-width.test.ts +1 -1
- package/src/__tests__/ascii-sequence-create-destroy.test.ts +1 -1
- package/src/__tests__/ascii-sequence-form-invariants.test.ts +48 -50
- package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +1 -1
- package/src/__tests__/ascii-sequence-notes.test.ts +1 -1
- package/src/__tests__/ascii-sequence-padding.test.ts +1 -1
- package/src/__tests__/ascii-sequence-self-arrow.test.ts +1 -1
- package/src/__tests__/ascii-shape-diamond.test.ts +3 -3
- package/src/__tests__/ascii-shape-rectangle.test.ts +5 -5
- package/src/__tests__/ascii-shape-rounded.test.ts +3 -3
- package/src/__tests__/ascii-shapes-circle.test.ts +3 -3
- package/src/__tests__/ascii-shapes-hexagon.test.ts +3 -3
- package/src/__tests__/ascii-shapes-special.test.ts +10 -5
- package/src/__tests__/ascii-shapes-stadium.test.ts +3 -3
- package/src/__tests__/ascii-shapes-state.test.ts +15 -5
- package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +1 -1
- package/src/__tests__/ascii-subgraph-bbox.test.ts +8 -4
- package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +1 -1
- package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +88 -25
- package/src/__tests__/ascii-subgraph-later-edgeless-301.test.ts +8 -4
- package/src/__tests__/ascii-subgraph-title-padding.test.ts +1 -1
- package/src/__tests__/ascii-territory-unit.test.ts +2 -2
- package/src/__tests__/ascii-validate.test.ts +1 -1
- package/src/__tests__/ascii.test.ts +5 -2
- package/src/__tests__/box-color.test.ts +1 -1
- package/src/__tests__/class-arrow-directions.test.ts +1 -1
- package/src/__tests__/class-click-interactions.test.ts +3 -6
- package/src/__tests__/class-integration.test.ts +1 -1
- package/src/__tests__/class-notes.test.ts +3 -3
- package/src/__tests__/class-parser-error-messages.test.ts +162 -0
- package/src/__tests__/class-parser.test.ts +6 -6
- package/src/__tests__/class-styling.test.ts +2 -2
- package/src/__tests__/cli-render.test.ts +16 -1
- package/src/__tests__/direction-override.test.ts +1 -1
- package/src/__tests__/draw-lines.test.ts +2 -2
- package/src/__tests__/edge-cell-styles.test.ts +5 -2
- package/src/__tests__/elk-layout-cache.test.ts +6 -4
- package/src/__tests__/er-click-unsupported.test.ts +5 -8
- package/src/__tests__/er-parser-error-messages.test.ts +82 -0
- package/src/__tests__/er-parser.test.ts +3 -6
- package/src/__tests__/expanded-shapes.test.ts +1 -1
- package/src/__tests__/grid-occupancy.test.ts +1 -1
- package/src/__tests__/mcp-check-sequence-activations-tool.test.ts +1 -1
- package/src/__tests__/mcp-fix-sequence-activations-tool.test.ts +134 -0
- package/src/__tests__/mcp-render-ascii-tool.test.ts +5 -4
- package/src/__tests__/mcp-render-svg-theme-guard.test.ts +3 -3
- package/src/__tests__/mcp-render-svg-tool.test.ts +1 -1
- package/src/__tests__/mcp-server.test.ts +30 -1
- package/src/__tests__/multiline-labels.test.ts +29 -18
- package/src/__tests__/parser.test.ts +117 -5
- package/src/__tests__/pathfinder.test.ts +11 -4
- package/src/__tests__/render-options-overrides.test.ts +28 -21
- package/src/__tests__/render-options-per-type.test.ts +13 -9
- package/src/__tests__/resolve-colors.test.ts +1 -1
- package/src/__tests__/self-loop-edges.test.ts +112 -0
- package/src/__tests__/sequence-activation-check.test.ts +7 -8
- package/src/__tests__/sequence-activation-fix.test.ts +91 -0
- package/src/__tests__/sequence-layout.test.ts +4 -7
- package/src/__tests__/sequence-parser-syntax-errors.test.ts +179 -0
- package/src/__tests__/sequence-parser.test.ts +3 -6
- package/src/__tests__/statements.test.ts +53 -35
- package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +21 -21
- package/src/__tests__/theme-palette-contrast.test.ts +84 -0
- package/src/__tests__/workspace-package-boundaries.test.ts +199 -25
- package/src/__tests__/xychart-ascii.test.ts +1 -1
- package/src/__tests__/xychart-colors.test.ts +3 -2
- package/src/__tests__/xychart-layout.test.ts +1 -1
- package/src/__tests__/xychart-parser-errors.test.ts +95 -0
- package/src/browser.ts +8 -2
- package/src/cli/mcp.ts +1 -1
- package/src/cli/render.ts +59 -25
- package/src/cli/web.ts +1 -1
- package/src/diagram-registry.ts +190 -25
- package/src/index.ts +34 -131
- package/src/parser.ts +102 -40
- /package/{src/ascii → packages/ascii-renderer/src}/ansi.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/canvas.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/converter.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/coords.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/display-width.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-arrows.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-boxes.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-bundles.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-lines.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw-subgraphs.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/draw.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/edge-bundling.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/edge-cell-styles.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/edge-routing.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/grid-occupancy.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/hyperlinks.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/lane-search.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/multiline-utils.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/pathfinder.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/circle.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/corners.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/diamond.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/hexagon.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/index.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/rectangle.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/rounded.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/special.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/stadium.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/state.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/shapes/types.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/territory.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/types.ts +0 -0
- /package/{src/ascii → packages/ascii-renderer/src}/validate.ts +0 -0
- /package/{src/mcp → packages/mcp/src}/index.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/class/format.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/class/types.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/er/types.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/expanded-shapes.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/sequence/box-color.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/sequence/types.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/xychart/colors.ts +0 -0
- /package/{src → packages/mermaid-parser/src}/xychart/types.ts +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Craft Docs
|
|
4
|
+
Copyright (c) 2026 Dustin Fadler
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
// mechanical sequence-diagram semantic check, up as Model Context Protocol
|
|
6
6
|
// tools. No rendering or checking logic lives here — see src/mcp/tools/*.ts
|
|
7
7
|
// for the thin per-tool adapters, src/index.ts / src/ascii/index.ts for the
|
|
8
|
-
// actual renderers, and src/sequence/activation-check.ts
|
|
8
|
+
// actual renderers, and packages/mermaid-parser/src/sequence/activation-check.ts
|
|
9
|
+
// for the check.
|
|
9
10
|
// ============================================================================
|
|
10
11
|
|
|
11
12
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
12
|
-
import { getPackageVersion } from '
|
|
13
|
+
import { getPackageVersion } from '../../../src/package-info.ts'
|
|
13
14
|
import { renderSvgInputShape, renderSvgHandler } from './tools/render-svg.ts'
|
|
14
15
|
import {
|
|
15
16
|
renderAsciiInputShape,
|
|
@@ -19,6 +20,10 @@ import {
|
|
|
19
20
|
checkSequenceActivationsInputShape,
|
|
20
21
|
checkSequenceActivationsHandler,
|
|
21
22
|
} from './tools/check-sequence-activations.ts'
|
|
23
|
+
import {
|
|
24
|
+
fixSequenceActivationsInputShape,
|
|
25
|
+
fixSequenceActivationsHandler,
|
|
26
|
+
} from './tools/fix-sequence-activations.ts'
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* Build a zombie-mermaid MCP server exposing `render_mermaid_svg`,
|
|
@@ -99,5 +104,32 @@ export function createMcpServer(): McpServer {
|
|
|
99
104
|
checkSequenceActivationsHandler,
|
|
100
105
|
)
|
|
101
106
|
|
|
107
|
+
server.registerTool(
|
|
108
|
+
'fix_mermaid_sequence_activations',
|
|
109
|
+
{
|
|
110
|
+
title: 'Fix Mermaid sequence diagram activation balance',
|
|
111
|
+
description:
|
|
112
|
+
'Check a Mermaid sequence diagram for activation/deactivation ' +
|
|
113
|
+
'imbalance and, where mechanically safe, return a corrected ' +
|
|
114
|
+
'version: a dangling "activate X" (or "+" shorthand) with no ' +
|
|
115
|
+
'matching "deactivate X" gets one appended. An unmatched ' +
|
|
116
|
+
'"deactivate X" with nothing open is reported but not auto-fixed ' +
|
|
117
|
+
'(no safe, unambiguous edit exists without source-position ' +
|
|
118
|
+
'tracking). Returns a JSON report ({ ok, fixedDiagram, ' +
|
|
119
|
+
'fixesApplied, remainingIssues }) rather than rendering anything. ' +
|
|
120
|
+
'Mechanical and deterministic — no LLM judgment involved. Only ' +
|
|
121
|
+
'sequence diagrams are supported.',
|
|
122
|
+
inputSchema: fixSequenceActivationsInputShape,
|
|
123
|
+
annotations: {
|
|
124
|
+
title: 'Fix Mermaid sequence diagram activation balance',
|
|
125
|
+
readOnlyHint: true,
|
|
126
|
+
destructiveHint: false,
|
|
127
|
+
idempotentHint: true,
|
|
128
|
+
openWorldHint: false,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
fixSequenceActivationsHandler,
|
|
132
|
+
)
|
|
133
|
+
|
|
102
134
|
return server
|
|
103
135
|
}
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
// render tools: every "activate X" (or "+" arrow shorthand) in a Mermaid
|
|
6
6
|
// sequence diagram must be closed by a matching "deactivate X" ("-"
|
|
7
7
|
// shorthand) before the diagram ends. No LLM judge involved — see
|
|
8
|
-
// src/sequence/activation-check.ts for the actual
|
|
9
|
-
// same activation-stack bookkeeping
|
|
10
|
-
// *draw*
|
|
8
|
+
// packages/mermaid-parser/src/sequence/activation-check.ts for the actual
|
|
9
|
+
// check, which reuses the same activation-stack bookkeeping
|
|
10
|
+
// packages/svg-renderer/src/sequence/layout.ts already runs to *draw*
|
|
11
|
+
// activation bars.
|
|
11
12
|
//
|
|
12
13
|
// Why this tool, and why it isn't duplicating agentic-mermaid's `verify`
|
|
13
14
|
// tool (github.com/adewale/agentic-mermaid): that tool's own docs describe
|
|
@@ -22,8 +23,10 @@
|
|
|
22
23
|
import { z } from 'zod'
|
|
23
24
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
|
|
24
25
|
import { detectDiagramType, splitStatements } from '@zombie-mermaid/core'
|
|
25
|
-
import {
|
|
26
|
-
|
|
26
|
+
import {
|
|
27
|
+
parseSequenceDiagram,
|
|
28
|
+
checkActivationBalance,
|
|
29
|
+
} from '@zombie-mermaid/mermaid-parser'
|
|
27
30
|
|
|
28
31
|
export const checkSequenceActivationsInputShape = {
|
|
29
32
|
diagram: z
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// zombie-mermaid MCP server — `fix_mermaid_sequence_activations` tool
|
|
3
|
+
//
|
|
4
|
+
// The "optionally proposes a fix" half of issue #539, complementing
|
|
5
|
+
// `check_mermaid_sequence_activations` (which only reports issues). Reuses
|
|
6
|
+
// packages/mermaid-parser/src/sequence/activation-fix.ts, which — see that
|
|
7
|
+
// file's header — only auto-fixes DANGLING_ACTIVATION (a mechanically safe,
|
|
8
|
+
// unambiguous append); UNMATCHED_DEACTIVATION comes back as a remaining
|
|
9
|
+
// issue for a human (or a future LLM-based layer) to resolve rather than
|
|
10
|
+
// risk an incorrect text edit.
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod'
|
|
14
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
|
|
15
|
+
import { detectDiagramType } from '@zombie-mermaid/core'
|
|
16
|
+
import { fixActivationBalance } from '@zombie-mermaid/mermaid-parser'
|
|
17
|
+
|
|
18
|
+
export const fixSequenceActivationsInputShape = {
|
|
19
|
+
diagram: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1, 'diagram must not be empty')
|
|
22
|
+
.describe(
|
|
23
|
+
'Mermaid sequence diagram source text, e.g. "sequenceDiagram\\n ' +
|
|
24
|
+
'activate A\\n A->>B: hi". Must start with "sequenceDiagram" — ' +
|
|
25
|
+
'this tool does not check other diagram types.',
|
|
26
|
+
),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FixSequenceActivationsToolArgs {
|
|
30
|
+
diagram: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* MCP tool handler for `fix_mermaid_sequence_activations`. Parses the given
|
|
35
|
+
* source as a Mermaid sequence diagram, checks activation/deactivation
|
|
36
|
+
* balance, and returns a structured JSON result: `ok`, the (possibly
|
|
37
|
+
* unchanged) `fixedDiagram`, `fixesApplied` (human-readable descriptions),
|
|
38
|
+
* and any `remainingIssues` that couldn't be auto-fixed.
|
|
39
|
+
*
|
|
40
|
+
* Returns an MCP tool error (`isError: true`, not a thrown exception) both
|
|
41
|
+
* for invalid Mermaid syntax and for a non-sequence diagram, matching
|
|
42
|
+
* `check-sequence-activations.ts`'s error handling.
|
|
43
|
+
*/
|
|
44
|
+
export function fixSequenceActivationsHandler(
|
|
45
|
+
input: FixSequenceActivationsToolArgs,
|
|
46
|
+
): CallToolResult {
|
|
47
|
+
try {
|
|
48
|
+
const diagramType = detectDiagramType(input.diagram)
|
|
49
|
+
if (diagramType !== 'sequence') {
|
|
50
|
+
return {
|
|
51
|
+
isError: true,
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: 'text',
|
|
55
|
+
text:
|
|
56
|
+
'This tool only fixes sequence diagrams (source must start ' +
|
|
57
|
+
`with "sequenceDiagram"); detected diagram type: "${diagramType}".`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const result = fixActivationBalance(input.diagram)
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
isError: true,
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: `Failed to fix diagram activations: ${err instanceof Error ? err.message : String(err)}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import { z } from 'zod'
|
|
10
10
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
|
|
11
|
-
import { renderMermaidASCII } from '
|
|
12
|
-
import type { AsciiRenderOptions } from '
|
|
11
|
+
import { renderMermaidASCII } from '@zombie-mermaid/ascii-renderer'
|
|
12
|
+
import type { AsciiRenderOptions } from '@zombie-mermaid/ascii-renderer'
|
|
13
13
|
|
|
14
14
|
export const renderAsciiInputShape = {
|
|
15
15
|
diagram: z
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { z } from 'zod'
|
|
10
10
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
|
|
11
|
-
import { renderMermaidSVG } from '
|
|
11
|
+
import { renderMermaidSVG } from '../../../../src/index.ts'
|
|
12
12
|
import { THEMES } from '@zombie-mermaid/core'
|
|
13
13
|
import type { DiagramColors } from '@zombie-mermaid/core'
|
|
14
14
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Craft Docs
|
|
4
|
+
Copyright (c) 2026 Dustin Fadler
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
tryApplyClassDef,
|
|
15
15
|
tryApplyCssClass,
|
|
16
16
|
tryApplyStyleStatement,
|
|
17
|
+
type Statement,
|
|
17
18
|
} from '@zombie-mermaid/core'
|
|
18
19
|
|
|
19
20
|
// ============================================================================
|
|
@@ -39,6 +40,27 @@ import {
|
|
|
39
40
|
// note "text" / note for Animal "line1\nline2"
|
|
40
41
|
// ============================================================================
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The set of relationship-arrow tokens `parseRelationship` recognizes,
|
|
45
|
+
* shared with the "does this line even look like a relationship attempt"
|
|
46
|
+
* check below so the two can't drift apart. Order matters for regex
|
|
47
|
+
* alternation (longer/more-specific tokens like `--|>` must be tried before
|
|
48
|
+
* the bare `--` fallback).
|
|
49
|
+
*/
|
|
50
|
+
const RELATIONSHIP_ARROW_SOURCE =
|
|
51
|
+
'<\\|--|<\\|\\.\\.|\\*--|o--|-->|--\\*|--o|--\\|>|\\.\\.>|\\.\\.\\|>|<--|<\\.\\.?|--'
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Matches a line that *contains* a relationship-arrow token somewhere, even
|
|
55
|
+
* if the overall shape (FROM/TO, cardinalities) doesn't parse. Used to
|
|
56
|
+
* scope the "malformed relationship" error (issue #761) to lines that
|
|
57
|
+
* already look like an attempt at this construct — mirroring the same
|
|
58
|
+
* "attempt, then validate" pattern #722 used for the ER and xychart-beta
|
|
59
|
+
* parsers, so a genuinely unrelated line (no arrow at all) still falls
|
|
60
|
+
* through silently rather than throwing on totally unrecognized syntax.
|
|
61
|
+
*/
|
|
62
|
+
const RELATIONSHIP_ARROW_MARKER = new RegExp(RELATIONSHIP_ARROW_SOURCE)
|
|
63
|
+
|
|
42
64
|
/**
|
|
43
65
|
* Parse a Mermaid class diagram.
|
|
44
66
|
* Expects the first line to be "classDiagram".
|
|
@@ -55,7 +77,7 @@ import {
|
|
|
55
77
|
// `noUncheckedIndexedAccess` can't see either guarantee, but removing the
|
|
56
78
|
// `!` would only replace a proven-safe assertion with an unreachable guard.
|
|
57
79
|
// Left as-is; no behavior change.
|
|
58
|
-
export function parseClassDiagram(lines:
|
|
80
|
+
export function parseClassDiagram(lines: Statement[]): ClassDiagram {
|
|
59
81
|
const diagram: ClassDiagram = {
|
|
60
82
|
classes: [],
|
|
61
83
|
relationships: [],
|
|
@@ -74,9 +96,14 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
74
96
|
// Track class body parsing
|
|
75
97
|
let currentClass: ClassNode | null = null
|
|
76
98
|
let braceDepth = 0
|
|
99
|
+
// Line the currently-open class body started on, for the unclosed-body
|
|
100
|
+
// error below (issue #761) — `currentClass`/`braceDepth` alone don't
|
|
101
|
+
// carry position.
|
|
102
|
+
let openClassLine: number | undefined
|
|
77
103
|
|
|
78
104
|
for (let i = 1; i < lines.length; i++) {
|
|
79
|
-
const
|
|
105
|
+
const stmt = lines[i]!
|
|
106
|
+
const line = stmt.text
|
|
80
107
|
|
|
81
108
|
// --- Inside a class body block ---
|
|
82
109
|
if (currentClass && braceDepth > 0) {
|
|
@@ -84,6 +111,7 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
84
111
|
braceDepth--
|
|
85
112
|
if (braceDepth === 0) {
|
|
86
113
|
currentClass = null
|
|
114
|
+
openClassLine = undefined
|
|
87
115
|
}
|
|
88
116
|
continue
|
|
89
117
|
}
|
|
@@ -95,8 +123,18 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
95
123
|
continue
|
|
96
124
|
}
|
|
97
125
|
|
|
126
|
+
// A line that starts with `<<` but doesn't match the full
|
|
127
|
+
// `<<name>>` shape is an attempted annotation with broken syntax
|
|
128
|
+
// (e.g. a missing closing `>>`) rather than a member — surface that
|
|
129
|
+
// instead of silently mis-parsing it as a member named "<<foo" (#761).
|
|
130
|
+
if (line.startsWith('<<')) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Line ${stmt.line}: Malformed class annotation "${line}" — expected "<<name>>" (e.g. "<<interface>>").`,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
98
136
|
// Parse member: visibility, name, type, optional parens for method
|
|
99
|
-
addMember(currentClass, line)
|
|
137
|
+
addMember(currentClass, line, stmt.line)
|
|
100
138
|
continue
|
|
101
139
|
}
|
|
102
140
|
|
|
@@ -155,6 +193,7 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
155
193
|
const id = declareClass(classBlockMatch[1]!, classBlockMatch[2])
|
|
156
194
|
currentClass = classMap.get(id) ?? null
|
|
157
195
|
braceDepth = 1
|
|
196
|
+
openClassLine = stmt.line
|
|
158
197
|
continue
|
|
159
198
|
}
|
|
160
199
|
|
|
@@ -177,8 +216,14 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
177
216
|
const annotMatch = body.match(/^<<(\w+)>>$/)
|
|
178
217
|
if (cls && annotMatch) {
|
|
179
218
|
cls.annotation = annotMatch[1]!
|
|
219
|
+
} else if (cls && body.startsWith('<<')) {
|
|
220
|
+
// Same broken-annotation-attempt case as the multi-line body
|
|
221
|
+
// branch above, just on the single-line form (#761).
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Line ${stmt.line}: Malformed class annotation "${body}" — expected "<<name>>" (e.g. "<<interface>>").`,
|
|
224
|
+
)
|
|
180
225
|
} else if (cls && body) {
|
|
181
|
-
addMember(cls, body)
|
|
226
|
+
addMember(cls, body, stmt.line)
|
|
182
227
|
}
|
|
183
228
|
continue
|
|
184
229
|
}
|
|
@@ -189,7 +234,7 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
189
234
|
// Make sure this isn't a relationship line (those have arrows)
|
|
190
235
|
const rest = inlineAttrMatch[2]!
|
|
191
236
|
if (!rest.match(/<\|--|--|\*--|o--|-->|\.\.>|\.\.\|>/)) {
|
|
192
|
-
addMember(ensureClass(classMap, inlineAttrMatch[1]!), rest)
|
|
237
|
+
addMember(ensureClass(classMap, inlineAttrMatch[1]!), rest, stmt.line)
|
|
193
238
|
continue
|
|
194
239
|
}
|
|
195
240
|
}
|
|
@@ -209,6 +254,25 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
209
254
|
diagram.relationships.push(rel)
|
|
210
255
|
continue
|
|
211
256
|
}
|
|
257
|
+
|
|
258
|
+
// A line containing a relationship-arrow token that still didn't parse
|
|
259
|
+
// above is a malformed/dangling relationship attempt — e.g. "Animal
|
|
260
|
+
// <|--" with no target, or an unsupported arrow shape — rather than an
|
|
261
|
+
// unrelated line that merely falls through. Scoped to lines that
|
|
262
|
+
// already look like an attempt (same "attempt, then validate" pattern
|
|
263
|
+
// #722 used for ER/xychart-beta) so genuinely unrecognized syntax with
|
|
264
|
+
// no arrow at all keeps falling through silently (#761).
|
|
265
|
+
if (RELATIONSHIP_ARROW_MARKER.test(line)) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Line ${stmt.line}: Malformed class-diagram relationship "${line}". Expected "FROM ARROW TO" (optionally with cardinalities and a ": label"), e.g. "Animal <|-- Dog" or 'A "1" --> "*" B : label'. ARROW must be one of <|--, <|.., *--, o--, -->, --*, --o, --|>, ..>, ..|>, <--, <.., or --.`,
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (currentClass !== null) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Line ${openClassLine}: Unclosed class body for "${currentClass.id}" — expected a closing "}" before the diagram ends.`,
|
|
275
|
+
)
|
|
212
276
|
}
|
|
213
277
|
|
|
214
278
|
diagram.classes = [...classMap.values()]
|
|
@@ -258,8 +322,8 @@ export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
|
258
322
|
}
|
|
259
323
|
|
|
260
324
|
/** Parse one member line and file it under the class's attributes or methods. */
|
|
261
|
-
function addMember(cls: ClassNode, line: string): void {
|
|
262
|
-
const member = parseMember(line)
|
|
325
|
+
function addMember(cls: ClassNode, line: string, lineNumber: number): void {
|
|
326
|
+
const member = parseMember(line, lineNumber)
|
|
263
327
|
if (!member) return
|
|
264
328
|
if (member.isMethod) {
|
|
265
329
|
cls.methods.push(member.member)
|
|
@@ -351,6 +415,7 @@ export function parseGenericTypes(input: string): string {
|
|
|
351
415
|
/** Parse a class member line (attribute or method) */
|
|
352
416
|
function parseMember(
|
|
353
417
|
line: string,
|
|
418
|
+
lineNumber: number,
|
|
354
419
|
): { member: ClassMember; isMethod: boolean } | null {
|
|
355
420
|
const trimmed = line.trim().replace(/;$/, '')
|
|
356
421
|
if (!trimmed) return null
|
|
@@ -369,6 +434,17 @@ function parseMember(
|
|
|
369
434
|
rest = rest.slice(1).trim()
|
|
370
435
|
}
|
|
371
436
|
|
|
437
|
+
// An attempted method signature with an opening "(" but no closing ")"
|
|
438
|
+
// at all can't be a genuine attribute either (an attribute name/type
|
|
439
|
+
// never legitimately contains a bare, unclosed paren) — surface it
|
|
440
|
+
// instead of silently parsing "eat(void" as a member literally named
|
|
441
|
+
// that (#761).
|
|
442
|
+
if (rest.includes('(') && !rest.includes(')')) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`Line ${lineNumber}: Malformed class member "${trimmed}" — unclosed "(" in a method signature. Expected e.g. "+eat() void" or "+eat(Food f) void".`,
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
|
|
372
448
|
// Mermaid renders `List~Observer~` as `List<Observer>` (its own
|
|
373
449
|
// `parseGenericTypes`). It's applied per field below — name, params and
|
|
374
450
|
// return type separately, the way mermaid's ClassMember does — rather
|
|
@@ -440,7 +516,9 @@ function parseRelationship(line: string): ClassRelationship | null {
|
|
|
440
516
|
// label separator that follows can't swallow it as a label of `::style`;
|
|
441
517
|
// it's re-joined onto the id here and split off again by the caller.
|
|
442
518
|
const match = line.match(
|
|
443
|
-
|
|
519
|
+
new RegExp(
|
|
520
|
+
`^(\\S+?)\\s+(?:"([^"]*?)"\\s+)?(${RELATIONSHIP_ARROW_SOURCE})\\s+(?:"([^"]*?)"\\s+)?(\\S+?)(?::::([\\w][\\w-]*))?(?:\\s*:\\s*(.+))?$`,
|
|
521
|
+
),
|
|
444
522
|
)
|
|
445
523
|
if (!match) return null
|
|
446
524
|
|
|
@@ -5,8 +5,11 @@ import type {
|
|
|
5
5
|
ErRelationship,
|
|
6
6
|
Cardinality,
|
|
7
7
|
} from './types.ts'
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
normalizeBrTags,
|
|
10
|
+
toDirection,
|
|
11
|
+
type Statement,
|
|
12
|
+
} from '@zombie-mermaid/core'
|
|
10
13
|
|
|
11
14
|
// ============================================================================
|
|
12
15
|
// ER diagram parser
|
|
@@ -49,7 +52,7 @@ import { toDirection } from '../parser.ts'
|
|
|
49
52
|
// concern from these `!`s) — `noUncheckedIndexedAccess` can't see either
|
|
50
53
|
// guarantee, but removing the `!` would only replace a proven-safe
|
|
51
54
|
// assertion with an unreachable guard. Left as-is; no behavior change.
|
|
52
|
-
export function parseErDiagram(lines:
|
|
55
|
+
export function parseErDiagram(lines: Statement[]): ErDiagram {
|
|
53
56
|
const diagram: ErDiagram = {
|
|
54
57
|
entities: [],
|
|
55
58
|
relationships: [],
|
|
@@ -61,7 +64,8 @@ export function parseErDiagram(lines: string[]): ErDiagram {
|
|
|
61
64
|
let currentEntity: ErEntity | null = null
|
|
62
65
|
|
|
63
66
|
for (let i = 1; i < lines.length; i++) {
|
|
64
|
-
const
|
|
67
|
+
const stmt = lines[i]!
|
|
68
|
+
const line = stmt.text
|
|
65
69
|
|
|
66
70
|
// --- Inside entity body ---
|
|
67
71
|
if (currentEntity) {
|
|
@@ -121,7 +125,7 @@ export function parseErDiagram(lines: string[]): ErDiagram {
|
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
// --- Relationship: `ENTITY1 cardinality1--cardinality2 ENTITY2 : label` ---
|
|
124
|
-
const rel = parseRelationshipLine(line)
|
|
128
|
+
const rel = parseRelationshipLine(line, stmt.line)
|
|
125
129
|
if (rel) {
|
|
126
130
|
// Ensure both entities exist
|
|
127
131
|
ensureEntity(entityMap, rel.entity1)
|
|
@@ -197,33 +201,62 @@ function parseAttribute(line: string): ErAttribute | null {
|
|
|
197
201
|
*
|
|
198
202
|
* Full pattern example: CUSTOMER ||--o{ ORDER : places
|
|
199
203
|
*/
|
|
200
|
-
function parseRelationshipLine(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
function parseRelationshipLine(
|
|
205
|
+
line: string,
|
|
206
|
+
lineNumber: number,
|
|
207
|
+
): ErRelationship | null {
|
|
208
|
+
// Loosely match a line shaped like a relationship attempt: two bare
|
|
209
|
+
// tokens flanking a `--`/`..`-based marker, with an optional `: label`.
|
|
210
|
+
// A line with no such marker at all doesn't look like a relationship
|
|
211
|
+
// attempt and falls through silently (returns null), same as before —
|
|
212
|
+
// that part of the parser's leniency is unchanged.
|
|
213
|
+
//
|
|
214
|
+
// Everything that *does* match this shape is validated below and either
|
|
215
|
+
// returns a fully-parsed relationship or throws an actionable error.
|
|
216
|
+
// Before this pass, an invalid cardinality token or a missing/empty
|
|
217
|
+
// label silently dropped the *entire* line (both entities included)
|
|
218
|
+
// with zero indication anything was wrong — see issue #541 (parser
|
|
219
|
+
// error-message quality audit — this parser had zero throw sites before
|
|
220
|
+
// this pass).
|
|
221
|
+
const attempt = line.match(
|
|
222
|
+
/^(\S+)\s+(\S*(?:--|\.\.)\S*)\s+(\S+)\s*(?::\s*(.*))?$/,
|
|
204
223
|
)
|
|
205
|
-
if (!
|
|
224
|
+
if (!attempt) return null
|
|
206
225
|
|
|
207
|
-
const entity1 =
|
|
208
|
-
const cardinalityStr =
|
|
209
|
-
const entity2 =
|
|
210
|
-
|
|
211
|
-
const rawLabel =
|
|
212
|
-
const label = normalizeBrTags(rawLabel)
|
|
226
|
+
const entity1 = attempt[1]!
|
|
227
|
+
const cardinalityStr = attempt[2]!
|
|
228
|
+
const entity2 = attempt[3]!
|
|
229
|
+
const hasLabel = attempt[4] !== undefined
|
|
230
|
+
const rawLabel = attempt[4]?.trim() ?? ''
|
|
213
231
|
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
// Cardinality symbols on each side of the line style:
|
|
233
|
+
// Left side (entity1): || |o }| }o
|
|
234
|
+
// Line: -- (identifying) or .. (non-identifying)
|
|
235
|
+
// Right side (entity2): || o| |{ o{
|
|
236
|
+
// `shapeMatch` only confirms the token is built from crow's-foot
|
|
237
|
+
// characters around a line-style marker — `parseLeftCardinality`/
|
|
238
|
+
// `parseRightCardinality` still reject a shape match that isn't one of
|
|
239
|
+
// the four valid two-character combinations per side (e.g. a lone "|").
|
|
240
|
+
const shapeMatch = cardinalityStr.match(/^([|o}{]*)(--|\.\.?)([|o}{]*)$/)
|
|
241
|
+
const cardinality1 = shapeMatch ? parseLeftCardinality(shapeMatch[1]!) : null
|
|
242
|
+
const cardinality2 = shapeMatch ? parseRightCardinality(shapeMatch[3]!) : null
|
|
217
243
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
244
|
+
if (!cardinality1 || !cardinality2) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Line ${lineNumber}: Invalid ER relationship cardinality "${cardinalityStr}" in "${line}". ` +
|
|
247
|
+
'Left side must be one of ||, |o, }|, }o; right side must be one of ||, o|, |{, o{ (e.g. "||--o{").',
|
|
248
|
+
)
|
|
249
|
+
}
|
|
221
250
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
251
|
+
if (!hasLabel || rawLabel.length === 0) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Line ${lineNumber}: ER relationship "${entity1} ${cardinalityStr} ${entity2}" is missing a ": label" — expected e.g. "${entity1} ${cardinalityStr} ${entity2} : label".`,
|
|
254
|
+
)
|
|
255
|
+
}
|
|
225
256
|
|
|
226
|
-
if
|
|
257
|
+
// Strip surrounding quotes if present, then normalize br tags.
|
|
258
|
+
const label = normalizeBrTags(rawLabel.replace(/^["']|["']$/g, ''))
|
|
259
|
+
const identifying = shapeMatch![2] === '--'
|
|
227
260
|
|
|
228
261
|
return { entity1, entity2, cardinality1, cardinality2, label, identifying }
|
|
229
262
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// @zombie-mermaid/mermaid-parser — per-type diagram parsers
|
|
3
|
+
//
|
|
4
|
+
// Class, ER, sequence, and XY chart diagrams have no shared generic model
|
|
5
|
+
// the way flowcharts/state diagrams do (`MermaidGraph`, parsed by the
|
|
6
|
+
// umbrella's own `src/parser.ts`). Both `svg-renderer` and `ascii-renderer`
|
|
7
|
+
// import each type's parse function and types directly — confirmed by grep
|
|
8
|
+
// (zombie-mermaid#624, umbrella #620, `monorepo-conversion-scoping.md`
|
|
9
|
+
// finding 2) — so this package's public API is every per-type parse
|
|
10
|
+
// function plus its types, not a single generic entry point.
|
|
11
|
+
//
|
|
12
|
+
// Each of `src/class/`, `src/er/`, `src/sequence/`, `src/xychart/` used to
|
|
13
|
+
// mix this parser half (`parser.ts`, `types.ts`, and — per file, not
|
|
14
|
+
// per-half-pair, see the scoping doc's addendum correction 3 —
|
|
15
|
+
// `class/format.ts`, `sequence/box-color.ts`, `sequence/activation-check.ts`,
|
|
16
|
+
// `xychart/colors.ts`) with a renderer half (`layout.ts`, `renderer.ts`) in
|
|
17
|
+
// the same directory. The renderer half moved into
|
|
18
|
+
// `packages/svg-renderer/src/<type>/` instead — see that package's `index.ts`
|
|
19
|
+
// header for the reverse dependency this split introduces (`svg-renderer`
|
|
20
|
+
// depends on this package for the positioned-diagram types and a handful of
|
|
21
|
+
// parser-side helpers, an ordinary acyclic workspace shape per finding 2).
|
|
22
|
+
//
|
|
23
|
+
// This package depends only on `@zombie-mermaid/core` — verified: no file
|
|
24
|
+
// below imports `elkjs`, `@zombie-mermaid/svg-renderer`, or anything from
|
|
25
|
+
// the umbrella. `toDirection` moved here from the umbrella's `src/parser.ts`
|
|
26
|
+
// (alongside `isDirection`, already `core` since #625) specifically so
|
|
27
|
+
// `er/parser.ts` below could use it without importing the umbrella and
|
|
28
|
+
// creating a cycle — see `packages/core/src/direction.ts`'s header.
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
export * from './class/parser.ts'
|
|
32
|
+
export * from './class/types.ts'
|
|
33
|
+
export * from './class/format.ts'
|
|
34
|
+
|
|
35
|
+
export * from './er/parser.ts'
|
|
36
|
+
export * from './er/types.ts'
|
|
37
|
+
|
|
38
|
+
export * from './sequence/parser.ts'
|
|
39
|
+
export * from './sequence/types.ts'
|
|
40
|
+
export * from './sequence/box-color.ts'
|
|
41
|
+
export * from './sequence/activation-check.ts'
|
|
42
|
+
export * from './sequence/activation-fix.ts'
|
|
43
|
+
|
|
44
|
+
export * from './xychart/parser.ts'
|
|
45
|
+
export * from './xychart/types.ts'
|
|
46
|
+
export * from './xychart/colors.ts'
|
|
47
|
+
|
|
48
|
+
export * from './expanded-shapes.ts'
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
// A mechanical, deterministic semantic check: every `activate X` (or the
|
|
5
5
|
// `+` arrow shorthand) must be closed by a matching `deactivate X` (or the
|
|
6
6
|
// `-` shorthand) before the diagram ends. This doesn't need an LLM judge —
|
|
7
|
-
// it's the same activation-stack bookkeeping
|
|
8
|
-
// runs to *draw*
|
|
9
|
-
//
|
|
7
|
+
// it's the same activation-stack bookkeeping
|
|
8
|
+
// packages/svg-renderer/src/sequence/layout.ts already runs to *draw*
|
|
9
|
+
// activation bars (see its `activationStacks` map), reused here to *report*
|
|
10
|
+
// imbalance instead of silently rendering around it.
|
|
10
11
|
//
|
|
11
12
|
// Motivation (issue #539, split from #536): research on LLM-generated
|
|
12
13
|
// Mermaid sequence diagrams found they fail mostly on activation handling
|