@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.
- 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 +153 -153
- 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 +3336 -1055
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3336 -1055
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +30 -29
- 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 +100 -55
- package/src/chart.ts +91 -28
- package/src/class/parser.ts +41 -12
- package/src/cli.ts +168 -61
- package/src/completion.ts +378 -183
- package/src/d3.ts +887 -288
- package/src/dgmo-mermaid.ts +16 -13
- package/src/dgmo-router.ts +69 -23
- package/src/echarts.ts +646 -153
- 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 +48 -14
- package/src/er/renderer.ts +112 -53
- package/src/gantt/calculator.ts +91 -29
- package/src/gantt/parser.ts +197 -71
- package/src/gantt/renderer.ts +1120 -350
- package/src/graph/flowchart-parser.ts +46 -25
- package/src/graph/state-parser.ts +47 -17
- package/src/infra/parser.ts +157 -53
- package/src/infra/renderer.ts +723 -271
- package/src/initiative-status/parser.ts +138 -44
- package/src/kanban/parser.ts +25 -14
- package/src/org/layout.ts +111 -44
- package/src/org/parser.ts +69 -22
- package/src/palettes/index.ts +3 -2
- package/src/sequence/parser.ts +193 -61
- package/src/sitemap/parser.ts +65 -29
- 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 +75 -31
|
@@ -13,9 +13,17 @@ import type {
|
|
|
13
13
|
} from './types';
|
|
14
14
|
import { VALID_STATUSES, STATUS_ALIASES } from './types';
|
|
15
15
|
import { inferParticipantType } from '../sequence/participant-inference';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
matchTagBlockHeading,
|
|
18
|
+
injectDefaultTagMetadata,
|
|
19
|
+
validateTagValues,
|
|
20
|
+
} from '../utils/tag-groups';
|
|
17
21
|
import type { TagGroup } from '../utils/tag-groups';
|
|
18
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
extractColor,
|
|
24
|
+
parseFirstLine,
|
|
25
|
+
OPTION_NOCOLON_RE,
|
|
26
|
+
} from '../utils/parsing';
|
|
19
27
|
|
|
20
28
|
// ============================================================
|
|
21
29
|
// Heuristic — does this content look like an initiative-status diagram?
|
|
@@ -38,10 +46,14 @@ export function looksLikeInitiativeStatus(content: string): boolean {
|
|
|
38
46
|
// Skip new-style first line (bare chart type name)
|
|
39
47
|
if (parseFirstLine(trimmed)) continue;
|
|
40
48
|
if (trimmed.includes('->')) hasArrow = true;
|
|
41
|
-
if (
|
|
49
|
+
if (
|
|
50
|
+
/\|\s*(done|doing|wip|blocked|paused|waiting|todo|na)\s*$/i.test(trimmed)
|
|
51
|
+
)
|
|
52
|
+
hasStatus = true;
|
|
42
53
|
// Indented arrow is a strong signal — only initiative-status uses this
|
|
43
54
|
const isIndented = line.length > 0 && line !== trimmed && /^\s/.test(line);
|
|
44
|
-
if (isIndented && (trimmed.startsWith('->') || /^-[^>].*->/.test(trimmed)))
|
|
55
|
+
if (isIndented && (trimmed.startsWith('->') || /^-[^>].*->/.test(trimmed)))
|
|
56
|
+
hasIndentedArrow = true;
|
|
45
57
|
if (hasArrow && hasStatus) return true;
|
|
46
58
|
}
|
|
47
59
|
return hasIndentedArrow;
|
|
@@ -67,7 +79,11 @@ export function parseNodeMetadata(
|
|
|
67
79
|
aliasMap: Map<string, string>,
|
|
68
80
|
lineNum?: number,
|
|
69
81
|
diagnostics?: DgmoError[]
|
|
70
|
-
): {
|
|
82
|
+
): {
|
|
83
|
+
status: InitiativeStatus;
|
|
84
|
+
metadata: Record<string, string>;
|
|
85
|
+
hadStatusWord: boolean;
|
|
86
|
+
} {
|
|
71
87
|
const metadata: Record<string, string> = {};
|
|
72
88
|
let status: InitiativeStatus = null;
|
|
73
89
|
let hadStatusWord = false;
|
|
@@ -125,7 +141,12 @@ export function parseNodeMetadata(
|
|
|
125
141
|
// Parser
|
|
126
142
|
// ============================================================
|
|
127
143
|
|
|
128
|
-
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
145
|
+
function _parseStatus(
|
|
146
|
+
raw: string,
|
|
147
|
+
line: number,
|
|
148
|
+
diagnostics: DgmoError[]
|
|
149
|
+
): InitiativeStatus {
|
|
129
150
|
const trimmed = raw.trim().toLowerCase();
|
|
130
151
|
if (!trimmed) return 'na';
|
|
131
152
|
const canonical = STATUS_ALIASES[trimmed] ?? trimmed;
|
|
@@ -191,7 +212,10 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
191
212
|
const firstLineResult = parseFirstLine(trimmed);
|
|
192
213
|
if (firstLineResult && !contentStarted) {
|
|
193
214
|
if (firstLineResult.chartType !== 'initiative-status') {
|
|
194
|
-
const diag = makeDgmoError(
|
|
215
|
+
const diag = makeDgmoError(
|
|
216
|
+
lineNum,
|
|
217
|
+
`Expected chart type "initiative-status", got "${firstLineResult.chartType}"`
|
|
218
|
+
);
|
|
195
219
|
result.diagnostics.push(diag);
|
|
196
220
|
result.error = formatDgmoError(diag);
|
|
197
221
|
return result;
|
|
@@ -212,7 +236,10 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
212
236
|
const colonIdx = pair.indexOf(':');
|
|
213
237
|
if (colonIdx > 0) {
|
|
214
238
|
const groupKey = pair.substring(0, colonIdx).trim().toLowerCase();
|
|
215
|
-
const value = pair
|
|
239
|
+
const value = pair
|
|
240
|
+
.substring(colonIdx + 1)
|
|
241
|
+
.trim()
|
|
242
|
+
.toLowerCase();
|
|
216
243
|
if (groupKey && value) {
|
|
217
244
|
if (!result.initialHiddenTagValues.has(groupKey)) {
|
|
218
245
|
result.initialHiddenTagValues.set(groupKey, new Set());
|
|
@@ -243,7 +270,11 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
243
270
|
if (tagBlockMatch) {
|
|
244
271
|
if (contentStarted) {
|
|
245
272
|
result.diagnostics.push(
|
|
246
|
-
makeDgmoError(
|
|
273
|
+
makeDgmoError(
|
|
274
|
+
lineNum,
|
|
275
|
+
'Tag groups must appear before diagram content',
|
|
276
|
+
'error'
|
|
277
|
+
)
|
|
247
278
|
);
|
|
248
279
|
continue;
|
|
249
280
|
}
|
|
@@ -254,7 +285,10 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
254
285
|
lineNumber: lineNum,
|
|
255
286
|
};
|
|
256
287
|
if (tagBlockMatch.alias) {
|
|
257
|
-
aliasMap.set(
|
|
288
|
+
aliasMap.set(
|
|
289
|
+
tagBlockMatch.alias.toLowerCase(),
|
|
290
|
+
tagBlockMatch.name.toLowerCase()
|
|
291
|
+
);
|
|
258
292
|
}
|
|
259
293
|
// Handle inline values from single-line tag declaration
|
|
260
294
|
if (tagBlockMatch.inlineValues) {
|
|
@@ -292,7 +326,7 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
292
326
|
continue;
|
|
293
327
|
}
|
|
294
328
|
// Non-indented line after tag group — close and fall through
|
|
295
|
-
currentTagGroup = null;
|
|
329
|
+
currentTagGroup = null; // eslint-disable-line no-useless-assignment
|
|
296
330
|
}
|
|
297
331
|
|
|
298
332
|
// Group header: [Group Name] or [Group Name] | metadata
|
|
@@ -343,13 +377,22 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
343
377
|
if (trimmed.startsWith('->') || /^-[^>].*->/.test(trimmed)) {
|
|
344
378
|
if (!lastNodeLabel) {
|
|
345
379
|
result.diagnostics.push(
|
|
346
|
-
makeDgmoError(
|
|
380
|
+
makeDgmoError(
|
|
381
|
+
lineNum,
|
|
382
|
+
'Indented edge has no preceding node to use as source',
|
|
383
|
+
'warning'
|
|
384
|
+
)
|
|
347
385
|
);
|
|
348
386
|
continue;
|
|
349
387
|
}
|
|
350
388
|
edgeText = `${lastNodeLabel} ${trimmed}`;
|
|
351
389
|
}
|
|
352
|
-
const edge = parseEdgeLine(
|
|
390
|
+
const edge = parseEdgeLine(
|
|
391
|
+
edgeText,
|
|
392
|
+
lineNum,
|
|
393
|
+
aliasMap,
|
|
394
|
+
result.diagnostics
|
|
395
|
+
);
|
|
353
396
|
if (edge) result.edges.push(edge);
|
|
354
397
|
continue;
|
|
355
398
|
}
|
|
@@ -358,28 +401,32 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
358
401
|
contentStarted = true;
|
|
359
402
|
currentTagGroup = null;
|
|
360
403
|
const node = parseNodeLine(trimmed, lineNum, aliasMap, result.diagnostics);
|
|
361
|
-
if (node) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
404
|
+
if (!node) {
|
|
405
|
+
result.diagnostics.push(
|
|
406
|
+
makeDgmoError(lineNum, `Unexpected line: '${trimmed}'.`, 'warning')
|
|
407
|
+
);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
lastNodeLabel = node.label;
|
|
411
|
+
if (nodeLabels.has(node.label)) {
|
|
412
|
+
result.diagnostics.push(
|
|
413
|
+
makeDgmoError(lineNum, `Duplicate node "${node.label}"`, 'warning')
|
|
414
|
+
);
|
|
415
|
+
} else {
|
|
416
|
+
nodeLabels.add(node.label);
|
|
417
|
+
}
|
|
418
|
+
// Cascade group metadata into node (group provides defaults, node overrides)
|
|
419
|
+
if (currentGroup && isIndented && currentGroup.metadata) {
|
|
420
|
+
for (const [key, val] of Object.entries(currentGroup.metadata)) {
|
|
421
|
+
if (!(key in node.metadata)) {
|
|
422
|
+
node.metadata[key] = val;
|
|
376
423
|
}
|
|
377
424
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
425
|
+
}
|
|
426
|
+
result.nodes.push(node);
|
|
427
|
+
// Add to current group if indented
|
|
428
|
+
if (currentGroup && isIndented) {
|
|
429
|
+
currentGroup.nodeLabels.push(node.label);
|
|
383
430
|
}
|
|
384
431
|
}
|
|
385
432
|
|
|
@@ -392,20 +439,40 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
|
|
|
392
439
|
for (const edge of result.edges) {
|
|
393
440
|
if (!nodeLabels.has(edge.source)) {
|
|
394
441
|
result.diagnostics.push(
|
|
395
|
-
makeDgmoError(
|
|
442
|
+
makeDgmoError(
|
|
443
|
+
edge.lineNumber,
|
|
444
|
+
`Edge source "${edge.source}" is not a declared node`,
|
|
445
|
+
'warning'
|
|
446
|
+
)
|
|
396
447
|
);
|
|
397
448
|
// Auto-create an implicit node
|
|
398
449
|
if (!result.nodes.some((n) => n.label === edge.source)) {
|
|
399
|
-
result.nodes.push({
|
|
450
|
+
result.nodes.push({
|
|
451
|
+
label: edge.source,
|
|
452
|
+
status: 'na',
|
|
453
|
+
shape: inferParticipantType(edge.source),
|
|
454
|
+
lineNumber: edge.lineNumber,
|
|
455
|
+
metadata: {},
|
|
456
|
+
});
|
|
400
457
|
nodeLabels.add(edge.source);
|
|
401
458
|
}
|
|
402
459
|
}
|
|
403
460
|
if (!nodeLabels.has(edge.target)) {
|
|
404
461
|
result.diagnostics.push(
|
|
405
|
-
makeDgmoError(
|
|
462
|
+
makeDgmoError(
|
|
463
|
+
edge.lineNumber,
|
|
464
|
+
`Edge target "${edge.target}" is not a declared node`,
|
|
465
|
+
'warning'
|
|
466
|
+
)
|
|
406
467
|
);
|
|
407
468
|
if (!result.nodes.some((n) => n.label === edge.target)) {
|
|
408
|
-
result.nodes.push({
|
|
469
|
+
result.nodes.push({
|
|
470
|
+
label: edge.target,
|
|
471
|
+
status: 'na',
|
|
472
|
+
shape: inferParticipantType(edge.target),
|
|
473
|
+
lineNumber: edge.lineNumber,
|
|
474
|
+
metadata: {},
|
|
475
|
+
});
|
|
409
476
|
nodeLabels.add(edge.target);
|
|
410
477
|
}
|
|
411
478
|
}
|
|
@@ -437,7 +504,12 @@ function parseNodeLine(
|
|
|
437
504
|
const label = trimmed.slice(0, pipeIdx).trim();
|
|
438
505
|
const metaSegment = trimmed.slice(pipeIdx + 1).trim();
|
|
439
506
|
if (!label) return null;
|
|
440
|
-
const { status, metadata, hadStatusWord } = parseNodeMetadata(
|
|
507
|
+
const { status, metadata, hadStatusWord } = parseNodeMetadata(
|
|
508
|
+
metaSegment,
|
|
509
|
+
aliasMap,
|
|
510
|
+
lineNum,
|
|
511
|
+
diagnostics
|
|
512
|
+
);
|
|
441
513
|
return {
|
|
442
514
|
label,
|
|
443
515
|
// Unknown status bare word → keep null; no bare word at all → default 'na'
|
|
@@ -447,7 +519,13 @@ function parseNodeLine(
|
|
|
447
519
|
metadata,
|
|
448
520
|
};
|
|
449
521
|
}
|
|
450
|
-
return {
|
|
522
|
+
return {
|
|
523
|
+
label: trimmed,
|
|
524
|
+
status: 'na',
|
|
525
|
+
shape: inferParticipantType(trimmed),
|
|
526
|
+
lineNumber: lineNum,
|
|
527
|
+
metadata: {},
|
|
528
|
+
};
|
|
451
529
|
}
|
|
452
530
|
|
|
453
531
|
function parseEdgeLine(
|
|
@@ -475,8 +553,15 @@ function parseEdgeLine(
|
|
|
475
553
|
const pipeIdx = targetRest.indexOf('|');
|
|
476
554
|
if (pipeIdx >= 0) {
|
|
477
555
|
const metaSegment = targetRest.slice(pipeIdx + 1).trim();
|
|
478
|
-
const parsed = parseNodeMetadata(
|
|
479
|
-
|
|
556
|
+
const parsed = parseNodeMetadata(
|
|
557
|
+
metaSegment,
|
|
558
|
+
aliasMap,
|
|
559
|
+
lineNum,
|
|
560
|
+
diagnostics
|
|
561
|
+
);
|
|
562
|
+
status = parsed.hadStatusWord
|
|
563
|
+
? (parsed.status ?? null)
|
|
564
|
+
: (parsed.status ?? 'na');
|
|
480
565
|
metadata = parsed.metadata;
|
|
481
566
|
targetRest = targetRest.slice(0, pipeIdx).trim();
|
|
482
567
|
}
|
|
@@ -499,7 +584,9 @@ function parseEdgeLine(
|
|
|
499
584
|
let rest = trimmed.slice(arrowIdx + 2).trim();
|
|
500
585
|
|
|
501
586
|
if (!source || !rest) {
|
|
502
|
-
diagnostics.push(
|
|
587
|
+
diagnostics.push(
|
|
588
|
+
makeDgmoError(lineNum, 'Edge is missing source or target')
|
|
589
|
+
);
|
|
503
590
|
return null;
|
|
504
591
|
}
|
|
505
592
|
|
|
@@ -509,8 +596,15 @@ function parseEdgeLine(
|
|
|
509
596
|
const pipeIdx = rest.indexOf('|');
|
|
510
597
|
if (pipeIdx >= 0) {
|
|
511
598
|
const metaSegment = rest.slice(pipeIdx + 1).trim();
|
|
512
|
-
const parsed = parseNodeMetadata(
|
|
513
|
-
|
|
599
|
+
const parsed = parseNodeMetadata(
|
|
600
|
+
metaSegment,
|
|
601
|
+
aliasMap,
|
|
602
|
+
lineNum,
|
|
603
|
+
diagnostics
|
|
604
|
+
);
|
|
605
|
+
status = parsed.hadStatusWord
|
|
606
|
+
? (parsed.status ?? null)
|
|
607
|
+
: (parsed.status ?? 'na');
|
|
514
608
|
metadata = parsed.metadata;
|
|
515
609
|
rest = rest.slice(0, pipeIdx).trim();
|
|
516
610
|
}
|
package/src/kanban/parser.ts
CHANGED
|
@@ -26,13 +26,9 @@ const COLUMN_RE = /^\[(.+?)\](?:\s*\(([^)]+)\))?\s*(?:\|\s*(.+))?$/;
|
|
|
26
26
|
const LEGACY_COLUMN_RE = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
|
|
27
27
|
|
|
28
28
|
/** Known kanban options (key-value). */
|
|
29
|
-
const KNOWN_OPTIONS = new Set([
|
|
30
|
-
'hide',
|
|
31
|
-
]);
|
|
29
|
+
const KNOWN_OPTIONS = new Set(['hide']);
|
|
32
30
|
/** Known kanban boolean options (bare keyword = on). */
|
|
33
|
-
const KNOWN_BOOLEANS = new Set<string>([
|
|
34
|
-
'no-auto-color',
|
|
35
|
-
]);
|
|
31
|
+
const KNOWN_BOOLEANS = new Set<string>(['no-auto-color']);
|
|
36
32
|
|
|
37
33
|
// ============================================================
|
|
38
34
|
// Parser
|
|
@@ -138,7 +134,10 @@ export function parseKanban(
|
|
|
138
134
|
lineNumber,
|
|
139
135
|
};
|
|
140
136
|
if (tagBlockMatch.alias) {
|
|
141
|
-
aliasMap.set(
|
|
137
|
+
aliasMap.set(
|
|
138
|
+
tagBlockMatch.alias.toLowerCase(),
|
|
139
|
+
tagBlockMatch.name.toLowerCase()
|
|
140
|
+
);
|
|
142
141
|
}
|
|
143
142
|
result.tagGroups.push(currentTagGroup);
|
|
144
143
|
continue;
|
|
@@ -157,7 +156,10 @@ export function parseKanban(
|
|
|
157
156
|
}
|
|
158
157
|
}
|
|
159
158
|
// Bare boolean option (single keyword, no value)
|
|
160
|
-
if (
|
|
159
|
+
if (
|
|
160
|
+
KNOWN_BOOLEANS.has(trimmed.toLowerCase()) &&
|
|
161
|
+
!COLUMN_RE.test(trimmed)
|
|
162
|
+
) {
|
|
161
163
|
result.options[trimmed.toLowerCase()] = 'on';
|
|
162
164
|
continue;
|
|
163
165
|
}
|
|
@@ -199,7 +201,12 @@ export function parseKanban(
|
|
|
199
201
|
if (LEGACY_COLUMN_RE.test(trimmed)) {
|
|
200
202
|
const legacyMatch = trimmed.match(LEGACY_COLUMN_RE)!;
|
|
201
203
|
const name = legacyMatch[1].replace(/\s*\(.*\)\s*$/, '').trim();
|
|
202
|
-
result.diagnostics.push(
|
|
204
|
+
result.diagnostics.push(
|
|
205
|
+
makeDgmoError(
|
|
206
|
+
lineNumber,
|
|
207
|
+
`'== ${name} ==' is no longer supported. Use '[${name}]' instead`
|
|
208
|
+
)
|
|
209
|
+
);
|
|
203
210
|
continue;
|
|
204
211
|
}
|
|
205
212
|
|
|
@@ -224,7 +231,7 @@ export function parseKanban(
|
|
|
224
231
|
columnCounter++;
|
|
225
232
|
const colName = columnMatch[1].trim();
|
|
226
233
|
const colColor = columnMatch[2]
|
|
227
|
-
? resolveColor(columnMatch[2].trim(), palette) ?? undefined
|
|
234
|
+
? (resolveColor(columnMatch[2].trim(), palette) ?? undefined)
|
|
228
235
|
: undefined;
|
|
229
236
|
|
|
230
237
|
// Parse pipe metadata (e.g., "| wip: 3, t: Sprint1")
|
|
@@ -233,7 +240,10 @@ export function parseKanban(
|
|
|
233
240
|
const pipeStr = columnMatch[3];
|
|
234
241
|
if (pipeStr) {
|
|
235
242
|
const pipeSegments = ['', pipeStr];
|
|
236
|
-
Object.assign(
|
|
243
|
+
Object.assign(
|
|
244
|
+
columnMetadata,
|
|
245
|
+
parsePipeMetadata(pipeSegments, aliasMap)
|
|
246
|
+
);
|
|
237
247
|
// Extract wip from metadata
|
|
238
248
|
if (columnMetadata.wip) {
|
|
239
249
|
const wipVal = parseInt(columnMetadata.wip, 10);
|
|
@@ -300,8 +310,8 @@ export function parseKanban(
|
|
|
300
310
|
continue;
|
|
301
311
|
}
|
|
302
312
|
|
|
303
|
-
// Un-indented non-column line in content phase —
|
|
304
|
-
|
|
313
|
+
// Un-indented non-column line in content phase — stray text
|
|
314
|
+
warn(lineNumber, `Unexpected line: '${trimmed}'.`);
|
|
305
315
|
}
|
|
306
316
|
|
|
307
317
|
// Finalize last card's endLineNumber
|
|
@@ -329,7 +339,8 @@ export function parseKanban(
|
|
|
329
339
|
for (const col of result.columns) {
|
|
330
340
|
for (const card of col.cards) {
|
|
331
341
|
for (const [tagKey, tagValue] of Object.entries(card.tags)) {
|
|
332
|
-
const groupKey =
|
|
342
|
+
const groupKey =
|
|
343
|
+
aliasMap.get(tagKey.toLowerCase()) ?? tagKey.toLowerCase();
|
|
333
344
|
const validValues = tagValueSets.get(groupKey);
|
|
334
345
|
if (validValues && !validValues.has(tagValue.toLowerCase())) {
|
|
335
346
|
const entries = result.tagGroups
|