trekoon 0.4.1 → 0.4.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.
- package/.agents/skills/trekoon/SKILL.md +97 -765
- package/.agents/skills/trekoon/reference/execution-with-team.md +91 -141
- package/.agents/skills/trekoon/reference/execution.md +188 -159
- package/.agents/skills/trekoon/reference/harness-primitives.md +77 -0
- package/.agents/skills/trekoon/reference/planning.md +213 -213
- package/.agents/skills/trekoon/reference/status-machine.md +21 -0
- package/.agents/skills/trekoon/reference/sync.md +82 -0
- package/README.md +29 -8
- package/docs/ai-agents.md +65 -6
- package/docs/commands.md +149 -5
- package/docs/machine-contracts.md +123 -0
- package/docs/quickstart.md +55 -3
- package/package.json +1 -1
- package/src/board/assets/app.js +47 -13
- package/src/board/assets/components/Component.js +20 -8
- package/src/board/assets/components/Workspace.js +9 -3
- package/src/board/assets/components/helpers.js +4 -0
- package/src/board/assets/runtime/delegation.js +8 -0
- package/src/board/assets/runtime/focus-trap.js +48 -0
- package/src/board/assets/state/actions.js +45 -4
- package/src/board/assets/state/api.js +304 -17
- package/src/board/assets/state/store.js +82 -11
- package/src/board/assets/state/url.js +10 -0
- package/src/board/assets/state/utils.js +2 -1
- package/src/board/event-bus.ts +81 -0
- package/src/board/routes.ts +430 -40
- package/src/board/server.ts +86 -10
- package/src/board/snapshot.ts +6 -0
- package/src/board/wal-watcher.ts +313 -0
- package/src/commands/board.ts +52 -17
- package/src/commands/epic.ts +7 -9
- package/src/commands/error-utils.ts +54 -1
- package/src/commands/help.ts +75 -10
- package/src/commands/migrate.ts +153 -24
- package/src/commands/quickstart.ts +7 -0
- package/src/commands/skills.ts +17 -5
- package/src/commands/subtask.ts +71 -10
- package/src/commands/suggest.ts +6 -13
- package/src/commands/task.ts +137 -88
- package/src/domain/batch-validation.ts +329 -0
- package/src/domain/cascade-planner.ts +412 -0
- package/src/domain/dependency-rules.ts +15 -0
- package/src/domain/mutation-service.ts +842 -187
- package/src/domain/search.ts +113 -0
- package/src/domain/tracker-domain.ts +167 -693
- package/src/domain/types.ts +56 -2
- package/src/export/render-markdown.ts +1 -2
- package/src/index.ts +37 -0
- package/src/runtime/cli-shell.ts +44 -0
- package/src/runtime/daemon.ts +700 -0
- package/src/storage/backup.ts +166 -0
- package/src/storage/database.ts +268 -4
- package/src/storage/migrations.ts +441 -22
- package/src/storage/path.ts +8 -0
- package/src/storage/schema.ts +5 -1
- package/src/sync/event-writes.ts +38 -11
- package/src/sync/git-context.ts +226 -8
- package/src/sync/service.ts +679 -156
package/src/commands/subtask.ts
CHANGED
|
@@ -39,6 +39,7 @@ const SEARCH_OPTIONS = ["fields", "preview"] as const;
|
|
|
39
39
|
const REPLACE_OPTIONS = ["search", "replace", "fields", "preview", "apply"] as const;
|
|
40
40
|
const CREATE_MANY_OPTIONS = ["task", "t", "subtask"] as const;
|
|
41
41
|
const UPDATE_OPTIONS = ["all", "ids", "append", "description", "d", "status", "s", "title", "owner"] as const;
|
|
42
|
+
const CLAIM_OPTIONS = ["owner"] as const;
|
|
42
43
|
const STATUS_CASCADE_UPDATE_STATUSES = ["done", "todo"] as const;
|
|
43
44
|
|
|
44
45
|
function parseIdsOption(rawIds: string | undefined): string[] {
|
|
@@ -892,10 +893,9 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
892
893
|
|
|
893
894
|
const targets = updateAll ? [...domain.listSubtasks()] : ids.map((id) => domain.getSubtaskOrThrow(id));
|
|
894
895
|
const subtasks = targets.map((target) =>
|
|
895
|
-
|
|
896
|
-
status
|
|
897
|
-
|
|
898
|
-
}),
|
|
896
|
+
append !== undefined
|
|
897
|
+
? mutations.appendToSubtaskDescription({ subtaskId: target.id, append, status })
|
|
898
|
+
: mutations.updateSubtask(target.id, { status }),
|
|
899
899
|
);
|
|
900
900
|
|
|
901
901
|
return okResult({
|
|
@@ -921,11 +921,10 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
921
921
|
});
|
|
922
922
|
}
|
|
923
923
|
|
|
924
|
-
const
|
|
925
|
-
append
|
|
926
|
-
?
|
|
927
|
-
:
|
|
928
|
-
const subtask = mutations.updateSubtask(subtaskId, { title, description: nextDescription, status, owner });
|
|
924
|
+
const subtask =
|
|
925
|
+
append !== undefined
|
|
926
|
+
? mutations.appendToSubtaskDescription({ subtaskId, append, status, owner })
|
|
927
|
+
: mutations.updateSubtask(subtaskId, { title, description, status, owner });
|
|
929
928
|
|
|
930
929
|
return okResult({
|
|
931
930
|
command: "subtask.update",
|
|
@@ -933,6 +932,68 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
933
932
|
data: { subtask },
|
|
934
933
|
});
|
|
935
934
|
}
|
|
935
|
+
case "claim": {
|
|
936
|
+
const claimUnknownOption = findUnknownOption(parsed, CLAIM_OPTIONS);
|
|
937
|
+
if (claimUnknownOption !== undefined) {
|
|
938
|
+
return unknownOption("subtask.claim", claimUnknownOption, CLAIM_OPTIONS);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const missingClaimOption = readMissingOptionValue(parsed.missingOptionValues, "owner");
|
|
942
|
+
if (missingClaimOption !== undefined) {
|
|
943
|
+
return failMissingOptionValue("subtask.claim", missingClaimOption);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const subtaskId: string = parsed.positional[1] ?? "";
|
|
947
|
+
if (subtaskId.length === 0) {
|
|
948
|
+
return failResult({
|
|
949
|
+
command: "subtask.claim",
|
|
950
|
+
human: "Provide a subtask id. Usage: trekoon subtask claim <id> --owner <owner>",
|
|
951
|
+
data: { code: "invalid_input" },
|
|
952
|
+
error: {
|
|
953
|
+
code: "invalid_input",
|
|
954
|
+
message: "Missing subtask id",
|
|
955
|
+
},
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const owner: string | undefined = readOption(parsed.options, "owner");
|
|
960
|
+
if (owner === undefined || owner.trim().length === 0) {
|
|
961
|
+
return failResult({
|
|
962
|
+
command: "subtask.claim",
|
|
963
|
+
human: "--owner is required. Usage: trekoon subtask claim <id> --owner <owner>",
|
|
964
|
+
data: { code: "invalid_input", option: "owner" },
|
|
965
|
+
error: {
|
|
966
|
+
code: "invalid_input",
|
|
967
|
+
message: "Missing required option --owner",
|
|
968
|
+
},
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const claimResult = mutations.claimSubtask({ subtaskId, owner });
|
|
973
|
+
|
|
974
|
+
if (claimResult.claimed) {
|
|
975
|
+
return okResult({
|
|
976
|
+
command: "subtask.claim",
|
|
977
|
+
human: `Claimed subtask ${subtaskId} for ${owner}`,
|
|
978
|
+
data: {
|
|
979
|
+
claimed: true,
|
|
980
|
+
currentOwner: claimResult.currentOwner,
|
|
981
|
+
currentStatus: claimResult.currentStatus,
|
|
982
|
+
subtask: claimResult.subtask,
|
|
983
|
+
},
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
return okResult({
|
|
988
|
+
command: "subtask.claim",
|
|
989
|
+
human: `Subtask ${subtaskId} not claimed: status=${claimResult.currentStatus}, owner=${claimResult.currentOwner ?? "none"}`,
|
|
990
|
+
data: {
|
|
991
|
+
claimed: false,
|
|
992
|
+
currentOwner: claimResult.currentOwner,
|
|
993
|
+
currentStatus: claimResult.currentStatus,
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
}
|
|
936
997
|
case "delete": {
|
|
937
998
|
const subtaskId: string = parsed.positional[1] ?? "";
|
|
938
999
|
const result = mutations.deleteSubtask(subtaskId);
|
|
@@ -946,7 +1007,7 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
946
1007
|
default:
|
|
947
1008
|
return failResult({
|
|
948
1009
|
command: "subtask",
|
|
949
|
-
human: "Usage: trekoon subtask <create|create-many|list|search|replace|update|delete>",
|
|
1010
|
+
human: "Usage: trekoon subtask <create|create-many|list|search|replace|update|delete|claim>",
|
|
950
1011
|
data: {
|
|
951
1012
|
args: context.args,
|
|
952
1013
|
},
|
package/src/commands/suggest.ts
CHANGED
|
@@ -44,14 +44,7 @@ function resolveActiveEpic(domain: TrackerDomain, epicId: string | undefined): E
|
|
|
44
44
|
return domain.getEpic(epicId);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
const inProgress = epics.find((epic) => epic.status === "in_progress");
|
|
49
|
-
if (inProgress) {
|
|
50
|
-
return inProgress;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const todo = epics.find((epic) => epic.status === "todo");
|
|
54
|
-
return todo ?? epics[0] ?? null;
|
|
47
|
+
return domain.findActiveEpic();
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
function findInProgressTasks(readiness: TaskReadinessResult): { count: number; first: { id: string; title: string } | null } {
|
|
@@ -76,7 +69,7 @@ function buildSuggestions(
|
|
|
76
69
|
recoveryRequired: boolean,
|
|
77
70
|
syncSummary: SyncStatusSummary,
|
|
78
71
|
readiness: TaskReadinessResult,
|
|
79
|
-
|
|
72
|
+
epicCount: number,
|
|
80
73
|
activeEpic: EpicRecord | null,
|
|
81
74
|
): readonly Suggestion[] {
|
|
82
75
|
const suggestions: Suggestion[] = [];
|
|
@@ -186,7 +179,7 @@ function buildSuggestions(
|
|
|
186
179
|
}
|
|
187
180
|
|
|
188
181
|
// Priority 8: No epics exist
|
|
189
|
-
if (suggestions.length < MAX_SUGGESTIONS &&
|
|
182
|
+
if (suggestions.length < MAX_SUGGESTIONS && epicCount === 0) {
|
|
190
183
|
suggestions.push({
|
|
191
184
|
priority: suggestions.length + 1,
|
|
192
185
|
action: "quickstart",
|
|
@@ -241,7 +234,7 @@ export async function runSuggest(context: CliContext): Promise<CliResult> {
|
|
|
241
234
|
|
|
242
235
|
const syncSummary = resolveSyncStatus(database, context.cwd, DEFAULT_SOURCE_BRANCH);
|
|
243
236
|
const domain = new TrackerDomain(database.db);
|
|
244
|
-
const
|
|
237
|
+
const epicCount = domain.countEpics();
|
|
245
238
|
const activeEpic = resolveActiveEpic(domain, epicId);
|
|
246
239
|
|
|
247
240
|
const readiness = buildTaskReadiness(domain, epicId ?? activeEpic?.id);
|
|
@@ -250,14 +243,14 @@ export async function runSuggest(context: CliContext): Promise<CliResult> {
|
|
|
250
243
|
diagnostics.recoveryRequired,
|
|
251
244
|
syncSummary,
|
|
252
245
|
readiness,
|
|
253
|
-
|
|
246
|
+
epicCount,
|
|
254
247
|
activeEpic,
|
|
255
248
|
);
|
|
256
249
|
|
|
257
250
|
const result: SuggestResult = {
|
|
258
251
|
suggestions,
|
|
259
252
|
context: {
|
|
260
|
-
totalEpics:
|
|
253
|
+
totalEpics: epicCount,
|
|
261
254
|
activeEpic: activeEpic?.id ?? null,
|
|
262
255
|
readyTasks: readiness.summary.readyCount,
|
|
263
256
|
blockedTasks: readiness.summary.blockedCount,
|
package/src/commands/task.ts
CHANGED
|
@@ -47,6 +47,7 @@ const SEARCH_OPTIONS = ["fields", "preview"] as const;
|
|
|
47
47
|
const REPLACE_OPTIONS = ["search", "replace", "fields", "preview", "apply"] as const;
|
|
48
48
|
const CREATE_MANY_OPTIONS = ["epic", "e", "task"] as const;
|
|
49
49
|
const UPDATE_OPTIONS = ["all", "ids", "append", "description", "d", "status", "s", "title", "t", "owner"] as const;
|
|
50
|
+
const CLAIM_OPTIONS = ["owner"] as const;
|
|
50
51
|
const STATUS_CASCADE_UPDATE_STATUSES = ["done", "todo"] as const;
|
|
51
52
|
|
|
52
53
|
function parseIdsOption(rawIds: string | undefined): string[] {
|
|
@@ -1179,10 +1180,9 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
1179
1180
|
|
|
1180
1181
|
const targets = updateAll ? [...domain.listTasks()] : ids.map((id) => domain.getTaskOrThrow(id));
|
|
1181
1182
|
const tasks = targets.map((target) =>
|
|
1182
|
-
|
|
1183
|
-
status
|
|
1184
|
-
|
|
1185
|
-
}),
|
|
1183
|
+
append !== undefined
|
|
1184
|
+
? mutations.appendToTaskDescription({ taskId: target.id, append, status })
|
|
1185
|
+
: mutations.updateTask(target.id, { status }),
|
|
1186
1186
|
);
|
|
1187
1187
|
|
|
1188
1188
|
return okResult({
|
|
@@ -1208,11 +1208,10 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
1208
1208
|
});
|
|
1209
1209
|
}
|
|
1210
1210
|
|
|
1211
|
-
const
|
|
1212
|
-
append
|
|
1213
|
-
?
|
|
1214
|
-
:
|
|
1215
|
-
const task = mutations.updateTask(taskId, { title, description: nextDescription, status, owner });
|
|
1211
|
+
const task =
|
|
1212
|
+
append !== undefined
|
|
1213
|
+
? mutations.appendToTaskDescription({ taskId, append, status, owner })
|
|
1214
|
+
: mutations.updateTask(taskId, { title, description, status, owner });
|
|
1216
1215
|
|
|
1217
1216
|
return okResult({
|
|
1218
1217
|
command: "task.update",
|
|
@@ -1247,98 +1246,148 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
1247
1246
|
});
|
|
1248
1247
|
}
|
|
1249
1248
|
|
|
1250
|
-
if (existingTask.status === "done")
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
//
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
title: item.task.title,
|
|
1302
|
-
status: item.task.status,
|
|
1303
|
-
wasBlockedBy: [taskId],
|
|
1304
|
-
}));
|
|
1305
|
-
|
|
1306
|
-
const nextTree = nextCandidate !== null ? domain.buildTaskTreeDetailed(nextCandidate.task.id) : null;
|
|
1307
|
-
const nextDeps = nextCandidate?.blockerSummary.blockedBy ?? [];
|
|
1308
|
-
|
|
1309
|
-
const readinessStats = {
|
|
1310
|
-
readyCount: readiness.summary.readyCount,
|
|
1311
|
-
blockedCount: readiness.summary.blockedCount,
|
|
1312
|
-
};
|
|
1249
|
+
// Note: the redundant `if (existingTask.status === "done")` pre-check
|
|
1250
|
+
// was removed in the cr-expert hardening pass — `markTaskDoneAtomically`
|
|
1251
|
+
// raises `already_done` itself inside the same transaction that would
|
|
1252
|
+
// perform the flip, eliminating a race window between this read and
|
|
1253
|
+
// the atomic write. The thrown DomainError propagates to the outer
|
|
1254
|
+
// catch and is rendered with the same `code: "already_done"` payload
|
|
1255
|
+
// by `unexpectedFailureResult`.
|
|
1256
|
+
|
|
1257
|
+
// Single-transaction atomic completion: the entire flow (status flip,
|
|
1258
|
+
// event emission, unblocked-diff snapshot) runs inside ONE
|
|
1259
|
+
// `BEGIN IMMEDIATE`/`COMMIT` pair. Bypasses the public transition
|
|
1260
|
+
// checker — this is the documented allowed exception (see
|
|
1261
|
+
// MutationService.markTaskDoneAtomically and docs/machine-contracts.md).
|
|
1262
|
+
// A crash mid-flight rolls the row back to its original status; the
|
|
1263
|
+
// task is never observable in a phantom `in_progress` state.
|
|
1264
|
+
const snapshot = mutations.markTaskDoneAtomically({
|
|
1265
|
+
taskId,
|
|
1266
|
+
computeSnapshot: ({ domain: txDomain, completed, preBlockedReverseDepIds }) => {
|
|
1267
|
+
const openSubtasks = txDomain.getOpenSubtasks(taskId);
|
|
1268
|
+
const readiness = buildTaskReadiness(txDomain, completed.epicId);
|
|
1269
|
+
const nextCandidate = readiness.candidates[0] ?? null;
|
|
1270
|
+
|
|
1271
|
+
const preBlockedIds = new Set<string>(preBlockedReverseDepIds);
|
|
1272
|
+
const unblockedTasks = readiness.candidates
|
|
1273
|
+
.filter((item) => preBlockedIds.has(item.task.id))
|
|
1274
|
+
.map((item) => ({
|
|
1275
|
+
id: item.task.id,
|
|
1276
|
+
kind: "task" as const,
|
|
1277
|
+
title: item.task.title,
|
|
1278
|
+
status: item.task.status,
|
|
1279
|
+
wasBlockedBy: [taskId],
|
|
1280
|
+
}));
|
|
1281
|
+
|
|
1282
|
+
const nextTree = nextCandidate !== null ? txDomain.buildTaskTreeDetailed(nextCandidate.task.id) : null;
|
|
1283
|
+
const nextDeps = nextCandidate?.blockerSummary.blockedBy ?? [];
|
|
1284
|
+
|
|
1285
|
+
return {
|
|
1286
|
+
completed,
|
|
1287
|
+
openSubtaskCount: openSubtasks.length,
|
|
1288
|
+
openSubtaskIds: openSubtasks.map((s) => s.id),
|
|
1289
|
+
unblocked: unblockedTasks,
|
|
1290
|
+
next: nextTree,
|
|
1291
|
+
nextCandidate,
|
|
1292
|
+
nextDeps,
|
|
1293
|
+
readiness: {
|
|
1294
|
+
readyCount: readiness.summary.readyCount,
|
|
1295
|
+
blockedCount: readiness.summary.blockedCount,
|
|
1296
|
+
},
|
|
1297
|
+
};
|
|
1298
|
+
},
|
|
1299
|
+
});
|
|
1313
1300
|
|
|
1314
|
-
const subtaskWarning = openSubtaskCount > 0
|
|
1315
|
-
? `Warning: ${openSubtaskCount} subtask(s) still open.`
|
|
1301
|
+
const subtaskWarning = snapshot.openSubtaskCount > 0
|
|
1302
|
+
? `Warning: ${snapshot.openSubtaskCount} subtask(s) still open.`
|
|
1316
1303
|
: null;
|
|
1317
1304
|
|
|
1318
|
-
let human = `Task ${completed.title} marked done.`;
|
|
1305
|
+
let human = `Task ${snapshot.completed.title} marked done.`;
|
|
1319
1306
|
if (subtaskWarning !== null) {
|
|
1320
1307
|
human += `\n${subtaskWarning}`;
|
|
1321
1308
|
}
|
|
1322
|
-
if (
|
|
1323
|
-
human += `\nUnblocked: ${
|
|
1309
|
+
if (snapshot.unblocked.length > 0) {
|
|
1310
|
+
human += `\nUnblocked: ${snapshot.unblocked.map((t) => t.title).join(", ")}`;
|
|
1324
1311
|
}
|
|
1325
|
-
if (
|
|
1326
|
-
human += `\nNext: ${formatTask(nextCandidate.task)}`;
|
|
1312
|
+
if (snapshot.next !== null && snapshot.nextCandidate !== null) {
|
|
1313
|
+
human += `\nNext: ${formatTask(snapshot.nextCandidate.task)}`;
|
|
1327
1314
|
}
|
|
1328
|
-
human += `\nReadiness: ready=${
|
|
1315
|
+
human += `\nReadiness: ready=${snapshot.readiness.readyCount}, blocked=${snapshot.readiness.blockedCount}.`;
|
|
1329
1316
|
|
|
1330
1317
|
return okResult({
|
|
1331
1318
|
command: "task.done",
|
|
1332
1319
|
human,
|
|
1333
1320
|
data: {
|
|
1334
|
-
completed,
|
|
1335
|
-
openSubtaskCount,
|
|
1336
|
-
openSubtaskIds,
|
|
1321
|
+
completed: snapshot.completed,
|
|
1322
|
+
openSubtaskCount: snapshot.openSubtaskCount,
|
|
1323
|
+
openSubtaskIds: snapshot.openSubtaskIds,
|
|
1337
1324
|
warning: subtaskWarning,
|
|
1338
|
-
unblocked:
|
|
1339
|
-
next:
|
|
1340
|
-
nextDeps,
|
|
1341
|
-
readiness:
|
|
1325
|
+
unblocked: snapshot.unblocked,
|
|
1326
|
+
next: snapshot.next,
|
|
1327
|
+
nextDeps: snapshot.nextDeps,
|
|
1328
|
+
readiness: snapshot.readiness,
|
|
1329
|
+
},
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
case "claim": {
|
|
1333
|
+
const claimUnknownOption = findUnknownOption(parsed, CLAIM_OPTIONS);
|
|
1334
|
+
if (claimUnknownOption !== undefined) {
|
|
1335
|
+
return unknownOption("task.claim", claimUnknownOption, CLAIM_OPTIONS);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const missingClaimOption = readMissingOptionValue(parsed.missingOptionValues, "owner");
|
|
1339
|
+
if (missingClaimOption !== undefined) {
|
|
1340
|
+
return failMissingOptionValue("task.claim", missingClaimOption);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
const taskId: string = parsed.positional[1] ?? "";
|
|
1344
|
+
if (taskId.length === 0) {
|
|
1345
|
+
return failResult({
|
|
1346
|
+
command: "task.claim",
|
|
1347
|
+
human: "Provide a task id. Usage: trekoon task claim <id> --owner <owner>",
|
|
1348
|
+
data: { code: "invalid_input" },
|
|
1349
|
+
error: {
|
|
1350
|
+
code: "invalid_input",
|
|
1351
|
+
message: "Missing task id",
|
|
1352
|
+
},
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const owner: string | undefined = readOption(parsed.options, "owner");
|
|
1357
|
+
if (owner === undefined || owner.trim().length === 0) {
|
|
1358
|
+
return failResult({
|
|
1359
|
+
command: "task.claim",
|
|
1360
|
+
human: "--owner is required. Usage: trekoon task claim <id> --owner <owner>",
|
|
1361
|
+
data: { code: "invalid_input", option: "owner" },
|
|
1362
|
+
error: {
|
|
1363
|
+
code: "invalid_input",
|
|
1364
|
+
message: "Missing required option --owner",
|
|
1365
|
+
},
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const claimResult = mutations.claimTask({ taskId, owner });
|
|
1370
|
+
|
|
1371
|
+
if (claimResult.claimed) {
|
|
1372
|
+
return okResult({
|
|
1373
|
+
command: "task.claim",
|
|
1374
|
+
human: `Claimed task ${taskId} for ${owner}`,
|
|
1375
|
+
data: {
|
|
1376
|
+
claimed: true,
|
|
1377
|
+
currentOwner: claimResult.currentOwner,
|
|
1378
|
+
currentStatus: claimResult.currentStatus,
|
|
1379
|
+
task: claimResult.task,
|
|
1380
|
+
},
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
return okResult({
|
|
1385
|
+
command: "task.claim",
|
|
1386
|
+
human: `Task ${taskId} not claimed: status=${claimResult.currentStatus}, owner=${claimResult.currentOwner ?? "none"}`,
|
|
1387
|
+
data: {
|
|
1388
|
+
claimed: false,
|
|
1389
|
+
currentOwner: claimResult.currentOwner,
|
|
1390
|
+
currentStatus: claimResult.currentStatus,
|
|
1342
1391
|
},
|
|
1343
1392
|
});
|
|
1344
1393
|
}
|
|
@@ -1355,7 +1404,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
1355
1404
|
default:
|
|
1356
1405
|
return failResult({
|
|
1357
1406
|
command: "task",
|
|
1358
|
-
human: "Usage: trekoon task <create|create-many|list|show|ready|next|done|search|replace|update|delete>",
|
|
1407
|
+
human: "Usage: trekoon task <create|create-many|list|show|ready|next|done|search|replace|update|delete|claim>",
|
|
1359
1408
|
data: {
|
|
1360
1409
|
args: context.args,
|
|
1361
1410
|
},
|