@diagrammo/dgmo 0.8.3 → 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 (112) 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 +153 -153
  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 +3336 -1055
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.js +3336 -1055
  18. package/dist/index.js.map +1 -1
  19. package/docs/language-reference.md +30 -29
  20. package/gallery/fixtures/arc.dgmo +18 -0
  21. package/gallery/fixtures/area.dgmo +19 -0
  22. package/gallery/fixtures/bar-stacked.dgmo +10 -0
  23. package/gallery/fixtures/bar.dgmo +10 -0
  24. package/gallery/fixtures/c4-full.dgmo +52 -0
  25. package/gallery/fixtures/c4.dgmo +17 -0
  26. package/gallery/fixtures/chord.dgmo +12 -0
  27. package/gallery/fixtures/class-basic.dgmo +14 -0
  28. package/gallery/fixtures/class-full.dgmo +43 -0
  29. package/gallery/fixtures/doughnut.dgmo +8 -0
  30. package/gallery/fixtures/flowchart-basic.dgmo +3 -0
  31. package/gallery/fixtures/flowchart-colors.dgmo +5 -0
  32. package/gallery/fixtures/flowchart-complex.dgmo +17 -0
  33. package/gallery/fixtures/flowchart-decision.dgmo +5 -0
  34. package/gallery/fixtures/flowchart-full.dgmo +13 -0
  35. package/gallery/fixtures/flowchart-groups.dgmo +10 -0
  36. package/gallery/fixtures/flowchart-loop.dgmo +7 -0
  37. package/gallery/fixtures/flowchart-nested.dgmo +7 -0
  38. package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
  39. package/gallery/fixtures/function.dgmo +8 -0
  40. package/gallery/fixtures/funnel.dgmo +7 -0
  41. package/gallery/fixtures/gantt-full.dgmo +49 -0
  42. package/gallery/fixtures/gantt.dgmo +42 -0
  43. package/gallery/fixtures/heatmap.dgmo +8 -0
  44. package/gallery/fixtures/infra-full.dgmo +78 -0
  45. package/gallery/fixtures/infra-overload.dgmo +25 -0
  46. package/gallery/fixtures/infra.dgmo +47 -0
  47. package/gallery/fixtures/initiative-status-full.dgmo +46 -0
  48. package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
  49. package/gallery/fixtures/initiative-status.dgmo +9 -0
  50. package/gallery/fixtures/line.dgmo +19 -0
  51. package/gallery/fixtures/multi-line.dgmo +11 -0
  52. package/gallery/fixtures/org-basic.dgmo +16 -0
  53. package/gallery/fixtures/org-full.dgmo +69 -0
  54. package/gallery/fixtures/org-teams.dgmo +25 -0
  55. package/gallery/fixtures/pie.dgmo +9 -0
  56. package/gallery/fixtures/polar-area.dgmo +8 -0
  57. package/gallery/fixtures/quadrant.dgmo +18 -0
  58. package/gallery/fixtures/radar.dgmo +8 -0
  59. package/gallery/fixtures/sankey.dgmo +31 -0
  60. package/gallery/fixtures/scatter.dgmo +21 -0
  61. package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
  62. package/gallery/fixtures/sequence-tags.dgmo +41 -0
  63. package/gallery/fixtures/sequence.dgmo +35 -0
  64. package/gallery/fixtures/sitemap-basic.dgmo +12 -0
  65. package/gallery/fixtures/sitemap-full.dgmo +156 -0
  66. package/gallery/fixtures/slope.dgmo +8 -0
  67. package/gallery/fixtures/spr-eras.dgmo +62 -0
  68. package/gallery/fixtures/state.dgmo +30 -0
  69. package/gallery/fixtures/timeline-intraday.dgmo +14 -0
  70. package/gallery/fixtures/timeline.dgmo +32 -0
  71. package/gallery/fixtures/venn.dgmo +10 -0
  72. package/gallery/fixtures/wordcloud.dgmo +24 -0
  73. package/package.json +51 -2
  74. package/src/c4/layout.ts +372 -90
  75. package/src/c4/parser.ts +100 -55
  76. package/src/chart.ts +91 -28
  77. package/src/class/parser.ts +41 -12
  78. package/src/cli.ts +168 -61
  79. package/src/completion.ts +378 -183
  80. package/src/d3.ts +887 -288
  81. package/src/dgmo-mermaid.ts +16 -13
  82. package/src/dgmo-router.ts +69 -23
  83. package/src/echarts.ts +646 -153
  84. package/src/editor/dgmo.grammar +69 -0
  85. package/src/editor/dgmo.grammar.d.ts +2 -0
  86. package/src/editor/dgmo.grammar.js +18 -0
  87. package/src/editor/dgmo.grammar.terms.d.ts +5 -0
  88. package/src/editor/dgmo.grammar.terms.js +35 -0
  89. package/src/editor/highlight.ts +36 -0
  90. package/src/editor/index.ts +28 -0
  91. package/src/editor/keywords.ts +220 -0
  92. package/src/editor/tokens.ts +30 -0
  93. package/src/er/parser.ts +48 -14
  94. package/src/er/renderer.ts +112 -53
  95. package/src/gantt/calculator.ts +91 -29
  96. package/src/gantt/parser.ts +197 -71
  97. package/src/gantt/renderer.ts +1120 -350
  98. package/src/graph/flowchart-parser.ts +46 -25
  99. package/src/graph/state-parser.ts +47 -17
  100. package/src/infra/parser.ts +157 -53
  101. package/src/infra/renderer.ts +723 -271
  102. package/src/initiative-status/parser.ts +138 -44
  103. package/src/kanban/parser.ts +25 -14
  104. package/src/org/layout.ts +111 -44
  105. package/src/org/parser.ts +69 -22
  106. package/src/palettes/index.ts +3 -2
  107. package/src/sequence/parser.ts +193 -61
  108. package/src/sitemap/parser.ts +65 -29
  109. package/src/utils/arrows.ts +2 -22
  110. package/src/utils/duration.ts +39 -21
  111. package/src/utils/legend-constants.ts +0 -2
  112. package/src/utils/parsing.ts +75 -31
package/src/completion.ts CHANGED
@@ -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,139 +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-horizontal': { description: 'Switch to horizontal bars' },
102
- color: { description: 'Bar color override' },
103
- })],
104
- ['line', withGlobals({
105
- series: { description: 'Series name(s)' },
106
- xlabel: { description: 'X-axis label' },
107
- ylabel: { description: 'Y-axis label' },
108
- })],
109
- ['pie', withGlobals({
110
- 'no-label-name': { description: 'Hide name from segment labels' },
111
- 'no-label-value': { description: 'Hide value from segment labels' },
112
- 'no-label-percent': { description: 'Hide percent from segment labels' },
113
- })],
114
- ['doughnut', withGlobals({
115
- 'no-label-name': { description: 'Hide name from segment labels' },
116
- 'no-label-value': { description: 'Hide value from segment labels' },
117
- 'no-label-percent': { description: 'Hide percent from segment labels' },
118
- })],
119
- ['area', withGlobals({
120
- series: { description: 'Series name(s)' },
121
- xlabel: { description: 'X-axis label' },
122
- ylabel: { description: 'Y-axis label' },
123
- })],
124
- ['polar-area', withGlobals({
125
- 'no-label-name': { description: 'Hide name from segment labels' },
126
- 'no-label-value': { description: 'Hide value from segment labels' },
127
- 'no-label-percent': { description: 'Hide percent from segment labels' },
128
- })],
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
+ ],
129
160
  ['radar', withGlobals()],
130
- ['bar-stacked', withGlobals({
131
- series: { description: 'Series name(s) (required)' },
132
- xlabel: { description: 'X-axis label' },
133
- ylabel: { description: 'Y-axis label' },
134
- 'orientation-horizontal': { description: 'Switch to horizontal bars' },
135
- })],
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
+ ],
136
170
 
137
171
  // ── Extended charts ──────────────────────────────────────
138
- ['scatter', withGlobals({
139
- 'no-labels': { description: 'Hide point labels' },
140
- xlabel: { description: 'X-axis label' },
141
- ylabel: { description: 'Y-axis label' },
142
- sizelabel: { description: 'Size axis label' },
143
- })],
144
- ['heatmap', withGlobals({
145
- columns: { description: 'Column labels (required)' },
146
- })],
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
+ ],
147
187
  ['sankey', withGlobals()],
148
188
  ['chord', withGlobals()],
149
189
  ['funnel', withGlobals()],
150
- ['function', withGlobals({
151
- x: { description: 'X-axis range (start to end)' },
152
- xlabel: { description: 'X-axis label' },
153
- ylabel: { description: 'Y-axis label' },
154
- shade: { description: 'Fill area below curves with translucent color' },
155
- })],
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
+ ],
156
199
 
157
200
  // ── Visualizations ───────────────────────────────────────
158
- ['slope', withGlobals({
159
- 'orientation-vertical': { description: 'Switch to vertical layout' },
160
- })],
161
- ['wordcloud', withGlobals({
162
- rotate: { description: 'Word rotation', values: ['none', 'mixed', 'angled'] },
163
- max: { description: 'Maximum word count' },
164
- size: { description: 'Font size range (min, max)' },
165
- })],
166
- ['arc', withGlobals({
167
- order: { description: 'Node ordering', values: ['appearance', 'name', 'group', 'degree'] },
168
- })],
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
+ ],
169
222
  ['timeline', withGlobals()],
170
- ['venn', withGlobals({
171
- values: { description: 'Show values', values: ['on', 'off'] },
172
- })],
173
- ['quadrant', withGlobals({
174
- 'x-axis': { description: 'X-axis labels (low, high)' },
175
- 'y-axis': { description: 'Y-axis labels (low, high)' },
176
- })],
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
+ ],
177
231
 
178
232
  // ── Diagrams ─────────────────────────────────────────────
179
- ['sequence', withGlobals({
180
- activations: { description: 'Show activation bars', values: ['on', 'off'] },
181
- 'collapse-notes': { description: 'Collapse note blocks', values: ['yes', 'no'] },
182
- 'active-tag': { description: 'Active tag group name' },
183
- })],
184
- ['flowchart', withGlobals({
185
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
186
- })],
187
- ['class', withGlobals({
188
- 'no-auto-color': { description: 'Disable automatic modifier-based coloring' },
189
- })],
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
+ ],
190
261
  ['er', withGlobals()],
191
- ['org', withGlobals({
192
- 'sub-node-label': { description: 'Label for sub-nodes' },
193
- 'show-sub-node-count': { description: 'Show sub-node counts' },
194
- })],
195
- ['kanban', withGlobals({
196
- 'no-auto-color': { description: 'Disable automatic card coloring' },
197
- })],
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
+ ],
198
275
  ['c4', withGlobals()],
199
276
  ['initiative-status', withGlobals()],
200
- ['state', withGlobals({
201
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
202
- color: { description: 'Color mode', values: ['off'] },
203
- })],
204
- ['sitemap', withGlobals({
205
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
206
- })],
207
- ['infra', withGlobals({
208
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
209
- 'default-latency-ms': { description: 'Default latency for all nodes' },
210
- 'default-uptime': { description: 'Default uptime for all nodes' },
211
- 'default-rps': { description: 'Default RPS capacity for all nodes' },
212
- 'slo-availability': { description: 'SLO availability target (0-1)' },
213
- 'slo-p90-latency-ms': { description: 'SLO p90 latency target in ms' },
214
- 'slo-warning-margin': { description: 'SLO warning margin percentage' },
215
- })],
216
- ['gantt', withGlobals({
217
- start: { description: 'Project start date (YYYY-MM-DD)' },
218
- 'today-marker': { description: 'Today marker (bare = on, or YYYY-MM-DD date)' },
219
- sort: { description: 'Sort order', values: ['time', 'group', 'tag'] },
220
- 'critical-path': { description: 'Show critical path' },
221
- dependencies: { description: 'Show dependencies' },
222
- })],
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
+ ],
223
316
  ]);
224
317
 
225
318
  // ============================================================
@@ -266,12 +359,13 @@ const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
266
359
  };
267
360
 
268
361
  /** All chart types with descriptions, for chart type autocomplete. Excludes `multi-line` alias. */
269
- export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> = [...ALL_CHART_TYPES]
270
- .filter(t => t !== 'multi-line')
271
- .map(name => ({
272
- name,
273
- description: CHART_TYPE_DESCRIPTIONS[name] ?? name,
274
- }));
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
+ }));
275
369
 
276
370
  // ============================================================
277
371
  // Entity types for `is a` declarations
@@ -283,8 +377,24 @@ export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> =
283
377
  * C4_IS_A_RE).
284
378
  */
285
379
  export const ENTITY_TYPES = new Map<string, string[]>([
286
- ['sequence', ['service', 'database', 'actor', 'queue', 'cache', 'gateway', 'external', 'networking', 'frontend']],
287
- ['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
+ ],
288
398
  ]);
289
399
 
290
400
  // ============================================================
@@ -305,53 +415,96 @@ export interface PipeKeySpec {
305
415
  * diagrams separates display names from identifiers and tag metadata.
306
416
  * Adding sequence would trigger false pipe-metadata completions on every `|`.
307
417
  */
308
- export const PIPE_METADATA = new Map<string, {
309
- node: Record<string, PipeKeySpec>;
310
- edge: Record<string, PipeKeySpec>;
311
- }>([
312
- ['infra', {
313
- node: {
314
- description: { description: 'Node description text' },
315
- instances: { description: 'Instance count or auto-scaling range (N-M)' },
316
- 'latency-ms': { description: 'Per-request latency in milliseconds' },
317
- 'max-rps': { description: 'Max requests per second per instance' },
318
- 'cache-hit': { description: 'Cache hit percentage (0-100)' },
319
- 'firewall-block': { description: 'Traffic blocked percentage' },
320
- 'ratelimit-rps': { description: 'Max RPS allowed through' },
321
- 'cb-error-threshold': { description: 'Circuit breaker error threshold %' },
322
- 'cb-latency-threshold-ms': { description: 'Circuit breaker latency threshold' },
323
- uptime: { description: 'Component availability (0-1)' },
324
- concurrency: { description: 'Concurrent request limit' },
325
- 'duration-ms': { description: 'Processing duration' },
326
- 'cold-start-ms': { description: 'Function cold-start time' },
327
- buffer: { description: 'Queue/buffer capacity' },
328
- 'drain-rate': { description: 'Queue drain rate' },
329
- 'retention-hours': { description: 'Data retention period' },
330
- partitions: { description: 'Queue/stream partition count' },
331
- 'slo-availability': { description: 'Node availability target (0-1)' },
332
- 'slo-p90-latency-ms': { description: 'Node p90 latency target' },
333
- '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
+ },
334
460
  },
335
- edge: {
336
- split: { description: 'Traffic split percentage (e.g., 60%)' },
337
- 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: {},
338
471
  },
339
- }],
340
- ['c4', {
341
- node: {
342
- description: { description: 'Element description' },
343
- tech: { description: 'Technology stack' },
344
- 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
+ },
345
481
  },
346
- edge: {},
347
- }],
348
- ['gantt', {
349
- node: {},
350
- edge: {
351
- // Gantt "edge" = dependency arrow (TaskA -> TaskB | offset 2bd)
352
- 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
+ },
353
506
  },
354
- }],
507
+ ],
355
508
  ]);
356
509
 
357
510
  // ============================================================
@@ -360,15 +513,18 @@ export const PIPE_METADATA = new Map<string, {
360
513
 
361
514
  /** All known directive keys, derived from COMPLETION_REGISTRY. Includes implicit keys. */
362
515
  export const METADATA_KEY_SET: ReadonlySet<string> = new Set([
363
- 'chart', 'title', // implicit directives recognized as metadata
364
- ...[...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
+ ),
365
521
  ]);
366
522
 
367
523
  // ============================================================
368
524
  // Sequence extractor
369
525
  // ============================================================
370
526
 
371
- const SEQ_ARROW_RE = /^(\S+)\s+(->|-[^>\s]*->|~>|~[^>\s]*~>)\s+(\S+)/;
527
+ const SEQ_ARROW_RE = /^(\S+)\s+(->|-.*->|~>|~.*~>)\s+(\S+)/;
372
528
  const SEQ_IS_A_RE = /^(\S+)\s+is\s+an?\s+/i;
373
529
  const SEQ_SECTION_RE = /^==/;
374
530
  const SEQ_STRUCTURAL_RE = /^(if|else|loop|parallel|end)\b/i;
@@ -484,7 +640,8 @@ export function extractTagDeclarations(docText: string): Map<string, string[]> {
484
640
  const trimmed = raw.trim();
485
641
 
486
642
  // Check for tag declaration — try explicit `alias` keyword first, then shorthand
487
- 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);
488
645
  if (tagMatch) {
489
646
  // Save previous tag group
490
647
  if (currentAlias !== null) {
@@ -507,11 +664,16 @@ export function extractTagDeclarations(docText: string): Map<string, string[]> {
507
664
  }
508
665
 
509
666
  // Collect indented tag values
510
- 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
+ ) {
511
672
  if (trimmed && !trimmed.startsWith('//')) {
512
673
  // Strip color annotation: Frontend(blue) → Frontend
513
674
  const colorIdx = trimmed.indexOf('(');
514
- const value = colorIdx > 0 ? trimmed.substring(0, colorIdx).trim() : trimmed;
675
+ const value =
676
+ colorIdx > 0 ? trimmed.substring(0, colorIdx).trim() : trimmed;
515
677
  if (value) currentValues.push(value);
516
678
  }
517
679
  continue;
@@ -563,7 +725,10 @@ function extractSitemapSymbols(docText: string): DiagramSymbols {
563
725
  if (METADATA_KEY_SET.has(firstToken)) continue;
564
726
 
565
727
  // Track tag blocks
566
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
728
+ if (/^tag\s+/i.test(trimmed)) {
729
+ inTagBlock = true;
730
+ continue;
731
+ }
567
732
  const indent = line.search(/\S/);
568
733
  if (inTagBlock) {
569
734
  if (indent > 0) continue;
@@ -583,13 +748,20 @@ function extractSitemapSymbols(docText: string): DiagramSymbols {
583
748
  const bareArrow = trimmed.match(SITEMAP_BARE_ARROW_RE);
584
749
  const labeledArrow = !bareArrow ? trimmed.match(SITEMAP_ARROW_RE) : null;
585
750
  if (bareArrow || labeledArrow) {
586
- const target = (bareArrow?.[1] ?? labeledArrow?.[1] ?? '').split('|')[0].trim();
751
+ const target = (bareArrow?.[1] ?? labeledArrow?.[1] ?? '')
752
+ .split('|')[0]
753
+ .trim();
587
754
  if (target && !entities.includes(target)) entities.push(target);
588
755
  continue;
589
756
  }
590
757
 
591
758
  // Indented metadata under a node (key: value) — skip
592
- 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
+ ) {
593
765
  continue;
594
766
  }
595
767
 
@@ -609,8 +781,10 @@ function extractSitemapSymbols(docText: string): DiagramSymbols {
609
781
  // ============================================================
610
782
 
611
783
  const C4_ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
612
- const C4_IS_A_RE = /^(.+?)\s+is\s+an?\s+(person|system|container|component|external|database)\b/i;
613
- 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+)/;
614
788
  const C4_SECTION_RE = /^(containers|components|deployment)\s*$/i;
615
789
 
616
790
  function extractC4Symbols(docText: string): DiagramSymbols {
@@ -631,7 +805,10 @@ function extractC4Symbols(docText: string): DiagramSymbols {
631
805
  const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
632
806
  if (METADATA_KEY_SET.has(firstToken)) continue;
633
807
 
634
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
808
+ if (/^tag\s+/i.test(trimmed)) {
809
+ inTagBlock = true;
810
+ continue;
811
+ }
635
812
  const indent = line.search(/\S/);
636
813
  if (inTagBlock) {
637
814
  if (indent > 0) continue;
@@ -668,7 +845,11 @@ function extractC4Symbols(docText: string): DiagramSymbols {
668
845
  }
669
846
  }
670
847
 
671
- return { kind: 'c4', entities, keywords: ['containers', 'components', 'deployment'] };
848
+ return {
849
+ kind: 'c4',
850
+ entities,
851
+ keywords: ['containers', 'components', 'deployment'],
852
+ };
672
853
  }
673
854
 
674
855
  // ============================================================
@@ -678,7 +859,7 @@ function extractC4Symbols(docText: string): DiagramSymbols {
678
859
  const GANTT_DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)\??\s+(.+)$/;
679
860
  const GANTT_DATE_RE = /^(\d{4}-\d{2}-\d{2}(?:\s\d{2}:\d{2})?)\s+(.+)$/;
680
861
  const GANTT_GROUP_RE = /^\[(.+?)\]/;
681
- const GANTT_STRUCTURAL_RE = /^(era|marker|holidays|workweek|parallel)\b/i;
862
+ const GANTT_STRUCTURAL_RE = /^(era|marker|holiday|workweek|parallel)\b/i;
682
863
 
683
864
  function extractGanttSymbols(docText: string): DiagramSymbols {
684
865
  const lines = docText.split('\n');
@@ -698,7 +879,10 @@ function extractGanttSymbols(docText: string): DiagramSymbols {
698
879
  const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
699
880
  if (METADATA_KEY_SET.has(firstToken)) continue;
700
881
 
701
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
882
+ if (/^tag\s+/i.test(trimmed)) {
883
+ inTagBlock = true;
884
+ continue;
885
+ }
702
886
  const indent = line.search(/\S/);
703
887
  if (inTagBlock) {
704
888
  if (indent > 0) continue;
@@ -723,7 +907,11 @@ function extractGanttSymbols(docText: string): DiagramSymbols {
723
907
  let taskName = durMatch[3].split('|')[0].trim();
724
908
  // Remove trailing dependency: "Task Name -> Other" → "Task Name"
725
909
  const arrowIdx = taskName.indexOf('->');
726
- 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();
727
915
  if (taskName && !entities.includes(taskName)) entities.push(taskName);
728
916
  continue;
729
917
  }
@@ -733,7 +921,11 @@ function extractGanttSymbols(docText: string): DiagramSymbols {
733
921
  if (dateMatch) {
734
922
  let taskName = dateMatch[2].split('|')[0].trim();
735
923
  const arrowIdx = taskName.indexOf('->');
736
- 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();
737
929
  if (taskName && !entities.includes(taskName)) entities.push(taskName);
738
930
  continue;
739
931
  }
@@ -766,7 +958,10 @@ function extractInitiativeStatusSymbols(docText: string): DiagramSymbols {
766
958
  const firstToken = trimmed.split(/\s+/)[0].toLowerCase();
767
959
  if (METADATA_KEY_SET.has(firstToken)) continue;
768
960
 
769
- if (/^tag\s+/i.test(trimmed)) { inTagBlock = true; continue; }
961
+ if (/^tag\s+/i.test(trimmed)) {
962
+ inTagBlock = true;
963
+ continue;
964
+ }
770
965
  const indent = line.search(/\S/);
771
966
  if (inTagBlock) {
772
967
  if (indent > 0) continue;