agentic-mermaid 0.1.0 → 0.1.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/CHANGELOG.md +149 -4
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +25 -32
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/fonts/FONT-LICENSES.md +9 -5
- package/assets/fonts/Inter-Bold.ttf +0 -0
- package/assets/fonts/Inter-Medium.ttf +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/fonts/Inter-SemiBold.ttf +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +81 -1822
- package/dist/agent.js +336 -36839
- package/dist/agentic-mermaid-mcp.js +7 -37908
- package/dist/am.js +33638 -36261
- package/dist/chunk-4QECACIA.js +4702 -0
- package/dist/chunk-CDPKJ2BQ.js +37 -0
- package/dist/chunk-QKDLMOJJ.js +3487 -0
- package/dist/chunk-WI35L7IR.js +615 -0
- package/dist/chunk-WN3YDOBG.js +149 -0
- package/dist/chunk-ZYMMKQ3I.js +62637 -0
- package/dist/index-D54dqSdC.d.ts +6777 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -32836
- package/dist/mcp-cli-SBQSTJFC.js +17 -0
- package/docs/README.md +14 -6
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +70 -43
- package/server.json +41 -0
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/AGENT_NATIVE.md +0 -602
- package/Instructions_for_agents.md +0 -83
- package/TODO.md +0 -662
- package/assets/hero.png +0 -0
- package/dist/index-x2Cj1dVv.d.ts +0 -1089
- package/docs/agent-api-cookbook.md +0 -308
- package/docs/agent-mutation-policy.md +0 -104
- package/docs/agent-workflow-examples.md +0 -46
- package/docs/api.md +0 -203
- package/docs/ascii.md +0 -103
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/comparison.md +0 -94
- package/docs/config.md +0 -105
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -71
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/custom-style-cookbook.md +0 -154
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -433
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -125
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/diagram-families.md +0 -193
- package/docs/features.md +0 -147
- package/docs/fork-differences.md +0 -130
- package/docs/getting-started.md +0 -175
- package/docs/issue-derived-test-cases.md +0 -156
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -496
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -118
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/mcp-code-mode-rationale.md +0 -101
- package/docs/mcp-http-transport.md +0 -223
- package/docs/mermaid-layout-complaints.md +0 -837
- package/docs/mutation-testing.md +0 -206
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/docs/quality.md +0 -212
- package/docs/react.md +0 -97
- package/docs/style-authoring.md +0 -133
- package/docs/testing-strategy.md +0 -335
- package/docs/theming.md +0 -162
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -364
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -337
- package/src/agent/families-builtin.ts +0 -934
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -826
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -414
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -439
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -117
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -979
- package/src/agent/verify.ts +0 -571
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -189
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -128
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1117
- package/src/cli/init-agent.ts +0 -147
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/font-manifest.ts +0 -27
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -520
- package/src/journey/parser.ts +0 -218
- package/src/journey/renderer.ts +0 -811
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -387
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -72
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -639
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -454
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- package/src/xychart/types.ts +0 -282
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Architecture structured body (BUILD-17: promotes architecture-beta from
|
|
3
|
-
// opaque-only fallback semantics to structured mutation, following the BUILD-15
|
|
4
|
-
// journey pilot).
|
|
5
|
-
//
|
|
6
|
-
// Modeled grammar (mirrors the legacy renderer parser, src/architecture/parser.ts):
|
|
7
|
-
// group <id>(<icon>)[<Label>] [in <parentGroup>]
|
|
8
|
-
// service <id>(<icon>)[<Label>] [in <group>]
|
|
9
|
-
// junction <id> [in <group>]
|
|
10
|
-
// <id>[{group}]:<SIDE> <arrow> <SIDE>:<id>[{group}] (SIDE ∈ L|R|T|B)
|
|
11
|
-
// arrows: -- --> <-- <--> and labeled -[label]- forms
|
|
12
|
-
//
|
|
13
|
-
// Structured-or-opaque: any other non-blank, non-comment line (accTitle,
|
|
14
|
-
// accDescr, the {group} boundary modifier, unmodeled syntax) returns null so
|
|
15
|
-
// the caller falls back to a lossless opaque body. Render support is unchanged
|
|
16
|
-
// — the legacy renderer keeps parsing the canonical source this module emits.
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
|
-
import { unknownOpMessage } from './mutation-ops.ts'
|
|
20
|
-
import type {
|
|
21
|
-
ArchitectureBody, ArchitectureGroup, ArchitectureService, ArchitectureJunction,
|
|
22
|
-
ArchitectureEdge, ArchitectureSide, ArchitectureMutationOp,
|
|
23
|
-
MutationError, Result, LayoutWarning, VerifyOptions,
|
|
24
|
-
} from './types.ts'
|
|
25
|
-
import { ok, err, DEFAULT_LABEL_CHAR_CAP } from './types.ts'
|
|
26
|
-
import { labelOverflowWarning } from './label-metrics.ts'
|
|
27
|
-
|
|
28
|
-
// ---- Parser -----------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
const IDENT = '[\\w-]+'
|
|
31
|
-
const ICON = '\\(([^)]+)\\)'
|
|
32
|
-
const LABEL = '\\[(.+)\\]'
|
|
33
|
-
|
|
34
|
-
const GROUP_RE = new RegExp(`^group\\s+(${IDENT})(?:${ICON})?(?:${LABEL})?(?:\\s+in\\s+(${IDENT}))?\\s*$`)
|
|
35
|
-
const SERVICE_RE = new RegExp(`^service\\s+(${IDENT})(?:${ICON})?(?:${LABEL})?(?:\\s+in\\s+(${IDENT}))?\\s*$`)
|
|
36
|
-
const JUNCTION_RE = new RegExp(`^junction\\s+(${IDENT})(?:\\s+in\\s+(${IDENT}))?\\s*$`)
|
|
37
|
-
// Endpoints: id:SIDE on the source, SIDE:id on the target. We deliberately do
|
|
38
|
-
// NOT model the {group} boundary modifier (id{group}:R) — those lines fall
|
|
39
|
-
// back to opaque, preserving the renderer's group-boundary feature losslessly.
|
|
40
|
-
const SOURCE_RE = new RegExp(`^(${IDENT}):(L|R|T|B)$`)
|
|
41
|
-
const TARGET_RE = new RegExp(`^(L|R|T|B):(${IDENT})$`)
|
|
42
|
-
|
|
43
|
-
const SIDES = new Set<ArchitectureSide>(['L', 'R', 'T', 'B'])
|
|
44
|
-
|
|
45
|
-
function normalizeText(value: string): string {
|
|
46
|
-
return value.split(/\r?\n/).map(part => part.trim()).filter(Boolean).join(' ')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface ParsedEndpoint { id: string; side: ArchitectureSide }
|
|
50
|
-
|
|
51
|
-
/** Parse an edge operator, returning the modeled arrow forms or null. */
|
|
52
|
-
function parseArrow(token: string): { label?: string; hasArrowStart: boolean; hasArrowEnd: boolean } | null {
|
|
53
|
-
const t = token.trim()
|
|
54
|
-
if (t === '<-->') return { hasArrowStart: true, hasArrowEnd: true }
|
|
55
|
-
if (t === '-->') return { hasArrowStart: false, hasArrowEnd: true }
|
|
56
|
-
if (t === '<--') return { hasArrowStart: true, hasArrowEnd: false }
|
|
57
|
-
if (t === '--') return { hasArrowStart: false, hasArrowEnd: false }
|
|
58
|
-
const m = t.match(/^(<)?-\[(.*)\]-(>)?$/)
|
|
59
|
-
if (!m) return null
|
|
60
|
-
const label = normalizeText(m[2] ?? '') || undefined
|
|
61
|
-
// A label that contains the bracket delimiter would not round-trip; reject.
|
|
62
|
-
if (label && (label.includes('[') || label.includes(']'))) return null
|
|
63
|
-
return { label, hasArrowStart: Boolean(m[1]), hasArrowEnd: Boolean(m[3]) }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function parseEdge(line: string): ArchitectureEdge | null {
|
|
67
|
-
const m = line.match(/^(\S+)\s+(.+)\s+(\S+)$/)
|
|
68
|
-
if (!m) return null
|
|
69
|
-
const src = m[1]!.match(SOURCE_RE)
|
|
70
|
-
const tgt = m[3]!.match(TARGET_RE)
|
|
71
|
-
if (!src || !tgt) return null
|
|
72
|
-
const arrow = parseArrow(m[2]!)
|
|
73
|
-
if (!arrow) return null
|
|
74
|
-
return {
|
|
75
|
-
source: { id: src[1]!, side: src[2] as ArchitectureSide },
|
|
76
|
-
target: { id: tgt[2]!, side: tgt[1] as ArchitectureSide },
|
|
77
|
-
label: arrow.label,
|
|
78
|
-
hasArrowStart: arrow.hasArrowStart,
|
|
79
|
-
hasArrowEnd: arrow.hasArrowEnd,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Parse architecture body lines (header excluded). Returns a structured body
|
|
85
|
-
* only if EVERY non-blank, non-comment line is a modeled group, service,
|
|
86
|
-
* junction, or edge, AND the diagram is internally consistent (no duplicate
|
|
87
|
-
* ids, every `in` parent and every edge endpoint resolves). Otherwise returns
|
|
88
|
-
* null (opaque fallback).
|
|
89
|
-
*/
|
|
90
|
-
export function parseArchitectureBody(lines: string[]): ArchitectureBody | null {
|
|
91
|
-
const groups: ArchitectureGroup[] = []
|
|
92
|
-
const services: ArchitectureService[] = []
|
|
93
|
-
const junctions: ArchitectureJunction[] = []
|
|
94
|
-
const edges: ArchitectureEdge[] = []
|
|
95
|
-
const ids = new Set<string>()
|
|
96
|
-
const groupIds = new Set<string>()
|
|
97
|
-
const endpointIds = new Set<string>() // services + junctions (valid edge endpoints)
|
|
98
|
-
const pendingParents: Array<{ parent: string }> = []
|
|
99
|
-
|
|
100
|
-
for (const raw of lines) {
|
|
101
|
-
const line = raw.trim()
|
|
102
|
-
if (!line) continue
|
|
103
|
-
if (line.startsWith('%%')) continue
|
|
104
|
-
|
|
105
|
-
const gm = line.match(GROUP_RE)
|
|
106
|
-
if (gm) {
|
|
107
|
-
const id = gm[1]!
|
|
108
|
-
if (ids.has(id)) return null
|
|
109
|
-
ids.add(id)
|
|
110
|
-
groupIds.add(id)
|
|
111
|
-
const icon = gm[2]?.trim() || undefined
|
|
112
|
-
const label = normalizeText(gm[3] ?? id)
|
|
113
|
-
const parentId = gm[4] ?? undefined
|
|
114
|
-
if (parentId) pendingParents.push({ parent: parentId })
|
|
115
|
-
groups.push({ id, label, icon, parentId })
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const sm = line.match(SERVICE_RE)
|
|
120
|
-
if (sm) {
|
|
121
|
-
const id = sm[1]!
|
|
122
|
-
if (ids.has(id)) return null
|
|
123
|
-
ids.add(id)
|
|
124
|
-
endpointIds.add(id)
|
|
125
|
-
const icon = sm[2]?.trim() || undefined
|
|
126
|
-
const label = normalizeText(sm[3] ?? id)
|
|
127
|
-
const parentId = sm[4] ?? undefined
|
|
128
|
-
if (parentId) pendingParents.push({ parent: parentId })
|
|
129
|
-
services.push({ id, label, icon, parentId })
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const jm = line.match(JUNCTION_RE)
|
|
134
|
-
if (jm) {
|
|
135
|
-
const id = jm[1]!
|
|
136
|
-
if (ids.has(id)) return null
|
|
137
|
-
ids.add(id)
|
|
138
|
-
endpointIds.add(id)
|
|
139
|
-
const parentId = jm[2] ?? undefined
|
|
140
|
-
if (parentId) pendingParents.push({ parent: parentId })
|
|
141
|
-
junctions.push({ id, parentId })
|
|
142
|
-
continue
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const edge = parseEdge(line)
|
|
146
|
-
if (edge) {
|
|
147
|
-
edges.push(edge)
|
|
148
|
-
continue
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Unmodeled line (accTitle/accDescr/{group} boundary/anything else) → opaque.
|
|
152
|
-
return null
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Validation: every `in` parent must be a declared group; every edge endpoint
|
|
156
|
-
// must be a declared service or junction. The legacy parser rejects diagrams
|
|
157
|
-
// that violate these, so a structured body must satisfy them to round-trip.
|
|
158
|
-
for (const { parent } of pendingParents) {
|
|
159
|
-
if (!groupIds.has(parent)) return null
|
|
160
|
-
}
|
|
161
|
-
for (const edge of edges) {
|
|
162
|
-
if (!endpointIds.has(edge.source.id) || !endpointIds.has(edge.target.id)) return null
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// The legacy renderer rejects an empty architecture diagram; model the same
|
|
166
|
-
// floor so a structured body always renders.
|
|
167
|
-
if (groups.length === 0 && services.length === 0 && junctions.length === 0) return null
|
|
168
|
-
|
|
169
|
-
return { kind: 'architecture', groups, services, junctions, edges }
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ---- Serializer -------------------------------------------------------------
|
|
173
|
-
|
|
174
|
-
function renderNode(keyword: 'group' | 'service', id: string, label: string, icon?: string, parentId?: string): string {
|
|
175
|
-
const iconPart = icon ? `(${icon})` : ''
|
|
176
|
-
const labelPart = `[${label}]`
|
|
177
|
-
const inPart = parentId ? ` in ${parentId}` : ''
|
|
178
|
-
return ` ${keyword} ${id}${iconPart}${labelPart}${inPart}`
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function renderArrow(edge: ArchitectureEdge): string {
|
|
182
|
-
if (edge.label !== undefined) {
|
|
183
|
-
return `${edge.hasArrowStart ? '<' : ''}-[${edge.label}]-${edge.hasArrowEnd ? '>' : ''}`
|
|
184
|
-
}
|
|
185
|
-
if (edge.hasArrowStart && edge.hasArrowEnd) return '<-->'
|
|
186
|
-
if (edge.hasArrowEnd) return '-->'
|
|
187
|
-
if (edge.hasArrowStart) return '<--'
|
|
188
|
-
return '--'
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function renderArchitecture(body: ArchitectureBody): string {
|
|
192
|
-
const lines: string[] = ['architecture-beta']
|
|
193
|
-
for (const g of body.groups) lines.push(renderNode('group', g.id, g.label, g.icon, g.parentId))
|
|
194
|
-
for (const s of body.services) lines.push(renderNode('service', s.id, s.label, s.icon, s.parentId))
|
|
195
|
-
for (const j of body.junctions) {
|
|
196
|
-
lines.push(` junction ${j.id}${j.parentId ? ` in ${j.parentId}` : ''}`)
|
|
197
|
-
}
|
|
198
|
-
for (const e of body.edges) {
|
|
199
|
-
lines.push(` ${e.source.id}:${e.source.side} ${renderArrow(e)} ${e.target.side}:${e.target.id}`)
|
|
200
|
-
}
|
|
201
|
-
return lines.join('\n') + '\n'
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ---- Mutator ----------------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
function cloneArchitecture(b: ArchitectureBody): ArchitectureBody {
|
|
207
|
-
return {
|
|
208
|
-
kind: 'architecture',
|
|
209
|
-
groups: b.groups.map(g => ({ ...g })),
|
|
210
|
-
services: b.services.map(s => ({ ...s })),
|
|
211
|
-
junctions: b.junctions.map(j => ({ ...j })),
|
|
212
|
-
edges: b.edges.map(e => ({
|
|
213
|
-
source: { ...e.source }, target: { ...e.target },
|
|
214
|
-
label: e.label, hasArrowStart: e.hasArrowStart, hasArrowEnd: e.hasArrowEnd,
|
|
215
|
-
})),
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function allIds(b: ArchitectureBody): Set<string> {
|
|
220
|
-
const s = new Set<string>()
|
|
221
|
-
for (const g of b.groups) s.add(g.id)
|
|
222
|
-
for (const sv of b.services) s.add(sv.id)
|
|
223
|
-
for (const j of b.junctions) s.add(j.id)
|
|
224
|
-
return s
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const ID_RE = /^[\w-]+$/
|
|
228
|
-
|
|
229
|
-
function validId(value: unknown, field: string): Result<string, MutationError> {
|
|
230
|
-
if (typeof value !== 'string' || !ID_RE.test(value)) {
|
|
231
|
-
return err({ code: 'INVALID_OP', message: `Architecture ${field} must match [A-Za-z0-9_-]+, got ${JSON.stringify(value)}` })
|
|
232
|
-
}
|
|
233
|
-
return ok(value)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function normalizeLabel(value: string, field: string): Result<string, MutationError> {
|
|
237
|
-
if (typeof value !== 'string') return err({ code: 'INVALID_OP', message: `Architecture ${field} must be a string` })
|
|
238
|
-
const normalized = normalizeText(value)
|
|
239
|
-
// Labels are rendered inside [...]; a bracket would break round-trip.
|
|
240
|
-
if (!normalized || normalized.includes('[') || normalized.includes(']')) {
|
|
241
|
-
return err({ code: 'INVALID_OP', message: `Architecture ${field} must be non-empty and must not contain [ or ]` })
|
|
242
|
-
}
|
|
243
|
-
return ok(normalized)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function validSide(value: unknown): value is ArchitectureSide {
|
|
247
|
-
return typeof value === 'string' && SIDES.has(value as ArchitectureSide)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/** Edge id convention: `<from>-><to>` (matches the flowchart edgeId idiom). */
|
|
251
|
-
export function architectureEdgeId(e: ArchitectureEdge): string {
|
|
252
|
-
return `${e.source.id}->${e.target.id}`
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export function mutateArchitecture(body: ArchitectureBody, op: ArchitectureMutationOp): Result<ArchitectureBody, MutationError> {
|
|
256
|
-
const next = cloneArchitecture(body)
|
|
257
|
-
|
|
258
|
-
switch (op.kind) {
|
|
259
|
-
case 'add_service': {
|
|
260
|
-
const id = validId(op.id, 'service id')
|
|
261
|
-
if (!id.ok) return id
|
|
262
|
-
if (allIds(next).has(id.value)) return err({ code: 'INVALID_OP', message: `Identifier "${id.value}" already exists` })
|
|
263
|
-
const label = normalizeLabel(op.label ?? id.value, 'service label')
|
|
264
|
-
if (!label.ok) return label
|
|
265
|
-
let icon: string | undefined
|
|
266
|
-
if (op.icon !== undefined && op.icon !== null) {
|
|
267
|
-
if (typeof op.icon !== 'string' || op.icon.includes('(') || op.icon.includes(')')) {
|
|
268
|
-
return err({ code: 'INVALID_OP', message: 'Architecture service icon must be a string without parentheses' })
|
|
269
|
-
}
|
|
270
|
-
icon = op.icon.trim() || undefined
|
|
271
|
-
}
|
|
272
|
-
if (op.group !== undefined && op.group !== null) {
|
|
273
|
-
const g = validId(op.group, 'group id')
|
|
274
|
-
if (!g.ok) return g
|
|
275
|
-
if (!next.groups.some(grp => grp.id === g.value)) return err({ code: 'GROUP_NOT_FOUND', message: `No group "${g.value}"` })
|
|
276
|
-
next.services.push({ id: id.value, label: label.value, icon, parentId: g.value })
|
|
277
|
-
} else {
|
|
278
|
-
next.services.push({ id: id.value, label: label.value, icon })
|
|
279
|
-
}
|
|
280
|
-
break
|
|
281
|
-
}
|
|
282
|
-
case 'remove_service': {
|
|
283
|
-
const idx = next.services.findIndex(s => s.id === op.id)
|
|
284
|
-
if (idx < 0) return err({ code: 'SERVICE_NOT_FOUND', message: `No service "${op.id}"` })
|
|
285
|
-
next.services.splice(idx, 1)
|
|
286
|
-
// Cascade: drop every edge that touches the removed service.
|
|
287
|
-
next.edges = next.edges.filter(e => e.source.id !== op.id && e.target.id !== op.id)
|
|
288
|
-
break
|
|
289
|
-
}
|
|
290
|
-
case 'rename_service': {
|
|
291
|
-
const from = validId(op.from, 'service id')
|
|
292
|
-
if (!from.ok) return from
|
|
293
|
-
const to = validId(op.to, 'service id')
|
|
294
|
-
if (!to.ok) return to
|
|
295
|
-
const svc = next.services.find(s => s.id === from.value)
|
|
296
|
-
if (!svc) return err({ code: 'SERVICE_NOT_FOUND', message: `No service "${from.value}"` })
|
|
297
|
-
if (from.value !== to.value && allIds(next).has(to.value)) {
|
|
298
|
-
return err({ code: 'INVALID_OP', message: `Identifier "${to.value}" already exists` })
|
|
299
|
-
}
|
|
300
|
-
svc.id = to.value
|
|
301
|
-
// Keep edges anchored to the renamed service.
|
|
302
|
-
for (const e of next.edges) {
|
|
303
|
-
if (e.source.id === from.value) e.source.id = to.value
|
|
304
|
-
if (e.target.id === from.value) e.target.id = to.value
|
|
305
|
-
}
|
|
306
|
-
break
|
|
307
|
-
}
|
|
308
|
-
case 'set_service_label': {
|
|
309
|
-
const svc = next.services.find(s => s.id === op.id)
|
|
310
|
-
if (!svc) return err({ code: 'SERVICE_NOT_FOUND', message: `No service "${op.id}"` })
|
|
311
|
-
const label = normalizeLabel(op.label, 'service label')
|
|
312
|
-
if (!label.ok) return label
|
|
313
|
-
svc.label = label.value
|
|
314
|
-
break
|
|
315
|
-
}
|
|
316
|
-
case 'set_service_icon': {
|
|
317
|
-
const svc = next.services.find(s => s.id === op.id)
|
|
318
|
-
if (!svc) return err({ code: 'SERVICE_NOT_FOUND', message: `No service "${op.id}"` })
|
|
319
|
-
if (op.icon === null) { delete svc.icon; break }
|
|
320
|
-
if (typeof op.icon !== 'string' || op.icon.includes('(') || op.icon.includes(')')) {
|
|
321
|
-
return err({ code: 'INVALID_OP', message: 'Architecture service icon must be a string without parentheses' })
|
|
322
|
-
}
|
|
323
|
-
const icon = op.icon.trim()
|
|
324
|
-
if (!icon) return err({ code: 'INVALID_OP', message: 'Architecture service icon must be non-empty (use null to clear)' })
|
|
325
|
-
svc.icon = icon
|
|
326
|
-
break
|
|
327
|
-
}
|
|
328
|
-
case 'move_service': {
|
|
329
|
-
const svc = next.services.find(s => s.id === op.id)
|
|
330
|
-
if (!svc) return err({ code: 'SERVICE_NOT_FOUND', message: `No service "${op.id}"` })
|
|
331
|
-
if (op.group === null) { delete svc.parentId; break }
|
|
332
|
-
const g = validId(op.group, 'group id')
|
|
333
|
-
if (!g.ok) return g
|
|
334
|
-
if (!next.groups.some(grp => grp.id === g.value)) return err({ code: 'GROUP_NOT_FOUND', message: `No group "${g.value}"` })
|
|
335
|
-
svc.parentId = g.value
|
|
336
|
-
break
|
|
337
|
-
}
|
|
338
|
-
case 'add_group': {
|
|
339
|
-
const id = validId(op.id, 'group id')
|
|
340
|
-
if (!id.ok) return id
|
|
341
|
-
if (allIds(next).has(id.value)) return err({ code: 'INVALID_OP', message: `Identifier "${id.value}" already exists` })
|
|
342
|
-
const label = normalizeLabel(op.label ?? id.value, 'group label')
|
|
343
|
-
if (!label.ok) return label
|
|
344
|
-
let icon: string | undefined
|
|
345
|
-
if (op.icon !== undefined && op.icon !== null) {
|
|
346
|
-
if (typeof op.icon !== 'string' || op.icon.includes('(') || op.icon.includes(')')) {
|
|
347
|
-
return err({ code: 'INVALID_OP', message: 'Architecture group icon must be a string without parentheses' })
|
|
348
|
-
}
|
|
349
|
-
icon = op.icon.trim() || undefined
|
|
350
|
-
}
|
|
351
|
-
let parentId: string | undefined
|
|
352
|
-
if (op.parent !== undefined && op.parent !== null) {
|
|
353
|
-
const p = validId(op.parent, 'group id')
|
|
354
|
-
if (!p.ok) return p
|
|
355
|
-
if (!next.groups.some(grp => grp.id === p.value)) return err({ code: 'GROUP_NOT_FOUND', message: `No group "${p.value}"` })
|
|
356
|
-
parentId = p.value
|
|
357
|
-
}
|
|
358
|
-
next.groups.push({ id: id.value, label: label.value, icon, parentId })
|
|
359
|
-
break
|
|
360
|
-
}
|
|
361
|
-
case 'remove_group': {
|
|
362
|
-
const idx = next.groups.findIndex(g => g.id === op.id)
|
|
363
|
-
if (idx < 0) return err({ code: 'GROUP_NOT_FOUND', message: `No group "${op.id}"` })
|
|
364
|
-
// Policy: refuse to remove a non-empty group. Removing a group that still
|
|
365
|
-
// owns services, junctions, or child groups would orphan members (the
|
|
366
|
-
// legacy parser requires every `in <group>` to resolve), so we make the
|
|
367
|
-
// caller move/remove members first rather than silently re-parenting or
|
|
368
|
-
// cascade-deleting an entire subtree. Tested in agent-architecture.test.ts.
|
|
369
|
-
const hasMembers =
|
|
370
|
-
next.services.some(s => s.parentId === op.id) ||
|
|
371
|
-
next.junctions.some(j => j.parentId === op.id) ||
|
|
372
|
-
next.groups.some(g => g.parentId === op.id)
|
|
373
|
-
if (hasMembers) return err({ code: 'INVALID_OP', message: `Group "${op.id}" is not empty; move or remove its members first` })
|
|
374
|
-
next.groups.splice(idx, 1)
|
|
375
|
-
break
|
|
376
|
-
}
|
|
377
|
-
case 'add_edge': {
|
|
378
|
-
const from = validId(op.from, 'service id')
|
|
379
|
-
if (!from.ok) return from
|
|
380
|
-
const to = validId(op.to, 'service id')
|
|
381
|
-
if (!to.ok) return to
|
|
382
|
-
const endpoints = new Set([...next.services.map(s => s.id), ...next.junctions.map(j => j.id)])
|
|
383
|
-
if (!endpoints.has(from.value)) return err({ code: 'SERVICE_NOT_FOUND', message: `No service/junction "${from.value}"` })
|
|
384
|
-
if (!endpoints.has(to.value)) return err({ code: 'SERVICE_NOT_FOUND', message: `No service/junction "${to.value}"` })
|
|
385
|
-
if (!validSide(op.fromSide)) return err({ code: 'INVALID_OP', message: `fromSide must be one of L|R|T|B, got ${JSON.stringify(op.fromSide)}` })
|
|
386
|
-
if (!validSide(op.toSide)) return err({ code: 'INVALID_OP', message: `toSide must be one of L|R|T|B, got ${JSON.stringify(op.toSide)}` })
|
|
387
|
-
let label: string | undefined
|
|
388
|
-
if (op.label !== undefined && op.label !== null) {
|
|
389
|
-
const l = normalizeLabel(op.label, 'edge label')
|
|
390
|
-
if (!l.ok) return l
|
|
391
|
-
label = l.value
|
|
392
|
-
}
|
|
393
|
-
next.edges.push({
|
|
394
|
-
source: { id: from.value, side: op.fromSide },
|
|
395
|
-
target: { id: to.value, side: op.toSide },
|
|
396
|
-
label,
|
|
397
|
-
hasArrowStart: op.hasArrowStart ?? false,
|
|
398
|
-
hasArrowEnd: op.hasArrowEnd ?? true,
|
|
399
|
-
})
|
|
400
|
-
break
|
|
401
|
-
}
|
|
402
|
-
case 'remove_edge': {
|
|
403
|
-
if (typeof op.index === 'number') {
|
|
404
|
-
if (!next.edges[op.index]) return err({ code: 'EDGE_NOT_FOUND', message: `No edge at index ${op.index}` })
|
|
405
|
-
next.edges.splice(op.index, 1)
|
|
406
|
-
} else if (typeof op.id === 'string') {
|
|
407
|
-
const idx = next.edges.findIndex(e => architectureEdgeId(e) === op.id)
|
|
408
|
-
if (idx < 0) return err({ code: 'EDGE_NOT_FOUND', message: `No edge "${op.id}"` })
|
|
409
|
-
next.edges.splice(idx, 1)
|
|
410
|
-
} else {
|
|
411
|
-
return err({ code: 'INVALID_OP', message: 'remove_edge requires index (number) or id (from->to)' })
|
|
412
|
-
}
|
|
413
|
-
break
|
|
414
|
-
}
|
|
415
|
-
default: {
|
|
416
|
-
const _x: never = op
|
|
417
|
-
return err({ code: 'INVALID_OP', message: unknownOpMessage('architecture', _x) })
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Preserve the structured floor: an architecture diagram must keep at least
|
|
422
|
-
// one node so it always renders.
|
|
423
|
-
if (next.groups.length === 0 && next.services.length === 0 && next.junctions.length === 0) {
|
|
424
|
-
return err({ code: 'INVALID_OP', message: 'Architecture diagram must keep at least one group, service, or junction' })
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return ok(next)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// ---- Verifier (FamilyPlugin.verify hook) ------------------------------------
|
|
431
|
-
|
|
432
|
-
export function verifyArchitecture(body: ArchitectureBody, opts: VerifyOptions): LayoutWarning[] {
|
|
433
|
-
const cap = opts.labelCharCap ?? DEFAULT_LABEL_CHAR_CAP
|
|
434
|
-
const warnings: LayoutWarning[] = []
|
|
435
|
-
const overflow = (target: string, text: string) => {
|
|
436
|
-
const w = labelOverflowWarning(target, text, cap)
|
|
437
|
-
if (w) warnings.push(w)
|
|
438
|
-
}
|
|
439
|
-
if (body.groups.length === 0 && body.services.length === 0 && body.junctions.length === 0) {
|
|
440
|
-
warnings.push({ code: 'EMPTY_DIAGRAM' })
|
|
441
|
-
}
|
|
442
|
-
for (const g of body.groups) overflow(g.id, g.label)
|
|
443
|
-
for (const s of body.services) overflow(s.id, s.label)
|
|
444
|
-
body.edges.forEach((e, i) => {
|
|
445
|
-
// Anchor any edge whose endpoints no longer resolve (defensive; mutate keeps
|
|
446
|
-
// these consistent, but an externally-synthesized body might not).
|
|
447
|
-
const ids = new Set([...body.services.map(s => s.id), ...body.junctions.map(j => j.id)])
|
|
448
|
-
if (!ids.has(e.source.id) || !ids.has(e.target.id)) {
|
|
449
|
-
warnings.push({
|
|
450
|
-
code: 'EDGE_MISANCHORED', edge: `edge#${i}:${architectureEdgeId(e)}`,
|
|
451
|
-
from: ids.has(e.source.id) ? e.source.id : undefined,
|
|
452
|
-
to: ids.has(e.target.id) ? e.target.id : undefined,
|
|
453
|
-
})
|
|
454
|
-
}
|
|
455
|
-
if (e.label !== undefined) overflow(`edge#${i}:${architectureEdgeId(e)}`, e.label)
|
|
456
|
-
})
|
|
457
|
-
return warnings
|
|
458
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EdgeRouteCertificate,
|
|
3
|
-
FamilyEdgeRouteCertificate,
|
|
4
|
-
LayoutRouteCertificate,
|
|
5
|
-
RegionContainmentCertificate,
|
|
6
|
-
RouteCertificate,
|
|
7
|
-
} from '../types.ts'
|
|
8
|
-
|
|
9
|
-
export type LayoutCertificateProof = 'edge-route' | 'region-containment'
|
|
10
|
-
|
|
11
|
-
export function layoutCertificateProof(cert: LayoutRouteCertificate): LayoutCertificateProof {
|
|
12
|
-
if ('elementId' in cert) return regionContainmentProof(cert)
|
|
13
|
-
return edgeRouteProof(cert)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function edgeRouteProof(cert: EdgeRouteCertificate): LayoutCertificateProof {
|
|
17
|
-
if ('family' in cert) return familyEdgeRouteProof(cert)
|
|
18
|
-
return flowchartEdgeRouteProof(cert)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function flowchartEdgeRouteProof(cert: RouteCertificate): LayoutCertificateProof {
|
|
22
|
-
switch (cert.invariant) {
|
|
23
|
-
case 'straight':
|
|
24
|
-
case 'explained-detour':
|
|
25
|
-
case 'bundle':
|
|
26
|
-
case 'outer-feedback':
|
|
27
|
-
case 'feedback-detour':
|
|
28
|
-
case 'self-loop':
|
|
29
|
-
case 'container-attach':
|
|
30
|
-
case 'unverified-shape':
|
|
31
|
-
return 'edge-route'
|
|
32
|
-
default:
|
|
33
|
-
return assertNever(cert)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function familyEdgeRouteProof(cert: FamilyEdgeRouteCertificate): LayoutCertificateProof {
|
|
38
|
-
switch (cert.family) {
|
|
39
|
-
case 'class':
|
|
40
|
-
case 'er':
|
|
41
|
-
return familyBoxRouteProof(cert.invariant)
|
|
42
|
-
case 'architecture':
|
|
43
|
-
return architectureRouteProof(cert.invariant)
|
|
44
|
-
case 'sequence':
|
|
45
|
-
return sequenceRouteProof(cert.invariant)
|
|
46
|
-
default:
|
|
47
|
-
return assertNever(cert)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function familyBoxRouteProof(invariant: Extract<FamilyEdgeRouteCertificate, { family: 'class' | 'er' }>['invariant']): LayoutCertificateProof {
|
|
52
|
-
switch (invariant) {
|
|
53
|
-
case 'orthogonal-box':
|
|
54
|
-
case 'unverified-family-route':
|
|
55
|
-
return 'edge-route'
|
|
56
|
-
default:
|
|
57
|
-
return assertNever(invariant)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function architectureRouteProof(invariant: Extract<FamilyEdgeRouteCertificate, { family: 'architecture' }>['invariant']): LayoutCertificateProof {
|
|
62
|
-
switch (invariant) {
|
|
63
|
-
case 'side-anchored':
|
|
64
|
-
case 'unverified-family-route':
|
|
65
|
-
return 'edge-route'
|
|
66
|
-
default:
|
|
67
|
-
return assertNever(invariant)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function sequenceRouteProof(invariant: Extract<FamilyEdgeRouteCertificate, { family: 'sequence' }>['invariant']): LayoutCertificateProof {
|
|
72
|
-
switch (invariant) {
|
|
73
|
-
case 'lifeline-message':
|
|
74
|
-
case 'self-message':
|
|
75
|
-
case 'unverified-family-route':
|
|
76
|
-
return 'edge-route'
|
|
77
|
-
default:
|
|
78
|
-
return assertNever(invariant)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function regionContainmentProof(cert: RegionContainmentCertificate): LayoutCertificateProof {
|
|
83
|
-
switch (cert.family) {
|
|
84
|
-
case 'timeline':
|
|
85
|
-
case 'xychart':
|
|
86
|
-
case 'pie':
|
|
87
|
-
case 'quadrant':
|
|
88
|
-
case 'gantt':
|
|
89
|
-
return regionInvariantProof(cert.invariant)
|
|
90
|
-
default:
|
|
91
|
-
return assertNever(cert.family)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function regionInvariantProof(invariant: RegionContainmentCertificate['invariant']): LayoutCertificateProof {
|
|
96
|
-
switch (invariant) {
|
|
97
|
-
case 'timeline-interval':
|
|
98
|
-
case 'plot-contained':
|
|
99
|
-
case 'legend-contained':
|
|
100
|
-
case 'section-contained':
|
|
101
|
-
case 'unverified-family-layout':
|
|
102
|
-
return 'region-containment'
|
|
103
|
-
default:
|
|
104
|
-
return assertNever(invariant)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function assertNever(value: never): never {
|
|
109
|
-
throw new Error(`Unhandled layout certificate variant: ${JSON.stringify(value)}`)
|
|
110
|
-
}
|