@diagrammo/dgmo 0.6.2 → 0.7.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.
- package/.claude/commands/dgmo.md +231 -13
- package/AGENTS.md +148 -0
- package/dist/cli.cjs +341 -165
- package/dist/index.cjs +4900 -1685
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +259 -18
- package/dist/index.d.ts +259 -18
- package/dist/index.js +4642 -1436
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/c4/layout.ts +0 -5
- package/src/c4/parser.ts +0 -16
- package/src/c4/renderer.ts +7 -11
- package/src/class/layout.ts +0 -1
- package/src/class/parser.ts +28 -0
- package/src/class/renderer.ts +189 -34
- package/src/cli.ts +566 -25
- package/src/colors.ts +3 -3
- package/src/completion.ts +58 -0
- package/src/d3.ts +179 -122
- package/src/dgmo-router.ts +3 -58
- package/src/echarts.ts +96 -55
- package/src/er/parser.ts +30 -1
- package/src/er/renderer.ts +12 -7
- package/src/gantt/calculator.ts +677 -0
- package/src/gantt/parser.ts +761 -0
- package/src/gantt/renderer.ts +2125 -0
- package/src/gantt/resolver.ts +144 -0
- package/src/gantt/types.ts +168 -0
- package/src/graph/flowchart-parser.ts +27 -4
- package/src/graph/flowchart-renderer.ts +1 -2
- package/src/graph/state-parser.ts +0 -1
- package/src/graph/state-renderer.ts +1 -3
- package/src/index.ts +37 -0
- package/src/infra/compute.ts +0 -7
- package/src/infra/layout.ts +0 -2
- package/src/infra/parser.ts +46 -4
- package/src/infra/renderer.ts +49 -27
- package/src/initiative-status/filter.ts +63 -0
- package/src/initiative-status/layout.ts +319 -67
- package/src/initiative-status/parser.ts +200 -25
- package/src/initiative-status/renderer.ts +298 -35
- package/src/initiative-status/types.ts +6 -0
- package/src/kanban/parser.ts +0 -2
- package/src/org/layout.ts +22 -59
- package/src/org/renderer.ts +11 -36
- package/src/palettes/dracula.ts +60 -0
- package/src/palettes/index.ts +8 -6
- package/src/palettes/monokai.ts +60 -0
- package/src/palettes/registry.ts +4 -2
- package/src/sequence/parser.ts +14 -11
- package/src/sequence/renderer.ts +5 -6
- package/src/sequence/tag-resolution.ts +0 -1
- package/src/sharing.ts +8 -0
- package/src/sitemap/layout.ts +1 -14
- package/src/sitemap/parser.ts +1 -2
- package/src/sitemap/renderer.ts +4 -7
- package/src/utils/arrows.ts +7 -7
- package/src/utils/duration.ts +212 -0
- package/src/utils/export-container.ts +40 -0
- package/src/utils/legend-constants.ts +1 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Gantt Dot-Notation Task Resolver
|
|
3
|
+
// ============================================================
|
|
4
|
+
//
|
|
5
|
+
// Resolves `-> TargetName` dependency references to actual tasks.
|
|
6
|
+
// Implements greedy right-to-left dot splitting for disambiguation.
|
|
7
|
+
|
|
8
|
+
import type { GanttTask, GanttNode } from './types';
|
|
9
|
+
|
|
10
|
+
export interface ResolverMatch {
|
|
11
|
+
task: GanttTask;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ResolverError {
|
|
15
|
+
kind: 'not_found' | 'ambiguous';
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ResolverResult = ResolverMatch | ResolverError;
|
|
20
|
+
|
|
21
|
+
export function isResolverError(r: ResolverResult): r is ResolverError {
|
|
22
|
+
return 'kind' in r;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Collect all tasks from a tree of GanttNodes, annotating each with its
|
|
27
|
+
* fully qualified group path (e.g., ["Backend", "API"]).
|
|
28
|
+
*/
|
|
29
|
+
export function collectTasks(nodes: GanttNode[]): GanttTask[] {
|
|
30
|
+
const tasks: GanttTask[] = [];
|
|
31
|
+
function walk(children: GanttNode[]) {
|
|
32
|
+
for (const node of children) {
|
|
33
|
+
if (node.kind === 'task') {
|
|
34
|
+
tasks.push(node);
|
|
35
|
+
} else if (node.kind === 'group' || node.kind === 'parallel') {
|
|
36
|
+
walk(node.children);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
walk(nodes);
|
|
41
|
+
return tasks;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a dependency target name to a task.
|
|
46
|
+
*
|
|
47
|
+
* Resolution strategy (greedy right-to-left):
|
|
48
|
+
* 1. Try the full string as an exact task label match
|
|
49
|
+
* 2. If no match, split at the last dot → group prefix + task label
|
|
50
|
+
* 3. Recurse for deeper paths
|
|
51
|
+
*
|
|
52
|
+
* Returns a match or an error with helpful suggestions.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveTaskName(
|
|
55
|
+
name: string,
|
|
56
|
+
allTasks: GanttTask[],
|
|
57
|
+
): ResolverResult {
|
|
58
|
+
const trimmed = name.trim();
|
|
59
|
+
|
|
60
|
+
// 1. Try exact label match (no dots involved)
|
|
61
|
+
const exactMatches = allTasks.filter(t => t.label === trimmed);
|
|
62
|
+
if (exactMatches.length === 1) {
|
|
63
|
+
return { task: exactMatches[0] };
|
|
64
|
+
}
|
|
65
|
+
if (exactMatches.length > 1) {
|
|
66
|
+
// Multiple tasks with same name — need disambiguation
|
|
67
|
+
const suggestions = exactMatches.map(t =>
|
|
68
|
+
t.groupPath.length > 0 ? `${t.groupPath.join('.')}.${t.label}` : t.label
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
kind: 'ambiguous',
|
|
72
|
+
message: `Multiple tasks match "${trimmed}". Did you mean ${suggestions.map(s => `\`${s}\``).join(' or ')}?`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 2. Try dot-notation: split at last dot (greedy right-to-left)
|
|
77
|
+
const lastDotIdx = trimmed.lastIndexOf('.');
|
|
78
|
+
if (lastDotIdx > 0) {
|
|
79
|
+
const groupPrefix = trimmed.substring(0, lastDotIdx);
|
|
80
|
+
const taskLabel = trimmed.substring(lastDotIdx + 1);
|
|
81
|
+
|
|
82
|
+
// Find tasks whose label matches and whose group path ends with the prefix
|
|
83
|
+
const matches = allTasks.filter(t => {
|
|
84
|
+
if (t.label !== taskLabel) return false;
|
|
85
|
+
return matchesGroupPath(t.groupPath, groupPrefix);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (matches.length === 1) {
|
|
89
|
+
return { task: matches[0] };
|
|
90
|
+
}
|
|
91
|
+
if (matches.length > 1) {
|
|
92
|
+
const suggestions = matches.map(t =>
|
|
93
|
+
t.groupPath.length > 0 ? `${t.groupPath.join('.')}.${t.label}` : t.label
|
|
94
|
+
);
|
|
95
|
+
return {
|
|
96
|
+
kind: 'ambiguous',
|
|
97
|
+
message: `Multiple tasks match "${trimmed}". Did you mean ${suggestions.map(s => `\`${s}\``).join(' or ')}?`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Try further left splits (for dots in group names)
|
|
102
|
+
// e.g., "U.S. Operations.Task A" — last dot split tried "U.S. Operations" + "Task A"
|
|
103
|
+
// Now try "U.S." + "Operations.Task A" — but that doesn't help.
|
|
104
|
+
// The greedy approach handles this: "U.S. Operations" is the group name.
|
|
105
|
+
// If the group name itself contains dots, the last dot split already tried the correct split.
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3. No match found — try case-insensitive as a fallback for suggestions
|
|
109
|
+
const caseInsensitive = allTasks.filter(t =>
|
|
110
|
+
t.label.toLowerCase() === trimmed.toLowerCase()
|
|
111
|
+
);
|
|
112
|
+
if (caseInsensitive.length > 0) {
|
|
113
|
+
return {
|
|
114
|
+
kind: 'not_found',
|
|
115
|
+
message: `No task found with name "${trimmed}". Did you mean "${caseInsensitive[0].label}" (case mismatch)?`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
kind: 'not_found',
|
|
121
|
+
message: `No task found with name "${trimmed}".`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if a task's group path matches a dot-separated prefix.
|
|
127
|
+
* The prefix can be a single group name or a dot-separated path.
|
|
128
|
+
* Matching is done from the end of the group path.
|
|
129
|
+
*
|
|
130
|
+
* Example: groupPath = ["Backend", "API"], prefix = "Backend" → true
|
|
131
|
+
* Example: groupPath = ["Backend", "API"], prefix = "API" → true
|
|
132
|
+
* Example: groupPath = ["Backend", "API"], prefix = "Backend.API" → true
|
|
133
|
+
*/
|
|
134
|
+
function matchesGroupPath(groupPath: string[], prefix: string): boolean {
|
|
135
|
+
// Simple case: prefix is a single segment
|
|
136
|
+
if (!prefix.includes('.')) {
|
|
137
|
+
return groupPath.some(g => g === prefix);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Multi-segment prefix: try matching from the start of the group path
|
|
141
|
+
const pathStr = groupPath.join('.');
|
|
142
|
+
// Check if the full prefix matches any contiguous section of the path
|
|
143
|
+
return pathStr === prefix || pathStr.endsWith('.' + prefix) || pathStr.startsWith(prefix + '.') || pathStr.includes('.' + prefix + '.');
|
|
144
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Gantt Chart Types
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
import type { DgmoError } from '../diagnostics';
|
|
6
|
+
import type { TagGroup } from '../utils/tag-groups';
|
|
7
|
+
|
|
8
|
+
// ── Duration ────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/** Calendar units: d (days), w (weeks), m (months), q (quarters), y (years). bd = business days. */
|
|
11
|
+
export type DurationUnit = 'd' | 'bd' | 'w' | 'm' | 'q' | 'y';
|
|
12
|
+
|
|
13
|
+
export interface Duration {
|
|
14
|
+
amount: number;
|
|
15
|
+
unit: DurationUnit;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Offset {
|
|
19
|
+
duration: Duration;
|
|
20
|
+
direction: 1 | -1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Parsed Elements ─────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export interface GanttDependency {
|
|
26
|
+
targetName: string; // raw string from `-> X` or `-> Group.X`
|
|
27
|
+
offset?: Offset;
|
|
28
|
+
lineNumber: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface GanttTask {
|
|
32
|
+
id: string; // unique, generated during parse (e.g. "group:taskIdx")
|
|
33
|
+
label: string;
|
|
34
|
+
duration: Duration | null; // null for explicit-date-only tasks
|
|
35
|
+
explicitStart?: string; // YYYY-MM-DD from `2024-01-15 -> 30d:` or `2024-01-15:`
|
|
36
|
+
uncertain: boolean;
|
|
37
|
+
progress: number | null; // 0-100 or null
|
|
38
|
+
offset?: Offset; // task-level offset: shifts start date forward (+) or backward (-)
|
|
39
|
+
dependencies: GanttDependency[];
|
|
40
|
+
metadata: Record<string, string>; // tag values from pipe metadata
|
|
41
|
+
lineNumber: number;
|
|
42
|
+
groupPath: string[]; // e.g. ["Backend", "API"] for nested groups
|
|
43
|
+
comment?: string; // accumulated // comment lines
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface GanttGroup {
|
|
47
|
+
name: string;
|
|
48
|
+
color: string | null;
|
|
49
|
+
metadata: Record<string, string>;
|
|
50
|
+
lineNumber: number;
|
|
51
|
+
children: GanttNode[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface GanttParallelBlock {
|
|
55
|
+
kind: 'parallel';
|
|
56
|
+
lineNumber: number;
|
|
57
|
+
children: GanttNode[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A node in the gantt tree: either a task, group, or parallel block. */
|
|
61
|
+
export type GanttNode =
|
|
62
|
+
| ({ kind: 'task' } & GanttTask)
|
|
63
|
+
| ({ kind: 'group' } & GanttGroup)
|
|
64
|
+
| GanttParallelBlock;
|
|
65
|
+
|
|
66
|
+
// ── Holidays ────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
export type Weekday = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
69
|
+
|
|
70
|
+
export interface HolidayDate {
|
|
71
|
+
date: string; // YYYY-MM-DD
|
|
72
|
+
label: string;
|
|
73
|
+
lineNumber: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface HolidayRange {
|
|
77
|
+
startDate: string; // YYYY-MM-DD
|
|
78
|
+
endDate: string; // YYYY-MM-DD
|
|
79
|
+
label: string;
|
|
80
|
+
lineNumber: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface GanttHolidays {
|
|
84
|
+
dates: HolidayDate[];
|
|
85
|
+
ranges: HolidayRange[];
|
|
86
|
+
workweek: Weekday[]; // default: ['mon', 'tue', 'wed', 'thu', 'fri']
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Eras & Markers (reuse timeline types) ───────────────────
|
|
90
|
+
|
|
91
|
+
export interface GanttEra {
|
|
92
|
+
startDate: string;
|
|
93
|
+
endDate: string;
|
|
94
|
+
label: string;
|
|
95
|
+
color: string | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface GanttMarker {
|
|
99
|
+
date: string;
|
|
100
|
+
label: string;
|
|
101
|
+
color: string | null;
|
|
102
|
+
lineNumber: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Chart Options ───────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export interface GanttOptions {
|
|
108
|
+
start: string | null; // YYYY[-MM[-DD]] or null for relative timeline
|
|
109
|
+
title: string | null;
|
|
110
|
+
titleLineNumber: number | null;
|
|
111
|
+
orientation: 'horizontal' | 'vertical';
|
|
112
|
+
todayMarker: 'off' | 'on' | string; // 'on' = current date, string = YYYY-MM-DD
|
|
113
|
+
criticalPath: boolean;
|
|
114
|
+
dependencies: boolean;
|
|
115
|
+
sort: 'default' | 'tag';
|
|
116
|
+
defaultSwimlaneGroup: string | null; // tag group name from `sort: tag:Team`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Parsed Result ───────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
export interface ParsedGantt {
|
|
122
|
+
nodes: GanttNode[]; // top-level tree (groups, tasks, parallel blocks)
|
|
123
|
+
holidays: GanttHolidays;
|
|
124
|
+
tagGroups: TagGroup[];
|
|
125
|
+
eras: GanttEra[];
|
|
126
|
+
markers: GanttMarker[];
|
|
127
|
+
options: GanttOptions;
|
|
128
|
+
diagnostics: DgmoError[];
|
|
129
|
+
error: string | null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Resolved Schedule ───────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
export interface ResolvedTask {
|
|
135
|
+
task: GanttTask;
|
|
136
|
+
startDate: Date;
|
|
137
|
+
endDate: Date;
|
|
138
|
+
isCriticalPath: boolean;
|
|
139
|
+
isUncertain: boolean; // true if task.uncertain OR any predecessor is uncertain
|
|
140
|
+
isMilestone: boolean;
|
|
141
|
+
groupPath: string[];
|
|
142
|
+
effectiveMetadata: Record<string, string>; // merged with inherited tags
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface ResolvedGroup {
|
|
146
|
+
name: string;
|
|
147
|
+
color: string | null;
|
|
148
|
+
metadata: Record<string, string>;
|
|
149
|
+
startDate: Date;
|
|
150
|
+
endDate: Date;
|
|
151
|
+
progress: number | null; // aggregate progress (weighted average)
|
|
152
|
+
lineNumber: number;
|
|
153
|
+
depth: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface ResolvedSchedule {
|
|
157
|
+
tasks: ResolvedTask[];
|
|
158
|
+
groups: ResolvedGroup[];
|
|
159
|
+
startDate: Date;
|
|
160
|
+
endDate: Date;
|
|
161
|
+
holidays: GanttHolidays;
|
|
162
|
+
tagGroups: TagGroup[];
|
|
163
|
+
eras: GanttEra[];
|
|
164
|
+
markers: GanttMarker[];
|
|
165
|
+
options: GanttOptions;
|
|
166
|
+
diagnostics: DgmoError[];
|
|
167
|
+
error: string | null;
|
|
168
|
+
}
|
|
@@ -90,10 +90,6 @@ function parseNodeRef(
|
|
|
90
90
|
*/
|
|
91
91
|
function splitArrows(line: string): string[] {
|
|
92
92
|
const segments: string[] = [];
|
|
93
|
-
// Match: optional `-label(color)->` or just `->`
|
|
94
|
-
// We scan left to right looking for `->` and work backwards to find the `-` start.
|
|
95
|
-
const arrowRe = /(?:^|\s)-([^>\s(][^(>]*?)?\s*(?:\(([^)]+)\))?\s*->|(?:^|\s)->/g;
|
|
96
|
-
|
|
97
93
|
let lastIndex = 0;
|
|
98
94
|
// Simpler approach: find all `->` positions, then determine if there's a label prefix
|
|
99
95
|
const arrowPositions: { start: number; end: number; label?: string; color?: string }[] = [];
|
|
@@ -482,3 +478,30 @@ export function looksLikeFlowchart(content: string): boolean {
|
|
|
482
478
|
|
|
483
479
|
return shapeNearArrow;
|
|
484
480
|
}
|
|
481
|
+
|
|
482
|
+
// ============================================================
|
|
483
|
+
// Symbol extraction (for completion API)
|
|
484
|
+
// ============================================================
|
|
485
|
+
|
|
486
|
+
import type { DiagramSymbols } from '../completion';
|
|
487
|
+
|
|
488
|
+
// Node ID: identifier at line start followed by a shape delimiter or space (arrow line)
|
|
489
|
+
const NODE_ID_RE = /^([a-zA-Z_][\w-]*)[\s([</{]/;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Extract node IDs (entities) from flowchart document text.
|
|
493
|
+
* Used by the dgmo completion API for ghost hints and popup completions.
|
|
494
|
+
*/
|
|
495
|
+
export function extractSymbols(docText: string): DiagramSymbols {
|
|
496
|
+
const entities: string[] = [];
|
|
497
|
+
let inMetadata = true;
|
|
498
|
+
for (const rawLine of docText.split('\n')) {
|
|
499
|
+
const line = rawLine.trim();
|
|
500
|
+
if (inMetadata && /^[a-z-]+\s*:/i.test(line)) continue;
|
|
501
|
+
inMetadata = false;
|
|
502
|
+
if (line.length === 0 || /^\s/.test(rawLine)) continue;
|
|
503
|
+
const m = NODE_ID_RE.exec(line);
|
|
504
|
+
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
505
|
+
}
|
|
506
|
+
return { kind: 'flowchart', entities, keywords: [] };
|
|
507
|
+
}
|
|
@@ -8,7 +8,7 @@ import { FONT_FAMILY } from '../fonts';
|
|
|
8
8
|
import type { PaletteColors } from '../palettes';
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import type { ParsedGraph, GraphShape } from './types';
|
|
11
|
-
import type { LayoutResult, LayoutNode
|
|
11
|
+
import type { LayoutResult, LayoutNode } from './layout';
|
|
12
12
|
import { parseFlowchart } from './flowchart-parser';
|
|
13
13
|
import { layoutGraph } from './layout';
|
|
14
14
|
|
|
@@ -246,7 +246,6 @@ export function renderFlowchart(
|
|
|
246
246
|
|
|
247
247
|
// Center the diagram in the area below the title
|
|
248
248
|
const scaledW = diagramW * scale;
|
|
249
|
-
const scaledH = diagramH * scale;
|
|
250
249
|
const offsetX = (width - scaledW) / 2;
|
|
251
250
|
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
252
251
|
|
|
@@ -8,7 +8,7 @@ import { FONT_FAMILY } from '../fonts';
|
|
|
8
8
|
import type { PaletteColors } from '../palettes';
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import type { ParsedGraph } from './types';
|
|
11
|
-
import type { LayoutResult, LayoutNode
|
|
11
|
+
import type { LayoutResult, LayoutNode } from './layout';
|
|
12
12
|
import { parseState } from './state-parser';
|
|
13
13
|
import { layoutGraph } from './layout';
|
|
14
14
|
|
|
@@ -76,8 +76,6 @@ function selfLoopPath(node: LayoutNode): string {
|
|
|
76
76
|
// Main renderer
|
|
77
77
|
// ============================================================
|
|
78
78
|
|
|
79
|
-
type GSelection = d3Selection.Selection<SVGGElement, unknown, null, undefined>;
|
|
80
|
-
|
|
81
79
|
export function renderState(
|
|
82
80
|
container: HTMLDivElement,
|
|
83
81
|
graph: ParsedGraph,
|
package/src/index.ts
CHANGED
|
@@ -217,10 +217,13 @@ export type {
|
|
|
217
217
|
} from './initiative-status/layout';
|
|
218
218
|
|
|
219
219
|
export { renderInitiativeStatus, renderInitiativeStatusForExport } from './initiative-status/renderer';
|
|
220
|
+
export type { ISRenderOptions } from './initiative-status/renderer';
|
|
220
221
|
|
|
221
222
|
export { collapseInitiativeStatus } from './initiative-status/collapse';
|
|
222
223
|
export type { CollapseResult } from './initiative-status/collapse';
|
|
223
224
|
|
|
225
|
+
export { filterInitiativeStatusByTags } from './initiative-status/filter';
|
|
226
|
+
|
|
224
227
|
export { parseSitemap, looksLikeSitemap } from './sitemap/parser';
|
|
225
228
|
|
|
226
229
|
export type {
|
|
@@ -259,6 +262,30 @@ export { renderInfra, parseAndLayoutInfra, computeInfraLegendGroups } from './in
|
|
|
259
262
|
export type { InfraLegendGroup } from './infra/renderer';
|
|
260
263
|
export type { CollapsedSitemapResult } from './sitemap/collapse';
|
|
261
264
|
|
|
265
|
+
// ── Gantt Chart ───────────────────────────────────────────
|
|
266
|
+
export { parseGantt } from './gantt/parser';
|
|
267
|
+
export { calculateSchedule } from './gantt/calculator';
|
|
268
|
+
export { renderGantt, buildTagLaneRowList } from './gantt/renderer';
|
|
269
|
+
export type { GanttInteractiveOptions, GanttRow, GanttGroupRow, GanttTaskRow, GanttLaneHeaderRow } from './gantt/renderer';
|
|
270
|
+
export { resolveTaskName, collectTasks } from './gantt/resolver';
|
|
271
|
+
export type {
|
|
272
|
+
ParsedGantt,
|
|
273
|
+
GanttTask,
|
|
274
|
+
GanttGroup,
|
|
275
|
+
GanttParallelBlock,
|
|
276
|
+
GanttNode,
|
|
277
|
+
GanttDependency,
|
|
278
|
+
GanttHolidays,
|
|
279
|
+
GanttEra,
|
|
280
|
+
GanttMarker,
|
|
281
|
+
GanttOptions,
|
|
282
|
+
Duration,
|
|
283
|
+
DurationUnit,
|
|
284
|
+
ResolvedSchedule,
|
|
285
|
+
ResolvedTask,
|
|
286
|
+
ResolvedGroup,
|
|
287
|
+
} from './gantt/types';
|
|
288
|
+
|
|
262
289
|
export { collapseOrgTree } from './org/collapse';
|
|
263
290
|
export type { CollapsedOrgResult } from './org/collapse';
|
|
264
291
|
|
|
@@ -361,6 +388,16 @@ export type {
|
|
|
361
388
|
DecodedDiagramUrl,
|
|
362
389
|
} from './sharing';
|
|
363
390
|
|
|
391
|
+
// ============================================================
|
|
392
|
+
// Completion (symbol extraction API)
|
|
393
|
+
// ============================================================
|
|
394
|
+
|
|
395
|
+
export {
|
|
396
|
+
registerExtractor,
|
|
397
|
+
extractDiagramSymbols,
|
|
398
|
+
} from './completion';
|
|
399
|
+
export type { DiagramSymbols, ExtractFn } from './completion';
|
|
400
|
+
|
|
364
401
|
// ============================================================
|
|
365
402
|
// Branding
|
|
366
403
|
// ============================================================
|
package/src/infra/compute.ts
CHANGED
|
@@ -22,8 +22,6 @@ import type {
|
|
|
22
22
|
InfraAvailabilityPercentiles,
|
|
23
23
|
InfraProperty,
|
|
24
24
|
} from './types';
|
|
25
|
-
import { INFRA_BEHAVIOR_KEYS } from './types';
|
|
26
|
-
|
|
27
25
|
// ============================================================
|
|
28
26
|
// Helpers
|
|
29
27
|
// ============================================================
|
|
@@ -71,11 +69,6 @@ function serverlessCapacity(node: InfraNode): number {
|
|
|
71
69
|
return concurrency / (durationMs / 1000);
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
/** Backward-compatible helper used by overload detection. */
|
|
75
|
-
function getInstances(node: InfraNode): number {
|
|
76
|
-
return getInstanceRange(node).min;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
72
|
/** Compute dynamic instance count based on load and max-rps. */
|
|
80
73
|
function computeDynamicInstances(node: InfraNode, computedRps: number): number {
|
|
81
74
|
const { min, max } = getInstanceRange(node);
|
package/src/infra/layout.ts
CHANGED
package/src/infra/parser.ts
CHANGED
|
@@ -11,11 +11,8 @@ import { measureIndent } from '../utils/parsing';
|
|
|
11
11
|
import type {
|
|
12
12
|
ParsedInfra,
|
|
13
13
|
InfraNode,
|
|
14
|
-
InfraEdge,
|
|
15
14
|
InfraGroup,
|
|
16
15
|
InfraTagGroup,
|
|
17
|
-
InfraTagValue,
|
|
18
|
-
InfraProperty,
|
|
19
16
|
} from './types';
|
|
20
17
|
import { INFRA_BEHAVIOR_KEYS, EDGE_ONLY_KEYS } from './types';
|
|
21
18
|
|
|
@@ -116,7 +113,6 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
116
113
|
};
|
|
117
114
|
|
|
118
115
|
const nodeMap = new Map<string, InfraNode>();
|
|
119
|
-
const edgeNodeId = 'edge';
|
|
120
116
|
|
|
121
117
|
const setError = (line: number, message: string) => {
|
|
122
118
|
const diag = makeDgmoError(line, message);
|
|
@@ -573,3 +569,49 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
573
569
|
|
|
574
570
|
return result;
|
|
575
571
|
}
|
|
572
|
+
|
|
573
|
+
// ============================================================
|
|
574
|
+
// Symbol extraction (for completion API)
|
|
575
|
+
// ============================================================
|
|
576
|
+
|
|
577
|
+
import type { DiagramSymbols } from '../completion';
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Extract component names (entities) from infra document text.
|
|
581
|
+
* Used by the dgmo completion API for ghost hints and popup completions.
|
|
582
|
+
*/
|
|
583
|
+
export function extractSymbols(docText: string): DiagramSymbols {
|
|
584
|
+
const entities: string[] = [];
|
|
585
|
+
let inMetadata = true;
|
|
586
|
+
let inTagGroup = false;
|
|
587
|
+
for (const rawLine of docText.split('\n')) {
|
|
588
|
+
const line = rawLine.trim();
|
|
589
|
+
if (line.length === 0) continue;
|
|
590
|
+
const indented = /^\s/.test(rawLine);
|
|
591
|
+
|
|
592
|
+
// Metadata phase: skip until first non-metadata root-level line.
|
|
593
|
+
// All lines (including indented) are skipped while inMetadata = true.
|
|
594
|
+
if (inMetadata) {
|
|
595
|
+
if (!indented && !/^[a-z-]+\s*:/i.test(line)) inMetadata = false;
|
|
596
|
+
else continue;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!indented) {
|
|
600
|
+
// Root-level: tag group declaration, group header, or component
|
|
601
|
+
if (/^tag\s*:/i.test(line)) { inTagGroup = true; continue; }
|
|
602
|
+
inTagGroup = false;
|
|
603
|
+
if (/^\[/.test(line)) continue; // group header
|
|
604
|
+
const m = COMPONENT_RE.exec(line);
|
|
605
|
+
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
606
|
+
} else {
|
|
607
|
+
// Indented: skip tag values, connections, and properties; extract grouped components
|
|
608
|
+
if (inTagGroup) continue;
|
|
609
|
+
if (/^->/.test(line)) continue; // simple connection
|
|
610
|
+
if (/^-[^>]+-?>/.test(line)) continue; // labeled connection
|
|
611
|
+
if (/^\w[\w-]*\s*:/.test(line)) continue; // property (key: value)
|
|
612
|
+
const m = COMPONENT_RE.exec(line);
|
|
613
|
+
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return { kind: 'infra', entities, keywords: [] };
|
|
617
|
+
}
|