trekoon 0.3.6 → 0.3.8
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/.agents/skills/trekoon/SKILL.md +198 -73
- package/.agents/skills/trekoon/reference/execution-with-team.md +9 -11
- package/.agents/skills/trekoon/reference/execution.md +26 -9
- package/.agents/skills/trekoon/reference/planning.md +48 -0
- package/README.md +39 -14
- package/docs/quickstart.md +21 -0
- package/package.json +1 -1
- package/src/board/assets/app.js +19 -25
- package/src/board/assets/components/Notice.js +18 -4
- package/src/board/assets/state/actions.js +6 -6
- package/src/board/assets/state/api.js +155 -31
- package/src/board/assets/state/store.js +38 -6
- package/src/board/assets/state/utils.js +123 -30
- package/src/board/routes.ts +397 -54
- package/src/board/server.ts +57 -4
- package/src/board/snapshot.ts +205 -173
- package/src/commands/board.ts +1 -1
- package/src/commands/events.ts +17 -11
- package/src/commands/quickstart.ts +10 -0
- package/src/commands/subtask.ts +2 -2
- package/src/domain/mutation-service.ts +452 -54
- package/src/domain/tracker-domain.ts +185 -7
- package/src/storage/migrations.ts +123 -0
- package/src/storage/path.ts +12 -1
- package/src/storage/schema.ts +18 -1
- package/src/storage/worktree-recovery.ts +12 -6
- package/src/sync/branch-db.ts +12 -1
- package/src/sync/event-writes.ts +47 -7
- package/src/sync/git-context.ts +10 -6
- package/src/sync/service.ts +759 -151
package/src/board/snapshot.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { TrackerDomain } from "../domain/tracker-domain";
|
|
2
2
|
import { type DependencyRecord, type EpicRecord, type SubtaskRecord, type TaskRecord } from "../domain/types";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
readonly title: string;
|
|
6
|
-
readonly description: string;
|
|
7
|
-
readonly text: string;
|
|
8
|
-
}
|
|
4
|
+
type BoardStatus = "todo" | "blocked" | "in_progress" | "done";
|
|
9
5
|
|
|
10
6
|
interface StatusCounts {
|
|
11
7
|
readonly total: number;
|
|
@@ -16,65 +12,63 @@ interface StatusCounts {
|
|
|
16
12
|
readonly other: number;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
interface FlatCounts extends Record<BoardStatus, number> {}
|
|
16
|
+
|
|
17
|
+
export interface BoardSnapshotDependency {
|
|
20
18
|
readonly id: string;
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
23
|
-
readonly
|
|
19
|
+
readonly sourceId: string;
|
|
20
|
+
readonly sourceKind: "task" | "subtask";
|
|
21
|
+
readonly dependsOnId: string;
|
|
22
|
+
readonly dependsOnKind: "task" | "subtask";
|
|
24
23
|
readonly createdAt: number;
|
|
25
24
|
readonly updatedAt: number;
|
|
26
|
-
readonly taskIds: readonly string[];
|
|
27
|
-
readonly counts: {
|
|
28
|
-
readonly tasks: StatusCounts;
|
|
29
|
-
readonly subtasks: StatusCounts;
|
|
30
|
-
};
|
|
31
|
-
readonly search: SearchFields;
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
interface BoardSnapshotSubtask {
|
|
35
28
|
readonly id: string;
|
|
36
|
-
readonly
|
|
29
|
+
readonly kind: "subtask";
|
|
30
|
+
readonly taskId: string;
|
|
37
31
|
readonly title: string;
|
|
38
32
|
readonly description: string;
|
|
39
33
|
readonly status: string;
|
|
34
|
+
readonly owner: string | null;
|
|
40
35
|
readonly createdAt: number;
|
|
41
36
|
readonly updatedAt: number;
|
|
42
|
-
readonly
|
|
37
|
+
readonly blockedBy: readonly string[];
|
|
38
|
+
readonly blocks: readonly string[];
|
|
43
39
|
readonly dependencyIds: readonly string[];
|
|
44
40
|
readonly dependentIds: readonly string[];
|
|
45
|
-
readonly
|
|
46
|
-
readonly subtasks: StatusCounts;
|
|
47
|
-
readonly dependencies: number;
|
|
48
|
-
readonly dependents: number;
|
|
49
|
-
};
|
|
50
|
-
readonly search: SearchFields;
|
|
41
|
+
readonly searchText: string;
|
|
51
42
|
}
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
interface BoardSnapshotTask {
|
|
54
45
|
readonly id: string;
|
|
55
|
-
readonly
|
|
46
|
+
readonly kind: "task";
|
|
47
|
+
readonly epicId: string;
|
|
56
48
|
readonly title: string;
|
|
57
49
|
readonly description: string;
|
|
58
50
|
readonly status: string;
|
|
51
|
+
readonly owner: string | null;
|
|
59
52
|
readonly createdAt: number;
|
|
60
53
|
readonly updatedAt: number;
|
|
54
|
+
readonly blockedBy: readonly string[];
|
|
55
|
+
readonly blocks: readonly string[];
|
|
61
56
|
readonly dependencyIds: readonly string[];
|
|
62
57
|
readonly dependentIds: readonly string[];
|
|
63
|
-
readonly
|
|
64
|
-
|
|
65
|
-
readonly dependents: number;
|
|
66
|
-
};
|
|
67
|
-
readonly search: SearchFields;
|
|
58
|
+
readonly subtasks: readonly BoardSnapshotSubtask[];
|
|
59
|
+
readonly searchText: string;
|
|
68
60
|
}
|
|
69
61
|
|
|
70
|
-
|
|
62
|
+
interface BoardSnapshotEpic {
|
|
71
63
|
readonly id: string;
|
|
72
|
-
readonly
|
|
73
|
-
readonly
|
|
74
|
-
readonly
|
|
75
|
-
readonly dependsOnKind: "task" | "subtask";
|
|
64
|
+
readonly title: string;
|
|
65
|
+
readonly description: string;
|
|
66
|
+
readonly status: string;
|
|
76
67
|
readonly createdAt: number;
|
|
77
68
|
readonly updatedAt: number;
|
|
69
|
+
readonly taskIds: readonly string[];
|
|
70
|
+
readonly counts: FlatCounts;
|
|
71
|
+
readonly searchText: string;
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
export interface BoardSnapshot {
|
|
@@ -91,57 +85,41 @@ export interface BoardSnapshot {
|
|
|
91
85
|
};
|
|
92
86
|
}
|
|
93
87
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (status === "in_progress" || status === "in-progress") {
|
|
104
|
-
return "inProgress";
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (status === "done") {
|
|
108
|
-
return "done";
|
|
109
|
-
}
|
|
88
|
+
interface SnapshotDeltaSelection {
|
|
89
|
+
readonly epicIds?: readonly string[];
|
|
90
|
+
readonly taskIds?: readonly string[];
|
|
91
|
+
readonly subtaskIds?: readonly string[];
|
|
92
|
+
readonly dependencyIds?: readonly string[];
|
|
93
|
+
readonly deletedSubtaskIds?: readonly string[];
|
|
94
|
+
readonly deletedDependencyIds?: readonly string[];
|
|
95
|
+
}
|
|
110
96
|
|
|
97
|
+
function normalizeStatusBucket(status: string): keyof Omit<StatusCounts, "total"> {
|
|
98
|
+
if (status === "todo") return "todo";
|
|
99
|
+
if (status === "blocked") return "blocked";
|
|
100
|
+
if (status === "in_progress" || status === "in-progress") return "inProgress";
|
|
101
|
+
if (status === "done") return "done";
|
|
111
102
|
return "other";
|
|
112
103
|
}
|
|
113
104
|
|
|
114
105
|
function countStatuses(records: readonly { readonly status: string }[]): StatusCounts {
|
|
115
|
-
const counts:
|
|
116
|
-
total: number;
|
|
117
|
-
todo: number;
|
|
118
|
-
blocked: number;
|
|
119
|
-
inProgress: number;
|
|
120
|
-
done: number;
|
|
121
|
-
other: number;
|
|
122
|
-
} = {
|
|
123
|
-
total: records.length,
|
|
124
|
-
todo: 0,
|
|
125
|
-
blocked: 0,
|
|
126
|
-
inProgress: 0,
|
|
127
|
-
done: 0,
|
|
128
|
-
other: 0,
|
|
129
|
-
};
|
|
130
|
-
|
|
106
|
+
const counts = { total: records.length, todo: 0, blocked: 0, inProgress: 0, done: 0, other: 0 };
|
|
131
107
|
for (const record of records) {
|
|
132
|
-
|
|
133
|
-
counts[bucket] += 1;
|
|
108
|
+
counts[normalizeStatusBucket(record.status)] += 1;
|
|
134
109
|
}
|
|
135
|
-
|
|
136
110
|
return counts;
|
|
137
111
|
}
|
|
138
112
|
|
|
139
|
-
function
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
113
|
+
function deriveFlatCounts(records: readonly { readonly status: string }[]): FlatCounts {
|
|
114
|
+
return records.reduce<FlatCounts>(
|
|
115
|
+
(counts, record) => {
|
|
116
|
+
if (record.status === "todo" || record.status === "blocked" || record.status === "in_progress" || record.status === "done") {
|
|
117
|
+
counts[record.status] += 1;
|
|
118
|
+
}
|
|
119
|
+
return counts;
|
|
120
|
+
},
|
|
121
|
+
{ todo: 0, blocked: 0, in_progress: 0, done: 0 },
|
|
122
|
+
);
|
|
145
123
|
}
|
|
146
124
|
|
|
147
125
|
function mapDependency(record: DependencyRecord): BoardSnapshotDependency {
|
|
@@ -156,122 +134,176 @@ function mapDependency(record: DependencyRecord): BoardSnapshotDependency {
|
|
|
156
134
|
};
|
|
157
135
|
}
|
|
158
136
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
137
|
+
function uniqueIds(ids: readonly string[]): string[] {
|
|
138
|
+
return [...new Set(ids.filter((id) => id.length > 0))];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildDependencyIndexes(dependenciesBySourceId: Map<string, readonly DependencyRecord[]>, sourceIds: readonly string[]) {
|
|
142
|
+
const dependencyIdsBySource = new Map<string, string[]>();
|
|
143
|
+
const blockedByIdsBySource = new Map<string, string[]>();
|
|
144
|
+
const dependentIdsByTarget = new Map<string, string[]>();
|
|
145
|
+
const blocksByTarget = new Map<string, string[]>();
|
|
164
146
|
const dependencies: BoardSnapshotDependency[] = [];
|
|
165
147
|
|
|
166
|
-
const
|
|
148
|
+
for (const sourceId of sourceIds) {
|
|
149
|
+
for (const dependency of dependenciesBySourceId.get(sourceId) ?? []) {
|
|
150
|
+
dependencies.push(mapDependency(dependency));
|
|
151
|
+
(dependencyIdsBySource.get(dependency.sourceId) ?? dependencyIdsBySource.set(dependency.sourceId, []).get(dependency.sourceId) ?? []).push(dependency.id);
|
|
152
|
+
(blockedByIdsBySource.get(dependency.sourceId) ?? blockedByIdsBySource.set(dependency.sourceId, []).get(dependency.sourceId) ?? []).push(dependency.dependsOnId);
|
|
153
|
+
(dependentIdsByTarget.get(dependency.dependsOnId) ?? dependentIdsByTarget.set(dependency.dependsOnId, []).get(dependency.dependsOnId) ?? []).push(dependency.id);
|
|
154
|
+
(blocksByTarget.get(dependency.dependsOnId) ?? blocksByTarget.set(dependency.dependsOnId, []).get(dependency.dependsOnId) ?? []).push(dependency.sourceId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { dependencies, dependencyIdsBySource, blockedByIdsBySource, dependentIdsByTarget, blocksByTarget };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function mapSnapshotSubtask(subtask: SubtaskRecord, indexes: ReturnType<typeof buildDependencyIndexes>): BoardSnapshotSubtask {
|
|
162
|
+
return {
|
|
163
|
+
id: subtask.id,
|
|
164
|
+
kind: "subtask",
|
|
165
|
+
taskId: subtask.taskId,
|
|
166
|
+
title: subtask.title,
|
|
167
|
+
description: subtask.description,
|
|
168
|
+
status: subtask.status,
|
|
169
|
+
owner: subtask.owner ?? null,
|
|
170
|
+
createdAt: subtask.createdAt,
|
|
171
|
+
updatedAt: subtask.updatedAt,
|
|
172
|
+
blockedBy: indexes.blockedByIdsBySource.get(subtask.id) ?? [],
|
|
173
|
+
blocks: indexes.blocksByTarget.get(subtask.id) ?? [],
|
|
174
|
+
dependencyIds: indexes.dependencyIdsBySource.get(subtask.id) ?? [],
|
|
175
|
+
dependentIds: indexes.dependentIdsByTarget.get(subtask.id) ?? [],
|
|
176
|
+
searchText: [subtask.title, subtask.description, subtask.status].join(" ").toLowerCase(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function mapSnapshotTask(task: TaskRecord, taskSubtasks: readonly BoardSnapshotSubtask[], indexes: ReturnType<typeof buildDependencyIndexes>): BoardSnapshotTask {
|
|
181
|
+
return {
|
|
182
|
+
id: task.id,
|
|
183
|
+
kind: "task",
|
|
184
|
+
epicId: task.epicId,
|
|
185
|
+
title: task.title,
|
|
186
|
+
description: task.description,
|
|
187
|
+
status: task.status,
|
|
188
|
+
owner: task.owner ?? null,
|
|
189
|
+
createdAt: task.createdAt,
|
|
190
|
+
updatedAt: task.updatedAt,
|
|
191
|
+
blockedBy: indexes.blockedByIdsBySource.get(task.id) ?? [],
|
|
192
|
+
blocks: indexes.blocksByTarget.get(task.id) ?? [],
|
|
193
|
+
dependencyIds: indexes.dependencyIdsBySource.get(task.id) ?? [],
|
|
194
|
+
dependentIds: indexes.dependentIdsByTarget.get(task.id) ?? [],
|
|
195
|
+
subtasks: taskSubtasks,
|
|
196
|
+
searchText: [task.title, task.description, task.status, ...taskSubtasks.map((subtask) => `${subtask.title} ${subtask.description} ${subtask.status}`)].join(" ").toLowerCase(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function mapSnapshotEpic(epic: EpicRecord, epicTasks: readonly BoardSnapshotTask[]): BoardSnapshotEpic {
|
|
201
|
+
return {
|
|
202
|
+
id: epic.id,
|
|
203
|
+
title: epic.title,
|
|
204
|
+
description: epic.description,
|
|
205
|
+
status: epic.status,
|
|
206
|
+
createdAt: epic.createdAt,
|
|
207
|
+
updatedAt: epic.updatedAt,
|
|
208
|
+
taskIds: epicTasks.map((task) => task.id),
|
|
209
|
+
counts: deriveFlatCounts(epicTasks),
|
|
210
|
+
searchText: [epic.title, epic.description, ...epicTasks.map((task) => task.searchText)].join(" ").toLowerCase(),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function buildBoardSnapshotDelta(domain: TrackerDomain, selection: SnapshotDeltaSelection): Record<string, unknown> {
|
|
215
|
+
const epicIds = uniqueIds(selection.epicIds ?? []);
|
|
216
|
+
const requestedTaskIds = uniqueIds(selection.taskIds ?? []);
|
|
217
|
+
const requestedSubtaskIds = uniqueIds(selection.subtaskIds ?? []);
|
|
218
|
+
const requestedDependencyIds = new Set(selection.dependencyIds ?? []);
|
|
219
|
+
const relatedTaskIds = uniqueIds([
|
|
220
|
+
...requestedTaskIds,
|
|
221
|
+
...requestedSubtaskIds.map((subtaskId) => domain.getSubtask(subtaskId)?.taskId ?? ""),
|
|
222
|
+
]);
|
|
223
|
+
const tasks = relatedTaskIds.map((taskId) => domain.getTask(taskId)).filter((task): task is TaskRecord => task !== null);
|
|
224
|
+
const subtasksByTaskId = new Map<string, readonly SubtaskRecord[]>();
|
|
225
|
+
for (const task of tasks) {
|
|
226
|
+
subtasksByTaskId.set(task.id, domain.listSubtasks(task.id));
|
|
227
|
+
}
|
|
228
|
+
const allSubtasks = uniqueIds([
|
|
229
|
+
...requestedSubtaskIds,
|
|
230
|
+
...[...subtasksByTaskId.values()].flatMap((taskSubtasks) => taskSubtasks.map((subtask) => subtask.id)),
|
|
231
|
+
]).map((subtaskId) => domain.getSubtask(subtaskId)).filter((subtask): subtask is SubtaskRecord => subtask !== null);
|
|
232
|
+
const sourceIds = uniqueIds([...tasks.map((task) => task.id), ...allSubtasks.map((subtask) => subtask.id)]);
|
|
233
|
+
const indexes = buildDependencyIndexes(domain.listDependenciesBySourceIds(sourceIds), sourceIds);
|
|
234
|
+
const snapshotSubtasksByTaskId = new Map<string, BoardSnapshotSubtask[]>();
|
|
235
|
+
for (const subtask of allSubtasks) {
|
|
236
|
+
const mappedSubtask = mapSnapshotSubtask(subtask, indexes);
|
|
237
|
+
const taskSubtasks = snapshotSubtasksByTaskId.get(subtask.taskId) ?? [];
|
|
238
|
+
taskSubtasks.push(mappedSubtask);
|
|
239
|
+
snapshotSubtasksByTaskId.set(subtask.taskId, taskSubtasks);
|
|
240
|
+
}
|
|
241
|
+
const snapshotTasks = tasks.map((task) => mapSnapshotTask(task, snapshotSubtasksByTaskId.get(task.id) ?? [], indexes));
|
|
242
|
+
const snapshotEpics = epicIds.map((epicId) => domain.getEpic(epicId)).filter((epic): epic is EpicRecord => epic !== null).map((epic) => mapSnapshotEpic(epic, snapshotTasks.filter((task) => task.epicId === epic.id)));
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
generatedAt: Date.now(),
|
|
246
|
+
epics: snapshotEpics,
|
|
247
|
+
tasks: snapshotTasks.filter((task) => requestedTaskIds.includes(task.id)),
|
|
248
|
+
subtasks: allSubtasks.map((subtask) => mapSnapshotSubtask(subtask, indexes)).filter((subtask) => requestedSubtaskIds.includes(subtask.id)),
|
|
249
|
+
dependencies: indexes.dependencies.filter((dependency) => requestedDependencyIds.has(dependency.id)),
|
|
250
|
+
deletedSubtaskIds: [...(selection.deletedSubtaskIds ?? [])],
|
|
251
|
+
deletedDependencyIds: [...(selection.deletedDependencyIds ?? [])],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function buildBoardSnapshot(domain: TrackerDomain): BoardSnapshot {
|
|
256
|
+
const generatedAt = Date.now();
|
|
257
|
+
const epics = domain.listEpics();
|
|
258
|
+
const tasks = domain.listTasks();
|
|
259
|
+
const subtasks = domain.listSubtasks();
|
|
260
|
+
const sourceIds = [...tasks.map((task) => task.id), ...subtasks.map((subtask) => subtask.id)];
|
|
261
|
+
const dependenciesBySourceId = domain.listDependenciesBySourceIds(sourceIds);
|
|
262
|
+
const subtasksByTaskId = new Map<string, SubtaskRecord[]>();
|
|
263
|
+
const tasksByEpicId = new Map<string, TaskRecord[]>();
|
|
264
|
+
const indexes = buildDependencyIndexes(dependenciesBySourceId, sourceIds);
|
|
265
|
+
|
|
167
266
|
for (const task of tasks) {
|
|
168
|
-
const existing =
|
|
267
|
+
const existing = tasksByEpicId.get(task.epicId) ?? [];
|
|
169
268
|
existing.push(task);
|
|
170
|
-
|
|
269
|
+
tasksByEpicId.set(task.epicId, existing);
|
|
171
270
|
}
|
|
172
271
|
|
|
173
|
-
const subtasksByTask = new Map<string, SubtaskRecord[]>();
|
|
174
272
|
for (const subtask of subtasks) {
|
|
175
|
-
const existing =
|
|
273
|
+
const existing = subtasksByTaskId.get(subtask.taskId) ?? [];
|
|
176
274
|
existing.push(subtask);
|
|
177
|
-
|
|
275
|
+
subtasksByTaskId.set(subtask.taskId, existing);
|
|
178
276
|
}
|
|
179
277
|
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
for (const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
sourceIds.push(dependency.id);
|
|
187
|
-
dependencyIdsBySource.set(dependency.sourceId, sourceIds);
|
|
188
|
-
const dependentIds = dependentIdsByTarget.get(dependency.dependsOnId) ?? [];
|
|
189
|
-
dependentIds.push(dependency.id);
|
|
190
|
-
dependentIdsByTarget.set(dependency.dependsOnId, dependentIds);
|
|
191
|
-
}
|
|
278
|
+
const snapshotSubtasks: BoardSnapshotSubtask[] = subtasks.map((subtask) => mapSnapshotSubtask(subtask, indexes));
|
|
279
|
+
const snapshotSubtasksByTaskId = new Map<string, BoardSnapshotSubtask[]>();
|
|
280
|
+
for (const subtask of snapshotSubtasks) {
|
|
281
|
+
const existing = snapshotSubtasksByTaskId.get(subtask.taskId) ?? [];
|
|
282
|
+
existing.push(subtask);
|
|
283
|
+
snapshotSubtasksByTaskId.set(subtask.taskId, existing);
|
|
192
284
|
}
|
|
193
285
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const dependentIds = dependentIdsByTarget.get(dependency.dependsOnId) ?? [];
|
|
201
|
-
dependentIds.push(dependency.id);
|
|
202
|
-
dependentIdsByTarget.set(dependency.dependsOnId, dependentIds);
|
|
203
|
-
}
|
|
286
|
+
const snapshotTasks: BoardSnapshotTask[] = tasks.map((task) => mapSnapshotTask(task, snapshotSubtasksByTaskId.get(task.id) ?? [], indexes));
|
|
287
|
+
const taskSearchTextByEpicId = new Map<string, string[]>();
|
|
288
|
+
for (const task of snapshotTasks) {
|
|
289
|
+
const existing = taskSearchTextByEpicId.get(task.epicId) ?? [];
|
|
290
|
+
existing.push(task.searchText);
|
|
291
|
+
taskSearchTextByEpicId.set(task.epicId, existing);
|
|
204
292
|
}
|
|
205
293
|
|
|
294
|
+
const snapshotEpics: BoardSnapshotEpic[] = epics.map((epic) => mapSnapshotEpic(epic, snapshotTasks.filter((task) => task.epicId === epic.id)));
|
|
295
|
+
|
|
206
296
|
return {
|
|
207
297
|
generatedAt,
|
|
208
|
-
epics:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
id: epic.id,
|
|
213
|
-
title: epic.title,
|
|
214
|
-
description: epic.description,
|
|
215
|
-
status: epic.status,
|
|
216
|
-
createdAt: epic.createdAt,
|
|
217
|
-
updatedAt: epic.updatedAt,
|
|
218
|
-
taskIds: epicTasks.map((task) => task.id),
|
|
219
|
-
counts: {
|
|
220
|
-
tasks: countStatuses(epicTasks),
|
|
221
|
-
subtasks: countStatuses(epicSubtasks),
|
|
222
|
-
},
|
|
223
|
-
search: buildSearchFields(epic.title, epic.description),
|
|
224
|
-
};
|
|
225
|
-
}),
|
|
226
|
-
tasks: tasks.map((task) => {
|
|
227
|
-
const taskSubtasks = subtasksByTask.get(task.id) ?? [];
|
|
228
|
-
const dependencyIds = dependencyIdsBySource.get(task.id) ?? [];
|
|
229
|
-
const dependentIds = dependentIdsByTarget.get(task.id) ?? [];
|
|
230
|
-
return {
|
|
231
|
-
id: task.id,
|
|
232
|
-
epicId: task.epicId,
|
|
233
|
-
title: task.title,
|
|
234
|
-
description: task.description,
|
|
235
|
-
status: task.status,
|
|
236
|
-
createdAt: task.createdAt,
|
|
237
|
-
updatedAt: task.updatedAt,
|
|
238
|
-
subtaskIds: taskSubtasks.map((subtask) => subtask.id),
|
|
239
|
-
dependencyIds,
|
|
240
|
-
dependentIds,
|
|
241
|
-
counts: {
|
|
242
|
-
subtasks: countStatuses(taskSubtasks),
|
|
243
|
-
dependencies: dependencyIds.length,
|
|
244
|
-
dependents: dependentIds.length,
|
|
245
|
-
},
|
|
246
|
-
search: buildSearchFields(task.title, task.description),
|
|
247
|
-
};
|
|
248
|
-
}),
|
|
249
|
-
subtasks: subtasks.map((subtask) => {
|
|
250
|
-
const dependencyIds = dependencyIdsBySource.get(subtask.id) ?? [];
|
|
251
|
-
const dependentIds = dependentIdsByTarget.get(subtask.id) ?? [];
|
|
252
|
-
return {
|
|
253
|
-
id: subtask.id,
|
|
254
|
-
taskId: subtask.taskId,
|
|
255
|
-
title: subtask.title,
|
|
256
|
-
description: subtask.description,
|
|
257
|
-
status: subtask.status,
|
|
258
|
-
createdAt: subtask.createdAt,
|
|
259
|
-
updatedAt: subtask.updatedAt,
|
|
260
|
-
dependencyIds,
|
|
261
|
-
dependentIds,
|
|
262
|
-
counts: {
|
|
263
|
-
dependencies: dependencyIds.length,
|
|
264
|
-
dependents: dependentIds.length,
|
|
265
|
-
},
|
|
266
|
-
search: buildSearchFields(subtask.title, subtask.description),
|
|
267
|
-
};
|
|
268
|
-
}),
|
|
269
|
-
dependencies,
|
|
298
|
+
epics: snapshotEpics,
|
|
299
|
+
tasks: snapshotTasks,
|
|
300
|
+
subtasks: snapshotSubtasks,
|
|
301
|
+
dependencies: indexes.dependencies,
|
|
270
302
|
counts: {
|
|
271
303
|
epics: countStatuses(epics),
|
|
272
304
|
tasks: countStatuses(tasks),
|
|
273
305
|
subtasks: countStatuses(subtasks),
|
|
274
|
-
dependencies: dependencies.length,
|
|
306
|
+
dependencies: indexes.dependencies.length,
|
|
275
307
|
},
|
|
276
308
|
};
|
|
277
309
|
}
|
package/src/commands/board.ts
CHANGED
|
@@ -121,7 +121,7 @@ export async function runBoard(context: CliContext): Promise<CliResult> {
|
|
|
121
121
|
return okResult({
|
|
122
122
|
command: "board.open",
|
|
123
123
|
human: [
|
|
124
|
-
`Board ready at ${server.
|
|
124
|
+
`Board ready at ${server.fallbackUrl}`,
|
|
125
125
|
launch.launched
|
|
126
126
|
? `Browser launched with ${launch.command}`
|
|
127
127
|
: `Browser launch failed: ${launch.errorMessage ?? "unknown failure"}`,
|
package/src/commands/events.ts
CHANGED
|
@@ -73,17 +73,23 @@ export async function runEvents(context: CliContext): Promise<CliResult> {
|
|
|
73
73
|
archive,
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
return okResult({
|
|
77
|
+
command: "events.prune",
|
|
78
|
+
human: [
|
|
79
|
+
dryRun ? "Dry run complete." : "Prune complete.",
|
|
80
|
+
`Retention days: ${summary.retentionDays}`,
|
|
81
|
+
`Candidates: ${summary.candidateCount}`,
|
|
82
|
+
`Archived: ${summary.archivedCount}`,
|
|
83
|
+
`Deleted: ${summary.deletedCount}`,
|
|
84
|
+
summary.staleCursorCount > 0
|
|
85
|
+
? `Sync guidance: ${summary.staleCursorCount} cursor(s) reference pruned history. Run 'trekoon sync pull --from <branch>' and rebuild if stale cursor hints persist.`
|
|
86
|
+
: "Sync guidance: pruning stayed within retained cursor history.",
|
|
87
|
+
archive
|
|
88
|
+
? "Retention automation: archived copies were kept before deletion."
|
|
89
|
+
: "Retention automation: rerun with --archive to keep retained copies before deletion.",
|
|
90
|
+
].join("\n"),
|
|
91
|
+
data: summary,
|
|
92
|
+
});
|
|
87
93
|
} catch (error: unknown) {
|
|
88
94
|
const busyFailure = sqliteBusyFailure("events.prune", error);
|
|
89
95
|
if (busyFailure !== null) {
|
|
@@ -4,6 +4,16 @@ import { type CliContext, type CliResult } from "../runtime/command-types";
|
|
|
4
4
|
const QUICKSTART_TEXT = [
|
|
5
5
|
"Trekoon quickstart",
|
|
6
6
|
"",
|
|
7
|
+
"Human workflow:",
|
|
8
|
+
" 1. Gather context through discussion, brainstorming, or research.",
|
|
9
|
+
" 2. Run: trekoon plan <goal>",
|
|
10
|
+
" Use this when you want Trekoon to turn the goal into an execution-ready epic.",
|
|
11
|
+
" 3. Run: trekoon <epic-id>",
|
|
12
|
+
" Use this to inspect the epic, next ready work, and blockers before execution.",
|
|
13
|
+
" 4. Run: trekoon <epic-id> execute",
|
|
14
|
+
" Use this when you want the agent to keep working until the epic is done,",
|
|
15
|
+
" all remaining work is blocked, or it needs your input.",
|
|
16
|
+
"",
|
|
7
17
|
"Agents: always use --toon on every command.",
|
|
8
18
|
"Aligned with: .agents/skills/trekoon/SKILL.md",
|
|
9
19
|
"",
|
package/src/commands/subtask.ts
CHANGED
|
@@ -935,12 +935,12 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
935
935
|
}
|
|
936
936
|
case "delete": {
|
|
937
937
|
const subtaskId: string = parsed.positional[1] ?? "";
|
|
938
|
-
mutations.deleteSubtask(subtaskId);
|
|
938
|
+
const result = mutations.deleteSubtask(subtaskId);
|
|
939
939
|
|
|
940
940
|
return okResult({
|
|
941
941
|
command: "subtask.delete",
|
|
942
942
|
human: `Deleted subtask ${subtaskId}`,
|
|
943
|
-
data: { id: subtaskId },
|
|
943
|
+
data: { id: subtaskId, deletedDependencyIds: result.deletedDependencyIds },
|
|
944
944
|
});
|
|
945
945
|
}
|
|
946
946
|
default:
|