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,414 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Journey structured body (BUILD-15 pilot for promoting source-level families).
|
|
3
|
-
//
|
|
4
|
-
// Modeled grammar (mirrors the legacy renderer parser, src/journey/parser.ts):
|
|
5
|
-
// title <text>
|
|
6
|
-
// accTitle: <text>
|
|
7
|
-
// accDescr: <text>
|
|
8
|
-
// accDescr { <multiline text> }
|
|
9
|
-
// section <label>
|
|
10
|
-
// <task text>: <score 1..5>[: <actor>[, <actor>…]]
|
|
11
|
-
//
|
|
12
|
-
// Structured-or-opaque: any other non-blank, non-comment line (unmodeled
|
|
13
|
-
// syntax, out-of-range scores) returns null so the caller falls back to a
|
|
14
|
-
// lossless opaque body. Render support stays unchanged — the legacy renderer
|
|
15
|
-
// keeps parsing canonical source.
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
import { unknownOpMessage } from './mutation-ops.ts'
|
|
19
|
-
import type {
|
|
20
|
-
JourneyBody, JourneySection, JourneyTask, JourneyMutationOp,
|
|
21
|
-
MutationError, Result, LayoutWarning, VerifyOptions,
|
|
22
|
-
} from './types.ts'
|
|
23
|
-
import { ok, err, DEFAULT_LABEL_CHAR_CAP } from './types.ts'
|
|
24
|
-
import { labelOverflowWarning } from './label-metrics.ts'
|
|
25
|
-
|
|
26
|
-
// ---- Parser -----------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
const TITLE_RE = /^title\s+(.+)$/i
|
|
29
|
-
const ACC_LINE_RE = (directive: 'accTitle' | 'accDescr') => new RegExp(`^${directive}\\s*:[ \\t]*(.+)$`, 'i')
|
|
30
|
-
const SECTION_RE = /^section\s+(.+)$/i
|
|
31
|
-
const TASK_RE = /^(.+?)\s*:\s*([0-9]+)\s*(?::\s*(.*))?$/
|
|
32
|
-
|
|
33
|
-
function normalizeText(value: string): string {
|
|
34
|
-
return normalizeJourneyLabel(value).split(/\r?\n/).map(part => part.trim()).filter(Boolean).join('\n')
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normalizeActorText(value: string): string {
|
|
38
|
-
return normalizeText(value).split(/\r?\n/).map(part => part.trim()).filter(Boolean).join(' / ')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function normalizeJourneyLabel(label: string): string {
|
|
42
|
-
return label
|
|
43
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
44
|
-
.replace(/\\n/g, '\n')
|
|
45
|
-
.replace(/<\/?(?:sub|sup|small|mark)\s*>/gi, '')
|
|
46
|
-
.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>')
|
|
47
|
-
.replace(/(?<!\*)\*([^\s*](?:[^*]*[^\s*])?)\*(?!\*)/g, '<i>$1</i>')
|
|
48
|
-
.replace(/~~(.+?)~~/g, '<s>$1</s>')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function formatJourneyInline(value: string): string {
|
|
52
|
-
return value.split(/\r?\n/).map(part => part.trim()).join('<br>')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function isJourneyComment(line: string): boolean {
|
|
56
|
-
return line.startsWith('%%') || line.startsWith('#') || line.startsWith('%')
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function validScore(score: number): boolean {
|
|
60
|
-
return Number.isInteger(score) && score >= 1 && score <= 5
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Parse journey body lines (header excluded). Returns a structured body only
|
|
65
|
-
* if EVERY non-blank, non-comment line is a title, section, or task with a
|
|
66
|
-
* valid 1..5 integer score. Otherwise returns null (opaque fallback).
|
|
67
|
-
*/
|
|
68
|
-
export function parseJourneyBody(lines: string[]): JourneyBody | null {
|
|
69
|
-
const body: JourneyBody = { kind: 'journey', sections: [] }
|
|
70
|
-
let currentSection: JourneySection | undefined
|
|
71
|
-
let sIdx = 0, tIdx = 0
|
|
72
|
-
|
|
73
|
-
const implicitSection = (): JourneySection => {
|
|
74
|
-
if (!currentSection) {
|
|
75
|
-
currentSection = { id: `section-${sIdx++}`, tasks: [] }
|
|
76
|
-
body.sections.push(currentSection)
|
|
77
|
-
}
|
|
78
|
-
return currentSection
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (let i = 0; i < lines.length; i++) {
|
|
82
|
-
const raw = lines[i]!
|
|
83
|
-
const line = raw.trim()
|
|
84
|
-
if (!line) continue
|
|
85
|
-
if (isJourneyComment(line)) continue
|
|
86
|
-
|
|
87
|
-
const accTitle = parseAccessibilityLine(line, 'accTitle')
|
|
88
|
-
if (accTitle !== undefined) {
|
|
89
|
-
body.accessibilityTitle = accTitle
|
|
90
|
-
continue
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const accDescrStart = line.match(/^accDescr\s*:?\s*\{\s*(.*)$/i)
|
|
94
|
-
if (accDescrStart) {
|
|
95
|
-
const initial = accDescrStart[1] ?? ''
|
|
96
|
-
const parsed = collectAccessibilityBlock(initial, lines, i)
|
|
97
|
-
if (!parsed) return null
|
|
98
|
-
body.accessibilityDescription = normalizeAccessibilityText(parsed.text)
|
|
99
|
-
i = parsed.nextIndex
|
|
100
|
-
continue
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const accDescr = parseAccessibilityLine(line, 'accDescr')
|
|
104
|
-
if (accDescr !== undefined) {
|
|
105
|
-
body.accessibilityDescription = accDescr
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const tm = line.match(TITLE_RE)
|
|
110
|
-
if (tm) {
|
|
111
|
-
const title = normalizeText(tm[1]!)
|
|
112
|
-
if (!title) return null
|
|
113
|
-
body.title = title
|
|
114
|
-
continue
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const sm = line.match(SECTION_RE)
|
|
118
|
-
if (sm) {
|
|
119
|
-
const label = normalizeText(sm[1]!)
|
|
120
|
-
if (!label || label.includes(':')) return null
|
|
121
|
-
currentSection = { id: `section-${sIdx++}`, label, tasks: [] }
|
|
122
|
-
body.sections.push(currentSection)
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const km = line.match(TASK_RE)
|
|
127
|
-
if (km) {
|
|
128
|
-
const text = normalizeText(km[1]!)
|
|
129
|
-
const score = Number.parseInt(km[2]!, 10)
|
|
130
|
-
if (!text || !validScore(score)) return null
|
|
131
|
-
const actors = (km[3] ?? '')
|
|
132
|
-
.split(',')
|
|
133
|
-
.map(normalizeActorText)
|
|
134
|
-
.filter(Boolean)
|
|
135
|
-
implicitSection().tasks.push({ id: `task-${tIdx++}`, text, score, actors })
|
|
136
|
-
continue
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Unmodeled line (anything else) → opaque fallback.
|
|
140
|
-
return null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Upstream parity: title/acc metadata-only journeys are renderable header
|
|
144
|
-
// furniture. A truly empty journey still stays opaque for source fidelity.
|
|
145
|
-
if (body.sections.length === 0 && body.sections.every(s => s.tasks.length === 0) && !body.title && !body.accessibilityTitle && !body.accessibilityDescription) {
|
|
146
|
-
return null
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return body
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ---- Serializer -------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
export function renderJourney(body: JourneyBody): string {
|
|
155
|
-
const lines: string[] = ['journey']
|
|
156
|
-
if (body.title) lines.push(` title ${formatJourneyInline(body.title)}`)
|
|
157
|
-
if (body.accessibilityTitle) lines.push(` accTitle: ${formatJourneyInline(body.accessibilityTitle)}`)
|
|
158
|
-
if (body.accessibilityDescription) {
|
|
159
|
-
if (body.accessibilityDescription.includes('\n')) {
|
|
160
|
-
lines.push(' accDescr {')
|
|
161
|
-
for (const line of body.accessibilityDescription.split(/\r?\n/)) {
|
|
162
|
-
lines.push(` ${line.trim()}`)
|
|
163
|
-
}
|
|
164
|
-
lines.push(' }')
|
|
165
|
-
} else {
|
|
166
|
-
lines.push(` accDescr: ${formatJourneyInline(body.accessibilityDescription)}`)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
for (const section of body.sections) {
|
|
170
|
-
if (section.label !== undefined) lines.push(` section ${formatJourneyInline(section.label)}`)
|
|
171
|
-
for (const task of section.tasks) {
|
|
172
|
-
const actors = task.actors.length > 0 ? `: ${task.actors.join(', ')}` : ''
|
|
173
|
-
lines.push(` ${formatJourneyInline(task.text)}: ${task.score}${actors}`)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return lines.join('\n') + '\n'
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ---- Mutator ----------------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
function cloneJourney(b: JourneyBody): JourneyBody {
|
|
182
|
-
return {
|
|
183
|
-
kind: 'journey',
|
|
184
|
-
title: b.title,
|
|
185
|
-
accessibilityTitle: b.accessibilityTitle,
|
|
186
|
-
accessibilityDescription: b.accessibilityDescription,
|
|
187
|
-
sections: b.sections.map(s => ({
|
|
188
|
-
id: s.id, label: s.label,
|
|
189
|
-
tasks: s.tasks.map(t => ({ id: t.id, text: t.text, score: t.score, actors: [...t.actors] })),
|
|
190
|
-
})),
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function makeIdAllocator(body: JourneyBody): (prefix: 'section' | 'task') => string {
|
|
195
|
-
const seen = new Set<string>()
|
|
196
|
-
for (const s of body.sections) { seen.add(s.id); for (const t of s.tasks) seen.add(t.id) }
|
|
197
|
-
return prefix => {
|
|
198
|
-
let n = 0
|
|
199
|
-
while (seen.has(`${prefix}-${n}`)) n++
|
|
200
|
-
const id = `${prefix}-${n}`
|
|
201
|
-
seen.add(id)
|
|
202
|
-
return id
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function normalizeOpText(value: string, field: string, opts: { allowColon?: boolean } = {}): Result<string, MutationError> {
|
|
207
|
-
if (typeof value !== 'string') return err({ code: 'INVALID_OP', message: `Journey ${field} must be a string` })
|
|
208
|
-
const normalized = normalizeText(value)
|
|
209
|
-
if (!normalized || (!opts.allowColon && normalized.includes(':'))) {
|
|
210
|
-
return err({ code: 'INVALID_OP', message: `Journey ${field} must be non-empty and must not contain :` })
|
|
211
|
-
}
|
|
212
|
-
return ok(normalized)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function normalizeActors(actors: string[] | undefined): Result<string[], MutationError> {
|
|
216
|
-
const out: string[] = []
|
|
217
|
-
for (const raw of actors ?? []) {
|
|
218
|
-
if (typeof raw !== 'string') return err({ code: 'INVALID_OP', message: 'Journey actor must be a string' })
|
|
219
|
-
const normalized = normalizeActorText(raw)
|
|
220
|
-
if (!normalized) return err({ code: 'INVALID_OP', message: 'Journey actor must be non-empty' })
|
|
221
|
-
out.push(normalized)
|
|
222
|
-
}
|
|
223
|
-
return ok(out)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function normalizeAccessibilityText(value: string): string {
|
|
227
|
-
return normalizeJourneyLabel(value)
|
|
228
|
-
.split(/\r?\n/)
|
|
229
|
-
.map(part => part.trim())
|
|
230
|
-
.filter(Boolean)
|
|
231
|
-
.join('\n')
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function parseAccessibilityLine(
|
|
235
|
-
line: string,
|
|
236
|
-
directive: 'accTitle' | 'accDescr',
|
|
237
|
-
): string | undefined {
|
|
238
|
-
const match = line.match(ACC_LINE_RE(directive))
|
|
239
|
-
return match ? normalizeAccessibilityText(match[1]!) : undefined
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function collectAccessibilityBlock(
|
|
243
|
-
initial: string,
|
|
244
|
-
lines: string[],
|
|
245
|
-
startIndex: number,
|
|
246
|
-
): { text: string; nextIndex: number } | null {
|
|
247
|
-
const initialEnd = initial.indexOf('}')
|
|
248
|
-
if (initialEnd !== -1) {
|
|
249
|
-
return {
|
|
250
|
-
text: initial.slice(0, initialEnd).trim(),
|
|
251
|
-
nextIndex: startIndex,
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const parts = [initial.trim()].filter(Boolean)
|
|
256
|
-
|
|
257
|
-
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
258
|
-
const line = lines[i]!
|
|
259
|
-
const end = line.indexOf('}')
|
|
260
|
-
if (end !== -1) {
|
|
261
|
-
const beforeBrace = line.slice(0, end).trim()
|
|
262
|
-
if (beforeBrace) parts.push(beforeBrace)
|
|
263
|
-
return {
|
|
264
|
-
text: parts.join('\n'),
|
|
265
|
-
nextIndex: i,
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
parts.push(line)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return null
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export function mutateJourney(body: JourneyBody, op: JourneyMutationOp): Result<JourneyBody, MutationError> {
|
|
275
|
-
const next = cloneJourney(body)
|
|
276
|
-
const nextId = makeIdAllocator(next)
|
|
277
|
-
|
|
278
|
-
const getSection = (i: number): JourneySection | undefined => next.sections[i]
|
|
279
|
-
const getTask = (si: number, ti: number): JourneyTask | undefined => getSection(si)?.tasks[ti]
|
|
280
|
-
|
|
281
|
-
switch (op.kind) {
|
|
282
|
-
case 'set_title': {
|
|
283
|
-
if (op.title === null) delete next.title
|
|
284
|
-
else {
|
|
285
|
-
const title = normalizeOpText(op.title, 'title', { allowColon: true })
|
|
286
|
-
if (!title.ok) return title
|
|
287
|
-
next.title = title.value
|
|
288
|
-
}
|
|
289
|
-
break
|
|
290
|
-
}
|
|
291
|
-
case 'add_section': {
|
|
292
|
-
const label = normalizeOpText(op.label, 'section label')
|
|
293
|
-
if (!label.ok) return label
|
|
294
|
-
next.sections.push({ id: nextId('section'), label: label.value, tasks: [] })
|
|
295
|
-
break
|
|
296
|
-
}
|
|
297
|
-
case 'remove_section': {
|
|
298
|
-
if (!getSection(op.index)) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
|
|
299
|
-
next.sections.splice(op.index, 1)
|
|
300
|
-
break
|
|
301
|
-
}
|
|
302
|
-
case 'set_section_label': {
|
|
303
|
-
const s = getSection(op.index)
|
|
304
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.index}` })
|
|
305
|
-
const label = normalizeOpText(op.label, 'section label')
|
|
306
|
-
if (!label.ok) return label
|
|
307
|
-
s.label = label.value
|
|
308
|
-
break
|
|
309
|
-
}
|
|
310
|
-
case 'add_task': {
|
|
311
|
-
const s = getSection(op.sectionIndex)
|
|
312
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
|
|
313
|
-
const text = normalizeOpText(op.text, 'task text')
|
|
314
|
-
if (!text.ok) return text
|
|
315
|
-
if (!validScore(op.score)) return err({ code: 'INVALID_OP', message: `Journey score must be an integer 1..5, got ${op.score}` })
|
|
316
|
-
const actors = normalizeActors(op.actors)
|
|
317
|
-
if (!actors.ok) return actors
|
|
318
|
-
s.tasks.push({ id: nextId('task'), text: text.value, score: op.score, actors: actors.value })
|
|
319
|
-
break
|
|
320
|
-
}
|
|
321
|
-
case 'remove_task': {
|
|
322
|
-
const s = getSection(op.sectionIndex)
|
|
323
|
-
if (!s) return err({ code: 'SECTION_NOT_FOUND', message: `No section at index ${op.sectionIndex}` })
|
|
324
|
-
if (!s.tasks[op.taskIndex]) return err({ code: 'TASK_NOT_FOUND', message: `No task at index ${op.taskIndex}` })
|
|
325
|
-
s.tasks.splice(op.taskIndex, 1)
|
|
326
|
-
if (s.label === undefined && s.tasks.length === 0) next.sections.splice(op.sectionIndex, 1)
|
|
327
|
-
break
|
|
328
|
-
}
|
|
329
|
-
case 'set_task_text': {
|
|
330
|
-
const t = getTask(op.sectionIndex, op.taskIndex)
|
|
331
|
-
if (!t) return err({ code: 'TASK_NOT_FOUND', message: `No task at (${op.sectionIndex},${op.taskIndex})` })
|
|
332
|
-
const text = normalizeOpText(op.text, 'task text')
|
|
333
|
-
if (!text.ok) return text
|
|
334
|
-
t.text = text.value
|
|
335
|
-
break
|
|
336
|
-
}
|
|
337
|
-
case 'set_task_score': {
|
|
338
|
-
const t = getTask(op.sectionIndex, op.taskIndex)
|
|
339
|
-
if (!t) return err({ code: 'TASK_NOT_FOUND', message: `No task at (${op.sectionIndex},${op.taskIndex})` })
|
|
340
|
-
if (!validScore(op.score)) return err({ code: 'INVALID_OP', message: `Journey score must be an integer 1..5, got ${op.score}` })
|
|
341
|
-
t.score = op.score
|
|
342
|
-
break
|
|
343
|
-
}
|
|
344
|
-
case 'set_task_actors': {
|
|
345
|
-
const t = getTask(op.sectionIndex, op.taskIndex)
|
|
346
|
-
if (!t) return err({ code: 'TASK_NOT_FOUND', message: `No task at (${op.sectionIndex},${op.taskIndex})` })
|
|
347
|
-
const actors = normalizeActors(op.actors)
|
|
348
|
-
if (!actors.ok) return actors
|
|
349
|
-
t.actors = actors.value
|
|
350
|
-
break
|
|
351
|
-
}
|
|
352
|
-
case 'rename_actor': {
|
|
353
|
-
const from = normalizeOpText(op.from, 'actor', { allowColon: true })
|
|
354
|
-
if (!from.ok) return from
|
|
355
|
-
const to = normalizeOpText(op.to, 'actor', { allowColon: true })
|
|
356
|
-
if (!to.ok) return to
|
|
357
|
-
let found = false
|
|
358
|
-
for (const s of next.sections) {
|
|
359
|
-
for (const t of s.tasks) {
|
|
360
|
-
t.actors = t.actors.map(a => {
|
|
361
|
-
if (a === from.value) { found = true; return to.value }
|
|
362
|
-
return a
|
|
363
|
-
})
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
if (!found) return err({ code: 'ACTOR_NOT_FOUND', message: `Actor "${from.value}" not found` })
|
|
367
|
-
break
|
|
368
|
-
}
|
|
369
|
-
default: {
|
|
370
|
-
const _x: never = op
|
|
371
|
-
return err({ code: 'INVALID_OP', message: unknownOpMessage('journey', _x) })
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Preserve the structured floor: a journey without a scored task cannot
|
|
376
|
-
// render, so refuse mutations that would EMPTY a non-empty journey. A body
|
|
377
|
-
// that starts empty (createMermaid/buildMermaid) may stay empty while ops
|
|
378
|
-
// build it up — title/sections first, tasks after.
|
|
379
|
-
const hadTask = body.sections.some(s => s.tasks.length > 0)
|
|
380
|
-
if (hadTask && next.sections.every(s => s.tasks.length === 0)) {
|
|
381
|
-
return err({ code: 'INVALID_OP', message: 'Journey must keep at least one scored task' })
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return ok(next)
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// ---- Verifier (FamilyPlugin.verify hook) ------------------------------------
|
|
388
|
-
|
|
389
|
-
export function verifyJourney(body: JourneyBody, opts: VerifyOptions): LayoutWarning[] {
|
|
390
|
-
const cap = opts.labelCharCap ?? DEFAULT_LABEL_CHAR_CAP
|
|
391
|
-
const warnings: LayoutWarning[] = []
|
|
392
|
-
const overflow = (target: string, text: string) => {
|
|
393
|
-
const w = labelOverflowWarning(target, text, cap)
|
|
394
|
-
if (w) warnings.push(w)
|
|
395
|
-
}
|
|
396
|
-
const hasTask = body.sections.some(s => s.tasks.length > 0)
|
|
397
|
-
const hasHeaderFurniture = body.title !== undefined
|
|
398
|
-
|| body.accessibilityTitle !== undefined
|
|
399
|
-
|| body.accessibilityDescription !== undefined
|
|
400
|
-
|| body.sections.length > 0
|
|
401
|
-
if (!hasTask && !hasHeaderFurniture) warnings.push({ code: 'EMPTY_DIAGRAM' })
|
|
402
|
-
if (body.title !== undefined) {
|
|
403
|
-
const w = labelOverflowWarning('title', body.title, Math.max(cap, 80))
|
|
404
|
-
if (w) warnings.push(w)
|
|
405
|
-
}
|
|
406
|
-
for (const s of body.sections) {
|
|
407
|
-
if (s.label !== undefined) overflow(s.id, s.label)
|
|
408
|
-
for (const t of s.tasks) {
|
|
409
|
-
overflow(t.id, t.text)
|
|
410
|
-
for (const a of t.actors) overflow(t.id, a)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return warnings
|
|
414
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Label display metrics — the LABEL_OVERFLOW cap measures what the renderer
|
|
3
|
-
// draws, not raw source characters. The render pipeline decodes XML entities
|
|
4
|
-
// (renderMermaidSVG runs decodeXML before parsing), converts <br> tags and
|
|
5
|
-
// literal \n to line breaks, and strips formatting tags before measuring text
|
|
6
|
-
// (multiline-utils.ts). Counting source chars instead flags legitimately-fine
|
|
7
|
-
// multi-line labels whose markup (`<br/>`, ` `) pads the count.
|
|
8
|
-
// ============================================================================
|
|
9
|
-
|
|
10
|
-
import { decodeXML } from 'entities'
|
|
11
|
-
import { normalizeBrTags, stripFormattingTags } from '../multiline-utils.ts'
|
|
12
|
-
import type { LayoutWarning } from './types.ts'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Length of a label as rendered: the longest line after entity decoding,
|
|
16
|
-
* <br>/\n splitting, and formatting-tag stripping. ` ` counts as one
|
|
17
|
-
* character; `<br/>` starts a new line and counts as zero.
|
|
18
|
-
*/
|
|
19
|
-
export function labelDisplayLength(label: string): number {
|
|
20
|
-
const rendered = stripFormattingTags(normalizeBrTags(decodeXML(label)))
|
|
21
|
-
return rendered.split('\n').reduce((max, line) => Math.max(max, line.length), 0)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Build a LABEL_OVERFLOW warning when the rendered length exceeds the cap, else null. */
|
|
25
|
-
export function labelOverflowWarning(target: string, text: string, cap: number): LayoutWarning | null {
|
|
26
|
-
const charCount = labelDisplayLength(text)
|
|
27
|
-
return charCount > cap ? { code: 'LABEL_OVERFLOW', target, charCount, limit: cap } : null
|
|
28
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Shared converter: ELK PositionedGraph → public RenderedLayout JSON.
|
|
3
|
-
// Previously implemented in two places (verify.ts and index.ts's layoutMermaid)
|
|
4
|
-
// — see DIVERGENCES "Cleanup findings". Single source of truth.
|
|
5
|
-
// ============================================================================
|
|
6
|
-
|
|
7
|
-
import type { PositionedGraph, PositionedNode, PositionedEdge, PositionedGroup } from '../types.ts'
|
|
8
|
-
import type {
|
|
9
|
-
DiagramKind, Finite, RenderedLayout, RenderedLayoutNode, RenderedLayoutEdge, RenderedLayoutGroup,
|
|
10
|
-
} from './types.ts'
|
|
11
|
-
import { toFinite } from './types.ts'
|
|
12
|
-
|
|
13
|
-
function f(n: number): Finite { return toFinite(Math.round(n)) }
|
|
14
|
-
|
|
15
|
-
function node(n: PositionedNode): RenderedLayoutNode {
|
|
16
|
-
return { id: n.id, x: f(n.x), y: f(n.y), w: f(n.width), h: f(n.height), shape: n.shape, label: n.label }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function edge(e: PositionedEdge, debug: boolean): RenderedLayoutEdge {
|
|
20
|
-
return {
|
|
21
|
-
id: `${e.source}->${e.target}`,
|
|
22
|
-
from: e.source, to: e.target,
|
|
23
|
-
path: e.points.map(p => [f(p.x), f(p.y)] as [Finite, Finite]),
|
|
24
|
-
label: e.label && e.labelPosition
|
|
25
|
-
? { x: f(e.labelPosition.x), y: f(e.labelPosition.y), text: e.label }
|
|
26
|
-
: undefined,
|
|
27
|
-
...(debug && e.routeCertificate ? { route: e.routeCertificate } : {}),
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function positionedToRenderedLayout(p: PositionedGraph, kind: DiagramKind, opts: { debug?: boolean } = {}): RenderedLayout {
|
|
32
|
-
return {
|
|
33
|
-
version: 1, kind,
|
|
34
|
-
nodes: p.nodes.map(node),
|
|
35
|
-
edges: p.edges.map(e => edge(e, opts.debug === true)),
|
|
36
|
-
groups: renderedGroups(p.groups, p.nodes),
|
|
37
|
-
bounds: { w: f(p.width), h: f(p.height) },
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function renderedGroups(groups: PositionedGroup[], nodes: PositionedNode[]): RenderedLayoutGroup[] {
|
|
42
|
-
const out: RenderedLayoutGroup[] = []
|
|
43
|
-
const containsNode = (g: PositionedGroup, n: PositionedNode): boolean =>
|
|
44
|
-
n.x >= g.x - 0.5 && n.y >= g.y - 0.5 && n.x + n.width <= g.x + g.width + 0.5 && n.y + n.height <= g.y + g.height + 0.5
|
|
45
|
-
const visit = (g: PositionedGroup, parentId?: string): void => {
|
|
46
|
-
const members = nodes
|
|
47
|
-
.filter(n => containsNode(g, n) && !g.children.some(child => containsNode(child, n)))
|
|
48
|
-
.map(n => n.id)
|
|
49
|
-
out.push({
|
|
50
|
-
id: g.id,
|
|
51
|
-
x: f(g.x), y: f(g.y), w: f(g.width), h: f(g.height),
|
|
52
|
-
members, label: g.label,
|
|
53
|
-
...(parentId ? { parentId } : {}),
|
|
54
|
-
})
|
|
55
|
-
for (const child of g.children) visit(child, g.id)
|
|
56
|
-
}
|
|
57
|
-
for (const g of groups) visit(g)
|
|
58
|
-
return out
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function emptyRenderedLayout(kind: DiagramKind): RenderedLayout {
|
|
62
|
-
return { version: 1, kind, nodes: [], edges: [], groups: [], bounds: { w: f(0), h: f(0) } }
|
|
63
|
-
}
|
package/src/agent/mutate.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// mutate: typed structural edits. Overloaded by family.
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
FlowchartValidDiagram, StateValidDiagram, SequenceValidDiagram, TimelineValidDiagram,
|
|
7
|
-
ClassValidDiagram, ErValidDiagram, JourneyValidDiagram, ArchitectureValidDiagram, XyChartValidDiagram, PieValidDiagram, QuadrantValidDiagram, GanttValidDiagram, MutableValidDiagram,
|
|
8
|
-
FlowchartMutationOp, StateMutationOp, SequenceMutationOp, TimelineMutationOp,
|
|
9
|
-
ClassMutationOp, ErMutationOp, JourneyMutationOp, ArchitectureMutationOp, XyChartMutationOp, PieMutationOp, QuadrantMutationOp, GanttMutationOp, AnyMutationOp,
|
|
10
|
-
MutationError, Result,
|
|
11
|
-
} from './types.ts'
|
|
12
|
-
import { ok, err } from './types.ts'
|
|
13
|
-
import { wrapperPrefix } from './serialize.ts'
|
|
14
|
-
import { logToolInvocation } from './trace-log.ts'
|
|
15
|
-
import { getFamily } from './families.ts'
|
|
16
|
-
import { validateOp, hasOpSchema } from './op-schema.ts'
|
|
17
|
-
import './families-builtin.ts' // registers built-in family mutate hooks
|
|
18
|
-
|
|
19
|
-
export function mutate(d: FlowchartValidDiagram, op: FlowchartMutationOp): Result<FlowchartValidDiagram, MutationError>
|
|
20
|
-
export function mutate(d: StateValidDiagram, op: StateMutationOp): Result<StateValidDiagram, MutationError>
|
|
21
|
-
export function mutate(d: SequenceValidDiagram, op: SequenceMutationOp): Result<SequenceValidDiagram, MutationError>
|
|
22
|
-
export function mutate(d: TimelineValidDiagram, op: TimelineMutationOp): Result<TimelineValidDiagram, MutationError>
|
|
23
|
-
export function mutate(d: ClassValidDiagram, op: ClassMutationOp): Result<ClassValidDiagram, MutationError>
|
|
24
|
-
export function mutate(d: ErValidDiagram, op: ErMutationOp): Result<ErValidDiagram, MutationError>
|
|
25
|
-
export function mutate(d: JourneyValidDiagram, op: JourneyMutationOp): Result<JourneyValidDiagram, MutationError>
|
|
26
|
-
export function mutate(d: ArchitectureValidDiagram, op: ArchitectureMutationOp): Result<ArchitectureValidDiagram, MutationError>
|
|
27
|
-
export function mutate(d: XyChartValidDiagram, op: XyChartMutationOp): Result<XyChartValidDiagram, MutationError>
|
|
28
|
-
export function mutate(d: PieValidDiagram, op: PieMutationOp): Result<PieValidDiagram, MutationError>
|
|
29
|
-
export function mutate(d: QuadrantValidDiagram, op: QuadrantMutationOp): Result<QuadrantValidDiagram, MutationError>
|
|
30
|
-
export function mutate(d: GanttValidDiagram, op: GanttMutationOp): Result<GanttValidDiagram, MutationError>
|
|
31
|
-
// General form for callers holding the union (e.g. the CLI): dispatch is by
|
|
32
|
-
// registry at runtime either way, so kind-agnostic call sites don't need a
|
|
33
|
-
// per-family narrowing cascade.
|
|
34
|
-
export function mutate(d: MutableValidDiagram, op: AnyMutationOp): Result<MutableValidDiagram, MutationError>
|
|
35
|
-
export function mutate(
|
|
36
|
-
d: MutableValidDiagram,
|
|
37
|
-
op: AnyMutationOp,
|
|
38
|
-
): Result<MutableValidDiagram, MutationError> {
|
|
39
|
-
// Log the OUTCOME (not just the call): an `{ok:false}` trace line records a
|
|
40
|
-
// failed op attempt — the observable signal the agent-usage eval reads to
|
|
41
|
-
// measure a run's op-error rate directly, rather than inferring retries from
|
|
42
|
-
// excess mutate-call counts.
|
|
43
|
-
const r = applyOneMutation(d, op)
|
|
44
|
-
logToolInvocation('mutate', r.ok)
|
|
45
|
-
return r
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function applyOneMutation(
|
|
49
|
-
d: MutableValidDiagram,
|
|
50
|
-
op: AnyMutationOp,
|
|
51
|
-
): Result<MutableValidDiagram, MutationError> {
|
|
52
|
-
// Every structured family mutates through its FamilyPlugin hook, then
|
|
53
|
-
// rebuilds canonicalSource from the new body so a mutated diagram never
|
|
54
|
-
// carries stale source. Lookup is by DIAGRAM kind, not body kind. State
|
|
55
|
-
// diagrams (BUILD-19) own a dedicated StateBody and bind the stateDiagram-v2
|
|
56
|
-
// header through their own plugin registration.
|
|
57
|
-
const plugin = getFamily(d.kind)
|
|
58
|
-
if (plugin?.mutate && plugin.serialize) {
|
|
59
|
-
const r = plugin.mutate(d.body, op)
|
|
60
|
-
if (!r.ok) return r
|
|
61
|
-
// 1C wrapper policy: a mutated diagram keeps its leading wrapper
|
|
62
|
-
// (frontmatter/directives/comments) byte-verbatim; only the body changes.
|
|
63
|
-
const canonicalSource = wrapperPrefix(d.meta) + plugin.serialize(r.value)
|
|
64
|
-
return ok({ ...d, body: r.value, canonicalSource } as MutableValidDiagram)
|
|
65
|
-
}
|
|
66
|
-
return err({ code: 'INVALID_OP', message: `Unsupported mutable diagram kind: ${d.kind}` })
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* mutate with a shape check in front — the one choke point every UNTYPED edit
|
|
71
|
-
* path funnels through: the declarative applyOps/buildChecked entrypoints, the
|
|
72
|
-
* Code Mode facade, and the CLI `--op`/`--ops` path. Typed callers keep using
|
|
73
|
-
* `mutate` directly, where the compiler already guarantees op shape;
|
|
74
|
-
* `mutateChecked` restores that guarantee at the boundaries where ops arrive as
|
|
75
|
-
* raw JSON.
|
|
76
|
-
*
|
|
77
|
-
* `validateOp` proves the op's SHAPE (field names, primitive types, enums)
|
|
78
|
-
* BEFORE the mutator runs, so a malformed op is rejected with a prescriptive
|
|
79
|
-
* INVALID_OP error instead of silently mangling the diagram. Semantics — does
|
|
80
|
-
* the referenced node exist, is the id a duplicate — stay in the mutator, which
|
|
81
|
-
* runs second on a shape-proven op. Result shape is identical to `mutate`, so
|
|
82
|
-
* this is a drop-in wherever the raw mutator was called with untyped ops.
|
|
83
|
-
*/
|
|
84
|
-
export function mutateChecked(d: MutableValidDiagram, op: unknown): Result<MutableValidDiagram, MutationError> {
|
|
85
|
-
if (hasOpSchema(d.kind)) {
|
|
86
|
-
const invalid = validateOp(d.kind, op)
|
|
87
|
-
// A shape rejection short-circuits before `mutate`, so record the failed
|
|
88
|
-
// attempt here — otherwise checked-path errors (e.g. the op-array slip)
|
|
89
|
-
// would go uncounted in the op-error rate.
|
|
90
|
-
if (invalid) { logToolInvocation('mutate', false); return err(invalid) }
|
|
91
|
-
}
|
|
92
|
-
return mutate(d, op as AnyMutationOp)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Flowchart graph mutation + helpers live in flowchart-body.ts; edgeIdOf is
|
|
96
|
-
// re-exported here for existing consumers (agent/index.ts, eval harness).
|
|
97
|
-
export { edgeIdOf } from './flowchart-body.ts'
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { BuiltinFamilyId } from './families.ts'
|
|
2
|
-
import type { DiagramKind } from './types.ts'
|
|
3
|
-
|
|
4
|
-
// Single source of truth for each family's structured mutation op `kind`s. It
|
|
5
|
-
// lives in the agent layer, next to the mutators, so two consumers read the
|
|
6
|
-
// same list: the CLI `capabilities` envelope (re-exported from src/cli/index.ts)
|
|
7
|
-
// and the mutators' "unknown op" errors, which name the family's valid ops
|
|
8
|
-
// instead of leaving a caller — especially a smaller model — to guess them.
|
|
9
|
-
export const MUTATION_OPS_BY_FAMILY = {
|
|
10
|
-
flowchart: ['add_node', 'remove_node', 'rename_node', 'set_label', 'add_edge', 'remove_edge'],
|
|
11
|
-
state: ['add_state', 'remove_state', 'rename_state', 'set_state_label', 'add_transition', 'remove_transition', 'set_transition_label', 'make_composite'],
|
|
12
|
-
sequence: ['add_participant', 'remove_participant', 'add_message', 'remove_message', 'set_message_text'],
|
|
13
|
-
timeline: ['set_title', 'add_section', 'remove_section', 'set_section_label', 'add_period', 'remove_period', 'set_period_label', 'add_event', 'remove_event', 'set_event_text'],
|
|
14
|
-
class: ['set_title', 'add_class', 'remove_class', 'rename_class', 'add_member', 'remove_member', 'add_relation', 'remove_relation', 'add_note', 'remove_note'],
|
|
15
|
-
er: ['add_entity', 'remove_entity', 'rename_entity', 'add_attribute', 'remove_attribute', 'add_relation', 'remove_relation'],
|
|
16
|
-
journey: ['set_title', 'add_section', 'remove_section', 'set_section_label', 'add_task', 'remove_task', 'set_task_text', 'set_task_score', 'set_task_actors', 'rename_actor'],
|
|
17
|
-
architecture: ['add_service', 'remove_service', 'rename_service', 'set_service_label', 'set_service_icon', 'move_service', 'add_group', 'remove_group', 'add_edge', 'remove_edge'],
|
|
18
|
-
xychart: ['set_title', 'set_x_axis', 'set_y_axis', 'add_series', 'remove_series', 'set_series_values', 'set_series_name', 'reorder_series'],
|
|
19
|
-
pie: ['set_title', 'set_show_data', 'add_slice', 'remove_slice', 'rename_slice', 'set_slice_value', 'reorder_slice'],
|
|
20
|
-
quadrant: ['set_title', 'set_axis_labels', 'set_quadrant_label', 'add_point', 'remove_point', 'move_point', 'rename_point'],
|
|
21
|
-
gantt: ['set_title', 'add_section', 'rename_section', 'remove_section', 'add_task', 'remove_task', 'rename_task', 'set_task_status', 'set_task_dates'],
|
|
22
|
-
} as const satisfies Record<BuiltinFamilyId, readonly string[]>
|
|
23
|
-
|
|
24
|
-
export type MutableFamilyId = keyof typeof MUTATION_OPS_BY_FAMILY
|
|
25
|
-
|
|
26
|
-
/** The valid mutation-op `kind`s for a family, as a display string — for
|
|
27
|
-
* prescriptive "unknown op" errors that hand the caller the actual menu. */
|
|
28
|
-
export function validOpsFor(family: DiagramKind): string {
|
|
29
|
-
const ops = (MUTATION_OPS_BY_FAMILY as Record<string, readonly string[]>)[family]
|
|
30
|
-
return ops ? ops.join(', ') : '(none — this family has no structured ops)'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Message for an op whose `kind` a family's mutator does not recognize: names
|
|
34
|
-
* the offending kind AND the family's valid ops, so the caller can correct it
|
|
35
|
-
* from the error alone instead of guessing which ops exist. */
|
|
36
|
-
export function unknownOpMessage(family: DiagramKind, op: unknown): string {
|
|
37
|
-
// Passing an op ARRAY where one op is expected is the single most common shape
|
|
38
|
-
// slip — it recurs on nearly every case across every model tier in the
|
|
39
|
-
// agent-usage eval (mutate() applies ONE op, but "apply these ops" reads as a
|
|
40
|
-
// list). Hand back the rule and the batch alternatives so the next action is
|
|
41
|
-
// in the message, not left to infer from a dumped array.
|
|
42
|
-
if (Array.isArray(op)) {
|
|
43
|
-
return `Expected a single ${family} op, got an array of ${op.length}. `
|
|
44
|
-
+ `Ops apply one at a time: call mutate() once per op, or pass the whole list to a batch `
|
|
45
|
-
+ `entrypoint — applyOps({ source, family, ops }) to edit source, or buildMermaid(kind, ops) `
|
|
46
|
-
+ `to author — valid ${family} ops: ${validOpsFor(family)}`
|
|
47
|
-
}
|
|
48
|
-
const kind = (op as { kind?: unknown } | null | undefined)?.kind
|
|
49
|
-
const got = typeof kind === 'string' ? `"${kind}"` : JSON.stringify(op)
|
|
50
|
-
return `Unknown ${family} op ${got} — valid ops: ${validOpsFor(family)}`
|
|
51
|
-
}
|