trak_flow 0.1.3
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.
- checksums.yaml +7 -0
- data/.envrc +3 -0
- data/CHANGELOG.md +69 -0
- data/COMMITS.md +196 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +281 -0
- data/README.md +479 -0
- data/Rakefile +16 -0
- data/bin/tf +6 -0
- data/bin/tf_mcp +81 -0
- data/docs/.keep +0 -0
- data/docs/api/database.md +434 -0
- data/docs/api/ruby-library.md +349 -0
- data/docs/api/task-model.md +341 -0
- data/docs/assets/stylesheets/extra.css +53 -0
- data/docs/assets/trak_flow.jpg +0 -0
- data/docs/cli/admin-commands.md +369 -0
- data/docs/cli/dependency-commands.md +321 -0
- data/docs/cli/label-commands.md +222 -0
- data/docs/cli/overview.md +163 -0
- data/docs/cli/plan-commands.md +344 -0
- data/docs/cli/task-commands.md +333 -0
- data/docs/core-concepts/dependencies.md +232 -0
- data/docs/core-concepts/labels.md +217 -0
- data/docs/core-concepts/overview.md +178 -0
- data/docs/core-concepts/plans-workflows.md +264 -0
- data/docs/core-concepts/tasks.md +205 -0
- data/docs/getting-started/configuration.md +120 -0
- data/docs/getting-started/installation.md +79 -0
- data/docs/getting-started/quick-start.md +245 -0
- data/docs/index.md +169 -0
- data/docs/mcp/integration.md +302 -0
- data/docs/mcp/overview.md +206 -0
- data/docs/mcp/resources.md +284 -0
- data/docs/mcp/tools.md +457 -0
- data/examples/basic_usage.rb +365 -0
- data/examples/cli_demo.sh +314 -0
- data/examples/mcp/Gemfile +9 -0
- data/examples/mcp/Gemfile.lock +226 -0
- data/examples/mcp/http_demo.rb +232 -0
- data/examples/mcp/stdio_demo.rb +146 -0
- data/lib/trak_flow/cli/admin_commands.rb +136 -0
- data/lib/trak_flow/cli/config_commands.rb +260 -0
- data/lib/trak_flow/cli/dep_commands.rb +71 -0
- data/lib/trak_flow/cli/label_commands.rb +76 -0
- data/lib/trak_flow/cli/main_commands.rb +386 -0
- data/lib/trak_flow/cli/plan_commands.rb +185 -0
- data/lib/trak_flow/cli/workflow_commands.rb +133 -0
- data/lib/trak_flow/cli.rb +110 -0
- data/lib/trak_flow/config/defaults.yml +114 -0
- data/lib/trak_flow/config/section.rb +74 -0
- data/lib/trak_flow/config.rb +276 -0
- data/lib/trak_flow/graph/dependency_graph.rb +288 -0
- data/lib/trak_flow/id_generator.rb +52 -0
- data/lib/trak_flow/mcp/resources/base_resource.rb +25 -0
- data/lib/trak_flow/mcp/resources/dependency_graph.rb +31 -0
- data/lib/trak_flow/mcp/resources/label_list.rb +21 -0
- data/lib/trak_flow/mcp/resources/plan_by_id.rb +27 -0
- data/lib/trak_flow/mcp/resources/plan_list.rb +21 -0
- data/lib/trak_flow/mcp/resources/task_by_id.rb +31 -0
- data/lib/trak_flow/mcp/resources/task_list.rb +21 -0
- data/lib/trak_flow/mcp/resources/task_next.rb +30 -0
- data/lib/trak_flow/mcp/resources/workflow_by_id.rb +27 -0
- data/lib/trak_flow/mcp/resources/workflow_list.rb +21 -0
- data/lib/trak_flow/mcp/server.rb +140 -0
- data/lib/trak_flow/mcp/tools/base_tool.rb +29 -0
- data/lib/trak_flow/mcp/tools/comment_add.rb +33 -0
- data/lib/trak_flow/mcp/tools/dep_add.rb +34 -0
- data/lib/trak_flow/mcp/tools/dep_remove.rb +25 -0
- data/lib/trak_flow/mcp/tools/label_add.rb +28 -0
- data/lib/trak_flow/mcp/tools/label_remove.rb +25 -0
- data/lib/trak_flow/mcp/tools/plan_add_step.rb +35 -0
- data/lib/trak_flow/mcp/tools/plan_create.rb +33 -0
- data/lib/trak_flow/mcp/tools/plan_run.rb +58 -0
- data/lib/trak_flow/mcp/tools/plan_start.rb +58 -0
- data/lib/trak_flow/mcp/tools/task_block.rb +27 -0
- data/lib/trak_flow/mcp/tools/task_close.rb +26 -0
- data/lib/trak_flow/mcp/tools/task_create.rb +51 -0
- data/lib/trak_flow/mcp/tools/task_defer.rb +27 -0
- data/lib/trak_flow/mcp/tools/task_start.rb +25 -0
- data/lib/trak_flow/mcp/tools/task_update.rb +36 -0
- data/lib/trak_flow/mcp/tools/workflow_discard.rb +28 -0
- data/lib/trak_flow/mcp/tools/workflow_summarize.rb +34 -0
- data/lib/trak_flow/mcp.rb +38 -0
- data/lib/trak_flow/models/comment.rb +71 -0
- data/lib/trak_flow/models/dependency.rb +96 -0
- data/lib/trak_flow/models/label.rb +90 -0
- data/lib/trak_flow/models/task.rb +188 -0
- data/lib/trak_flow/storage/database.rb +638 -0
- data/lib/trak_flow/storage/jsonl.rb +259 -0
- data/lib/trak_flow/time_parser.rb +15 -0
- data/lib/trak_flow/version.rb +5 -0
- data/lib/trak_flow.rb +100 -0
- data/mkdocs.yml +143 -0
- metadata +392 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# Database API Reference
|
|
2
|
+
|
|
3
|
+
TrakFlow uses SQLite for fast querying with JSONL as the persistent source of truth.
|
|
4
|
+
|
|
5
|
+
## Class: TrakFlow::Storage::Database
|
|
6
|
+
|
|
7
|
+
### Initialization
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
db = TrakFlow::Storage::Database.new(path)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `path` | String | Path to SQLite database file |
|
|
16
|
+
|
|
17
|
+
### Task Methods
|
|
18
|
+
|
|
19
|
+
#### find_task
|
|
20
|
+
|
|
21
|
+
Find a task by ID.
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
task = db.find_task("tf-abc123")
|
|
25
|
+
# Returns: TrakFlow::Models::Task or nil
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
#### list_tasks
|
|
29
|
+
|
|
30
|
+
List tasks with optional filters.
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
tasks = db.list_tasks(
|
|
34
|
+
status: "open",
|
|
35
|
+
type: "bug",
|
|
36
|
+
priority: [0, 1],
|
|
37
|
+
assignee: "claude",
|
|
38
|
+
parent_id: nil,
|
|
39
|
+
label: "frontend",
|
|
40
|
+
include_closed: false,
|
|
41
|
+
include_ephemeral: false,
|
|
42
|
+
limit: 100,
|
|
43
|
+
offset: 0
|
|
44
|
+
)
|
|
45
|
+
# Returns: Array<TrakFlow::Models::Task>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Filter | Type | Description |
|
|
49
|
+
|--------|------|-------------|
|
|
50
|
+
| `status` | String/Array | Filter by status(es) |
|
|
51
|
+
| `type` | String/Array | Filter by type(s) |
|
|
52
|
+
| `priority` | Integer/Array | Filter by priority(ies) |
|
|
53
|
+
| `assignee` | String | Filter by assignee |
|
|
54
|
+
| `parent_id` | String | Filter by parent |
|
|
55
|
+
| `label` | String/Array | Filter by label(s) |
|
|
56
|
+
| `include_closed` | Boolean | Include closed tasks |
|
|
57
|
+
| `include_ephemeral` | Boolean | Include ephemeral tasks |
|
|
58
|
+
| `limit` | Integer | Max results |
|
|
59
|
+
| `offset` | Integer | Skip results |
|
|
60
|
+
|
|
61
|
+
#### insert_task
|
|
62
|
+
|
|
63
|
+
Insert a new task.
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
db.insert_task(task)
|
|
67
|
+
# Returns: TrakFlow::Models::Task
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### update_task
|
|
71
|
+
|
|
72
|
+
Update an existing task.
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
db.update_task(task)
|
|
76
|
+
# Returns: TrakFlow::Models::Task
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### delete_task
|
|
80
|
+
|
|
81
|
+
Delete a task.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
db.delete_task("tf-abc123")
|
|
85
|
+
# Returns: true
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Ready Tasks
|
|
89
|
+
|
|
90
|
+
#### find_ready_tasks
|
|
91
|
+
|
|
92
|
+
Find tasks with no open blocking dependencies.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
tasks = db.find_ready_tasks(
|
|
96
|
+
type: "bug",
|
|
97
|
+
priority: [0, 1],
|
|
98
|
+
limit: 10
|
|
99
|
+
)
|
|
100
|
+
# Returns: Array<TrakFlow::Models::Task>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### is_ready?
|
|
104
|
+
|
|
105
|
+
Check if a task is ready.
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
db.is_ready?("tf-abc123")
|
|
109
|
+
# Returns: Boolean
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Plan & Workflow Methods
|
|
113
|
+
|
|
114
|
+
#### find_plans
|
|
115
|
+
|
|
116
|
+
Find all Plan blueprints.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
plans = db.find_plans
|
|
120
|
+
# Returns: Array<TrakFlow::Models::Task> where plan? == true
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### find_workflows
|
|
124
|
+
|
|
125
|
+
Find Workflows, optionally filtered by source Plan.
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
workflows = db.find_workflows(plan_id: "tf-plan1")
|
|
129
|
+
# Returns: Array<TrakFlow::Models::Task> where workflow? == true
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### find_ephemeral_workflows
|
|
133
|
+
|
|
134
|
+
Find ephemeral Workflows.
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
workflows = db.find_ephemeral_workflows
|
|
138
|
+
# Returns: Array<TrakFlow::Models::Task> where ephemeral? == true
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### mark_as_plan
|
|
142
|
+
|
|
143
|
+
Convert a task to a Plan.
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
db.mark_as_plan("tf-abc123")
|
|
147
|
+
# Returns: TrakFlow::Models::Task
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Dependency Methods
|
|
151
|
+
|
|
152
|
+
#### add_dependency
|
|
153
|
+
|
|
154
|
+
Add a dependency between tasks.
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
db.add_dependency(
|
|
158
|
+
source_id: "tf-design",
|
|
159
|
+
target_id: "tf-implement",
|
|
160
|
+
type: "blocks"
|
|
161
|
+
)
|
|
162
|
+
# Returns: TrakFlow::Models::Dependency
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### remove_dependency
|
|
166
|
+
|
|
167
|
+
Remove a dependency.
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
db.remove_dependency(
|
|
171
|
+
source_id: "tf-design",
|
|
172
|
+
target_id: "tf-implement"
|
|
173
|
+
)
|
|
174
|
+
# Returns: true
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### dependencies_for
|
|
178
|
+
|
|
179
|
+
Get dependencies for a task.
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
deps = db.dependencies_for("tf-abc123")
|
|
183
|
+
# Returns: {
|
|
184
|
+
# blocked_by: Array<Dependency>,
|
|
185
|
+
# blocks: Array<Dependency>,
|
|
186
|
+
# related: Array<Dependency>
|
|
187
|
+
# }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### all_dependencies
|
|
191
|
+
|
|
192
|
+
Get all dependencies.
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
deps = db.all_dependencies
|
|
196
|
+
# Returns: Array<TrakFlow::Models::Dependency>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### would_create_cycle?
|
|
200
|
+
|
|
201
|
+
Check if adding a dependency would create a cycle.
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
db.would_create_cycle?("tf-a", "tf-b")
|
|
205
|
+
# Returns: Boolean
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Label Methods
|
|
209
|
+
|
|
210
|
+
#### add_label
|
|
211
|
+
|
|
212
|
+
Add a label to a task.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
db.add_label("tf-abc123", "frontend")
|
|
216
|
+
# Returns: TrakFlow::Models::Label
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### remove_label
|
|
220
|
+
|
|
221
|
+
Remove a label from a task.
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
db.remove_label("tf-abc123", "frontend")
|
|
225
|
+
# Returns: true
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### labels_for
|
|
229
|
+
|
|
230
|
+
Get labels for a task.
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
labels = db.labels_for("tf-abc123")
|
|
234
|
+
# Returns: Array<String>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### all_labels
|
|
238
|
+
|
|
239
|
+
Get all labels with task counts.
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
labels = db.all_labels
|
|
243
|
+
# Returns: Hash<String, Integer>
|
|
244
|
+
# Example: {"frontend" => 5, "backend" => 3}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### tasks_with_label
|
|
248
|
+
|
|
249
|
+
Find tasks with a label.
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
tasks = db.tasks_with_label("frontend")
|
|
253
|
+
# Returns: Array<TrakFlow::Models::Task>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Comment Methods
|
|
257
|
+
|
|
258
|
+
#### add_comment
|
|
259
|
+
|
|
260
|
+
Add a comment to a task.
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
db.add_comment(
|
|
264
|
+
task_id: "tf-abc123",
|
|
265
|
+
author: "claude",
|
|
266
|
+
content: "Working on this now"
|
|
267
|
+
)
|
|
268
|
+
# Returns: TrakFlow::Models::Comment
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### comments_for
|
|
272
|
+
|
|
273
|
+
Get comments for a task.
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
comments = db.comments_for("tf-abc123")
|
|
277
|
+
# Returns: Array<TrakFlow::Models::Comment>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Maintenance Methods
|
|
281
|
+
|
|
282
|
+
#### rebuild!
|
|
283
|
+
|
|
284
|
+
Rebuild the database from JSONL.
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
db.rebuild!(jsonl_path)
|
|
288
|
+
# Returns: Integer (number of tasks loaded)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### garbage_collect!
|
|
292
|
+
|
|
293
|
+
Remove old ephemeral Workflows.
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
count = db.garbage_collect!(older_than: 7.days.ago)
|
|
297
|
+
# Returns: Integer (number deleted)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### vacuum!
|
|
301
|
+
|
|
302
|
+
Optimize database storage.
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
db.vacuum!
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Statistics Methods
|
|
309
|
+
|
|
310
|
+
#### stats
|
|
311
|
+
|
|
312
|
+
Get database statistics.
|
|
313
|
+
|
|
314
|
+
```ruby
|
|
315
|
+
stats = db.stats
|
|
316
|
+
# Returns: {
|
|
317
|
+
# total_tasks: 47,
|
|
318
|
+
# by_status: {"open" => 12, "closed" => 27, ...},
|
|
319
|
+
# by_type: {"task" => 30, "bug" => 8, ...},
|
|
320
|
+
# by_priority: {0 => 2, 1 => 8, ...},
|
|
321
|
+
# plans: 3,
|
|
322
|
+
# workflows: 5,
|
|
323
|
+
# dependencies: 34,
|
|
324
|
+
# labels: 12
|
|
325
|
+
# }
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Schema
|
|
329
|
+
|
|
330
|
+
### tasks Table
|
|
331
|
+
|
|
332
|
+
```sql
|
|
333
|
+
CREATE TABLE tasks (
|
|
334
|
+
id TEXT PRIMARY KEY,
|
|
335
|
+
title TEXT NOT NULL,
|
|
336
|
+
description TEXT,
|
|
337
|
+
status TEXT DEFAULT 'open',
|
|
338
|
+
priority INTEGER DEFAULT 2,
|
|
339
|
+
type TEXT DEFAULT 'task',
|
|
340
|
+
assignee TEXT,
|
|
341
|
+
parent_id TEXT,
|
|
342
|
+
created_at TEXT,
|
|
343
|
+
updated_at TEXT,
|
|
344
|
+
closed_at TEXT,
|
|
345
|
+
content_hash TEXT,
|
|
346
|
+
plan BOOLEAN DEFAULT 0,
|
|
347
|
+
source_plan_id TEXT,
|
|
348
|
+
ephemeral BOOLEAN DEFAULT 0,
|
|
349
|
+
notes TEXT
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
CREATE INDEX idx_tasks_status ON tasks(status);
|
|
353
|
+
CREATE INDEX idx_tasks_priority ON tasks(priority);
|
|
354
|
+
CREATE INDEX idx_tasks_type ON tasks(type);
|
|
355
|
+
CREATE INDEX idx_tasks_parent_id ON tasks(parent_id);
|
|
356
|
+
CREATE INDEX idx_tasks_plan ON tasks(plan);
|
|
357
|
+
CREATE INDEX idx_tasks_source_plan_id ON tasks(source_plan_id);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### dependencies Table
|
|
361
|
+
|
|
362
|
+
```sql
|
|
363
|
+
CREATE TABLE dependencies (
|
|
364
|
+
id TEXT PRIMARY KEY,
|
|
365
|
+
source_id TEXT NOT NULL,
|
|
366
|
+
target_id TEXT NOT NULL,
|
|
367
|
+
type TEXT DEFAULT 'blocks',
|
|
368
|
+
created_at TEXT,
|
|
369
|
+
FOREIGN KEY (source_id) REFERENCES tasks(id),
|
|
370
|
+
FOREIGN KEY (target_id) REFERENCES tasks(id),
|
|
371
|
+
UNIQUE(source_id, target_id)
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
CREATE INDEX idx_deps_source ON dependencies(source_id);
|
|
375
|
+
CREATE INDEX idx_deps_target ON dependencies(target_id);
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### labels Table
|
|
379
|
+
|
|
380
|
+
```sql
|
|
381
|
+
CREATE TABLE labels (
|
|
382
|
+
id TEXT PRIMARY KEY,
|
|
383
|
+
task_id TEXT NOT NULL,
|
|
384
|
+
name TEXT NOT NULL,
|
|
385
|
+
created_at TEXT,
|
|
386
|
+
FOREIGN KEY (task_id) REFERENCES tasks(id),
|
|
387
|
+
UNIQUE(task_id, name)
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
CREATE INDEX idx_labels_task_id ON labels(task_id);
|
|
391
|
+
CREATE INDEX idx_labels_name ON labels(name);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### comments Table
|
|
395
|
+
|
|
396
|
+
```sql
|
|
397
|
+
CREATE TABLE comments (
|
|
398
|
+
id TEXT PRIMARY KEY,
|
|
399
|
+
task_id TEXT NOT NULL,
|
|
400
|
+
author TEXT,
|
|
401
|
+
content TEXT NOT NULL,
|
|
402
|
+
created_at TEXT,
|
|
403
|
+
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
CREATE INDEX idx_comments_task_id ON comments(task_id);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Transaction Support
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
db.transaction do
|
|
413
|
+
task1 = db.insert_task(task1)
|
|
414
|
+
task2 = db.insert_task(task2)
|
|
415
|
+
db.add_dependency(task1.id, task2.id)
|
|
416
|
+
end
|
|
417
|
+
# All operations succeed or all fail
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Error Handling
|
|
421
|
+
|
|
422
|
+
```ruby
|
|
423
|
+
begin
|
|
424
|
+
db.find_task("invalid-id")
|
|
425
|
+
rescue TrakFlow::Storage::NotFoundError => e
|
|
426
|
+
puts "Not found: #{e.message}"
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
begin
|
|
430
|
+
db.add_dependency("tf-a", "tf-a")
|
|
431
|
+
rescue TrakFlow::Storage::ValidationError => e
|
|
432
|
+
puts "Invalid: #{e.message}"
|
|
433
|
+
end
|
|
434
|
+
```
|