@diagrammo/dgmo 0.8.2 → 0.8.4

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 (120) hide show
  1. package/.claude/commands/dgmo-diagram-this.md +60 -0
  2. package/.claude/commands/dgmo-document-project.md +128 -0
  3. package/.claude/commands/dgmo.md +185 -50
  4. package/.cursorrules +32 -37
  5. package/.github/copilot-instructions.md +35 -44
  6. package/.windsurfrules +32 -37
  7. package/README.md +4 -4
  8. package/dist/cli.cjs +189 -194
  9. package/dist/editor.cjs +336 -0
  10. package/dist/editor.cjs.map +1 -0
  11. package/dist/editor.d.cts +27 -0
  12. package/dist/editor.d.ts +27 -0
  13. package/dist/editor.js +305 -0
  14. package/dist/editor.js.map +1 -0
  15. package/dist/index.cjs +3699 -1564
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +7 -6
  18. package/dist/index.d.ts +7 -6
  19. package/dist/index.js +3699 -1564
  20. package/dist/index.js.map +1 -1
  21. package/docs/language-reference.md +822 -1060
  22. package/gallery/fixtures/arc.dgmo +18 -0
  23. package/gallery/fixtures/area.dgmo +19 -0
  24. package/gallery/fixtures/bar-stacked.dgmo +10 -0
  25. package/gallery/fixtures/bar.dgmo +10 -0
  26. package/gallery/fixtures/c4-full.dgmo +52 -0
  27. package/gallery/fixtures/c4.dgmo +17 -0
  28. package/gallery/fixtures/chord.dgmo +12 -0
  29. package/gallery/fixtures/class-basic.dgmo +14 -0
  30. package/gallery/fixtures/class-full.dgmo +43 -0
  31. package/gallery/fixtures/doughnut.dgmo +8 -0
  32. package/gallery/fixtures/flowchart-basic.dgmo +3 -0
  33. package/gallery/fixtures/flowchart-colors.dgmo +5 -0
  34. package/gallery/fixtures/flowchart-complex.dgmo +17 -0
  35. package/gallery/fixtures/flowchart-decision.dgmo +5 -0
  36. package/gallery/fixtures/flowchart-full.dgmo +13 -0
  37. package/gallery/fixtures/flowchart-groups.dgmo +10 -0
  38. package/gallery/fixtures/flowchart-loop.dgmo +7 -0
  39. package/gallery/fixtures/flowchart-nested.dgmo +7 -0
  40. package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
  41. package/gallery/fixtures/function.dgmo +8 -0
  42. package/gallery/fixtures/funnel.dgmo +7 -0
  43. package/gallery/fixtures/gantt-full.dgmo +49 -0
  44. package/gallery/fixtures/gantt.dgmo +42 -0
  45. package/gallery/fixtures/heatmap.dgmo +8 -0
  46. package/gallery/fixtures/infra-full.dgmo +78 -0
  47. package/gallery/fixtures/infra-overload.dgmo +25 -0
  48. package/gallery/fixtures/infra.dgmo +47 -0
  49. package/gallery/fixtures/initiative-status-full.dgmo +46 -0
  50. package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
  51. package/gallery/fixtures/initiative-status.dgmo +9 -0
  52. package/gallery/fixtures/line.dgmo +19 -0
  53. package/gallery/fixtures/multi-line.dgmo +11 -0
  54. package/gallery/fixtures/org-basic.dgmo +16 -0
  55. package/gallery/fixtures/org-full.dgmo +69 -0
  56. package/gallery/fixtures/org-teams.dgmo +25 -0
  57. package/gallery/fixtures/pie.dgmo +9 -0
  58. package/gallery/fixtures/polar-area.dgmo +8 -0
  59. package/gallery/fixtures/quadrant.dgmo +18 -0
  60. package/gallery/fixtures/radar.dgmo +8 -0
  61. package/gallery/fixtures/sankey.dgmo +31 -0
  62. package/gallery/fixtures/scatter.dgmo +21 -0
  63. package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
  64. package/gallery/fixtures/sequence-tags.dgmo +41 -0
  65. package/gallery/fixtures/sequence.dgmo +35 -0
  66. package/gallery/fixtures/sitemap-basic.dgmo +12 -0
  67. package/gallery/fixtures/sitemap-full.dgmo +156 -0
  68. package/gallery/fixtures/slope.dgmo +8 -0
  69. package/gallery/fixtures/spr-eras.dgmo +62 -0
  70. package/gallery/fixtures/state.dgmo +30 -0
  71. package/gallery/fixtures/timeline-intraday.dgmo +14 -0
  72. package/gallery/fixtures/timeline.dgmo +32 -0
  73. package/gallery/fixtures/venn.dgmo +10 -0
  74. package/gallery/fixtures/wordcloud.dgmo +24 -0
  75. package/package.json +51 -2
  76. package/src/c4/layout.ts +372 -90
  77. package/src/c4/parser.ts +113 -62
  78. package/src/chart.ts +149 -64
  79. package/src/class/parser.ts +84 -28
  80. package/src/class/renderer.ts +2 -2
  81. package/src/cli.ts +179 -77
  82. package/src/completion.ts +381 -182
  83. package/src/d3.ts +1026 -428
  84. package/src/dgmo-mermaid.ts +16 -13
  85. package/src/dgmo-router.ts +70 -24
  86. package/src/echarts.ts +682 -169
  87. package/src/editor/dgmo.grammar +69 -0
  88. package/src/editor/dgmo.grammar.d.ts +2 -0
  89. package/src/editor/dgmo.grammar.js +18 -0
  90. package/src/editor/dgmo.grammar.terms.d.ts +5 -0
  91. package/src/editor/dgmo.grammar.terms.js +35 -0
  92. package/src/editor/highlight.ts +36 -0
  93. package/src/editor/index.ts +28 -0
  94. package/src/editor/keywords.ts +220 -0
  95. package/src/editor/tokens.ts +30 -0
  96. package/src/er/parser.ts +55 -29
  97. package/src/er/renderer.ts +112 -53
  98. package/src/gantt/calculator.ts +91 -29
  99. package/src/gantt/parser.ts +291 -97
  100. package/src/gantt/renderer.ts +1120 -350
  101. package/src/graph/flowchart-parser.ts +48 -75
  102. package/src/graph/state-parser.ts +54 -27
  103. package/src/infra/parser.ts +161 -177
  104. package/src/infra/renderer.ts +723 -271
  105. package/src/infra/types.ts +0 -1
  106. package/src/initiative-status/parser.ts +144 -56
  107. package/src/kanban/parser.ts +27 -19
  108. package/src/org/layout.ts +111 -44
  109. package/src/org/parser.ts +71 -27
  110. package/src/org/resolver.ts +3 -3
  111. package/src/palettes/index.ts +3 -2
  112. package/src/render.ts +1 -2
  113. package/src/sequence/parser.ts +209 -100
  114. package/src/sitemap/parser.ts +73 -44
  115. package/src/utils/arrows.ts +2 -22
  116. package/src/utils/duration.ts +39 -21
  117. package/src/utils/legend-constants.ts +0 -2
  118. package/src/utils/parsing.ts +82 -72
  119. package/src/utils/tag-groups.ts +4 -41
  120. package/src/infra/serialize.ts +0 -67
package/src/completion.ts CHANGED
@@ -43,7 +43,7 @@ export function registerExtractor(kind: ChartType, fn: ExtractFn): void {
43
43
  * Returns null if the chart type is unknown or has no registered extractor.
44
44
  */
45
45
  export function extractDiagramSymbols(docText: string): DiagramSymbols | null {
46
- // Parse chartType from first line — supports bare type name and old `chart:` syntax.
46
+ // Parse chartType from first line — bare type name.
47
47
  let chartType: string | null = null;
48
48
  for (const line of docText.split('\n')) {
49
49
  const trimmed = line.trim();
@@ -79,7 +79,18 @@ export interface DirectiveSpec {
79
79
  const GLOBAL_DIRECTIVES: Record<string, DirectiveValueSpec> = {
80
80
  palette: {
81
81
  description: 'Color palette name',
82
- values: ['nord', 'solarized', 'catppuccin', 'rose-pine', 'gruvbox', 'tokyo-night', 'one-dark', 'bold', 'dracula', 'monokai'],
82
+ values: [
83
+ 'nord',
84
+ 'solarized',
85
+ 'catppuccin',
86
+ 'rose-pine',
87
+ 'gruvbox',
88
+ 'tokyo-night',
89
+ 'one-dark',
90
+ 'bold',
91
+ 'dracula',
92
+ 'monokai',
93
+ ],
83
94
  },
84
95
  theme: {
85
96
  description: 'Color theme',
@@ -87,134 +98,221 @@ const GLOBAL_DIRECTIVES: Record<string, DirectiveValueSpec> = {
87
98
  },
88
99
  };
89
100
 
90
- function withGlobals(directives: Record<string, DirectiveValueSpec> = {}): DirectiveSpec {
101
+ function withGlobals(
102
+ directives: Record<string, DirectiveValueSpec> = {}
103
+ ): DirectiveSpec {
91
104
  return { directives: { ...GLOBAL_DIRECTIVES, ...directives } };
92
105
  }
93
106
 
94
107
  /** Chart-type → directive specifications. Every chart type has at least palette + theme. */
95
108
  export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
96
109
  // ── Data charts ──────────────────────────────────────────
97
- ['bar', withGlobals({
98
- series: { description: 'Series name(s)' },
99
- xlabel: { description: 'X-axis label' },
100
- ylabel: { description: 'Y-axis label' },
101
- orientation: { description: 'Layout direction', values: ['horizontal', 'vertical'] },
102
- labels: { description: 'Label format', values: ['name', 'value', 'percent', 'full'] },
103
- color: { description: 'Bar color override' },
104
- })],
105
- ['line', withGlobals({
106
- series: { description: 'Series name(s)' },
107
- xlabel: { description: 'X-axis label' },
108
- ylabel: { description: 'Y-axis label' },
109
- labels: { description: 'Label format', values: ['name', 'value', 'percent', 'full'] },
110
- })],
111
- ['pie', withGlobals({
112
- labels: { description: 'Label format', values: ['name', 'value', 'percent', 'full'] },
113
- })],
114
- ['doughnut', withGlobals({
115
- labels: { description: 'Label format', values: ['name', 'value', 'percent', 'full'] },
116
- })],
117
- ['area', withGlobals({
118
- series: { description: 'Series name(s)' },
119
- xlabel: { description: 'X-axis label' },
120
- ylabel: { description: 'Y-axis label' },
121
- labels: { description: 'Label format', values: ['name', 'value', 'percent', 'full'] },
122
- })],
123
- ['polar-area', withGlobals({
124
- labels: { description: 'Label format', values: ['name', 'value', 'percent', 'full'] },
125
- })],
110
+ [
111
+ 'bar',
112
+ withGlobals({
113
+ series: { description: 'Series name(s)' },
114
+ 'x-label': { description: 'X-axis label' },
115
+ 'y-label': { description: 'Y-axis label' },
116
+ 'orientation-horizontal': { description: 'Switch to horizontal bars' },
117
+ color: { description: 'Bar color override' },
118
+ }),
119
+ ],
120
+ [
121
+ 'line',
122
+ withGlobals({
123
+ series: { description: 'Series name(s)' },
124
+ 'x-label': { description: 'X-axis label' },
125
+ 'y-label': { description: 'Y-axis label' },
126
+ }),
127
+ ],
128
+ [
129
+ 'pie',
130
+ withGlobals({
131
+ 'no-label-name': { description: 'Hide name from segment labels' },
132
+ 'no-label-value': { description: 'Hide value from segment labels' },
133
+ 'no-label-percent': { description: 'Hide percent from segment labels' },
134
+ }),
135
+ ],
136
+ [
137
+ 'doughnut',
138
+ withGlobals({
139
+ 'no-label-name': { description: 'Hide name from segment labels' },
140
+ 'no-label-value': { description: 'Hide value from segment labels' },
141
+ 'no-label-percent': { description: 'Hide percent from segment labels' },
142
+ }),
143
+ ],
144
+ [
145
+ 'area',
146
+ withGlobals({
147
+ series: { description: 'Series name(s)' },
148
+ 'x-label': { description: 'X-axis label' },
149
+ 'y-label': { description: 'Y-axis label' },
150
+ }),
151
+ ],
152
+ [
153
+ 'polar-area',
154
+ withGlobals({
155
+ 'no-label-name': { description: 'Hide name from segment labels' },
156
+ 'no-label-value': { description: 'Hide value from segment labels' },
157
+ 'no-label-percent': { description: 'Hide percent from segment labels' },
158
+ }),
159
+ ],
126
160
  ['radar', withGlobals()],
127
- ['bar-stacked', withGlobals({
128
- series: { description: 'Series name(s) (required)' },
129
- xlabel: { description: 'X-axis label' },
130
- ylabel: { description: 'Y-axis label' },
131
- orientation: { description: 'Layout direction', values: ['horizontal', 'vertical'] },
132
- })],
161
+ [
162
+ 'bar-stacked',
163
+ withGlobals({
164
+ series: { description: 'Series name(s) (required)' },
165
+ 'x-label': { description: 'X-axis label' },
166
+ 'y-label': { description: 'Y-axis label' },
167
+ 'orientation-horizontal': { description: 'Switch to horizontal bars' },
168
+ }),
169
+ ],
133
170
 
134
171
  // ── Extended charts ──────────────────────────────────────
135
- ['scatter', withGlobals({
136
- labels: { description: 'Show labels', values: ['on', 'off'] },
137
- xlabel: { description: 'X-axis label' },
138
- ylabel: { description: 'Y-axis label' },
139
- sizelabel: { description: 'Size axis label' },
140
- })],
141
- ['heatmap', withGlobals({
142
- columns: { description: 'Column labels (required)' },
143
- })],
172
+ [
173
+ 'scatter',
174
+ withGlobals({
175
+ 'no-labels': { description: 'Hide point labels' },
176
+ 'x-label': { description: 'X-axis label' },
177
+ 'y-label': { description: 'Y-axis label' },
178
+ 'size-label': { description: 'Size axis label' },
179
+ }),
180
+ ],
181
+ [
182
+ 'heatmap',
183
+ withGlobals({
184
+ columns: { description: 'Column labels (required)' },
185
+ }),
186
+ ],
144
187
  ['sankey', withGlobals()],
145
188
  ['chord', withGlobals()],
146
189
  ['funnel', withGlobals()],
147
- ['function', withGlobals({
148
- x: { description: 'X-axis range (start to end)' },
149
- xlabel: { description: 'X-axis label' },
150
- ylabel: { description: 'Y-axis label' },
151
- })],
190
+ [
191
+ 'function',
192
+ withGlobals({
193
+ x: { description: 'X-axis range (start to end)' },
194
+ 'x-label': { description: 'X-axis label' },
195
+ 'y-label': { description: 'Y-axis label' },
196
+ shade: { description: 'Fill area below curves with translucent color' },
197
+ }),
198
+ ],
152
199
 
153
200
  // ── Visualizations ───────────────────────────────────────
154
- ['slope', withGlobals({
155
- orientation: { description: 'Layout direction', values: ['horizontal', 'vertical'] },
156
- })],
157
- ['wordcloud', withGlobals({
158
- rotate: { description: 'Word rotation', values: ['none', 'mixed', 'angled'] },
159
- max: { description: 'Maximum word count' },
160
- size: { description: 'Font size range (min, max)' },
161
- })],
162
- ['arc', withGlobals({
163
- order: { description: 'Node ordering', values: ['appearance', 'name', 'group', 'degree'] },
164
- orientation: { description: 'Layout direction' },
165
- })],
166
- ['timeline', withGlobals({
167
- scale: { description: 'Show time scale', values: ['on', 'off'] },
168
- sort: { description: 'Sort order', values: ['time', 'group', 'tag'] },
169
- swimlanes: { description: 'Show swimlanes', values: ['on', 'off'] },
170
- })],
171
- ['venn', withGlobals({
172
- values: { description: 'Show values', values: ['on', 'off'] },
173
- })],
174
- ['quadrant', withGlobals({
175
- 'x-axis': { description: 'X-axis labels (low, high)' },
176
- 'y-axis': { description: 'Y-axis labels (low, high)' },
177
- })],
201
+ ['slope', withGlobals()],
202
+ [
203
+ 'wordcloud',
204
+ withGlobals({
205
+ rotate: {
206
+ description: 'Word rotation',
207
+ values: ['none', 'mixed', 'angled'],
208
+ },
209
+ max: { description: 'Maximum word count' },
210
+ size: { description: 'Font size range (min, max)' },
211
+ }),
212
+ ],
213
+ [
214
+ 'arc',
215
+ withGlobals({
216
+ order: {
217
+ description: 'Node ordering',
218
+ values: ['appearance', 'name', 'group', 'degree'],
219
+ },
220
+ }),
221
+ ],
222
+ ['timeline', withGlobals()],
223
+ ['venn', withGlobals()],
224
+ [
225
+ 'quadrant',
226
+ withGlobals({
227
+ 'x-label': { description: 'X-axis labels (low, high)' },
228
+ 'y-label': { description: 'Y-axis labels (low, high)' },
229
+ }),
230
+ ],
178
231
 
179
232
  // ── Diagrams ─────────────────────────────────────────────
180
- ['sequence', withGlobals({
181
- activations: { description: 'Show activation bars', values: ['on', 'off'] },
182
- 'collapse-notes': { description: 'Collapse note blocks', values: ['yes', 'no'] },
183
- 'active-tag': { description: 'Active tag group name' },
184
- })],
185
- ['flowchart', withGlobals()],
186
- ['class', withGlobals()],
233
+ [
234
+ 'sequence',
235
+ withGlobals({
236
+ activations: {
237
+ description: 'Show activation bars',
238
+ values: ['on', 'off'],
239
+ },
240
+ 'collapse-notes': {
241
+ description: 'Collapse note blocks',
242
+ values: ['yes', 'no'],
243
+ },
244
+ 'active-tag': { description: 'Active tag group name' },
245
+ }),
246
+ ],
247
+ [
248
+ 'flowchart',
249
+ withGlobals({
250
+ 'direction-lr': { description: 'Switch to left-to-right layout' },
251
+ }),
252
+ ],
253
+ [
254
+ 'class',
255
+ withGlobals({
256
+ 'no-auto-color': {
257
+ description: 'Disable automatic modifier-based coloring',
258
+ },
259
+ }),
260
+ ],
187
261
  ['er', withGlobals()],
188
- ['org', withGlobals({
189
- 'sub-node-label': { description: 'Label for sub-nodes' },
190
- 'show-sub-node-count': { description: 'Show sub-node counts' },
191
- })],
192
- ['kanban', withGlobals()],
262
+ [
263
+ 'org',
264
+ withGlobals({
265
+ 'sub-node-label': { description: 'Label for sub-nodes' },
266
+ 'show-sub-node-count': { description: 'Show sub-node counts' },
267
+ }),
268
+ ],
269
+ [
270
+ 'kanban',
271
+ withGlobals({
272
+ 'no-auto-color': { description: 'Disable automatic card coloring' },
273
+ }),
274
+ ],
193
275
  ['c4', withGlobals()],
194
276
  ['initiative-status', withGlobals()],
195
- ['state', withGlobals({
196
- direction: { description: 'Layout direction', values: ['TB', 'LR'] },
197
- color: { description: 'Color mode', values: ['off'] },
198
- })],
199
- ['sitemap', withGlobals({
200
- direction: { description: 'Layout direction', values: ['TB', 'LR'] },
201
- })],
202
- ['infra', withGlobals({
203
- direction: { description: 'Layout direction', values: ['LR', 'TB'] },
204
- 'default-latency-ms': { description: 'Default latency for all nodes' },
205
- 'default-uptime': { description: 'Default uptime for all nodes' },
206
- 'default-rps': { description: 'Default RPS capacity for all nodes' },
207
- 'slo-availability': { description: 'SLO availability target (0-1)' },
208
- 'slo-p90-latency-ms': { description: 'SLO p90 latency target in ms' },
209
- 'slo-warning-margin': { description: 'SLO warning margin percentage' },
210
- })],
211
- ['gantt', withGlobals({
212
- start: { description: 'Project start date (YYYY-MM-DD)' },
213
- 'today-marker': { description: 'Today marker (bare = on, or YYYY-MM-DD date)' },
214
- sort: { description: 'Sort order', values: ['time', 'group', 'tag'] },
215
- 'critical-path': { description: 'Show critical path' },
216
- dependencies: { description: 'Show dependencies' },
217
- })],
277
+ [
278
+ 'state',
279
+ withGlobals({
280
+ 'direction-tb': { description: 'Switch to top-to-bottom layout' },
281
+ color: { description: 'Color mode', values: ['off'] },
282
+ }),
283
+ ],
284
+ [
285
+ 'sitemap',
286
+ withGlobals({
287
+ 'direction-tb': { description: 'Switch to top-to-bottom layout' },
288
+ }),
289
+ ],
290
+ [
291
+ 'infra',
292
+ withGlobals({
293
+ 'direction-tb': { description: 'Switch to top-to-bottom layout' },
294
+ animate: { description: 'Enable traffic animation' },
295
+ 'no-animate': { description: 'Disable traffic animation' },
296
+ 'default-latency-ms': { description: 'Default latency for all nodes' },
297
+ 'default-uptime': { description: 'Default uptime for all nodes' },
298
+ 'default-rps': { description: 'Default RPS capacity for all nodes' },
299
+ 'slo-availability': { description: 'SLO availability target (0-1)' },
300
+ 'slo-p90-latency-ms': { description: 'SLO p90 latency target in ms' },
301
+ 'slo-warning-margin': { description: 'SLO warning margin percentage' },
302
+ }),
303
+ ],
304
+ [
305
+ 'gantt',
306
+ withGlobals({
307
+ start: { description: 'Project start date (YYYY-MM-DD)' },
308
+ 'today-marker': {
309
+ description: 'Today marker (bare = on, or YYYY-MM-DD date)',
310
+ },
311
+ sort: { description: 'Sort order', values: ['time', 'group', 'tag'] },
312
+ 'critical-path': { description: 'Show critical path' },
313
+ dependencies: { description: 'Show dependencies' },
314
+ }),
315
+ ],
218
316
  ]);
219
317
 
220
318
  // ============================================================
@@ -261,12 +359,13 @@ const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
261
359
  };
262
360
 
263
361
  /** All chart types with descriptions, for chart type autocomplete. Excludes `multi-line` alias. */
264
- export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> = [...ALL_CHART_TYPES]
265
- .filter(t => t !== 'multi-line')
266
- .map(name => ({
267
- name,
268
- description: CHART_TYPE_DESCRIPTIONS[name] ?? name,
269
- }));
362
+ export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> =
363
+ [...ALL_CHART_TYPES]
364
+ .filter((t) => t !== 'multi-line')
365
+ .map((name) => ({
366
+ name,
367
+ description: CHART_TYPE_DESCRIPTIONS[name] ?? name,
368
+ }));
270
369
 
271
370
  // ============================================================
272
371
  // Entity types for `is a` declarations
@@ -275,12 +374,27 @@ export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> =
275
374
  /**
276
375
  * Entity types for `Name is a <type>` declarations, keyed by chart type.
277
376
  * Values are sourced from parser constants (VALID_PARTICIPANT_TYPES,
278
- * VALID_NODE_TYPES, C4_IS_A_RE).
377
+ * C4_IS_A_RE).
279
378
  */
280
379
  export const ENTITY_TYPES = new Map<string, string[]>([
281
- ['sequence', ['service', 'database', 'actor', 'queue', 'cache', 'gateway', 'external', 'networking', 'frontend']],
282
- ['infra', ['database', 'cache', 'queue', 'service', 'gateway', 'storage', 'function', 'network']],
283
- ['c4', ['person', 'system', 'container', 'component', 'external', 'database']],
380
+ [
381
+ 'sequence',
382
+ [
383
+ 'service',
384
+ 'database',
385
+ 'actor',
386
+ 'queue',
387
+ 'cache',
388
+ 'gateway',
389
+ 'external',
390
+ 'networking',
391
+ 'frontend',
392
+ ],
393
+ ],
394
+ [
395
+ 'c4',
396
+ ['person', 'system', 'container', 'component', 'external', 'database'],
397
+ ],
284
398
  ]);
285
399
 
286
400
  // ============================================================
@@ -301,53 +415,96 @@ export interface PipeKeySpec {
301
415
  * diagrams separates display names from identifiers and tag metadata.
302
416
  * Adding sequence would trigger false pipe-metadata completions on every `|`.
303
417
  */
304
- export const PIPE_METADATA = new Map<string, {
305
- node: Record<string, PipeKeySpec>;
306
- edge: Record<string, PipeKeySpec>;
307
- }>([
308
- ['infra', {
309
- node: {
310
- description: { description: 'Node description text' },
311
- instances: { description: 'Instance count or auto-scaling range (N-M)' },
312
- 'latency-ms': { description: 'Per-request latency in milliseconds' },
313
- 'max-rps': { description: 'Max requests per second per instance' },
314
- 'cache-hit': { description: 'Cache hit percentage (0-100)' },
315
- 'firewall-block': { description: 'Traffic blocked percentage' },
316
- 'ratelimit-rps': { description: 'Max RPS allowed through' },
317
- 'cb-error-threshold': { description: 'Circuit breaker error threshold %' },
318
- 'cb-latency-threshold-ms': { description: 'Circuit breaker latency threshold' },
319
- uptime: { description: 'Component availability (0-1)' },
320
- concurrency: { description: 'Concurrent request limit' },
321
- 'duration-ms': { description: 'Processing duration' },
322
- 'cold-start-ms': { description: 'Function cold-start time' },
323
- buffer: { description: 'Queue/buffer capacity' },
324
- 'drain-rate': { description: 'Queue drain rate' },
325
- 'retention-hours': { description: 'Data retention period' },
326
- partitions: { description: 'Queue/stream partition count' },
327
- 'slo-availability': { description: 'Node availability target (0-1)' },
328
- 'slo-p90-latency-ms': { description: 'Node p90 latency target' },
329
- 'slo-warning-margin': { description: 'Node SLO warning margin' },
418
+ export const PIPE_METADATA = new Map<
419
+ string,
420
+ {
421
+ node: Record<string, PipeKeySpec>;
422
+ edge: Record<string, PipeKeySpec>;
423
+ }
424
+ >([
425
+ [
426
+ 'infra',
427
+ {
428
+ node: {
429
+ description: { description: 'Node description text' },
430
+ instances: {
431
+ description: 'Instance count or auto-scaling range (N-M)',
432
+ },
433
+ 'latency-ms': { description: 'Per-request latency in milliseconds' },
434
+ 'max-rps': { description: 'Max requests per second per instance' },
435
+ 'cache-hit': { description: 'Cache hit percentage (0-100)' },
436
+ 'firewall-block': { description: 'Traffic blocked percentage' },
437
+ 'ratelimit-rps': { description: 'Max RPS allowed through' },
438
+ 'cb-error-threshold': {
439
+ description: 'Circuit breaker error threshold %',
440
+ },
441
+ 'cb-latency-threshold-ms': {
442
+ description: 'Circuit breaker latency threshold',
443
+ },
444
+ uptime: { description: 'Component availability (0-1)' },
445
+ concurrency: { description: 'Concurrent request limit' },
446
+ 'duration-ms': { description: 'Processing duration' },
447
+ 'cold-start-ms': { description: 'Function cold-start time' },
448
+ buffer: { description: 'Queue/buffer capacity' },
449
+ 'drain-rate': { description: 'Queue drain rate' },
450
+ 'retention-hours': { description: 'Data retention period' },
451
+ partitions: { description: 'Queue/stream partition count' },
452
+ 'slo-availability': { description: 'Node availability target (0-1)' },
453
+ 'slo-p90-latency-ms': { description: 'Node p90 latency target' },
454
+ 'slo-warning-margin': { description: 'Node SLO warning margin' },
455
+ },
456
+ edge: {
457
+ split: { description: 'Traffic split percentage (e.g., 60%)' },
458
+ fanout: { description: 'Fanout multiplier (integer >= 1)' },
459
+ },
330
460
  },
331
- edge: {
332
- split: { description: 'Traffic split percentage (e.g., 60%)' },
333
- fanout: { description: 'Fanout multiplier (integer >= 1)' },
461
+ ],
462
+ [
463
+ 'c4',
464
+ {
465
+ node: {
466
+ description: { description: 'Element description' },
467
+ tech: { description: 'Technology stack' },
468
+ technology: { description: 'Technology stack (alias for tech)' },
469
+ },
470
+ edge: {},
334
471
  },
335
- }],
336
- ['c4', {
337
- node: {
338
- description: { description: 'Element description' },
339
- tech: { description: 'Technology stack' },
340
- technology: { description: 'Technology stack (alias for tech)' },
472
+ ],
473
+ [
474
+ 'gantt',
475
+ {
476
+ node: {},
477
+ edge: {
478
+ // Gantt "edge" = dependency arrow (TaskA -> TaskB | offset 2bd)
479
+ offset: { description: 'Dependency offset (e.g., 2bd, -1w)' },
480
+ },
341
481
  },
342
- edge: {},
343
- }],
344
- ['gantt', {
345
- node: {},
346
- edge: {
347
- // Gantt "edge" = dependency arrow (TaskA -> TaskB | offset 2bd)
348
- offset: { description: 'Dependency offset (e.g., 2bd, -1w)' },
482
+ ],
483
+ [
484
+ 'initiative-status',
485
+ {
486
+ node: {
487
+ done: { description: 'Completed' },
488
+ doing: { description: 'In progress' },
489
+ todo: { description: 'Not started' },
490
+ blocked: { description: 'Blocked' },
491
+ na: { description: 'Not applicable' },
492
+ wip: { description: 'Work in progress (alias for doing)' },
493
+ paused: { description: 'Paused (alias for blocked)' },
494
+ waiting: { description: 'Waiting (alias for blocked)' },
495
+ },
496
+ edge: {
497
+ done: { description: 'Completed' },
498
+ doing: { description: 'In progress' },
499
+ todo: { description: 'Not started' },
500
+ blocked: { description: 'Blocked' },
501
+ na: { description: 'Not applicable' },
502
+ wip: { description: 'Work in progress (alias for doing)' },
503
+ paused: { description: 'Paused (alias for blocked)' },
504
+ waiting: { description: 'Waiting (alias for blocked)' },
505
+ },
349
506
  },
350
- }],
507
+ ],
351
508
  ]);
352
509
 
353
510
  // ============================================================
@@ -356,15 +513,18 @@ export const PIPE_METADATA = new Map<string, {
356
513
 
357
514
  /** All known directive keys, derived from COMPLETION_REGISTRY. Includes implicit keys. */
358
515
  export const METADATA_KEY_SET: ReadonlySet<string> = new Set([
359
- 'chart', 'title', // implicit directives recognized as metadata
360
- ...[...COMPLETION_REGISTRY.values()].flatMap(spec => Object.keys(spec.directives)),
516
+ 'chart',
517
+ 'title', // implicit directives recognized as metadata
518
+ ...[...COMPLETION_REGISTRY.values()].flatMap((spec) =>
519
+ Object.keys(spec.directives)
520
+ ),
361
521
  ]);
362
522
 
363
523
  // ============================================================
364
524
  // Sequence extractor
365
525
  // ============================================================
366
526
 
367
- const SEQ_ARROW_RE = /^(\S+)\s+(->|-[^>\s]*->|~>|~[^>\s]*~>)\s+(\S+)/;
527
+ const SEQ_ARROW_RE = /^(\S+)\s+(->|-.*->|~>|~.*~>)\s+(\S+)/;
368
528
  const SEQ_IS_A_RE = /^(\S+)\s+is\s+an?\s+/i;
369
529
  const SEQ_SECTION_RE = /^==/;
370
530
  const SEQ_STRUCTURAL_RE = /^(if|else|loop|parallel|end)\b/i;
@@ -480,7 +640,8 @@ export function extractTagDeclarations(docText: string): Map<string, string[]> {
480
640
  const trimmed = raw.trim();
481
641
 
482
642
  // Check for tag declaration — try explicit `alias` keyword first, then shorthand
483
- const tagMatch = trimmed.match(TAG_DECL_EXPLICIT_RE) ?? trimmed.match(TAG_DECL_SHORT_RE);
643
+ const tagMatch =
644
+ trimmed.match(TAG_DECL_EXPLICIT_RE) ?? trimmed.match(TAG_DECL_SHORT_RE);
484
645
  if (tagMatch) {
485
646
  // Save previous tag group
486
647
  if (currentAlias !== null) {
@@ -503,11 +664,16 @@ export function extractTagDeclarations(docText: string): Map<string, string[]> {
503
664
  }
504
665
 
505
666
  // Collect indented tag values
506
- if (currentAlias !== null && raw.length > 0 && (raw[0] === ' ' || raw[0] === '\t')) {
667
+ if (
668
+ currentAlias !== null &&
669
+ raw.length > 0 &&
670
+ (raw[0] === ' ' || raw[0] === '\t')
671
+ ) {
507
672
  if (trimmed && !trimmed.startsWith('//')) {
508
673
  // Strip color annotation: Frontend(blue) → Frontend
509
674
  const colorIdx = trimmed.indexOf('(');
510
- const value = colorIdx > 0 ? trimmed.substring(0, colorIdx).trim() : trimmed;
675
+ const value =
676
+ colorIdx > 0 ? trimmed.substring(0, colorIdx).trim() : trimmed;
511
677
  if (value) currentValues.push(value);
512
678
  }
513
679
  continue;
@@ -559,7 +725,10 @@ function extractSitemapSymbols(docText: string): DiagramSymbols {
559
725
  if (METADATA_KEY_SET.has(firstToken)) continue;
560
726
 
561
727
  // Track tag blocks
562
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
728
+ if (/^tag\s+/i.test(trimmed)) {
729
+ inTagBlock = true;
730
+ continue;
731
+ }
563
732
  const indent = line.search(/\S/);
564
733
  if (inTagBlock) {
565
734
  if (indent > 0) continue;
@@ -579,13 +748,20 @@ function extractSitemapSymbols(docText: string): DiagramSymbols {
579
748
  const bareArrow = trimmed.match(SITEMAP_BARE_ARROW_RE);
580
749
  const labeledArrow = !bareArrow ? trimmed.match(SITEMAP_ARROW_RE) : null;
581
750
  if (bareArrow || labeledArrow) {
582
- const target = (bareArrow?.[1] ?? labeledArrow?.[1] ?? '').split('|')[0].trim();
751
+ const target = (bareArrow?.[1] ?? labeledArrow?.[1] ?? '')
752
+ .split('|')[0]
753
+ .trim();
583
754
  if (target && !entities.includes(target)) entities.push(target);
584
755
  continue;
585
756
  }
586
757
 
587
758
  // Indented metadata under a node (key: value) — skip
588
- if (indent > 0 && lastNodeIndent >= 0 && indent > lastNodeIndent && SITEMAP_METADATA_RE.test(trimmed)) {
759
+ if (
760
+ indent > 0 &&
761
+ lastNodeIndent >= 0 &&
762
+ indent > lastNodeIndent &&
763
+ SITEMAP_METADATA_RE.test(trimmed)
764
+ ) {
589
765
  continue;
590
766
  }
591
767
 
@@ -605,8 +781,10 @@ function extractSitemapSymbols(docText: string): DiagramSymbols {
605
781
  // ============================================================
606
782
 
607
783
  const C4_ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
608
- const C4_IS_A_RE = /^(.+?)\s+is\s+an?\s+(person|system|container|component|external|database)\b/i;
609
- const C4_ARROW_RE = /^(\S+)\s+(?:->|-[^>\s]*->|~>|~[^>\s]*~>|<->|<-[^>\s]*->|<~>|<~[^>\s]*~>)\s+(\S+)/;
784
+ const C4_IS_A_RE =
785
+ /^(.+?)\s+is\s+an?\s+(person|system|container|component|external|database)\b/i;
786
+ const C4_ARROW_RE =
787
+ /^(\S+)\s+(?:->|-.*->|~>|~.*~>|<->|<-.*->|<~>|<~.*~>)\s+(\S+)/;
610
788
  const C4_SECTION_RE = /^(containers|components|deployment)\s*$/i;
611
789
 
612
790
  function extractC4Symbols(docText: string): DiagramSymbols {
@@ -627,7 +805,10 @@ function extractC4Symbols(docText: string): DiagramSymbols {
627
805
  const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
628
806
  if (METADATA_KEY_SET.has(firstToken)) continue;
629
807
 
630
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
808
+ if (/^tag\s+/i.test(trimmed)) {
809
+ inTagBlock = true;
810
+ continue;
811
+ }
631
812
  const indent = line.search(/\S/);
632
813
  if (inTagBlock) {
633
814
  if (indent > 0) continue;
@@ -664,7 +845,11 @@ function extractC4Symbols(docText: string): DiagramSymbols {
664
845
  }
665
846
  }
666
847
 
667
- return { kind: 'c4', entities, keywords: ['containers', 'components', 'deployment'] };
848
+ return {
849
+ kind: 'c4',
850
+ entities,
851
+ keywords: ['containers', 'components', 'deployment'],
852
+ };
668
853
  }
669
854
 
670
855
  // ============================================================
@@ -674,7 +859,7 @@ function extractC4Symbols(docText: string): DiagramSymbols {
674
859
  const GANTT_DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)\??\s+(.+)$/;
675
860
  const GANTT_DATE_RE = /^(\d{4}-\d{2}-\d{2}(?:\s\d{2}:\d{2})?)\s+(.+)$/;
676
861
  const GANTT_GROUP_RE = /^\[(.+?)\]/;
677
- const GANTT_STRUCTURAL_RE = /^(era|marker|holidays|workweek|parallel)\b/i;
862
+ const GANTT_STRUCTURAL_RE = /^(era|marker|holiday|workweek|parallel)\b/i;
678
863
 
679
864
  function extractGanttSymbols(docText: string): DiagramSymbols {
680
865
  const lines = docText.split('\n');
@@ -694,7 +879,10 @@ function extractGanttSymbols(docText: string): DiagramSymbols {
694
879
  const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
695
880
  if (METADATA_KEY_SET.has(firstToken)) continue;
696
881
 
697
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
882
+ if (/^tag\s+/i.test(trimmed)) {
883
+ inTagBlock = true;
884
+ continue;
885
+ }
698
886
  const indent = line.search(/\S/);
699
887
  if (inTagBlock) {
700
888
  if (indent > 0) continue;
@@ -719,7 +907,11 @@ function extractGanttSymbols(docText: string): DiagramSymbols {
719
907
  let taskName = durMatch[3].split('|')[0].trim();
720
908
  // Remove trailing dependency: "Task Name -> Other" → "Task Name"
721
909
  const arrowIdx = taskName.indexOf('->');
722
- if (arrowIdx > 0) taskName = taskName.substring(0, arrowIdx).replace(/-[^>]*$/, '').trim();
910
+ if (arrowIdx > 0)
911
+ taskName = taskName
912
+ .substring(0, arrowIdx)
913
+ .replace(/-[^>]*$/, '')
914
+ .trim();
723
915
  if (taskName && !entities.includes(taskName)) entities.push(taskName);
724
916
  continue;
725
917
  }
@@ -729,7 +921,11 @@ function extractGanttSymbols(docText: string): DiagramSymbols {
729
921
  if (dateMatch) {
730
922
  let taskName = dateMatch[2].split('|')[0].trim();
731
923
  const arrowIdx = taskName.indexOf('->');
732
- if (arrowIdx > 0) taskName = taskName.substring(0, arrowIdx).replace(/-[^>]*$/, '').trim();
924
+ if (arrowIdx > 0)
925
+ taskName = taskName
926
+ .substring(0, arrowIdx)
927
+ .replace(/-[^>]*$/, '')
928
+ .trim();
733
929
  if (taskName && !entities.includes(taskName)) entities.push(taskName);
734
930
  continue;
735
931
  }
@@ -762,7 +958,10 @@ function extractInitiativeStatusSymbols(docText: string): DiagramSymbols {
762
958
  const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
763
959
  if (METADATA_KEY_SET.has(firstToken)) continue;
764
960
 
765
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
961
+ if (/^tag\s+/i.test(trimmed)) {
962
+ inTagBlock = true;
963
+ continue;
964
+ }
766
965
  const indent = line.search(/\S/);
767
966
  if (inTagBlock) {
768
967
  if (indent > 0) continue;