trekoon 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -48,10 +48,10 @@ npm i -g trekoon
48
48
  - `trekoon init`
49
49
  - `trekoon help [command]`
50
50
  - `trekoon quickstart`
51
- - `trekoon epic <create|list|show|search|replace|update|delete>`
52
- - `trekoon task <create|list|show|ready|next|search|replace|update|delete>`
53
- - `trekoon subtask <create|list|search|replace|update|delete>`
54
- - `trekoon dep <add|remove|list|reverse>`
51
+ - `trekoon epic <create|expand|list|show|search|replace|update|delete>`
52
+ - `trekoon task <create|create-many|list|show|ready|next|search|replace|update|delete>`
53
+ - `trekoon subtask <create|create-many|list|search|replace|update|delete>`
54
+ - `trekoon dep <add|add-many|remove|list|reverse>`
55
55
  - `trekoon events prune [--dry-run] [--archive] [--retention-days <n>]`
56
56
  - `trekoon migrate <status|rollback> [--to-version <n>]`
57
57
  - `trekoon sync <status|pull|resolve|conflicts>`
@@ -146,6 +146,42 @@ trekoon task list --limit 25
146
146
  trekoon task list --all --view compact
147
147
  ```
148
148
 
149
+ ### 2a) Preferred one-shot epic creation
150
+
151
+ When you already know the epic tree, create the epic, tasks, subtasks, and
152
+ dependencies in one invocation.
153
+
154
+ ```bash
155
+ trekoon epic create \
156
+ --title "Batch command rollout" \
157
+ --description "Ship one-shot planning workflows" \
158
+ --task "task-a|First task|First description|todo" \
159
+ --task "task-b|Second task|Second description|todo" \
160
+ --subtask "@task-a|sub-a|First subtask|Subtask description|todo" \
161
+ --dep "@task-b|@task-a" \
162
+ --dep "@sub-a|@task-a"
163
+ ```
164
+
165
+ Use this when:
166
+
167
+ - the epic does not exist yet
168
+ - later records need to reference earlier created records via `@temp-key`
169
+ - you want one atomic create step and one machine response with mappings/counts
170
+
171
+ Compact machine output adds:
172
+
173
+ ```text
174
+ command: epic.create
175
+ data:
176
+ epic: created epic row
177
+ tasks[]: created tasks in input order
178
+ subtasks[]: created subtasks in input order
179
+ dependencies[]: created dependencies in input order
180
+ result:
181
+ mappings[]: { kind: task|subtask, tempKey, id }
182
+ counts: { tasks, subtasks, dependencies }
183
+ ```
184
+
149
185
  ### 3) Add dependencies
150
186
 
151
187
  ```bash
@@ -153,6 +189,181 @@ trekoon dep add <task-id> <depends-on-id>
153
189
  trekoon dep list <task-id>
154
190
  ```
155
191
 
192
+ ### 3a) Batch planning commands
193
+
194
+ Use compact batch commands when one invocation needs to create or link multiple
195
+ items atomically. Use the single-item commands when you already have persisted
196
+ UUIDs and only need one mutation.
197
+
198
+ #### `task create-many`
199
+
200
+ Create multiple tasks under one epic in declared order.
201
+
202
+ ```bash
203
+ trekoon task create-many \
204
+ --epic <epic-id> \
205
+ --task "seed-api|Design API|Define batch grammar|todo" \
206
+ --task "seed-cli|Wire CLI|Hook parser and output|in_progress"
207
+ ```
208
+
209
+ Compact spec:
210
+
211
+ - `--task <temp-key>|<title>|<description>|<status>`
212
+ - escape `\|`, `\\`, `\n`, `\r`, `\t`
213
+ - repeated `--task` flags are preserved in the exact order provided
214
+ - temp keys are local mapping labels, not persisted IDs
215
+
216
+ Rollback semantics:
217
+
218
+ - Trekoon validates the full batch before inserts
219
+ - duplicate temp keys, empty required fields, or invalid input fail the whole
220
+ command
221
+ - no partial task rows are kept on failure
222
+
223
+ Compact machine output:
224
+
225
+ ```text
226
+ command: task.create-many
227
+ data:
228
+ epicId: <epic-id>
229
+ tasks[]: created task rows in input order
230
+ result:
231
+ mappings[]: { kind: task, tempKey, id }
232
+ ```
233
+
234
+ #### `subtask create-many`
235
+
236
+ Create multiple subtasks under one existing task.
237
+
238
+ ```bash
239
+ trekoon subtask create-many <task-id> \
240
+ --subtask "seed-tests|Write tests|Cover happy path|todo" \
241
+ --subtask "seed-docs|Document flow|Add operator notes|todo"
242
+ ```
243
+
244
+ Equivalent explicit parent form:
245
+
246
+ ```bash
247
+ trekoon subtask create-many \
248
+ --task <task-id> \
249
+ --subtask "seed-tests|Write tests|Cover happy path|todo"
250
+ ```
251
+
252
+ Rules:
253
+
254
+ - positional `<task-id>` or `--task <task-id>` may be used
255
+ - if both are provided, they must be identical or the command fails
256
+ - repeated `--subtask` flags are applied in declared order
257
+
258
+ Rollback semantics:
259
+
260
+ - full batch prevalidation happens before inserts
261
+ - duplicate temp keys, conflicting task ids, or invalid specs abort the whole
262
+ command
263
+ - no partial subtasks are kept on failure
264
+
265
+ Compact machine output:
266
+
267
+ ```text
268
+ command: subtask.create-many
269
+ data:
270
+ taskId: <task-id>
271
+ subtasks[]: created subtask rows in input order
272
+ result:
273
+ mappings[]: { kind: subtask, tempKey, id }
274
+ ```
275
+
276
+ #### `dep add-many`
277
+
278
+ Create multiple dependency edges in one ordered, transactional operation.
279
+
280
+ ```bash
281
+ trekoon dep add-many \
282
+ --dep "<task-b>|<task-a>" \
283
+ --dep "<subtask-c>|<task-b>"
284
+ ```
285
+
286
+ Compact spec:
287
+
288
+ - `--dep <source-ref>|<depends-on-ref>`
289
+ - repeated `--dep` flags are applied in declared order
290
+ - standalone `dep add-many` resolves persisted IDs only
291
+ - `@temp-key` refs are **not** resolved from earlier commands; they are reserved
292
+ for same-invocation workflows such as `epic expand`
293
+
294
+ Rollback semantics:
295
+
296
+ - validation covers the full dependency set before insert
297
+ - missing ids, unresolved `@temp-key` refs, duplicates, or cycles fail the whole
298
+ batch
299
+ - no partial dependency edges are inserted on failure
300
+
301
+ Compact machine output:
302
+
303
+ ```text
304
+ command: dep.add-many
305
+ data:
306
+ dependencies[]: created dependency rows in input order
307
+ result:
308
+ mappings[]: []
309
+ ```
310
+
311
+ #### `epic expand`
312
+
313
+ Expand one existing epic by creating tasks, subtasks, and dependencies in one
314
+ transaction. Use this when the epic already exists and you want to add a linked
315
+ batch later.
316
+
317
+ ```bash
318
+ trekoon epic expand <epic-id> \
319
+ --task "task-api|Design API|Define compact grammar|todo" \
320
+ --task "task-cli|Wire CLI|Hook parser and output|todo" \
321
+ --subtask "@task-api|sub-tests|Write tests|Cover parser cases|todo" \
322
+ --dep "@task-cli|@task-api" \
323
+ --dep "@sub-tests|@task-api"
324
+ ```
325
+
326
+ Compact specs:
327
+
328
+ - `--task <temp-key>|<title>|<description>|<status>`
329
+ - `--subtask <parent-ref>|<temp-key>|<title>|<description>|<status>`
330
+ - `--dep <source-ref>|<depends-on-ref>`
331
+ - `@temp-key` refs may target tasks/subtasks declared earlier in the same
332
+ `epic expand` invocation
333
+
334
+ Background phases:
335
+
336
+ 1. validate all compact specs and duplicate temp keys
337
+ 2. create tasks transactionally
338
+ 3. resolve subtask parent temp keys and create subtasks
339
+ 4. resolve dependency refs and link dependencies
340
+ 5. append task, subtask, then dependency events
341
+ 6. roll back the full expansion if any phase fails
342
+
343
+ Compact machine output:
344
+
345
+ ```text
346
+ command: epic.expand
347
+ data:
348
+ epicId: <epic-id>
349
+ tasks[]: created tasks in input order
350
+ subtasks[]: created subtasks in input order
351
+ dependencies[]: created dependencies in input order
352
+ result:
353
+ mappings[]: { kind: task|subtask, tempKey, id }
354
+ counts: { tasks, subtasks, dependencies }
355
+ ```
356
+
357
+ When to choose which command:
358
+
359
+ - use `task create-many` for sibling tasks under one known epic
360
+ - use `subtask create-many` for sibling subtasks under one known task
361
+ - use `dep add-many` only when every endpoint already has a persisted ID
362
+ - use `epic create` with batch specs when the epic does not exist yet and the
363
+ whole graph is known up front
364
+ - use `epic expand` when the epic already exists and one batch must add linked
365
+ tasks/subtasks/dependencies with `@temp-key` references
366
+
156
367
  ### 4) AI execution loop for agents
157
368
 
158
369
  Run this loop each session to pick next work deterministically:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trekoon",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "AI-first local issue tracker CLI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,11 +1,25 @@
1
+ import {
2
+ COMPACT_TEMP_KEY_PREFIX,
3
+ type CompactEntityRef,
4
+ type CompactEntityIdRef,
5
+ type CompactTempKey,
6
+ type CompactTempKeyRef,
7
+ } from "../domain/types";
8
+
1
9
  export interface ParsedArgs {
2
10
  readonly positional: readonly string[];
3
11
  readonly options: ReadonlyMap<string, string>;
12
+ readonly optionEntries: readonly ParsedOptionEntry[];
4
13
  readonly flags: ReadonlySet<string>;
5
14
  readonly missingOptionValues: ReadonlySet<string>;
6
15
  readonly providedOptions: readonly string[];
7
16
  }
8
17
 
18
+ export interface ParsedOptionEntry {
19
+ readonly key: string;
20
+ readonly value: string;
21
+ }
22
+
9
23
  export const SEARCH_REPLACE_FIELDS = ["title", "description"] as const;
10
24
 
11
25
  export type SearchReplaceField = (typeof SEARCH_REPLACE_FIELDS)[number];
@@ -21,11 +35,18 @@ export interface PreviewApplyModeSelection {
21
35
  readonly conflict: boolean;
22
36
  }
23
37
 
38
+ export interface ParsedCompactFields {
39
+ readonly fields: readonly string[];
40
+ readonly invalidEscape: string | null;
41
+ readonly hasDanglingEscape: boolean;
42
+ }
43
+
24
44
  const LONG_PREFIX = "--";
25
45
 
26
46
  export function parseArgs(args: readonly string[]): ParsedArgs {
27
47
  const positional: string[] = [];
28
48
  const options = new Map<string, string>();
49
+ const optionEntries: ParsedOptionEntry[] = [];
29
50
  const flags = new Set<string>();
30
51
  const missingOptionValues = new Set<string>();
31
52
  const providedOptions: string[] = [];
@@ -51,12 +72,14 @@ export function parseArgs(args: readonly string[]): ParsedArgs {
51
72
  }
52
73
 
53
74
  options.set(key, value);
75
+ optionEntries.push({ key, value });
54
76
  index += 1;
55
77
  }
56
78
 
57
79
  return {
58
80
  positional,
59
81
  options,
82
+ optionEntries,
60
83
  flags,
61
84
  missingOptionValues,
62
85
  providedOptions,
@@ -78,6 +101,11 @@ export function hasFlag(flags: ReadonlySet<string>, ...keys: string[]): boolean
78
101
  return keys.some((key) => flags.has(key));
79
102
  }
80
103
 
104
+ export function readOptions(optionEntries: readonly ParsedOptionEntry[], ...keys: string[]): string[] {
105
+ const allowedKeys = new Set<string>(keys);
106
+ return optionEntries.filter((entry) => allowedKeys.has(entry.key)).map((entry) => entry.value);
107
+ }
108
+
81
109
  export function readMissingOptionValue(
82
110
  missingOptionValues: ReadonlySet<string>,
83
111
  ...keys: string[]
@@ -178,6 +206,90 @@ export function resolvePreviewApplyMode(
178
206
  };
179
207
  }
180
208
 
209
+ export function isValidCompactTempKey(value: string): value is CompactTempKey {
210
+ return /^[A-Za-z][A-Za-z0-9._-]{0,63}$/u.test(value);
211
+ }
212
+
213
+ export function parseCompactFields(rawValue: string): ParsedCompactFields {
214
+ const fields: string[] = [];
215
+ let current = "";
216
+ let escaping = false;
217
+
218
+ for (const character of rawValue) {
219
+ if (escaping) {
220
+ switch (character) {
221
+ case "|":
222
+ current += "|";
223
+ break;
224
+ case "\\":
225
+ current += "\\";
226
+ break;
227
+ case "n":
228
+ current += "\n";
229
+ break;
230
+ case "r":
231
+ current += "\r";
232
+ break;
233
+ case "t":
234
+ current += "\t";
235
+ break;
236
+ default:
237
+ return {
238
+ fields,
239
+ invalidEscape: `\\${character}`,
240
+ hasDanglingEscape: false,
241
+ };
242
+ }
243
+
244
+ escaping = false;
245
+ continue;
246
+ }
247
+
248
+ if (character === "\\") {
249
+ escaping = true;
250
+ continue;
251
+ }
252
+
253
+ if (character === "|") {
254
+ fields.push(current);
255
+ current = "";
256
+ continue;
257
+ }
258
+
259
+ current += character;
260
+ }
261
+
262
+ if (escaping) {
263
+ return {
264
+ fields,
265
+ invalidEscape: null,
266
+ hasDanglingEscape: true,
267
+ };
268
+ }
269
+
270
+ fields.push(current);
271
+ return {
272
+ fields,
273
+ invalidEscape: null,
274
+ hasDanglingEscape: false,
275
+ };
276
+ }
277
+
278
+ export function parseCompactEntityRef(rawValue: string): CompactEntityRef {
279
+ if (rawValue.startsWith(COMPACT_TEMP_KEY_PREFIX)) {
280
+ const tempKey = rawValue.slice(COMPACT_TEMP_KEY_PREFIX.length);
281
+ return {
282
+ kind: "temp_key",
283
+ tempKey,
284
+ } satisfies CompactTempKeyRef;
285
+ }
286
+
287
+ return {
288
+ kind: "id",
289
+ id: rawValue,
290
+ } satisfies CompactEntityIdRef;
291
+ }
292
+
181
293
  function levenshteinDistance(source: string, target: string): number {
182
294
  const sourceLength = source.length;
183
295
  const targetLength = target.length;
@@ -268,3 +380,7 @@ export function readEnumOption<const T extends readonly string[]>(
268
380
 
269
381
  return allowed.includes(value) ? value : undefined;
270
382
  }
383
+
384
+ export function readUnexpectedPositionals(parsed: ParsedArgs, expectedCount: number): readonly string[] {
385
+ return parsed.positional.slice(expectedCount);
386
+ }
@@ -1,43 +1,172 @@
1
- import { parseArgs } from "./arg-parser";
1
+ import {
2
+ findUnknownOption,
3
+ isValidCompactTempKey,
4
+ parseArgs,
5
+ parseCompactEntityRef,
6
+ parseCompactFields,
7
+ readMissingOptionValue,
8
+ readOptions,
9
+ readUnexpectedPositionals,
10
+ suggestOptions,
11
+ } from "./arg-parser";
12
+ import { unexpectedFailureResult } from "./error-utils";
2
13
 
3
14
  import { MutationService } from "../domain/mutation-service";
4
15
  import { TrackerDomain } from "../domain/tracker-domain";
5
- import { DomainError } from "../domain/types";
16
+ import {
17
+ COMPACT_TEMP_KEY_PREFIX,
18
+ type CompactBatchResultContract,
19
+ type CompactDependencySpec,
20
+ type CompactEntityRef,
21
+ } from "../domain/types";
6
22
  import { failResult, okResult } from "../io/output";
7
23
  import { type CliContext, type CliResult } from "../runtime/command-types";
8
- import { openTrekoonDatabase } from "../storage/database";
24
+ import { openTrekoonDatabase, type TrekoonDatabase } from "../storage/database";
9
25
 
10
26
  function failFromError(error: unknown): CliResult {
11
- if (error instanceof DomainError) {
12
- return failResult({
13
- command: "dep",
14
- human: error.message,
15
- data: {
16
- code: error.code,
17
- ...(error.details ?? {}),
18
- },
19
- error: {
20
- code: error.code,
21
- message: error.message,
22
- },
23
- });
24
- }
25
-
26
- return failResult({
27
+ return unexpectedFailureResult(error, {
27
28
  command: "dep",
28
29
  human: "Unexpected dep command failure",
29
- data: {},
30
+ });
31
+ }
32
+
33
+ const ADD_MANY_OPTIONS = ["dep"] as const;
34
+
35
+ function unknownOption(command: string, option: string, allowedOptions: readonly string[]): CliResult {
36
+ const suggestions = suggestOptions(option, allowedOptions).map((suggestion) => `--${suggestion}`);
37
+ const suggestionMessage = suggestions.length > 0 ? ` Did you mean ${suggestions.join(" or ")}?` : "";
38
+ return failResult({
39
+ command,
40
+ human: `Unknown option --${option}.${suggestionMessage}`,
41
+ data: {
42
+ option: `--${option}`,
43
+ allowedOptions: allowedOptions.map((allowedOption) => `--${allowedOption}`),
44
+ suggestions,
45
+ },
46
+ error: {
47
+ code: "unknown_option",
48
+ message: `Unknown option --${option}`,
49
+ },
50
+ });
51
+ }
52
+
53
+ function failMissingOptionValue(command: string, option: string): CliResult {
54
+ return failResult({
55
+ command,
56
+ human: `Option --${option} requires a value.`,
57
+ data: {
58
+ code: "invalid_input",
59
+ option,
60
+ },
61
+ error: {
62
+ code: "invalid_input",
63
+ message: `Option --${option} requires a value`,
64
+ },
65
+ });
66
+ }
67
+
68
+ function failBatchSpec(command: string, human: string, data: Record<string, unknown>): CliResult {
69
+ return failResult({
70
+ command,
71
+ human,
72
+ data,
30
73
  error: {
31
- code: "internal_error",
32
- message: "Unexpected dep command failure",
74
+ code: "invalid_input",
75
+ message: human,
33
76
  },
34
77
  });
35
78
  }
36
79
 
80
+ function failUnexpectedPositionals(command: string, unexpected: readonly string[]): CliResult {
81
+ return failBatchSpec(command, `Unexpected positional arguments: ${unexpected.join(", ")}.`, {
82
+ unexpectedPositionals: unexpected,
83
+ });
84
+ }
85
+
86
+ function validateCompactEntityRef(index: number, rawSpec: string, label: string, reference: CompactEntityRef): CliResult | undefined {
87
+ if (reference.kind === "temp_key" && !isValidCompactTempKey(reference.tempKey)) {
88
+ return failBatchSpec("dep.add-many", `${label} in --dep spec ${index + 1} must use ${COMPACT_TEMP_KEY_PREFIX}<temp-key> with letters, numbers, dot, dash, or underscore.`, {
89
+ option: "dep",
90
+ index,
91
+ rawSpec,
92
+ reference,
93
+ });
94
+ }
95
+
96
+ if (reference.kind === "id" && reference.id.trim().length === 0) {
97
+ return failBatchSpec("dep.add-many", `${label} in --dep spec ${index + 1} is required.`, {
98
+ option: "dep",
99
+ index,
100
+ rawSpec,
101
+ reference,
102
+ });
103
+ }
104
+
105
+ return undefined;
106
+ }
107
+
108
+ function parseDependencySpecs(rawSpecs: readonly string[]): { specs: CompactDependencySpec[]; error?: CliResult } {
109
+ const specs: CompactDependencySpec[] = [];
110
+
111
+ for (const [index, rawSpec] of rawSpecs.entries()) {
112
+ const parsed = parseCompactFields(rawSpec);
113
+ if (parsed.invalidEscape !== null) {
114
+ return {
115
+ specs: [],
116
+ error: failBatchSpec("dep.add-many", `Invalid escape sequence ${parsed.invalidEscape} in --dep spec ${index + 1}.`, {
117
+ option: "dep",
118
+ index,
119
+ rawSpec,
120
+ }),
121
+ };
122
+ }
123
+
124
+ if (parsed.hasDanglingEscape) {
125
+ return {
126
+ specs: [],
127
+ error: failBatchSpec("dep.add-many", `Trailing escape in --dep spec ${index + 1}.`, {
128
+ option: "dep",
129
+ index,
130
+ rawSpec,
131
+ }),
132
+ };
133
+ }
134
+
135
+ if (parsed.fields.length !== 2) {
136
+ return {
137
+ specs: [],
138
+ error: failBatchSpec("dep.add-many", `Dependency specs must use <source-ref>|<depends-on-ref> in --dep spec ${index + 1}.`, {
139
+ option: "dep",
140
+ index,
141
+ rawSpec,
142
+ fields: parsed.fields,
143
+ }),
144
+ };
145
+ }
146
+
147
+ const source = parseCompactEntityRef(parsed.fields[0] ?? "");
148
+ const sourceError = validateCompactEntityRef(index, rawSpec, "Source ref", source);
149
+ if (sourceError !== undefined) {
150
+ return { specs: [], error: sourceError };
151
+ }
152
+
153
+ const dependsOn = parseCompactEntityRef(parsed.fields[1] ?? "");
154
+ const dependsOnError = validateCompactEntityRef(index, rawSpec, "Depends-on ref", dependsOn);
155
+ if (dependsOnError !== undefined) {
156
+ return { specs: [], error: dependsOnError };
157
+ }
158
+
159
+ specs.push({ source, dependsOn });
160
+ }
161
+
162
+ return { specs };
163
+ }
164
+
37
165
  export async function runDep(context: CliContext): Promise<CliResult> {
38
- const database = openTrekoonDatabase(context.cwd);
166
+ let database: TrekoonDatabase | undefined;
39
167
 
40
168
  try {
169
+ database = openTrekoonDatabase(context.cwd);
41
170
  const parsed = parseArgs(context.args);
42
171
  const subcommand: string | undefined = parsed.positional[0];
43
172
  const sourceId: string = parsed.positional[1] ?? "";
@@ -55,6 +184,49 @@ export async function runDep(context: CliContext): Promise<CliResult> {
55
184
  data: { dependency },
56
185
  });
57
186
  }
187
+ case "add-many": {
188
+ const addManyUnknownOption = findUnknownOption(parsed, ADD_MANY_OPTIONS);
189
+ if (addManyUnknownOption !== undefined) {
190
+ return unknownOption("dep.add-many", addManyUnknownOption, ADD_MANY_OPTIONS);
191
+ }
192
+
193
+ const missingAddManyOption = readMissingOptionValue(parsed.missingOptionValues, "dep");
194
+ if (missingAddManyOption !== undefined) {
195
+ return failMissingOptionValue("dep.add-many", missingAddManyOption);
196
+ }
197
+
198
+ const unexpectedPositionals = readUnexpectedPositionals(parsed, 1);
199
+ if (unexpectedPositionals.length > 0) {
200
+ return failUnexpectedPositionals("dep.add-many", unexpectedPositionals);
201
+ }
202
+
203
+ const rawSpecs = readOptions(parsed.optionEntries, "dep");
204
+ if (rawSpecs.length === 0) {
205
+ return failBatchSpec("dep.add-many", "Provide at least one --dep spec.", {
206
+ option: "dep",
207
+ });
208
+ }
209
+
210
+ const specResult = parseDependencySpecs(rawSpecs);
211
+ if (specResult.error !== undefined) {
212
+ return specResult.error;
213
+ }
214
+
215
+ const created = mutations.addDependencyBatch({
216
+ specs: specResult.specs,
217
+ });
218
+ const result: CompactBatchResultContract = created.result;
219
+ return okResult({
220
+ command: "dep.add-many",
221
+ human: `Added ${created.dependencies.length} dependenc${created.dependencies.length === 1 ? "y" : "ies"}: ${created.dependencies
222
+ .map((dependency) => `${dependency.sourceId} -> ${dependency.dependsOnId}`)
223
+ .join("\n")}`,
224
+ data: {
225
+ dependencies: created.dependencies,
226
+ result,
227
+ },
228
+ });
229
+ }
58
230
  case "remove": {
59
231
  const removed: number = mutations.removeDependency(sourceId, dependsOnId);
60
232
 
@@ -108,7 +280,7 @@ export async function runDep(context: CliContext): Promise<CliResult> {
108
280
  default:
109
281
  return failResult({
110
282
  command: "dep",
111
- human: "Usage: trekoon dep <add|remove|list|reverse>",
283
+ human: "Usage: trekoon dep <add|add-many|remove|list|reverse>",
112
284
  data: {
113
285
  args: context.args,
114
286
  },
@@ -121,6 +293,6 @@ export async function runDep(context: CliContext): Promise<CliResult> {
121
293
  } catch (error: unknown) {
122
294
  return failFromError(error);
123
295
  } finally {
124
- database.close();
296
+ database?.close();
125
297
  }
126
298
  }