@diagrammo/dgmo 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.claude/skills/dgmo-chart/SKILL.md +28 -0
  2. package/.claude/skills/dgmo-generate/SKILL.md +1 -0
  3. package/.claude/skills/dgmo-sequence/SKILL.md +24 -1
  4. package/.cursorrules +27 -2
  5. package/.github/copilot-instructions.md +36 -3
  6. package/.windsurfrules +27 -2
  7. package/README.md +12 -3
  8. package/dist/cli.cjs +611 -153
  9. package/dist/index.cjs +8371 -3200
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +502 -58
  12. package/dist/index.d.ts +502 -58
  13. package/dist/index.js +8594 -3444
  14. package/dist/index.js.map +1 -1
  15. package/docs/ai-integration.md +1 -1
  16. package/docs/language-reference.md +336 -17
  17. package/docs/migration-sequence-color-to-tags.md +98 -0
  18. package/package.json +1 -1
  19. package/src/c4/renderer.ts +1 -20
  20. package/src/class/renderer.ts +1 -11
  21. package/src/cli.ts +40 -0
  22. package/src/d3.ts +92 -2
  23. package/src/dgmo-router.ts +11 -0
  24. package/src/echarts.ts +74 -8
  25. package/src/er/parser.ts +29 -3
  26. package/src/er/renderer.ts +1 -15
  27. package/src/graph/flowchart-parser.ts +7 -30
  28. package/src/graph/flowchart-renderer.ts +62 -69
  29. package/src/graph/layout.ts +5 -0
  30. package/src/graph/state-parser.ts +388 -0
  31. package/src/graph/state-renderer.ts +496 -0
  32. package/src/graph/types.ts +4 -2
  33. package/src/index.ts +42 -1
  34. package/src/infra/compute.ts +1113 -0
  35. package/src/infra/layout.ts +575 -0
  36. package/src/infra/parser.ts +559 -0
  37. package/src/infra/renderer.ts +1509 -0
  38. package/src/infra/roles.ts +60 -0
  39. package/src/infra/serialize.ts +67 -0
  40. package/src/infra/types.ts +221 -0
  41. package/src/infra/validation.ts +192 -0
  42. package/src/initiative-status/layout.ts +56 -61
  43. package/src/initiative-status/renderer.ts +13 -13
  44. package/src/kanban/renderer.ts +1 -24
  45. package/src/org/layout.ts +28 -37
  46. package/src/org/parser.ts +16 -1
  47. package/src/org/renderer.ts +159 -121
  48. package/src/org/resolver.ts +90 -23
  49. package/src/palettes/color-utils.ts +30 -0
  50. package/src/render.ts +2 -0
  51. package/src/sequence/parser.ts +202 -42
  52. package/src/sequence/renderer.ts +576 -113
  53. package/src/sequence/tag-resolution.ts +163 -0
  54. package/src/sitemap/collapse.ts +187 -0
  55. package/src/sitemap/layout.ts +738 -0
  56. package/src/sitemap/parser.ts +489 -0
  57. package/src/sitemap/renderer.ts +774 -0
  58. package/src/sitemap/types.ts +42 -0
  59. package/src/utils/tag-groups.ts +119 -0
@@ -0,0 +1,163 @@
1
+ // ============================================================
2
+ // Sequence Diagram Tag Resolution
3
+ // ============================================================
4
+ //
5
+ // Resolves effective tag values for participants and messages
6
+ // using the priority chain: explicit > group > receiver-inherit > default > neutral
7
+
8
+ import type { TagGroup } from '../utils/tag-groups';
9
+ import type {
10
+ ParsedSequenceDgmo,
11
+ SequenceParticipant,
12
+ SequenceMessage,
13
+ SequenceGroup,
14
+ } from './parser';
15
+
16
+ /**
17
+ * Resolved tag values for all sequence elements.
18
+ * Used by the renderer to look up colors via tag group entries.
19
+ */
20
+ export interface ResolvedTagMap {
21
+ /** participantId → resolved tag value for the active group */
22
+ participants: Map<string, string | undefined>;
23
+ /** message lineNumber → tag value from explicit metadata */
24
+ messages: Map<number, string | undefined>;
25
+ }
26
+
27
+ /**
28
+ * Propagate group-level tag metadata to contained participants.
29
+ * Only sets values for keys not already present on the participant.
30
+ *
31
+ * Mutates `participantMeta` in-place.
32
+ */
33
+ export function propagateGroupTags(
34
+ participantMeta: Map<string, Record<string, string>>,
35
+ groups: ReadonlyArray<SequenceGroup>,
36
+ ): void {
37
+ for (const group of groups) {
38
+ if (!group.metadata) continue;
39
+ for (const id of group.participantIds) {
40
+ const meta = participantMeta.get(id);
41
+ if (!meta) continue;
42
+ for (const [key, value] of Object.entries(group.metadata)) {
43
+ if (!(key in meta)) {
44
+ meta[key] = value;
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Compute receiver inheritance for a specific tag group key.
53
+ *
54
+ * For each participant without an explicit value for `groupKey`,
55
+ * collect values from all incoming tagged messages. If exactly one
56
+ * unique value exists, the participant inherits it.
57
+ *
58
+ * Returns a map of participantId → inherited value.
59
+ */
60
+ export function computeReceiverInheritance(
61
+ participants: ReadonlyArray<SequenceParticipant>,
62
+ messages: ReadonlyArray<SequenceMessage>,
63
+ groupKey: string,
64
+ participantMeta: Map<string, Record<string, string>>,
65
+ ): Map<string, string> {
66
+ const inheritance = new Map<string, string>();
67
+
68
+ for (const p of participants) {
69
+ const meta = participantMeta.get(p.id);
70
+ // Skip if already has a value for this group key
71
+ if (meta?.[groupKey]) continue;
72
+
73
+ const incomingValues = new Set<string>();
74
+ for (const msg of messages) {
75
+ if (msg.to === p.id && msg.metadata?.[groupKey]) {
76
+ incomingValues.add(msg.metadata[groupKey]);
77
+ }
78
+ }
79
+
80
+ if (incomingValues.size === 1) {
81
+ inheritance.set(p.id, [...incomingValues][0]);
82
+ }
83
+ }
84
+
85
+ return inheritance;
86
+ }
87
+
88
+ /**
89
+ * Full tag resolution for sequence diagrams.
90
+ *
91
+ * Priority: explicit metadata > group propagation > receiver inheritance > default > neutral
92
+ *
93
+ * Pure function — does not mutate `parsed`.
94
+ */
95
+ export function resolveSequenceTags(
96
+ parsed: ParsedSequenceDgmo,
97
+ activeTagGroup: string,
98
+ ): ResolvedTagMap {
99
+ const result: ResolvedTagMap = {
100
+ participants: new Map(),
101
+ messages: new Map(),
102
+ };
103
+
104
+ // Find the active tag group
105
+ const group = parsed.tagGroups.find(
106
+ (g) => g.name.toLowerCase() === activeTagGroup.toLowerCase(),
107
+ );
108
+ if (!group) {
109
+ // No matching group — all neutral
110
+ for (const p of parsed.participants) {
111
+ result.participants.set(p.id, undefined);
112
+ }
113
+ for (const m of parsed.messages) {
114
+ result.messages.set(m.lineNumber, undefined);
115
+ }
116
+ return result;
117
+ }
118
+
119
+ const groupKey = group.name.toLowerCase();
120
+
121
+ // Clone participant metadata (don't mutate parsed data)
122
+ const participantMeta = new Map<string, Record<string, string>>();
123
+ for (const p of parsed.participants) {
124
+ participantMeta.set(p.id, { ...(p.metadata || {}) });
125
+ }
126
+
127
+ // Step 1: Group propagation
128
+ propagateGroupTags(participantMeta, parsed.groups);
129
+
130
+ // Step 2: Receiver inheritance
131
+ const inherited = computeReceiverInheritance(
132
+ parsed.participants,
133
+ parsed.messages,
134
+ groupKey,
135
+ participantMeta,
136
+ );
137
+ for (const [id, value] of inherited) {
138
+ const meta = participantMeta.get(id)!;
139
+ meta[groupKey] = value;
140
+ }
141
+
142
+ // Step 3: Default value injection
143
+ if (group.defaultValue) {
144
+ for (const p of parsed.participants) {
145
+ const meta = participantMeta.get(p.id)!;
146
+ if (!(groupKey in meta)) {
147
+ meta[groupKey] = group.defaultValue;
148
+ }
149
+ }
150
+ }
151
+
152
+ // Build result maps
153
+ for (const p of parsed.participants) {
154
+ const meta = participantMeta.get(p.id)!;
155
+ result.participants.set(p.id, meta[groupKey] || undefined);
156
+ }
157
+
158
+ for (const msg of parsed.messages) {
159
+ result.messages.set(msg.lineNumber, msg.metadata?.[groupKey] || undefined);
160
+ }
161
+
162
+ return result;
163
+ }
@@ -0,0 +1,187 @@
1
+ // ============================================================
2
+ // Sitemap Collapse/Expand — prune subtrees + re-terminate arrows
3
+ // ============================================================
4
+
5
+ import type { SitemapNode, SitemapEdge, ParsedSitemap } from './types';
6
+
7
+ // ============================================================
8
+ // Types
9
+ // ============================================================
10
+
11
+ export interface CollapsedSitemapResult {
12
+ /** ParsedSitemap with collapsed subtrees pruned (deep-cloned, never mutates original) */
13
+ parsed: ParsedSitemap;
14
+ /** nodeId → count of hidden descendants */
15
+ hiddenCounts: Map<string, number>;
16
+ }
17
+
18
+ // ============================================================
19
+ // Helpers
20
+ // ============================================================
21
+
22
+ function cloneNode(node: SitemapNode): SitemapNode {
23
+ return {
24
+ id: node.id,
25
+ label: node.label,
26
+ metadata: { ...node.metadata },
27
+ children: node.children.map(cloneNode),
28
+ parentId: node.parentId,
29
+ isContainer: node.isContainer,
30
+ lineNumber: node.lineNumber,
31
+ color: node.color,
32
+ };
33
+ }
34
+
35
+ function countDescendants(node: SitemapNode): number {
36
+ let count = 0;
37
+ for (const child of node.children) {
38
+ count += (child.isContainer ? 0 : 1) + countDescendants(child);
39
+ }
40
+ return count;
41
+ }
42
+
43
+ /** Compute hidden counts from the ORIGINAL (unpruned) tree. */
44
+ function computeHiddenCounts(
45
+ nodes: SitemapNode[],
46
+ collapsedIds: Set<string>,
47
+ hiddenCounts: Map<string, number>,
48
+ ): void {
49
+ for (const node of nodes) {
50
+ if (collapsedIds.has(node.id) && node.children.length > 0) {
51
+ hiddenCounts.set(node.id, countDescendants(node));
52
+ }
53
+ computeHiddenCounts(node.children, collapsedIds, hiddenCounts);
54
+ }
55
+ }
56
+
57
+ /** Remove children of collapsed nodes on the cloned tree. */
58
+ function pruneCollapsed(node: SitemapNode, collapsedIds: Set<string>): void {
59
+ for (const child of node.children) {
60
+ pruneCollapsed(child, collapsedIds);
61
+ }
62
+ if (collapsedIds.has(node.id) && node.children.length > 0) {
63
+ node.children = [];
64
+ }
65
+ }
66
+
67
+ /** Collect all node IDs reachable in a tree. */
68
+ function collectNodeIds(nodes: SitemapNode[], ids: Set<string>): void {
69
+ for (const node of nodes) {
70
+ ids.add(node.id);
71
+ collectNodeIds(node.children, ids);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Find the outermost visible ancestor for a hidden node.
77
+ * Walk up the original tree to find which collapsed container should absorb the arrow.
78
+ */
79
+ function findVisibleAncestor(
80
+ nodeId: string,
81
+ parentMap: Map<string, string>,
82
+ visibleIds: Set<string>,
83
+ collapsedIds: Set<string>,
84
+ ): string | null {
85
+ let current = nodeId;
86
+ while (true) {
87
+ const parentId = parentMap.get(current);
88
+ if (!parentId) return null;
89
+ // If the parent is visible and is a collapsed container, re-terminate here
90
+ if (visibleIds.has(parentId) && collapsedIds.has(parentId)) {
91
+ return parentId;
92
+ }
93
+ current = parentId;
94
+ }
95
+ }
96
+
97
+ /** Build nodeId → parentId map from the original tree. */
98
+ function buildParentMap(nodes: SitemapNode[], map: Map<string, string>): void {
99
+ for (const node of nodes) {
100
+ for (const child of node.children) {
101
+ map.set(child.id, node.id);
102
+ buildParentMap([child], map);
103
+ }
104
+ }
105
+ }
106
+
107
+ // ============================================================
108
+ // Main
109
+ // ============================================================
110
+
111
+ export function collapseSitemapTree(
112
+ original: ParsedSitemap,
113
+ collapsedIds: Set<string>,
114
+ ): CollapsedSitemapResult {
115
+ const hiddenCounts = new Map<string, number>();
116
+
117
+ if (collapsedIds.size === 0) {
118
+ return { parsed: original, hiddenCounts };
119
+ }
120
+
121
+ // Compute counts from the ORIGINAL tree before pruning
122
+ computeHiddenCounts(original.roots, collapsedIds, hiddenCounts);
123
+
124
+ // Deep-clone roots and prune collapsed subtrees
125
+ const clonedRoots = original.roots.map(cloneNode);
126
+ for (const root of clonedRoots) {
127
+ pruneCollapsed(root, collapsedIds);
128
+ }
129
+
130
+ // Collect visible node IDs after pruning
131
+ const visibleIds = new Set<string>();
132
+ collectNodeIds(clonedRoots, visibleIds);
133
+
134
+ // Build parent map from the ORIGINAL tree for ancestor lookup
135
+ const parentMap = new Map<string, string>();
136
+ buildParentMap(original.roots, parentMap);
137
+
138
+ // Re-terminate edges that reference hidden nodes.
139
+ // No deduplication — multiple edges between the same collapsed pair are kept
140
+ // so each retains its own label (e.g. "settings" and "billing" both show).
141
+ // Layout uses dagre multigraph to route each edge separately.
142
+ const newEdges: SitemapEdge[] = [];
143
+
144
+ for (const edge of original.edges) {
145
+ let sourceId = edge.sourceId;
146
+ let targetId = edge.targetId;
147
+
148
+ const sourceVisible = visibleIds.has(sourceId);
149
+ const targetVisible = visibleIds.has(targetId);
150
+
151
+ if (sourceVisible && targetVisible) {
152
+ // Both visible — keep as-is
153
+ newEdges.push({ ...edge });
154
+ continue;
155
+ }
156
+
157
+ // Re-terminate hidden endpoints
158
+ if (!sourceVisible) {
159
+ const ancestor = findVisibleAncestor(sourceId, parentMap, visibleIds, collapsedIds);
160
+ if (!ancestor) continue; // both endpoints hidden with no visible ancestor
161
+ sourceId = ancestor;
162
+ }
163
+ if (!targetVisible) {
164
+ const ancestor = findVisibleAncestor(targetId, parentMap, visibleIds, collapsedIds);
165
+ if (!ancestor) continue;
166
+ targetId = ancestor;
167
+ }
168
+
169
+ // Remove self-loops (both endpoints re-terminated to same collapsed group)
170
+ if (sourceId === targetId) continue;
171
+
172
+ newEdges.push({
173
+ ...edge,
174
+ sourceId,
175
+ targetId,
176
+ });
177
+ }
178
+
179
+ return {
180
+ parsed: {
181
+ ...original,
182
+ roots: clonedRoots,
183
+ edges: newEdges,
184
+ },
185
+ hiddenCounts,
186
+ };
187
+ }