clawvault 2.2.1 → 2.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.
- package/bin/clawvault.js +6 -0
- package/bin/register-task-commands.js +257 -0
- package/dist/chunk-MDIH26GC.js +183 -0
- package/dist/chunk-NGVAEFT2.js +352 -0
- package/dist/commands/archive.js +1 -1
- package/dist/commands/backlog.d.ts +53 -0
- package/dist/commands/backlog.js +119 -0
- package/dist/commands/blocked.d.ts +25 -0
- package/dist/commands/blocked.js +43 -0
- package/dist/commands/canvas.d.ts +20 -0
- package/dist/commands/canvas.js +309 -0
- package/dist/commands/context.js +3 -3
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/graph.js +2 -2
- package/dist/commands/link.js +3 -3
- package/dist/commands/migrate-observations.js +2 -2
- package/dist/commands/observe.js +3 -3
- package/dist/commands/rebuild.js +2 -2
- package/dist/commands/recover.js +2 -2
- package/dist/commands/reflect.js +1 -1
- package/dist/commands/replay.js +3 -3
- package/dist/commands/sleep.js +5 -5
- package/dist/commands/status.js +1 -1
- package/dist/commands/task.d.ts +71 -0
- package/dist/commands/task.js +189 -0
- package/dist/commands/wake.js +7 -7
- package/dist/index.js +38 -38
- package/dist/lib/canvas-layout.d.ts +115 -0
- package/dist/lib/canvas-layout.js +34 -0
- package/dist/lib/task-utils.d.ts +159 -0
- package/dist/lib/task-utils.js +46 -0
- package/package.json +2 -2
- package/dist/{chunk-WZI3OAE5.js → chunk-5WR6RRPX.js} +3 -3
- package/dist/{chunk-L6NB43WV.js → chunk-6BBTI7NV.js} +3 -3
- package/dist/{chunk-73P7XCQM.js → chunk-DPS7NYIU.js} +3 -3
- package/dist/{chunk-MILVYUPK.js → chunk-IWYZAXKJ.js} +3 -3
- package/dist/{chunk-H7JW4L7H.js → chunk-OZ7RIXTO.js} +3 -3
- package/dist/{chunk-LB6P4CD5.js → chunk-PTSEIWXZ.js} +6 -6
- package/dist/{chunk-I5X6J4FX.js → chunk-SOTWYGH7.js} +6 -6
package/bin/clawvault.js
CHANGED
|
@@ -16,6 +16,7 @@ import { registerResilienceCommands } from './register-resilience-commands.js';
|
|
|
16
16
|
import { registerSessionLifecycleCommands } from './register-session-lifecycle-commands.js';
|
|
17
17
|
import { registerTemplateCommands } from './register-template-commands.js';
|
|
18
18
|
import { registerVaultOperationsCommands } from './register-vault-operations-commands.js';
|
|
19
|
+
import { registerTaskCommands } from './register-task-commands.js';
|
|
19
20
|
import {
|
|
20
21
|
getVault,
|
|
21
22
|
resolveVaultPath,
|
|
@@ -82,5 +83,10 @@ registerVaultOperationsCommands(program, {
|
|
|
82
83
|
path
|
|
83
84
|
});
|
|
84
85
|
|
|
86
|
+
registerTaskCommands(program, {
|
|
87
|
+
chalk,
|
|
88
|
+
resolveVaultPath
|
|
89
|
+
});
|
|
90
|
+
|
|
85
91
|
// Parse and run
|
|
86
92
|
program.parse();
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task tracking command registrations for ClawVault
|
|
3
|
+
* Registers task, backlog, blocked, and canvas commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function registerTaskCommands(
|
|
7
|
+
program,
|
|
8
|
+
{ chalk, resolveVaultPath }
|
|
9
|
+
) {
|
|
10
|
+
// === TASK ===
|
|
11
|
+
const taskCmd = program
|
|
12
|
+
.command('task')
|
|
13
|
+
.description('Task management');
|
|
14
|
+
|
|
15
|
+
// task add
|
|
16
|
+
taskCmd
|
|
17
|
+
.command('add <title>')
|
|
18
|
+
.description('Add a new task')
|
|
19
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
20
|
+
.option('--owner <owner>', 'Task owner')
|
|
21
|
+
.option('--project <project>', 'Project name')
|
|
22
|
+
.option('--priority <priority>', 'Priority (critical, high, medium, low)')
|
|
23
|
+
.option('--due <date>', 'Due date (YYYY-MM-DD)')
|
|
24
|
+
.action(async (title, options) => {
|
|
25
|
+
try {
|
|
26
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
27
|
+
const { taskCommand } = await import('../dist/commands/task.js');
|
|
28
|
+
await taskCommand(vaultPath, 'add', {
|
|
29
|
+
title,
|
|
30
|
+
options: {
|
|
31
|
+
owner: options.owner,
|
|
32
|
+
project: options.project,
|
|
33
|
+
priority: options.priority,
|
|
34
|
+
due: options.due
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// task list
|
|
44
|
+
taskCmd
|
|
45
|
+
.command('list')
|
|
46
|
+
.description('List tasks')
|
|
47
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
48
|
+
.option('--owner <owner>', 'Filter by owner')
|
|
49
|
+
.option('--project <project>', 'Filter by project')
|
|
50
|
+
.option('--status <status>', 'Filter by status (open, in-progress, blocked, done)')
|
|
51
|
+
.option('--priority <priority>', 'Filter by priority')
|
|
52
|
+
.option('--json', 'Output as JSON')
|
|
53
|
+
.action(async (options) => {
|
|
54
|
+
try {
|
|
55
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
56
|
+
const { taskCommand } = await import('../dist/commands/task.js');
|
|
57
|
+
await taskCommand(vaultPath, 'list', {
|
|
58
|
+
options: {
|
|
59
|
+
owner: options.owner,
|
|
60
|
+
project: options.project,
|
|
61
|
+
status: options.status,
|
|
62
|
+
priority: options.priority,
|
|
63
|
+
json: options.json
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// task update
|
|
73
|
+
taskCmd
|
|
74
|
+
.command('update <slug>')
|
|
75
|
+
.description('Update a task')
|
|
76
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
77
|
+
.option('--status <status>', 'New status')
|
|
78
|
+
.option('--owner <owner>', 'New owner')
|
|
79
|
+
.option('--project <project>', 'New project')
|
|
80
|
+
.option('--priority <priority>', 'New priority')
|
|
81
|
+
.option('--blocked-by <blocker>', 'What is blocking this task')
|
|
82
|
+
.option('--due <date>', 'New due date')
|
|
83
|
+
.action(async (slug, options) => {
|
|
84
|
+
try {
|
|
85
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
86
|
+
const { taskCommand } = await import('../dist/commands/task.js');
|
|
87
|
+
await taskCommand(vaultPath, 'update', {
|
|
88
|
+
slug,
|
|
89
|
+
options: {
|
|
90
|
+
status: options.status,
|
|
91
|
+
owner: options.owner,
|
|
92
|
+
project: options.project,
|
|
93
|
+
priority: options.priority,
|
|
94
|
+
blockedBy: options.blockedBy,
|
|
95
|
+
due: options.due
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// task done
|
|
105
|
+
taskCmd
|
|
106
|
+
.command('done <slug>')
|
|
107
|
+
.description('Mark a task as done')
|
|
108
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
109
|
+
.action(async (slug, options) => {
|
|
110
|
+
try {
|
|
111
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
112
|
+
const { taskCommand } = await import('../dist/commands/task.js');
|
|
113
|
+
await taskCommand(vaultPath, 'done', { slug });
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// task show
|
|
121
|
+
taskCmd
|
|
122
|
+
.command('show <slug>')
|
|
123
|
+
.description('Show task details')
|
|
124
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
125
|
+
.option('--json', 'Output as JSON')
|
|
126
|
+
.action(async (slug, options) => {
|
|
127
|
+
try {
|
|
128
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
129
|
+
const { taskCommand } = await import('../dist/commands/task.js');
|
|
130
|
+
await taskCommand(vaultPath, 'show', {
|
|
131
|
+
slug,
|
|
132
|
+
options: { json: options.json }
|
|
133
|
+
});
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// === BACKLOG ===
|
|
141
|
+
const backlogCmd = program
|
|
142
|
+
.command('backlog')
|
|
143
|
+
.description('Backlog management');
|
|
144
|
+
|
|
145
|
+
// backlog add (also supports "backlog <title>" shorthand)
|
|
146
|
+
backlogCmd
|
|
147
|
+
.command('add <title>')
|
|
148
|
+
.description('Add item to backlog')
|
|
149
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
150
|
+
.option('--source <source>', 'Source of the idea')
|
|
151
|
+
.option('--project <project>', 'Project name')
|
|
152
|
+
.action(async (title, options) => {
|
|
153
|
+
try {
|
|
154
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
155
|
+
const { backlogCommand } = await import('../dist/commands/backlog.js');
|
|
156
|
+
await backlogCommand(vaultPath, 'add', {
|
|
157
|
+
title,
|
|
158
|
+
options: {
|
|
159
|
+
source: options.source,
|
|
160
|
+
project: options.project
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// backlog list
|
|
170
|
+
backlogCmd
|
|
171
|
+
.command('list')
|
|
172
|
+
.description('List backlog items')
|
|
173
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
174
|
+
.option('--project <project>', 'Filter by project')
|
|
175
|
+
.option('--json', 'Output as JSON')
|
|
176
|
+
.action(async (options) => {
|
|
177
|
+
try {
|
|
178
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
179
|
+
const { backlogCommand } = await import('../dist/commands/backlog.js');
|
|
180
|
+
await backlogCommand(vaultPath, 'list', {
|
|
181
|
+
options: {
|
|
182
|
+
project: options.project,
|
|
183
|
+
json: options.json
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// backlog promote
|
|
193
|
+
backlogCmd
|
|
194
|
+
.command('promote <slug>')
|
|
195
|
+
.description('Promote backlog item to task')
|
|
196
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
197
|
+
.option('--owner <owner>', 'Task owner')
|
|
198
|
+
.option('--priority <priority>', 'Task priority')
|
|
199
|
+
.option('--due <date>', 'Due date')
|
|
200
|
+
.action(async (slug, options) => {
|
|
201
|
+
try {
|
|
202
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
203
|
+
const { backlogCommand } = await import('../dist/commands/backlog.js');
|
|
204
|
+
await backlogCommand(vaultPath, 'promote', {
|
|
205
|
+
slug,
|
|
206
|
+
options: {
|
|
207
|
+
owner: options.owner,
|
|
208
|
+
priority: options.priority,
|
|
209
|
+
due: options.due
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// === BLOCKED ===
|
|
219
|
+
program
|
|
220
|
+
.command('blocked')
|
|
221
|
+
.description('View blocked tasks')
|
|
222
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
223
|
+
.option('--project <project>', 'Filter by project')
|
|
224
|
+
.option('--json', 'Output as JSON')
|
|
225
|
+
.action(async (options) => {
|
|
226
|
+
try {
|
|
227
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
228
|
+
const { blockedCommand } = await import('../dist/commands/blocked.js');
|
|
229
|
+
await blockedCommand(vaultPath, {
|
|
230
|
+
project: options.project,
|
|
231
|
+
json: options.json
|
|
232
|
+
});
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// === CANVAS ===
|
|
240
|
+
program
|
|
241
|
+
.command('canvas')
|
|
242
|
+
.description('Generate Obsidian canvas dashboard')
|
|
243
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
244
|
+
.option('--output <path>', 'Output file path (default: dashboard.canvas)')
|
|
245
|
+
.action(async (options) => {
|
|
246
|
+
try {
|
|
247
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
248
|
+
const { canvasCommand } = await import('../dist/commands/canvas.js');
|
|
249
|
+
await canvasCommand(vaultPath, {
|
|
250
|
+
output: options.output
|
|
251
|
+
});
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/lib/canvas-layout.ts
|
|
2
|
+
import * as crypto from "crypto";
|
|
3
|
+
var CANVAS_COLORS = {
|
|
4
|
+
RED: "1",
|
|
5
|
+
// Critical, blocked
|
|
6
|
+
ORANGE: "2",
|
|
7
|
+
// High priority
|
|
8
|
+
YELLOW: "3",
|
|
9
|
+
// Medium priority
|
|
10
|
+
GREEN: "4",
|
|
11
|
+
// Done, success
|
|
12
|
+
CYAN: "5",
|
|
13
|
+
// Stats
|
|
14
|
+
PURPLE: "6"
|
|
15
|
+
// Knowledge graph
|
|
16
|
+
};
|
|
17
|
+
var LAYOUT = {
|
|
18
|
+
LEFT_COLUMN_X: 0,
|
|
19
|
+
LEFT_COLUMN_WIDTH: 500,
|
|
20
|
+
RIGHT_COLUMN_X: 550,
|
|
21
|
+
RIGHT_COLUMN_WIDTH: 450,
|
|
22
|
+
GROUP_PADDING: 20,
|
|
23
|
+
NODE_SPACING: 15,
|
|
24
|
+
GROUP_SPACING: 50,
|
|
25
|
+
DEFAULT_NODE_WIDTH: 280,
|
|
26
|
+
DEFAULT_NODE_HEIGHT: 80,
|
|
27
|
+
FILE_NODE_HEIGHT: 60,
|
|
28
|
+
SMALL_NODE_HEIGHT: 50,
|
|
29
|
+
GROUP_HEADER_HEIGHT: 40
|
|
30
|
+
};
|
|
31
|
+
function generateId() {
|
|
32
|
+
return crypto.randomBytes(8).toString("hex");
|
|
33
|
+
}
|
|
34
|
+
function createTextNode(x, y, width, height, text, color) {
|
|
35
|
+
const node = {
|
|
36
|
+
id: generateId(),
|
|
37
|
+
type: "text",
|
|
38
|
+
x,
|
|
39
|
+
y,
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
text
|
|
43
|
+
};
|
|
44
|
+
if (color) node.color = color;
|
|
45
|
+
return node;
|
|
46
|
+
}
|
|
47
|
+
function createFileNode(x, y, width, height, file, color) {
|
|
48
|
+
const node = {
|
|
49
|
+
id: generateId(),
|
|
50
|
+
type: "file",
|
|
51
|
+
x,
|
|
52
|
+
y,
|
|
53
|
+
width,
|
|
54
|
+
height,
|
|
55
|
+
file
|
|
56
|
+
};
|
|
57
|
+
if (color) node.color = color;
|
|
58
|
+
return node;
|
|
59
|
+
}
|
|
60
|
+
function createGroupNode(x, y, width, height, label, color) {
|
|
61
|
+
const node = {
|
|
62
|
+
id: generateId(),
|
|
63
|
+
type: "group",
|
|
64
|
+
x,
|
|
65
|
+
y,
|
|
66
|
+
width,
|
|
67
|
+
height,
|
|
68
|
+
label
|
|
69
|
+
};
|
|
70
|
+
if (color) node.color = color;
|
|
71
|
+
return node;
|
|
72
|
+
}
|
|
73
|
+
function createEdge(fromNode, fromSide, toNode, toSide, label, color) {
|
|
74
|
+
const edge = {
|
|
75
|
+
id: generateId(),
|
|
76
|
+
fromNode,
|
|
77
|
+
fromSide,
|
|
78
|
+
toNode,
|
|
79
|
+
toSide
|
|
80
|
+
};
|
|
81
|
+
if (label) edge.label = label;
|
|
82
|
+
if (color) edge.color = color;
|
|
83
|
+
return edge;
|
|
84
|
+
}
|
|
85
|
+
function stackNodesVertically(nodes, startX, startY, spacing = LAYOUT.NODE_SPACING) {
|
|
86
|
+
let currentY = startY;
|
|
87
|
+
const positionedNodes = [];
|
|
88
|
+
for (const node of nodes) {
|
|
89
|
+
positionedNodes.push({
|
|
90
|
+
...node,
|
|
91
|
+
x: startX,
|
|
92
|
+
y: currentY
|
|
93
|
+
});
|
|
94
|
+
currentY += node.height + spacing;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
nodes: positionedNodes,
|
|
98
|
+
totalHeight: currentY - startY - spacing
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createGroupWithNodes(groupX, groupY, groupWidth, label, childNodes, color) {
|
|
102
|
+
const padding = LAYOUT.GROUP_PADDING;
|
|
103
|
+
const headerHeight = LAYOUT.GROUP_HEADER_HEIGHT;
|
|
104
|
+
const stacked = stackNodesVertically(
|
|
105
|
+
childNodes,
|
|
106
|
+
groupX + padding,
|
|
107
|
+
groupY + headerHeight + padding
|
|
108
|
+
);
|
|
109
|
+
const groupHeight = headerHeight + padding * 2 + stacked.totalHeight + LAYOUT.NODE_SPACING;
|
|
110
|
+
const group = createGroupNode(groupX, groupY, groupWidth, groupHeight, label, color);
|
|
111
|
+
return {
|
|
112
|
+
group,
|
|
113
|
+
nodes: stacked.nodes
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function getPriorityColor(priority) {
|
|
117
|
+
switch (priority) {
|
|
118
|
+
case "critical":
|
|
119
|
+
return CANVAS_COLORS.RED;
|
|
120
|
+
case "high":
|
|
121
|
+
return CANVAS_COLORS.ORANGE;
|
|
122
|
+
case "medium":
|
|
123
|
+
return CANVAS_COLORS.YELLOW;
|
|
124
|
+
default:
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function truncateText(text, maxChars) {
|
|
129
|
+
if (text.length <= maxChars) return text;
|
|
130
|
+
return text.slice(0, maxChars - 3) + "...";
|
|
131
|
+
}
|
|
132
|
+
function formatCanvasText(lines) {
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
function calculateColumnHeight(groups) {
|
|
136
|
+
let height = 0;
|
|
137
|
+
for (let i = 0; i < groups.length; i++) {
|
|
138
|
+
height += groups[i].group.height;
|
|
139
|
+
if (i < groups.length - 1) {
|
|
140
|
+
height += LAYOUT.GROUP_SPACING;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return height;
|
|
144
|
+
}
|
|
145
|
+
function positionGroupsVertically(groups, startY = 0) {
|
|
146
|
+
let currentY = startY;
|
|
147
|
+
const positioned = [];
|
|
148
|
+
for (const { group, nodes } of groups) {
|
|
149
|
+
const yOffset = currentY - group.y;
|
|
150
|
+
positioned.push({
|
|
151
|
+
group: { ...group, y: currentY },
|
|
152
|
+
nodes: nodes.map((n) => ({ ...n, y: n.y + yOffset }))
|
|
153
|
+
});
|
|
154
|
+
currentY += group.height + LAYOUT.GROUP_SPACING;
|
|
155
|
+
}
|
|
156
|
+
return positioned;
|
|
157
|
+
}
|
|
158
|
+
function flattenGroups(groups) {
|
|
159
|
+
const nodes = [];
|
|
160
|
+
for (const { group, nodes: childNodes } of groups) {
|
|
161
|
+
nodes.push(group);
|
|
162
|
+
nodes.push(...childNodes);
|
|
163
|
+
}
|
|
164
|
+
return nodes;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
CANVAS_COLORS,
|
|
169
|
+
LAYOUT,
|
|
170
|
+
generateId,
|
|
171
|
+
createTextNode,
|
|
172
|
+
createFileNode,
|
|
173
|
+
createGroupNode,
|
|
174
|
+
createEdge,
|
|
175
|
+
stackNodesVertically,
|
|
176
|
+
createGroupWithNodes,
|
|
177
|
+
getPriorityColor,
|
|
178
|
+
truncateText,
|
|
179
|
+
formatCanvasText,
|
|
180
|
+
calculateColumnHeight,
|
|
181
|
+
positionGroupsVertically,
|
|
182
|
+
flattenGroups
|
|
183
|
+
};
|