@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.
- package/.claude/commands/dgmo-diagram-this.md +60 -0
- package/.claude/commands/dgmo-document-project.md +128 -0
- package/.claude/commands/dgmo.md +185 -50
- package/.cursorrules +32 -37
- package/.github/copilot-instructions.md +35 -44
- package/.windsurfrules +32 -37
- package/README.md +4 -4
- package/dist/cli.cjs +189 -194
- package/dist/editor.cjs +336 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +27 -0
- package/dist/editor.d.ts +27 -0
- package/dist/editor.js +305 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.cjs +3699 -1564
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +3699 -1564
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +822 -1060
- package/gallery/fixtures/arc.dgmo +18 -0
- package/gallery/fixtures/area.dgmo +19 -0
- package/gallery/fixtures/bar-stacked.dgmo +10 -0
- package/gallery/fixtures/bar.dgmo +10 -0
- package/gallery/fixtures/c4-full.dgmo +52 -0
- package/gallery/fixtures/c4.dgmo +17 -0
- package/gallery/fixtures/chord.dgmo +12 -0
- package/gallery/fixtures/class-basic.dgmo +14 -0
- package/gallery/fixtures/class-full.dgmo +43 -0
- package/gallery/fixtures/doughnut.dgmo +8 -0
- package/gallery/fixtures/flowchart-basic.dgmo +3 -0
- package/gallery/fixtures/flowchart-colors.dgmo +5 -0
- package/gallery/fixtures/flowchart-complex.dgmo +17 -0
- package/gallery/fixtures/flowchart-decision.dgmo +5 -0
- package/gallery/fixtures/flowchart-full.dgmo +13 -0
- package/gallery/fixtures/flowchart-groups.dgmo +10 -0
- package/gallery/fixtures/flowchart-loop.dgmo +7 -0
- package/gallery/fixtures/flowchart-nested.dgmo +7 -0
- package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
- package/gallery/fixtures/function.dgmo +8 -0
- package/gallery/fixtures/funnel.dgmo +7 -0
- package/gallery/fixtures/gantt-full.dgmo +49 -0
- package/gallery/fixtures/gantt.dgmo +42 -0
- package/gallery/fixtures/heatmap.dgmo +8 -0
- package/gallery/fixtures/infra-full.dgmo +78 -0
- package/gallery/fixtures/infra-overload.dgmo +25 -0
- package/gallery/fixtures/infra.dgmo +47 -0
- package/gallery/fixtures/initiative-status-full.dgmo +46 -0
- package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
- package/gallery/fixtures/initiative-status.dgmo +9 -0
- package/gallery/fixtures/line.dgmo +19 -0
- package/gallery/fixtures/multi-line.dgmo +11 -0
- package/gallery/fixtures/org-basic.dgmo +16 -0
- package/gallery/fixtures/org-full.dgmo +69 -0
- package/gallery/fixtures/org-teams.dgmo +25 -0
- package/gallery/fixtures/pie.dgmo +9 -0
- package/gallery/fixtures/polar-area.dgmo +8 -0
- package/gallery/fixtures/quadrant.dgmo +18 -0
- package/gallery/fixtures/radar.dgmo +8 -0
- package/gallery/fixtures/sankey.dgmo +31 -0
- package/gallery/fixtures/scatter.dgmo +21 -0
- package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
- package/gallery/fixtures/sequence-tags.dgmo +41 -0
- package/gallery/fixtures/sequence.dgmo +35 -0
- package/gallery/fixtures/sitemap-basic.dgmo +12 -0
- package/gallery/fixtures/sitemap-full.dgmo +156 -0
- package/gallery/fixtures/slope.dgmo +8 -0
- package/gallery/fixtures/spr-eras.dgmo +62 -0
- package/gallery/fixtures/state.dgmo +30 -0
- package/gallery/fixtures/timeline-intraday.dgmo +14 -0
- package/gallery/fixtures/timeline.dgmo +32 -0
- package/gallery/fixtures/venn.dgmo +10 -0
- package/gallery/fixtures/wordcloud.dgmo +24 -0
- package/package.json +51 -2
- package/src/c4/layout.ts +372 -90
- package/src/c4/parser.ts +113 -62
- package/src/chart.ts +149 -64
- package/src/class/parser.ts +84 -28
- package/src/class/renderer.ts +2 -2
- package/src/cli.ts +179 -77
- package/src/completion.ts +381 -182
- package/src/d3.ts +1026 -428
- package/src/dgmo-mermaid.ts +16 -13
- package/src/dgmo-router.ts +70 -24
- package/src/echarts.ts +682 -169
- package/src/editor/dgmo.grammar +69 -0
- package/src/editor/dgmo.grammar.d.ts +2 -0
- package/src/editor/dgmo.grammar.js +18 -0
- package/src/editor/dgmo.grammar.terms.d.ts +5 -0
- package/src/editor/dgmo.grammar.terms.js +35 -0
- package/src/editor/highlight.ts +36 -0
- package/src/editor/index.ts +28 -0
- package/src/editor/keywords.ts +220 -0
- package/src/editor/tokens.ts +30 -0
- package/src/er/parser.ts +55 -29
- package/src/er/renderer.ts +112 -53
- package/src/gantt/calculator.ts +91 -29
- package/src/gantt/parser.ts +291 -97
- package/src/gantt/renderer.ts +1120 -350
- package/src/graph/flowchart-parser.ts +48 -75
- package/src/graph/state-parser.ts +54 -27
- package/src/infra/parser.ts +161 -177
- package/src/infra/renderer.ts +723 -271
- package/src/infra/types.ts +0 -1
- package/src/initiative-status/parser.ts +144 -56
- package/src/kanban/parser.ts +27 -19
- package/src/org/layout.ts +111 -44
- package/src/org/parser.ts +71 -27
- package/src/org/resolver.ts +3 -3
- package/src/palettes/index.ts +3 -2
- package/src/render.ts +1 -2
- package/src/sequence/parser.ts +209 -100
- package/src/sitemap/parser.ts +73 -44
- package/src/utils/arrows.ts +2 -22
- package/src/utils/duration.ts +39 -21
- package/src/utils/legend-constants.ts +0 -2
- package/src/utils/parsing.ts +82 -72
- package/src/utils/tag-groups.ts +4 -41
- 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 —
|
|
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: [
|
|
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(
|
|
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
|
-
[
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
[
|
|
124
|
-
|
|
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
|
-
[
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
[
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
[
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
[
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
[
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
[
|
|
189
|
-
'
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
[
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
[
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
'
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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 }> =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
name
|
|
268
|
-
|
|
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
|
-
*
|
|
377
|
+
* C4_IS_A_RE).
|
|
279
378
|
*/
|
|
280
379
|
export const ENTITY_TYPES = new Map<string, string[]>([
|
|
281
|
-
[
|
|
282
|
-
|
|
283
|
-
|
|
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<
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
[
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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',
|
|
360
|
-
|
|
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+(
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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)) {
|
|
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] ?? '')
|
|
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 (
|
|
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 =
|
|
609
|
-
|
|
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)) {
|
|
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 {
|
|
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|
|
|
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)) {
|
|
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)
|
|
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)
|
|
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)) {
|
|
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;
|