trekoon 0.1.5 → 0.1.6
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 +11 -2
- package/package.json +1 -1
- package/src/commands/dep.ts +5 -3
- package/src/commands/epic.ts +7 -5
- package/src/commands/help.ts +1 -1
- package/src/commands/skills.ts +210 -29
- package/src/commands/subtask.ts +7 -5
- package/src/commands/sync.ts +98 -3
- package/src/commands/task.ts +7 -5
- package/src/domain/mutation-operations.ts +27 -0
- package/src/domain/mutation-service.ts +169 -0
- package/src/sync/service.ts +350 -64
- package/src/sync/types.ts +35 -0
package/src/commands/task.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { hasFlag, parseArgs, parseStrictPositiveInt, readEnumOption, readMissingOptionValue, readOption } from "./arg-parser";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { MutationService } from "../domain/mutation-service";
|
|
4
4
|
import { TrackerDomain } from "../domain/tracker-domain";
|
|
5
|
+
import { DomainError, type TaskRecord } from "../domain/types";
|
|
5
6
|
import { formatHumanTable } from "../io/human-table";
|
|
6
7
|
import { failResult, okResult } from "../io/output";
|
|
7
8
|
import { type CliContext, type CliResult } from "../runtime/command-types";
|
|
@@ -192,6 +193,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
192
193
|
const parsed = parseArgs(context.args);
|
|
193
194
|
const subcommand: string | undefined = parsed.positional[0];
|
|
194
195
|
const domain = new TrackerDomain(database.db);
|
|
196
|
+
const mutations = new MutationService(database.db, context.cwd);
|
|
195
197
|
|
|
196
198
|
switch (subcommand) {
|
|
197
199
|
case "create": {
|
|
@@ -207,7 +209,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
207
209
|
const title: string | undefined = readOption(parsed.options, "title", "t");
|
|
208
210
|
const description: string | undefined = readOption(parsed.options, "description", "d");
|
|
209
211
|
const status: string | undefined = readOption(parsed.options, "status", "s");
|
|
210
|
-
const task =
|
|
212
|
+
const task = mutations.createTask({
|
|
211
213
|
epicId: epicId ?? "",
|
|
212
214
|
title: title ?? "",
|
|
213
215
|
description: description ?? "",
|
|
@@ -482,7 +484,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
482
484
|
|
|
483
485
|
const targets = updateAll ? [...domain.listTasks()] : ids.map((id) => domain.getTaskOrThrow(id));
|
|
484
486
|
const tasks = targets.map((target) =>
|
|
485
|
-
|
|
487
|
+
mutations.updateTask(target.id, {
|
|
486
488
|
status,
|
|
487
489
|
description: append === undefined ? undefined : appendLine(target.description, append),
|
|
488
490
|
}),
|
|
@@ -515,7 +517,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
515
517
|
append === undefined
|
|
516
518
|
? description
|
|
517
519
|
: appendLine(domain.getTaskOrThrow(taskId).description, append);
|
|
518
|
-
const task =
|
|
520
|
+
const task = mutations.updateTask(taskId, { title, description: nextDescription, status });
|
|
519
521
|
|
|
520
522
|
return okResult({
|
|
521
523
|
command: "task.update",
|
|
@@ -525,7 +527,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
525
527
|
}
|
|
526
528
|
case "delete": {
|
|
527
529
|
const taskId: string = parsed.positional[1] ?? "";
|
|
528
|
-
|
|
530
|
+
mutations.deleteTask(taskId);
|
|
529
531
|
|
|
530
532
|
return okResult({
|
|
531
533
|
command: "task.delete",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const ENTITY_OPERATIONS = {
|
|
2
|
+
epic: {
|
|
3
|
+
created: "epic.created",
|
|
4
|
+
updated: "epic.updated",
|
|
5
|
+
deleted: "epic.deleted",
|
|
6
|
+
},
|
|
7
|
+
task: {
|
|
8
|
+
created: "task.created",
|
|
9
|
+
updated: "task.updated",
|
|
10
|
+
deleted: "task.deleted",
|
|
11
|
+
},
|
|
12
|
+
subtask: {
|
|
13
|
+
created: "subtask.created",
|
|
14
|
+
updated: "subtask.updated",
|
|
15
|
+
deleted: "subtask.deleted",
|
|
16
|
+
},
|
|
17
|
+
dependency: {
|
|
18
|
+
added: "dependency.added",
|
|
19
|
+
removed: "dependency.removed",
|
|
20
|
+
},
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export type MutationOperation =
|
|
24
|
+
| (typeof ENTITY_OPERATIONS)["epic"][keyof (typeof ENTITY_OPERATIONS)["epic"]]
|
|
25
|
+
| (typeof ENTITY_OPERATIONS)["task"][keyof (typeof ENTITY_OPERATIONS)["task"]]
|
|
26
|
+
| (typeof ENTITY_OPERATIONS)["subtask"][keyof (typeof ENTITY_OPERATIONS)["subtask"]]
|
|
27
|
+
| (typeof ENTITY_OPERATIONS)["dependency"][keyof (typeof ENTITY_OPERATIONS)["dependency"]];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { type Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import { appendEventWithGitContext } from "../sync/event-writes";
|
|
4
|
+
import { ENTITY_OPERATIONS } from "./mutation-operations";
|
|
5
|
+
import { TrackerDomain } from "./tracker-domain";
|
|
6
|
+
import { type DependencyRecord, type EpicRecord, type SubtaskRecord, type TaskRecord } from "./types";
|
|
7
|
+
|
|
8
|
+
export class MutationService {
|
|
9
|
+
readonly #db: Database;
|
|
10
|
+
readonly #cwd: string;
|
|
11
|
+
readonly #domain: TrackerDomain;
|
|
12
|
+
|
|
13
|
+
constructor(db: Database, cwd: string) {
|
|
14
|
+
this.#db = db;
|
|
15
|
+
this.#cwd = cwd;
|
|
16
|
+
this.#domain = new TrackerDomain(db);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
createEpic(input: { title: string; description: string; status?: string | undefined }): EpicRecord {
|
|
20
|
+
return this.#db.transaction((): EpicRecord => {
|
|
21
|
+
const epic = this.#domain.createEpic(input);
|
|
22
|
+
this.#appendEntityEvent("epic", epic.id, ENTITY_OPERATIONS.epic.created, {
|
|
23
|
+
title: epic.title,
|
|
24
|
+
description: epic.description,
|
|
25
|
+
status: epic.status,
|
|
26
|
+
});
|
|
27
|
+
return epic;
|
|
28
|
+
})();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
updateEpic(
|
|
32
|
+
id: string,
|
|
33
|
+
input: { title?: string | undefined; description?: string | undefined; status?: string | undefined },
|
|
34
|
+
): EpicRecord {
|
|
35
|
+
return this.#db.transaction((): EpicRecord => {
|
|
36
|
+
const epic = this.#domain.updateEpic(id, input);
|
|
37
|
+
this.#appendEntityEvent("epic", epic.id, ENTITY_OPERATIONS.epic.updated, {
|
|
38
|
+
title: epic.title,
|
|
39
|
+
description: epic.description,
|
|
40
|
+
status: epic.status,
|
|
41
|
+
});
|
|
42
|
+
return epic;
|
|
43
|
+
})();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
deleteEpic(id: string): void {
|
|
47
|
+
this.#db.transaction((): void => {
|
|
48
|
+
this.#domain.deleteEpic(id);
|
|
49
|
+
this.#appendEntityEvent("epic", id, ENTITY_OPERATIONS.epic.deleted, {});
|
|
50
|
+
})();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
createTask(input: { epicId: string; title: string; description: string; status?: string | undefined }): TaskRecord {
|
|
54
|
+
return this.#db.transaction((): TaskRecord => {
|
|
55
|
+
const task = this.#domain.createTask(input);
|
|
56
|
+
this.#appendEntityEvent("task", task.id, ENTITY_OPERATIONS.task.created, {
|
|
57
|
+
epic_id: task.epicId,
|
|
58
|
+
title: task.title,
|
|
59
|
+
description: task.description,
|
|
60
|
+
status: task.status,
|
|
61
|
+
});
|
|
62
|
+
return task;
|
|
63
|
+
})();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
updateTask(
|
|
67
|
+
id: string,
|
|
68
|
+
input: { title?: string | undefined; description?: string | undefined; status?: string | undefined },
|
|
69
|
+
): TaskRecord {
|
|
70
|
+
return this.#db.transaction((): TaskRecord => {
|
|
71
|
+
const task = this.#domain.updateTask(id, input);
|
|
72
|
+
this.#appendEntityEvent("task", task.id, ENTITY_OPERATIONS.task.updated, {
|
|
73
|
+
epic_id: task.epicId,
|
|
74
|
+
title: task.title,
|
|
75
|
+
description: task.description,
|
|
76
|
+
status: task.status,
|
|
77
|
+
});
|
|
78
|
+
return task;
|
|
79
|
+
})();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
deleteTask(id: string): void {
|
|
83
|
+
this.#db.transaction((): void => {
|
|
84
|
+
this.#domain.deleteTask(id);
|
|
85
|
+
this.#appendEntityEvent("task", id, ENTITY_OPERATIONS.task.deleted, {});
|
|
86
|
+
})();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
createSubtask(input: {
|
|
90
|
+
taskId: string;
|
|
91
|
+
title: string;
|
|
92
|
+
description?: string | undefined;
|
|
93
|
+
status?: string | undefined;
|
|
94
|
+
}): SubtaskRecord {
|
|
95
|
+
return this.#db.transaction((): SubtaskRecord => {
|
|
96
|
+
const subtask = this.#domain.createSubtask(input);
|
|
97
|
+
this.#appendEntityEvent("subtask", subtask.id, ENTITY_OPERATIONS.subtask.created, {
|
|
98
|
+
task_id: subtask.taskId,
|
|
99
|
+
title: subtask.title,
|
|
100
|
+
description: subtask.description,
|
|
101
|
+
status: subtask.status,
|
|
102
|
+
});
|
|
103
|
+
return subtask;
|
|
104
|
+
})();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
updateSubtask(
|
|
108
|
+
id: string,
|
|
109
|
+
input: { title?: string | undefined; description?: string | undefined; status?: string | undefined },
|
|
110
|
+
): SubtaskRecord {
|
|
111
|
+
return this.#db.transaction((): SubtaskRecord => {
|
|
112
|
+
const subtask = this.#domain.updateSubtask(id, input);
|
|
113
|
+
this.#appendEntityEvent("subtask", subtask.id, ENTITY_OPERATIONS.subtask.updated, {
|
|
114
|
+
task_id: subtask.taskId,
|
|
115
|
+
title: subtask.title,
|
|
116
|
+
description: subtask.description,
|
|
117
|
+
status: subtask.status,
|
|
118
|
+
});
|
|
119
|
+
return subtask;
|
|
120
|
+
})();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
deleteSubtask(id: string): void {
|
|
124
|
+
this.#db.transaction((): void => {
|
|
125
|
+
this.#domain.deleteSubtask(id);
|
|
126
|
+
this.#appendEntityEvent("subtask", id, ENTITY_OPERATIONS.subtask.deleted, {});
|
|
127
|
+
})();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
addDependency(sourceId: string, dependsOnId: string): DependencyRecord {
|
|
131
|
+
return this.#db.transaction((): DependencyRecord => {
|
|
132
|
+
const dependency = this.#domain.addDependency(sourceId, dependsOnId);
|
|
133
|
+
this.#appendEntityEvent("dependency", dependency.id, ENTITY_OPERATIONS.dependency.added, {
|
|
134
|
+
source_id: dependency.sourceId,
|
|
135
|
+
source_kind: dependency.sourceKind,
|
|
136
|
+
depends_on_id: dependency.dependsOnId,
|
|
137
|
+
depends_on_kind: dependency.dependsOnKind,
|
|
138
|
+
});
|
|
139
|
+
return dependency;
|
|
140
|
+
})();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
removeDependency(sourceId: string, dependsOnId: string): number {
|
|
144
|
+
return this.#db.transaction((): number => {
|
|
145
|
+
const removed = this.#domain.removeDependency(sourceId, dependsOnId);
|
|
146
|
+
if (removed > 0) {
|
|
147
|
+
this.#appendEntityEvent("dependency", `${sourceId}->${dependsOnId}`, ENTITY_OPERATIONS.dependency.removed, {
|
|
148
|
+
source_id: sourceId,
|
|
149
|
+
depends_on_id: dependsOnId,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return removed;
|
|
153
|
+
})();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#appendEntityEvent(
|
|
157
|
+
entityKind: "epic" | "task" | "subtask" | "dependency",
|
|
158
|
+
entityId: string,
|
|
159
|
+
operation: string,
|
|
160
|
+
fields: Record<string, unknown>,
|
|
161
|
+
): void {
|
|
162
|
+
appendEventWithGitContext(this.#db, this.#cwd, {
|
|
163
|
+
entityKind,
|
|
164
|
+
entityId,
|
|
165
|
+
operation,
|
|
166
|
+
fields,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|