@diagrammo/dgmo 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/dgmo.md +17 -32
- package/.github/copilot-instructions.md +1 -1
- package/README.md +7 -1
- package/SKILL.md +1 -1
- package/dist/advanced.cjs +462 -465
- package/dist/advanced.d.cts +2 -51
- package/dist/advanced.d.ts +2 -51
- package/dist/advanced.js +462 -466
- package/dist/auto.cjs +463 -142
- package/dist/auto.js +91 -91
- package/dist/auto.mjs +463 -142
- package/dist/cli.cjs +143 -326
- package/dist/index.cjs +462 -141
- package/dist/index.js +462 -141
- package/docs/ai-integration.md +46 -26
- package/docs/language-reference.md +2 -2
- package/package.json +2 -2
- package/src/advanced.ts +0 -12
- package/src/c4/parser.ts +15 -6
- package/src/chart.ts +1 -1
- package/src/class/parser.ts +6 -2
- package/src/cli-banner.ts +2 -3
- package/src/cli.ts +539 -1223
- package/src/cycle/parser.ts +4 -1
- package/src/dgmo-router.ts +16 -2
- package/src/diagnostics.ts +0 -0
- package/src/echarts.ts +5 -1
- package/src/er/parser.ts +5 -1
- package/src/gantt/parser.ts +13 -4
- package/src/graph/flowchart-parser.ts +5 -1
- package/src/graph/state-parser.ts +5 -1
- package/src/infra/parser.ts +10 -6
- package/src/journey-map/parser.ts +5 -2
- package/src/kanban/parser.ts +6 -2
- package/src/map/context-labels.ts +29 -19
- package/src/map/layout.ts +403 -108
- package/src/map/legend-band.ts +28 -0
- package/src/map/parser.ts +13 -4
- package/src/map/renderer.ts +74 -23
- package/src/map/resolver.ts +2 -2
- package/src/org/parser.ts +8 -2
- package/src/pyramid/parser.ts +4 -1
- package/src/raci/parser.ts +4 -1
- package/src/ring/parser.ts +4 -1
- package/src/sequence/parser.ts +6 -3
- package/src/sitemap/parser.ts +12 -3
- package/src/tech-radar/parser.ts +6 -3
- package/src/utils/parsing.ts +2 -2
- package/src/utils/tag-groups.ts +3 -3
- package/src/visualizations/parse.ts +9 -3
- package/src/migrate/data-comma.ts +0 -117
- package/src/migrate/embedded.ts +0 -238
- package/src/migrate/index.ts +0 -295
- package/src/migrate/line-classifier.ts +0 -128
- package/src/migrate/line-transformer.ts +0 -218
package/src/cycle/parser.ts
CHANGED
|
@@ -352,7 +352,10 @@ export function parseCycle(content: string): ParsedCycle {
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
// Indented line with no context
|
|
355
|
-
warn(
|
|
355
|
+
warn(
|
|
356
|
+
lineNum,
|
|
357
|
+
`Indented line "${trimmed}" has no step above it — add a step on a non-indented line first, then indent its details. (§21)`
|
|
358
|
+
);
|
|
356
359
|
continue;
|
|
357
360
|
}
|
|
358
361
|
}
|
package/src/dgmo-router.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { looksLikePert } from './pert/parser';
|
|
|
14
14
|
// part of the derived chartTypeParsers, which come from the registry).
|
|
15
15
|
import { parseVisualization } from './visualizations/parse';
|
|
16
16
|
import { parseFirstLine } from './utils/parsing';
|
|
17
|
-
import { makeDgmoError, suggest } from './diagnostics';
|
|
17
|
+
import { makeDgmoError, suggest, dedupeDiagnostics } from './diagnostics';
|
|
18
18
|
import type { DgmoError } from './diagnostics';
|
|
19
19
|
import { chartTypes } from './chart-types';
|
|
20
20
|
import {
|
|
@@ -145,7 +145,7 @@ export function getAllChartTypes(): string[] {
|
|
|
145
145
|
/**
|
|
146
146
|
* Canonical descriptions for every supported chart type. Derived from
|
|
147
147
|
* `chartTypes` so there is exactly one place to update when adding a new
|
|
148
|
-
* type. Consumed by the CLI
|
|
148
|
+
* type. Consumed by the CLI `dgmo types` command, the editor autocomplete
|
|
149
149
|
* popup, and the MCP `list_chart_types` tool.
|
|
150
150
|
*/
|
|
151
151
|
export const CHART_TYPE_DESCRIPTIONS: Record<string, string> =
|
|
@@ -185,6 +185,20 @@ const ALL_KNOWN_TYPES: ReadonlySet<string> = new Set(knownChartTypeIds);
|
|
|
185
185
|
export function parseDgmo(content: string): {
|
|
186
186
|
diagnostics: DgmoError[];
|
|
187
187
|
chartType: string | null;
|
|
188
|
+
} {
|
|
189
|
+
// Dedupe at the parse boundary so one offending line never reports the same
|
|
190
|
+
// problem N times — keeps the fix-loop signal clean for the CLI, the editor,
|
|
191
|
+
// and the MCP `validate_diagram` tool.
|
|
192
|
+
const result = parseDgmoUndeduped(content);
|
|
193
|
+
return {
|
|
194
|
+
diagnostics: dedupeDiagnostics(result.diagnostics),
|
|
195
|
+
chartType: result.chartType,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseDgmoUndeduped(content: string): {
|
|
200
|
+
diagnostics: DgmoError[];
|
|
201
|
+
chartType: string | null;
|
|
188
202
|
} {
|
|
189
203
|
const chartType = parseDgmoChartType(content);
|
|
190
204
|
|
package/src/diagnostics.ts
CHANGED
|
Binary file
|
package/src/echarts.ts
CHANGED
|
@@ -964,7 +964,11 @@ function parseExtendedChartFull(
|
|
|
964
964
|
|
|
965
965
|
// Catch-all: nothing matched this line
|
|
966
966
|
result.diagnostics.push(
|
|
967
|
-
makeDgmoError(
|
|
967
|
+
makeDgmoError(
|
|
968
|
+
lineNumber,
|
|
969
|
+
`Unexpected line: '${trimmed}'. Expected a data row ('Label value', space-separated) or a known option.`,
|
|
970
|
+
'warning'
|
|
971
|
+
)
|
|
968
972
|
);
|
|
969
973
|
}
|
|
970
974
|
|
package/src/er/parser.ts
CHANGED
|
@@ -572,7 +572,11 @@ export function parseERDiagram(
|
|
|
572
572
|
|
|
573
573
|
// Catch-all: nothing matched this line
|
|
574
574
|
result.diagnostics.push(
|
|
575
|
-
makeDgmoError(
|
|
575
|
+
makeDgmoError(
|
|
576
|
+
lineNumber,
|
|
577
|
+
`Unexpected line: '${trimmed}'. A table is a bare name at indent 0 ('users'); columns are indented and SPACE-separated, no colon ('id int pk'); relationships are indented ('1--* orders'). (§9)`,
|
|
578
|
+
'warning'
|
|
579
|
+
)
|
|
576
580
|
);
|
|
577
581
|
}
|
|
578
582
|
|
package/src/gantt/parser.ts
CHANGED
|
@@ -382,7 +382,10 @@ export function parseGantt(
|
|
|
382
382
|
// Skip comments inside holidays
|
|
383
383
|
if (COMMENT_RE.test(line)) continue;
|
|
384
384
|
|
|
385
|
-
warn(
|
|
385
|
+
warn(
|
|
386
|
+
lineNumber,
|
|
387
|
+
`Unrecognized holiday entry: "${line}". Use a date '2024-02-19 Presidents Day' or a range '2024-05-27 -> 2024-05-29 Memorial Weekend'. (§13.3)`
|
|
388
|
+
);
|
|
386
389
|
continue;
|
|
387
390
|
}
|
|
388
391
|
}
|
|
@@ -413,7 +416,10 @@ export function parseGantt(
|
|
|
413
416
|
lineNumber,
|
|
414
417
|
});
|
|
415
418
|
} else {
|
|
416
|
-
warn(
|
|
419
|
+
warn(
|
|
420
|
+
lineNumber,
|
|
421
|
+
`Unrecognized era entry: "${line}". Use 'YYYY-MM-DD -> YYYY-MM-DD Label [color]' (e.g. '2026-04-06 -> 2026-04-10 Conference purple'). (§13.5)`
|
|
422
|
+
);
|
|
417
423
|
}
|
|
418
424
|
continue;
|
|
419
425
|
}
|
|
@@ -444,7 +450,10 @@ export function parseGantt(
|
|
|
444
450
|
lineNumber,
|
|
445
451
|
});
|
|
446
452
|
} else {
|
|
447
|
-
warn(
|
|
453
|
+
warn(
|
|
454
|
+
lineNumber,
|
|
455
|
+
`Unrecognized marker entry: "${line}". Use 'YYYY-MM-DD Label [color]' (e.g. '2026-03-27 Board Review' or '2026-06-15 Release green'). (§13.6)`
|
|
456
|
+
);
|
|
448
457
|
}
|
|
449
458
|
continue;
|
|
450
459
|
}
|
|
@@ -664,7 +673,7 @@ export function parseGantt(
|
|
|
664
673
|
} else {
|
|
665
674
|
softError(
|
|
666
675
|
lineNumber,
|
|
667
|
-
`Arrow
|
|
676
|
+
`Arrow '-> ${content.name}' has no source task — indent it under the task it follows (e.g. 'Design 5bd' then indented '-> ${content.name} 3bd'). (§13)`
|
|
668
677
|
);
|
|
669
678
|
}
|
|
670
679
|
}
|
|
@@ -629,7 +629,11 @@ export function parseFlowchart(
|
|
|
629
629
|
|
|
630
630
|
if (key === 'no-color') {
|
|
631
631
|
result.diagnostics.push(
|
|
632
|
-
makeDgmoError(
|
|
632
|
+
makeDgmoError(
|
|
633
|
+
lineNumber,
|
|
634
|
+
'"no-color" was removed — node colors are assigned automatically by shape; delete this line.',
|
|
635
|
+
'warning'
|
|
636
|
+
)
|
|
633
637
|
);
|
|
634
638
|
continue;
|
|
635
639
|
}
|
|
@@ -389,7 +389,11 @@ export function parseState(
|
|
|
389
389
|
|
|
390
390
|
if (key === 'no-color') {
|
|
391
391
|
result.diagnostics.push(
|
|
392
|
-
makeDgmoError(
|
|
392
|
+
makeDgmoError(
|
|
393
|
+
lineNumber,
|
|
394
|
+
'"no-color" was removed — state colors are assigned automatically; delete this line.',
|
|
395
|
+
'warning'
|
|
396
|
+
)
|
|
393
397
|
);
|
|
394
398
|
continue;
|
|
395
399
|
}
|
package/src/infra/parser.ts
CHANGED
|
@@ -560,7 +560,7 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
560
560
|
}
|
|
561
561
|
warn(
|
|
562
562
|
lineNumber,
|
|
563
|
-
`Invalid tag value '${trimmed}' in tag group '${currentTagGroup.name}'.`
|
|
563
|
+
`Invalid tag value '${trimmed}' in tag group '${currentTagGroup.name}' — a value is a bare word with an optional trailing color (e.g. 'Production red'); no ':' or '|'.`
|
|
564
564
|
);
|
|
565
565
|
continue;
|
|
566
566
|
}
|
|
@@ -654,7 +654,7 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
654
654
|
const n = deprecatedFanout[1];
|
|
655
655
|
setError(
|
|
656
656
|
lineNumber,
|
|
657
|
-
`'x${n}' fanout syntax is no longer supported — use '
|
|
657
|
+
`'x${n}' fanout syntax is no longer supported — use 'fanout: ${n}' instead`
|
|
658
658
|
);
|
|
659
659
|
continue;
|
|
660
660
|
}
|
|
@@ -869,8 +869,12 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
869
869
|
const valueLooksNumeric = /^[\d.]+%?$/.test(rawVal);
|
|
870
870
|
if (hint || valueLooksNumeric) {
|
|
871
871
|
// Likely a typo — warn
|
|
872
|
-
let msg = `Unknown property '${key}'.`;
|
|
872
|
+
let msg = `Unknown infra property '${key}'.`;
|
|
873
873
|
if (hint) msg += ` ${hint}`;
|
|
874
|
+
else
|
|
875
|
+
msg +=
|
|
876
|
+
` Properties are colon-keyed 'key: value' from the known schema` +
|
|
877
|
+
` (e.g. latency-ms, max-rps, cache-hit, instances, concurrency).`;
|
|
874
878
|
warn(lineNumber, msg);
|
|
875
879
|
} else if (!currentNode.isEdge) {
|
|
876
880
|
// Likely prose — collect as description
|
|
@@ -884,7 +888,7 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
884
888
|
if (EDGE_ONLY_KEYS.has(key) && !currentNode.isEdge) {
|
|
885
889
|
warn(
|
|
886
890
|
lineNumber,
|
|
887
|
-
`Property '${key}'
|
|
891
|
+
`Property '${key}' belongs on the entry point — declare it indented under an 'internet' or 'edge' node, not on '${currentNode.label}'.`
|
|
888
892
|
);
|
|
889
893
|
}
|
|
890
894
|
|
|
@@ -1014,8 +1018,8 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
1014
1018
|
warn(
|
|
1015
1019
|
lineNumber,
|
|
1016
1020
|
currentGroup
|
|
1017
|
-
? `Unexpected line: '${trimmed}'.
|
|
1018
|
-
: `Unexpected line: '${trimmed}'. Expected a
|
|
1021
|
+
? `Unexpected line: '${trimmed}'. Inside a group, indent a component name under the '[Group]'.`
|
|
1022
|
+
: `Unexpected line: '${trimmed}'. Expected a node ('Api Gateway'), a group ('[Backend]'), a tag group ('tag Tier as t'), or an option ('default-rps 100'); node properties go indented under a node as 'key: value'.`
|
|
1019
1023
|
);
|
|
1020
1024
|
}
|
|
1021
1025
|
|
|
@@ -378,7 +378,7 @@ export function parseJourneyMap(
|
|
|
378
378
|
if (trimmed.includes('|') || !PHASE_RE.test(trimmed)) {
|
|
379
379
|
warn(
|
|
380
380
|
lineNumber,
|
|
381
|
-
|
|
381
|
+
`Step "${trimmed}" sits outside any phase and is ignored — indent it under a '[Phase]' header (per §22).`
|
|
382
382
|
);
|
|
383
383
|
continue;
|
|
384
384
|
}
|
|
@@ -450,7 +450,10 @@ export function parseJourneyMap(
|
|
|
450
450
|
|
|
451
451
|
// Unrecognized line
|
|
452
452
|
if (indent > 0 && !currentPhase && hasPhases) {
|
|
453
|
-
warn(
|
|
453
|
+
warn(
|
|
454
|
+
lineNumber,
|
|
455
|
+
`Indented line outside any phase — add a '[Phase]' header above it, or unindent it to a top-level step. (§22)`
|
|
456
|
+
);
|
|
454
457
|
}
|
|
455
458
|
}
|
|
456
459
|
|
package/src/kanban/parser.ts
CHANGED
|
@@ -58,7 +58,8 @@ const KNOWN_OPTIONS = new Set(['hide', 'active-tag']);
|
|
|
58
58
|
/** Known kanban boolean options (bare keyword = on). */
|
|
59
59
|
const KNOWN_BOOLEANS = new Set<string>(['solid-fill', 'no-title']);
|
|
60
60
|
const REMOVED_BOOLEANS: Record<string, string> = {
|
|
61
|
-
'no-auto-color':
|
|
61
|
+
'no-auto-color':
|
|
62
|
+
'"no-auto-color" was removed — colors are always auto-assigned from the palette; delete this line.',
|
|
62
63
|
};
|
|
63
64
|
|
|
64
65
|
// ============================================================
|
|
@@ -364,7 +365,10 @@ export function parseKanban(
|
|
|
364
365
|
}
|
|
365
366
|
|
|
366
367
|
if (!currentColumn) {
|
|
367
|
-
warn(
|
|
368
|
+
warn(
|
|
369
|
+
lineNumber,
|
|
370
|
+
'Card line found before any column. Declare a column first with \'[Column Name]\', then indent cards beneath it. (§11)'
|
|
371
|
+
);
|
|
368
372
|
continue;
|
|
369
373
|
}
|
|
370
374
|
|
|
@@ -51,6 +51,14 @@ export interface ContextLabelArgs {
|
|
|
51
51
|
readonly height: number;
|
|
52
52
|
readonly waterBodies?: WaterBodies | undefined;
|
|
53
53
|
readonly countries: readonly CountryCandidate[];
|
|
54
|
+
/** Muted, contrast-floored land tone for country labels — computed in layout
|
|
55
|
+
* against the neutral basemap and shared with the orientation backdrop so a
|
|
56
|
+
* context country and an orientation region read identically. Absent ⇒ the
|
|
57
|
+
* legacy `palette.textMuted`. */
|
|
58
|
+
readonly countryTone?: string | undefined;
|
|
59
|
+
/** Muted, contrast-floored tone for water labels (against the water fill).
|
|
60
|
+
* Absent ⇒ the legacy `mix(blue, textMuted, 50)`. */
|
|
61
|
+
readonly waterTone?: string | undefined;
|
|
54
62
|
readonly palette: PaletteColors;
|
|
55
63
|
readonly project: (lon: number, lat: number) => [number, number] | null;
|
|
56
64
|
/** Collision test against every committed data/region/POI/route obstacle. */
|
|
@@ -80,15 +88,15 @@ const EDGE_CLAMP_MARGIN = 8; // px inset for edge-clamped ocean labels
|
|
|
80
88
|
const EDGE_CLAMP_OVERSHOOT = 0.35; // max off-frame overshoot (× dim) to still clamp
|
|
81
89
|
|
|
82
90
|
// Country labels scale with their projected footprint: a big landmass (Canada,
|
|
83
|
-
// Mexico on a US view) reads as a
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
91
|
+
// Mexico on a US view) reads as a larger backdrop name; a small one stays at the
|
|
92
|
+
// base font. Size metric is the footprint's linear extent (√bbox-area) as a
|
|
93
|
+
// fraction of the canvas's linear extent (√canvas-area), so the ramp is
|
|
94
|
+
// resolution-independent. Below MIN ⇒ base font; at/above MAX ⇒ max font; linear
|
|
95
|
+
// between. Colour does NOT change with size — subordination is by muting (the
|
|
96
|
+
// contrast-floored tone) + size only, never by fading toward bg (Phase 1).
|
|
88
97
|
const COUNTRY_FONT_MAX = 22; // px ceiling for the largest footprint
|
|
89
98
|
const COUNTRY_SIZE_FRAC_MIN = 0.06; // footprint linear-frac at base font
|
|
90
99
|
const COUNTRY_SIZE_FRAC_MAX = 0.32; // footprint linear-frac at max font
|
|
91
|
-
const COUNTRY_FADE_MAX = 45; // % blend toward bg at max font (subdue big names)
|
|
92
100
|
|
|
93
101
|
// Multi-position country dodging (map-context-neighbor-labels). A country gets
|
|
94
102
|
// several interior on-its-own-land positions so its label can dodge a colliding
|
|
@@ -287,6 +295,8 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
287
295
|
waterBodies,
|
|
288
296
|
countries,
|
|
289
297
|
palette,
|
|
298
|
+
countryTone,
|
|
299
|
+
waterTone,
|
|
290
300
|
project,
|
|
291
301
|
collides,
|
|
292
302
|
contentPoints,
|
|
@@ -306,10 +316,14 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
306
316
|
const budget = labelBudget(width, height, band);
|
|
307
317
|
if (budget <= 0) return [];
|
|
308
318
|
|
|
309
|
-
// Subordinate cartographic colours
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
319
|
+
// Subordinate cartographic colours. Both are muted but CONTRAST-FLOORED in the
|
|
320
|
+
// caller (layout) against the substrate they sit on, and shared with the
|
|
321
|
+
// orientation backdrop so a context country and an orientation region match.
|
|
322
|
+
// Water stays the blue-gray italic convention; country = the muted land tone.
|
|
323
|
+
// Fallbacks keep the pre-floor behaviour for unit tests that omit the tones.
|
|
324
|
+
const waterColor =
|
|
325
|
+
waterTone ?? mix(palette.colors.blue, palette.textMuted, 50);
|
|
326
|
+
const countryColor = countryTone ?? palette.textMuted;
|
|
313
327
|
const haloColor = palette.bg;
|
|
314
328
|
|
|
315
329
|
type Candidate = {
|
|
@@ -475,15 +489,11 @@ export function placeContextLabels(args: ContextLabelArgs): PlacedLabel[] {
|
|
|
475
489
|
)
|
|
476
490
|
);
|
|
477
491
|
const fontSize = Math.round(FONT + t * (COUNTRY_FONT_MAX - FONT));
|
|
478
|
-
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
-
// names toward white instead, which read as illegible once proximity started
|
|
484
|
-
// surfacing small neighbours like Belarus/Georgia.)
|
|
485
|
-
const color =
|
|
486
|
-
fade > 0 ? mix(countryColor, palette.bg, 100 - fade) : countryColor;
|
|
492
|
+
// No fade. Subordination is by MUTING (the floored tone) + smaller size, not
|
|
493
|
+
// by fading toward bg — fading a floored tone toward the background would
|
|
494
|
+
// re-break the contrast it was floored to guarantee (the old behaviour washed
|
|
495
|
+
// big names like Canada toward illegibility). Footprint still drives font size.
|
|
496
|
+
const color = countryColor;
|
|
487
497
|
// Always the full country name — never an ISO abbreviation. If the name
|
|
488
498
|
// doesn't fit the footprint, drop the label rather than abbreviate.
|
|
489
499
|
const text = c.name;
|