@diagrammo/dgmo 0.2.27 → 0.3.0

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 (49) hide show
  1. package/.claude/skills/dgmo-chart/SKILL.md +107 -0
  2. package/.claude/skills/dgmo-flowchart/SKILL.md +61 -0
  3. package/.claude/skills/dgmo-generate/SKILL.md +58 -0
  4. package/.claude/skills/dgmo-sequence/SKILL.md +83 -0
  5. package/.cursorrules +117 -0
  6. package/.github/copilot-instructions.md +117 -0
  7. package/.windsurfrules +117 -0
  8. package/README.md +10 -3
  9. package/dist/cli.cjs +366 -918
  10. package/dist/index.cjs +581 -396
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +39 -24
  13. package/dist/index.d.ts +39 -24
  14. package/dist/index.js +578 -395
  15. package/dist/index.js.map +1 -1
  16. package/docs/ai-integration.md +125 -0
  17. package/docs/language-reference.md +786 -0
  18. package/package.json +15 -8
  19. package/src/c4/parser.ts +90 -74
  20. package/src/c4/renderer.ts +13 -12
  21. package/src/c4/types.ts +6 -4
  22. package/src/chart.ts +3 -2
  23. package/src/class/layout.ts +17 -12
  24. package/src/class/parser.ts +22 -52
  25. package/src/class/renderer.ts +44 -46
  26. package/src/class/types.ts +1 -1
  27. package/src/cli.ts +130 -19
  28. package/src/d3.ts +1 -1
  29. package/src/dgmo-mermaid.ts +1 -1
  30. package/src/dgmo-router.ts +1 -1
  31. package/src/echarts.ts +33 -13
  32. package/src/er/parser.ts +34 -43
  33. package/src/er/types.ts +1 -1
  34. package/src/graph/flowchart-parser.ts +2 -25
  35. package/src/graph/types.ts +1 -1
  36. package/src/index.ts +5 -0
  37. package/src/initiative-status/parser.ts +36 -7
  38. package/src/initiative-status/types.ts +1 -1
  39. package/src/kanban/parser.ts +32 -53
  40. package/src/kanban/renderer.ts +9 -8
  41. package/src/kanban/types.ts +6 -14
  42. package/src/org/parser.ts +47 -87
  43. package/src/org/resolver.ts +11 -12
  44. package/src/sequence/parser.ts +97 -15
  45. package/src/sequence/renderer.ts +62 -69
  46. package/src/utils/arrows.ts +75 -0
  47. package/src/utils/inline-markdown.ts +75 -0
  48. package/src/utils/parsing.ts +67 -0
  49. package/src/utils/tag-groups.ts +76 -0
package/src/er/parser.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { resolveColor } from '../colors';
2
2
  import type { PaletteColors } from '../palettes';
3
3
  import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
4
+ import { measureIndent } from '../utils/parsing';
4
5
  import type {
5
6
  ParsedERDiagram,
6
7
  ERTable,
@@ -13,16 +14,6 @@ import type {
13
14
  // Helpers
14
15
  // ============================================================
15
16
 
16
- function measureIndent(line: string): number {
17
- let indent = 0;
18
- for (const ch of line) {
19
- if (ch === ' ') indent++;
20
- else if (ch === '\t') indent += 4;
21
- else break;
22
- }
23
- return indent;
24
- }
25
-
26
17
  function tableId(name: string): string {
27
18
  return name.toLowerCase().trim();
28
19
  }
@@ -50,42 +41,39 @@ const CONSTRAINT_MAP: Record<string, ERConstraint> = {
50
41
  // Cardinality parsing
51
42
  // ============================================================
52
43
 
53
- // Cardinality keyword map
54
- const CARD_WORD: Record<string, ERCardinality> = {
55
- one: '1',
56
- many: '*',
57
- '1': '1',
58
- '*': '*',
59
- '?': '?',
60
- zero: '?',
61
- };
62
-
63
44
  /**
64
- * Parse a cardinality side token (e.g. "1", "*", "?", "one", "many", "zero").
45
+ * Parse a cardinality side token (symbolic only: "1", "*", "?").
65
46
  */
66
47
  function parseCardSide(token: string): ERCardinality | null {
67
- return CARD_WORD[token.toLowerCase()] ?? null;
48
+ if (token === '1' || token === '*' || token === '?') return token;
49
+ return null;
68
50
  }
69
51
 
70
52
  /**
71
- * Try to parse a relationship line with cardinality.
53
+ * Try to parse a relationship line with symbolic cardinality.
72
54
  *
73
- * Supported forms:
55
+ * Supported form:
74
56
  * tableName 1--* tableName : label
75
57
  * tableName 1-* tableName : label
76
- * tableName one-to-many tableName : label
77
- * tableName one to many tableName : label
78
- * tableName 1 to many tableName : label
79
58
  * tableName ?--1 tableName : label
80
59
  */
81
60
  const REL_SYMBOLIC_RE =
82
61
  /^([a-zA-Z_]\w*)\s+([1*?])\s*-{1,2}\s*([1*?])\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/;
83
62
 
63
+ /** Detects keyword cardinality forms to emit helpful error */
84
64
  const REL_KEYWORD_RE =
85
- /^([a-zA-Z_]\w*)\s+(one|many|zero|1|\*|\?)[- ]to[- ](one|many|zero|1|\*|\?)\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/i;
65
+ /^([a-zA-Z_]\w*)\s+(one|many|zero)[- ]to[- ](one|many|zero)\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/i;
66
+
67
+ const KEYWORD_TO_SYMBOL: Record<string, string> = {
68
+ one: '1',
69
+ many: '*',
70
+ zero: '?',
71
+ };
86
72
 
87
73
  function parseRelationship(
88
- trimmed: string
74
+ trimmed: string,
75
+ lineNumber: number,
76
+ pushError: (line: number, message: string) => void,
89
77
  ): {
90
78
  source: string;
91
79
  target: string;
@@ -109,20 +97,16 @@ function parseRelationship(
109
97
  }
110
98
  }
111
99
 
112
- // Keyword / natural: one-to-many, one to many, 1 to many, etc.
100
+ // Keyword / natural: produce helpful error with symbolic suggestion
113
101
  const kw = trimmed.match(REL_KEYWORD_RE);
114
102
  if (kw) {
115
- const fromCard = parseCardSide(kw[2]);
116
- const toCard = parseCardSide(kw[3]);
117
- if (fromCard && toCard) {
118
- return {
119
- source: kw[1],
120
- target: kw[4],
121
- from: fromCard,
122
- to: toCard,
123
- label: kw[5]?.trim(),
124
- };
125
- }
103
+ const fromSym = KEYWORD_TO_SYMBOL[kw[2].toLowerCase()] ?? kw[2];
104
+ const toSym = KEYWORD_TO_SYMBOL[kw[3].toLowerCase()] ?? kw[3];
105
+ pushError(
106
+ lineNumber,
107
+ `Use symbolic cardinality (1--*, ?--1, *--*) instead of "${kw[2]}-to-${kw[3]}". Example: ${kw[1]} ${fromSym}--${toSym} ${kw[4]}`,
108
+ );
109
+ return null;
126
110
  }
127
111
 
128
112
  return null;
@@ -157,6 +141,7 @@ export function parseERDiagram(
157
141
  tables: [],
158
142
  relationships: [],
159
143
  diagnostics: [],
144
+ error: null,
160
145
  };
161
146
 
162
147
  const fail = (line: number, message: string): ParsedERDiagram => {
@@ -166,6 +151,12 @@ export function parseERDiagram(
166
151
  return result;
167
152
  };
168
153
 
154
+ const pushError = (line: number, message: string): void => {
155
+ const diag = makeDgmoError(line, message);
156
+ result.diagnostics.push(diag);
157
+ if (!result.error) result.error = formatDgmoError(diag);
158
+ };
159
+
169
160
  const tableMap = new Map<string, ERTable>();
170
161
  let currentTable: ERTable | null = null;
171
162
  let contentStarted = false;
@@ -257,7 +248,7 @@ export function parseERDiagram(
257
248
  contentStarted = true;
258
249
 
259
250
  // Try relationship
260
- const rel = parseRelationship(trimmed);
251
+ const rel = parseRelationship(trimmed, lineNumber, pushError);
261
252
  if (rel) {
262
253
  getOrCreateTable(rel.source, lineNumber);
263
254
  getOrCreateTable(rel.target, lineNumber);
@@ -347,7 +338,7 @@ export function looksLikeERDiagram(content: string): boolean {
347
338
  hasTableDecl = true;
348
339
  }
349
340
  // Check for relationship patterns
350
- if (REL_SYMBOLIC_RE.test(trimmed) || REL_KEYWORD_RE.test(trimmed)) {
341
+ if (REL_SYMBOLIC_RE.test(trimmed)) {
351
342
  hasRelationship = true;
352
343
  }
353
344
  }
package/src/er/types.ts CHANGED
@@ -39,5 +39,5 @@ export interface ParsedERDiagram {
39
39
  tables: ERTable[];
40
40
  relationships: ERRelationship[];
41
41
  diagnostics: DgmoError[];
42
- error?: string;
42
+ error: string | null;
43
43
  }
@@ -1,6 +1,7 @@
1
1
  import { resolveColor } from '../colors';
2
2
  import type { PaletteColors } from '../palettes';
3
3
  import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
4
+ import { measureIndent, extractColor } from '../utils/parsing';
4
5
  import type {
5
6
  ParsedGraph,
6
7
  GraphNode,
@@ -14,16 +15,6 @@ import type {
14
15
  // Helpers
15
16
  // ============================================================
16
17
 
17
- function measureIndent(line: string): number {
18
- let indent = 0;
19
- for (const ch of line) {
20
- if (ch === ' ') indent++;
21
- else if (ch === '\t') indent += 4;
22
- else break;
23
- }
24
- return indent;
25
- }
26
-
27
18
  function nodeId(shape: GraphShape, label: string): string {
28
19
  return `${shape}:${label.toLowerCase().trim()}`;
29
20
  }
@@ -35,21 +26,6 @@ interface NodeRef {
35
26
  color?: string;
36
27
  }
37
28
 
38
- const COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
39
-
40
- function extractColor(
41
- label: string,
42
- palette?: PaletteColors
43
- ): { label: string; color?: string } {
44
- const m = label.match(COLOR_SUFFIX_RE);
45
- if (!m) return { label };
46
- const colorName = m[1].trim();
47
- return {
48
- label: label.substring(0, m.index!).trim(),
49
- color: resolveColor(colorName, palette),
50
- };
51
- }
52
-
53
29
  /**
54
30
  * Try to parse a node reference from a text fragment.
55
31
  * Order matters: subroutine & document before process.
@@ -239,6 +215,7 @@ export function parseFlowchart(
239
215
  edges: [],
240
216
  options: {},
241
217
  diagnostics: [],
218
+ error: null,
242
219
  };
243
220
 
244
221
  const fail = (line: number, message: string): ParsedGraph => {
@@ -45,5 +45,5 @@ export interface ParsedGraph {
45
45
  groups?: GraphGroup[];
46
46
  options: Record<string, string>;
47
47
  diagnostics: DgmoError[];
48
- error?: string;
48
+ error: string | null;
49
49
  }
package/src/index.ts CHANGED
@@ -126,6 +126,11 @@ export type {
126
126
  GraphDirection,
127
127
  } from './graph/types';
128
128
 
129
+ export type { TagGroup, TagEntry } from './utils/tag-groups';
130
+
131
+ export { parseInlineMarkdown, truncateBareUrl } from './utils/inline-markdown';
132
+ export type { InlineSpan } from './utils/inline-markdown';
133
+
129
134
  export { parseOrg } from './org/parser';
130
135
  export type {
131
136
  ParsedOrg,
@@ -29,14 +29,14 @@ export function looksLikeInitiativeStatus(content: string): boolean {
29
29
  let hasIndentedArrow = false;
30
30
  for (const line of lines) {
31
31
  const trimmed = line.trim();
32
- if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) continue;
32
+ if (!trimmed || trimmed.startsWith('//')) continue;
33
33
  if (trimmed.match(/^chart\s*:/i)) continue;
34
34
  if (trimmed.match(/^title\s*:/i)) continue;
35
35
  if (trimmed.includes('->')) hasArrow = true;
36
36
  if (/\|\s*(done|wip|todo|na)\s*$/i.test(trimmed)) hasStatus = true;
37
37
  // Indented arrow is a strong signal — only initiative-status uses this
38
38
  const isIndented = line.length > 0 && line !== trimmed && /^\s/.test(line);
39
- if (isIndented && trimmed.startsWith('->')) hasIndentedArrow = true;
39
+ if (isIndented && (trimmed.startsWith('->') || /^-[^>].*->/.test(trimmed))) hasIndentedArrow = true;
40
40
  if (hasArrow && hasStatus) return true;
41
41
  }
42
42
  return hasIndentedArrow;
@@ -68,7 +68,7 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
68
68
  groups: [],
69
69
  options: {},
70
70
  diagnostics: [],
71
- error: undefined,
71
+ error: null,
72
72
  };
73
73
 
74
74
  const lines = content.split('\n');
@@ -82,7 +82,7 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
82
82
  const trimmed = raw.trim();
83
83
 
84
84
  // Skip blanks and comments
85
- if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) continue;
85
+ if (!trimmed || trimmed.startsWith('//')) continue;
86
86
 
87
87
  // chart: header
88
88
  const chartMatch = trimmed.match(/^chart\s*:\s*(.+)/i);
@@ -123,11 +123,11 @@ export function parseInitiativeStatus(content: string): ParsedInitiativeStatus {
123
123
  currentGroup = null;
124
124
  }
125
125
 
126
- // Edge: contains `->`
126
+ // Edge: contains `->` or labeled form `-label->`
127
127
  if (trimmed.includes('->')) {
128
128
  let edgeText = trimmed;
129
- // Indented `-> Target` shorthand — prepend the last node label as source
130
- if (trimmed.startsWith('->')) {
129
+ // Indented `-> Target` or `-label-> Target` shorthand
130
+ if (trimmed.startsWith('->') || /^-[^>].*->/.test(trimmed)) {
131
131
  if (!lastNodeLabel) {
132
132
  result.diagnostics.push(
133
133
  makeDgmoError(lineNum, 'Indented edge has no preceding node to use as source', 'warning')
@@ -222,6 +222,35 @@ function parseEdgeLine(
222
222
  // or: <source> -> <target> | <status>
223
223
  // or: <source> -> <target>: <label>
224
224
  // or: <source> -> <target>
225
+ // or: <source> -<label>-> <target> [| <status>]
226
+
227
+ // Check for labeled arrow form: SOURCE -LABEL-> TARGET [| status]
228
+ const labeledMatch = trimmed.match(/^(\S+)\s+-(.+)->\s+(.+)$/);
229
+ if (labeledMatch) {
230
+ const source = labeledMatch[1];
231
+ const label = labeledMatch[2].trim();
232
+ let targetRest = labeledMatch[3].trim();
233
+
234
+ if (label) {
235
+ // Extract status from end (after last |)
236
+ let status: InitiativeStatus = 'na';
237
+ const lastPipe = targetRest.lastIndexOf('|');
238
+ if (lastPipe >= 0) {
239
+ const statusRaw = targetRest.slice(lastPipe + 1).trim();
240
+ status = parseStatus(statusRaw, lineNum, diagnostics);
241
+ targetRest = targetRest.slice(0, lastPipe).trim();
242
+ }
243
+
244
+ const target = targetRest.trim();
245
+ if (!target) {
246
+ diagnostics.push(makeDgmoError(lineNum, 'Edge is missing target'));
247
+ return null;
248
+ }
249
+
250
+ return { source, target, label, status, lineNumber: lineNum };
251
+ }
252
+ // Empty label — fall through to plain arrow parsing
253
+ }
225
254
 
226
255
  const arrowIdx = trimmed.indexOf('->');
227
256
  if (arrowIdx < 0) return null;
@@ -39,5 +39,5 @@ export interface ParsedInitiativeStatus {
39
39
  groups: ISGroup[];
40
40
  options: Record<string, string>;
41
41
  diagnostics: DgmoError[];
42
- error?: string;
42
+ error: string | null;
43
43
  }
@@ -1,7 +1,14 @@
1
- import { resolveColor } from '../colors';
2
1
  import type { PaletteColors } from '../palettes';
3
2
  import type { DgmoError } from '../diagnostics';
4
3
  import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
4
+ import { matchTagBlockHeading } from '../utils/tag-groups';
5
+ import {
6
+ measureIndent,
7
+ extractColor,
8
+ CHART_TYPE_RE,
9
+ TITLE_RE,
10
+ OPTION_RE,
11
+ } from '../utils/parsing';
5
12
  import type {
6
13
  ParsedKanban,
7
14
  KanbanColumn,
@@ -14,40 +21,7 @@ import type {
14
21
  // Regex patterns
15
22
  // ============================================================
16
23
 
17
- const CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
18
- const TITLE_RE = /^title\s*:\s*(.+)/i;
19
- const OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
20
- const GROUP_HEADING_RE =
21
- /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
22
24
  const COLUMN_RE = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
23
- const COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
24
-
25
- // ============================================================
26
- // Helpers
27
- // ============================================================
28
-
29
- function measureIndent(line: string): number {
30
- let indent = 0;
31
- for (const ch of line) {
32
- if (ch === ' ') indent++;
33
- else if (ch === '\t') indent += 4;
34
- else break;
35
- }
36
- return indent;
37
- }
38
-
39
- function extractColor(
40
- label: string,
41
- palette?: PaletteColors
42
- ): { label: string; color?: string } {
43
- const m = label.match(COLOR_SUFFIX_RE);
44
- if (!m) return { label };
45
- const colorName = m[1].trim();
46
- return {
47
- label: label.substring(0, m.index!).trim(),
48
- color: resolveColor(colorName, palette),
49
- };
50
- }
51
25
 
52
26
  // ============================================================
53
27
  // Parser
@@ -63,6 +37,7 @@ export function parseKanban(
63
37
  tagGroups: [],
64
38
  options: {},
65
39
  diagnostics: [],
40
+ error: null,
66
41
  };
67
42
 
68
43
  const fail = (line: number, message: string): ParsedKanban => {
@@ -146,10 +121,32 @@ export function parseKanban(
146
121
  }
147
122
  }
148
123
 
124
+ // Tag group heading — `tag: Name` (new) or `## Name` (deprecated)
125
+ // Must be checked BEFORE OPTION_RE to prevent `tag: Rank` being swallowed as option
126
+ if (!contentStarted) {
127
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
128
+ if (tagBlockMatch) {
129
+ if (tagBlockMatch.deprecated) {
130
+ warn(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups — use 'tag: ${tagBlockMatch.name}' instead`);
131
+ }
132
+ currentTagGroup = {
133
+ name: tagBlockMatch.name,
134
+ alias: tagBlockMatch.alias,
135
+ entries: [],
136
+ lineNumber,
137
+ };
138
+ if (tagBlockMatch.alias) {
139
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
140
+ }
141
+ result.tagGroups.push(currentTagGroup);
142
+ continue;
143
+ }
144
+ }
145
+
149
146
  // Generic header options (key: value before content/tag groups)
150
147
  if (!contentStarted && !currentTagGroup && measureIndent(line) === 0) {
151
148
  const optMatch = trimmed.match(OPTION_RE);
152
- if (optMatch && !trimmed.startsWith('##') && !COLUMN_RE.test(trimmed)) {
149
+ if (optMatch && !COLUMN_RE.test(trimmed)) {
153
150
  const key = optMatch[1].trim().toLowerCase();
154
151
  if (key !== 'chart' && key !== 'title') {
155
152
  result.options[key] = optMatch[2].trim();
@@ -158,24 +155,6 @@ export function parseKanban(
158
155
  }
159
156
  }
160
157
 
161
- // ## Tag group heading
162
- const groupMatch = trimmed.match(GROUP_HEADING_RE);
163
- if (groupMatch && !contentStarted) {
164
- const groupName = groupMatch[1].trim();
165
- const alias = groupMatch[2] || undefined;
166
- currentTagGroup = {
167
- name: groupName,
168
- alias,
169
- entries: [],
170
- lineNumber,
171
- };
172
- if (alias) {
173
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
174
- }
175
- result.tagGroups.push(currentTagGroup);
176
- continue;
177
- }
178
-
179
158
  // Tag group entries (indented Value(color) [default] under ## heading)
180
159
  if (currentTagGroup && !contentStarted) {
181
160
  const indent = measureIndent(line);
@@ -5,6 +5,7 @@
5
5
  import * as d3Selection from 'd3-selection';
6
6
  import { FONT_FAMILY } from '../fonts';
7
7
  import type { PaletteColors } from '../palettes';
8
+ import { renderInlineText } from '../utils/inline-markdown';
8
9
  import type { ParsedKanban, KanbanColumn, KanbanCard, KanbanTagGroup } from './types';
9
10
  import { parseKanban } from './parser';
10
11
  import { isArchiveColumn } from './mutations';
@@ -498,14 +499,14 @@ export function renderKanban(
498
499
  .attr('stroke', cardStroke)
499
500
  .attr('stroke-width', CARD_STROKE_WIDTH);
500
501
 
501
- // Card title
502
- cg.append('text')
502
+ // Card title (inline markdown)
503
+ const titleEl = cg.append('text')
503
504
  .attr('x', cx + CARD_PADDING_X)
504
505
  .attr('y', cy + CARD_PADDING_Y + CARD_TITLE_FONT_SIZE)
505
506
  .attr('font-size', CARD_TITLE_FONT_SIZE)
506
507
  .attr('font-weight', '500')
507
- .attr('fill', palette.text)
508
- .text(card.title);
508
+ .attr('fill', palette.text);
509
+ renderInlineText(titleEl, card.title, palette, CARD_TITLE_FONT_SIZE);
509
510
 
510
511
  // Separator + metadata
511
512
  if (hasMeta) {
@@ -542,14 +543,14 @@ export function renderKanban(
542
543
  metaY += CARD_META_LINE_HEIGHT;
543
544
  }
544
545
 
545
- // Detail lines
546
+ // Detail lines (inline markdown)
546
547
  for (const detail of card.details) {
547
- cg.append('text')
548
+ const detailEl = cg.append('text')
548
549
  .attr('x', cx + CARD_PADDING_X)
549
550
  .attr('y', metaY)
550
551
  .attr('font-size', CARD_META_FONT_SIZE)
551
- .attr('fill', palette.textMuted)
552
- .text(detail);
552
+ .attr('fill', palette.textMuted);
553
+ renderInlineText(detailEl, detail, palette, CARD_META_FONT_SIZE);
553
554
 
554
555
  metaY += CARD_META_LINE_HEIGHT;
555
556
  }
@@ -1,18 +1,10 @@
1
1
  import type { DgmoError } from '../diagnostics';
2
+ import type { TagGroup, TagEntry } from '../utils/tag-groups';
2
3
 
3
- export interface KanbanTagEntry {
4
- value: string;
5
- color: string;
6
- lineNumber: number;
7
- }
8
-
9
- export interface KanbanTagGroup {
10
- name: string;
11
- alias?: string;
12
- entries: KanbanTagEntry[];
13
- defaultValue?: string;
14
- lineNumber: number;
15
- }
4
+ /** @deprecated Use `TagEntry` from `utils/tag-groups` */
5
+ export type KanbanTagEntry = TagEntry;
6
+ /** @deprecated Use `TagGroup` from `utils/tag-groups` */
7
+ export type KanbanTagGroup = TagGroup;
16
8
 
17
9
  export interface KanbanCard {
18
10
  id: string;
@@ -41,5 +33,5 @@ export interface ParsedKanban {
41
33
  tagGroups: KanbanTagGroup[];
42
34
  options: Record<string, string>;
43
35
  diagnostics: DgmoError[];
44
- error?: string;
36
+ error: string | null;
45
37
  }