trekoon 0.2.9 → 0.3.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/.agents/skills/trekoon/SKILL.md +162 -26
- package/README.md +18 -15
- package/docs/ai-agents.md +49 -4
- package/docs/commands.md +90 -16
- package/docs/machine-contracts.md +120 -0
- package/docs/plans/r1-unified-skill-rewrite.md +290 -0
- package/docs/plans/r10-suggest-command-skill-integration.md +152 -0
- package/docs/plans/r9-task-done-diff-skill-integration.md +113 -0
- package/docs/quickstart.md +41 -12
- package/package.json +23 -1
- package/src/board/assets/app.js +1 -0
- package/src/board/assets/components/EpicRow.js +21 -6
- package/src/board/assets/components/EpicsOverview.js +5 -1
- package/src/board/assets/components/Notice.js +19 -12
- package/src/board/assets/components/Workspace.js +16 -5
- package/src/board/assets/components/helpers.js +17 -0
- package/src/board/assets/runtime/clipboard.js +34 -0
- package/src/board/assets/runtime/delegation.js +33 -0
- package/src/board/assets/state/actions.js +68 -0
- package/src/board/assets/state/store.js +1 -0
- package/src/board/assets/styles/board.css +156 -36
- package/src/board/routes.ts +2 -0
- package/src/commands/epic.ts +74 -3
- package/src/commands/session.ts +7 -75
- package/src/commands/subtask.ts +7 -5
- package/src/commands/suggest.ts +283 -0
- package/src/commands/sync-helpers.ts +75 -0
- package/src/commands/task-readiness.ts +8 -20
- package/src/commands/task.ts +59 -3
- package/src/domain/mutation-service.ts +69 -42
- package/src/domain/tracker-domain.ts +151 -22
- package/src/domain/types.ts +12 -0
- package/src/index.ts +1 -1
- package/src/io/output.ts +4 -2
- package/src/runtime/cli-shell.ts +26 -3
- package/src/runtime/command-types.ts +1 -1
- package/src/storage/database.ts +43 -1
- package/src/storage/events-retention.ts +57 -8
- package/src/storage/migrations.ts +58 -3
- package/src/sync/service.ts +101 -24
- package/src/sync/types.ts +1 -0
|
@@ -381,11 +381,165 @@ body.board-scroll-locked {
|
|
|
381
381
|
min-width: 0;
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
.board-epic-
|
|
384
|
+
.board-epic-row__meta-row {
|
|
385
385
|
display: flex;
|
|
386
386
|
flex-wrap: wrap;
|
|
387
387
|
align-items: center;
|
|
388
|
-
gap:
|
|
388
|
+
gap: 6px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.board-copy-btn,
|
|
392
|
+
.board-wh__notes-btn {
|
|
393
|
+
display: inline-flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
justify-content: center;
|
|
396
|
+
gap: 6px;
|
|
397
|
+
min-height: 36px;
|
|
398
|
+
padding: 0 10px;
|
|
399
|
+
border: 1px solid var(--board-border);
|
|
400
|
+
border-radius: 10px;
|
|
401
|
+
background: rgba(255, 255, 255, 0.03);
|
|
402
|
+
color: var(--board-text-muted);
|
|
403
|
+
font-size: 0.75rem;
|
|
404
|
+
font-weight: 600;
|
|
405
|
+
white-space: nowrap;
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
touch-action: manipulation;
|
|
408
|
+
transition:
|
|
409
|
+
border-color 0.15s ease,
|
|
410
|
+
background-color 0.15s ease,
|
|
411
|
+
color 0.15s ease,
|
|
412
|
+
box-shadow 0.15s ease;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.board-copy-btn:hover,
|
|
416
|
+
.board-wh__notes-btn:hover {
|
|
417
|
+
border-color: var(--board-border-strong);
|
|
418
|
+
background: rgba(255, 255, 255, 0.045);
|
|
419
|
+
color: var(--board-text);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.board-copy-btn:focus-visible,
|
|
423
|
+
.board-wh__notes-btn:focus-visible {
|
|
424
|
+
outline: none;
|
|
425
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--board-bg) 78%, transparent), 0 0 0 4px var(--board-border-strong);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.board-copy-btn--active {
|
|
429
|
+
border-color: color-mix(in srgb, var(--board-success) 26%, var(--board-border-strong));
|
|
430
|
+
background: color-mix(in srgb, var(--board-success) 12%, rgba(255, 255, 255, 0.05));
|
|
431
|
+
color: var(--board-text);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.board-copy-btn--active:hover {
|
|
435
|
+
border-color: color-mix(in srgb, var(--board-success) 36%, var(--board-border-strong));
|
|
436
|
+
background: color-mix(in srgb, var(--board-success) 16%, rgba(255, 255, 255, 0.06));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.board-inline-icon {
|
|
440
|
+
display: block;
|
|
441
|
+
width: 16px;
|
|
442
|
+
height: 16px;
|
|
443
|
+
flex-shrink: 0;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.board-inline-icon--sm {
|
|
447
|
+
width: 14px;
|
|
448
|
+
height: 14px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.board-copy-btn--icon {
|
|
452
|
+
min-height: 30px;
|
|
453
|
+
width: 30px;
|
|
454
|
+
padding: 0;
|
|
455
|
+
border-radius: 999px;
|
|
456
|
+
flex-shrink: 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.board-copy-btn--epic-row {
|
|
460
|
+
min-height: 24px;
|
|
461
|
+
width: 24px;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.board-copy-btn--epic-row .board-inline-icon--sm {
|
|
465
|
+
width: 12px;
|
|
466
|
+
height: 12px;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.board-copy-btn__label {
|
|
470
|
+
line-height: 1;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.board-toast-region {
|
|
474
|
+
position: fixed;
|
|
475
|
+
right: 16px;
|
|
476
|
+
bottom: 16px;
|
|
477
|
+
z-index: 30;
|
|
478
|
+
width: min(100vw - 24px, 380px);
|
|
479
|
+
pointer-events: none;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.board-toast {
|
|
483
|
+
display: flex;
|
|
484
|
+
align-items: flex-start;
|
|
485
|
+
gap: 12px;
|
|
486
|
+
width: 100%;
|
|
487
|
+
padding: 12px 14px;
|
|
488
|
+
border: 1px solid var(--board-border);
|
|
489
|
+
border-radius: 18px;
|
|
490
|
+
background: color-mix(in srgb, var(--board-shell) 94%, transparent);
|
|
491
|
+
box-shadow: var(--board-shadow);
|
|
492
|
+
backdrop-filter: blur(18px);
|
|
493
|
+
pointer-events: auto;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.board-toast--success {
|
|
497
|
+
border-color: color-mix(in srgb, var(--board-success) 24%, var(--board-border));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.board-toast--error {
|
|
501
|
+
border-color: color-mix(in srgb, var(--board-danger) 28%, var(--board-border));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.board-toast__icon {
|
|
505
|
+
display: inline-flex;
|
|
506
|
+
align-items: center;
|
|
507
|
+
justify-content: center;
|
|
508
|
+
width: 34px;
|
|
509
|
+
height: 34px;
|
|
510
|
+
border-radius: 999px;
|
|
511
|
+
flex-shrink: 0;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.board-toast__icon--success {
|
|
515
|
+
background: color-mix(in srgb, var(--board-success) 16%, transparent);
|
|
516
|
+
color: color-mix(in srgb, var(--board-success) 78%, white);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.board-toast__icon--error {
|
|
520
|
+
background: color-mix(in srgb, var(--board-danger) 14%, transparent);
|
|
521
|
+
color: color-mix(in srgb, var(--board-danger) 72%, white);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.board-toast__content {
|
|
525
|
+
min-width: 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.board-toast__title {
|
|
529
|
+
margin: 0;
|
|
530
|
+
font-size: 0.72rem;
|
|
531
|
+
font-weight: 700;
|
|
532
|
+
line-height: 1.2;
|
|
533
|
+
letter-spacing: 0.12em;
|
|
534
|
+
text-transform: uppercase;
|
|
535
|
+
color: var(--board-text-soft);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.board-toast__message {
|
|
539
|
+
margin: 4px 0 0;
|
|
540
|
+
font-size: 0.9rem;
|
|
541
|
+
line-height: 1.45;
|
|
542
|
+
color: var(--board-text-muted);
|
|
389
543
|
}
|
|
390
544
|
|
|
391
545
|
.board-epic-row__title {
|
|
@@ -1045,40 +1199,6 @@ body.board-scroll-locked {
|
|
|
1045
1199
|
line-height: 1;
|
|
1046
1200
|
}
|
|
1047
1201
|
|
|
1048
|
-
.board-wh__notes-btn {
|
|
1049
|
-
display: inline-flex;
|
|
1050
|
-
align-items: center;
|
|
1051
|
-
justify-content: center;
|
|
1052
|
-
gap: 6px;
|
|
1053
|
-
min-height: 36px;
|
|
1054
|
-
padding: 0 10px;
|
|
1055
|
-
border: 1px solid var(--board-border);
|
|
1056
|
-
border-radius: 10px;
|
|
1057
|
-
background: rgba(255, 255, 255, 0.03);
|
|
1058
|
-
color: var(--board-text-muted);
|
|
1059
|
-
font-size: 0.75rem;
|
|
1060
|
-
font-weight: 600;
|
|
1061
|
-
white-space: nowrap;
|
|
1062
|
-
cursor: pointer;
|
|
1063
|
-
touch-action: manipulation;
|
|
1064
|
-
transition:
|
|
1065
|
-
border-color 0.15s ease,
|
|
1066
|
-
background-color 0.15s ease,
|
|
1067
|
-
color 0.15s ease,
|
|
1068
|
-
box-shadow 0.15s ease;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
.board-wh__notes-btn:hover {
|
|
1072
|
-
border-color: var(--board-border-strong);
|
|
1073
|
-
background: rgba(255, 255, 255, 0.045);
|
|
1074
|
-
color: var(--board-text);
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
.board-wh__notes-btn:focus-visible {
|
|
1078
|
-
outline: none;
|
|
1079
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--board-bg) 78%, transparent), 0 0 0 4px var(--board-border-strong);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
1202
|
.board-wh__notes-btn--active {
|
|
1083
1203
|
border-color: color-mix(in srgb, var(--board-border-strong) 60%, var(--board-border));
|
|
1084
1204
|
background: color-mix(in srgb, var(--board-accent-soft) 55%, rgba(255, 255, 255, 0.03));
|
package/src/board/routes.ts
CHANGED
|
@@ -229,6 +229,7 @@ export function createBoardApiHandler(context: BoardRouteContext): (request: Req
|
|
|
229
229
|
title: readOptionalString(body, "title"),
|
|
230
230
|
description: readOptionalString(body, "description"),
|
|
231
231
|
status: readOptionalString(body, "status"),
|
|
232
|
+
owner: readOptionalString(body, "owner"),
|
|
232
233
|
});
|
|
233
234
|
return buildMutationResponse(domain, { task });
|
|
234
235
|
}
|
|
@@ -240,6 +241,7 @@ export function createBoardApiHandler(context: BoardRouteContext): (request: Req
|
|
|
240
241
|
title: readOptionalString(body, "title"),
|
|
241
242
|
description: readOptionalString(body, "description"),
|
|
242
243
|
status: readOptionalString(body, "status"),
|
|
244
|
+
owner: readOptionalString(body, "owner"),
|
|
243
245
|
});
|
|
244
246
|
return buildMutationResponse(domain, { subtask });
|
|
245
247
|
}
|
package/src/commands/epic.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
suggestOptions,
|
|
19
19
|
} from "./arg-parser";
|
|
20
20
|
import { unexpectedFailureResult } from "./error-utils";
|
|
21
|
+
import { buildTaskReadiness } from "./task-readiness";
|
|
21
22
|
|
|
22
23
|
import { MutationService } from "../domain/mutation-service";
|
|
23
24
|
import { TrackerDomain } from "../domain/tracker-domain";
|
|
@@ -44,7 +45,7 @@ function formatEpic(epic: EpicRecord): string {
|
|
|
44
45
|
const VIEW_MODES = ["table", "compact", "tree", "detail"] as const;
|
|
45
46
|
const LIST_VIEW_MODES = ["table", "compact"] as const;
|
|
46
47
|
const DEFAULT_LIST_LIMIT = 10;
|
|
47
|
-
const DEFAULT_OPEN_STATUSES = ["in_progress", "
|
|
48
|
+
const DEFAULT_OPEN_STATUSES = ["in_progress", "todo"] as const;
|
|
48
49
|
const CREATE_OPTIONS = ["title", "t", "description", "d", "status", "s", "task", "subtask", "dep"] as const;
|
|
49
50
|
const LIST_OPTIONS = ["status", "s", "limit", "l", "cursor", "all", "view"] as const;
|
|
50
51
|
const SHOW_OPTIONS = ["view", "all"] as const;
|
|
@@ -115,7 +116,7 @@ function formatSearchHuman(matches: readonly SearchEntityMatch[], emptyMessage:
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
function getStatusPriority(status: string): number {
|
|
118
|
-
if (status === "in_progress"
|
|
119
|
+
if (status === "in_progress") {
|
|
119
120
|
return 0;
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -1391,6 +1392,76 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
1391
1392
|
data: { epic },
|
|
1392
1393
|
});
|
|
1393
1394
|
}
|
|
1395
|
+
case "progress": {
|
|
1396
|
+
const epicId: string = parsed.positional[1] ?? "";
|
|
1397
|
+
if (epicId.length === 0) {
|
|
1398
|
+
return failResult({
|
|
1399
|
+
command: "epic.progress",
|
|
1400
|
+
human: "Provide an epic id. Usage: trekoon epic progress <epic-id>",
|
|
1401
|
+
data: { code: "invalid_input" },
|
|
1402
|
+
error: {
|
|
1403
|
+
code: "invalid_input",
|
|
1404
|
+
message: "Missing epic id",
|
|
1405
|
+
},
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const epic = domain.getEpic(epicId);
|
|
1410
|
+
if (!epic) {
|
|
1411
|
+
return failResult({
|
|
1412
|
+
command: "epic.progress",
|
|
1413
|
+
human: `Epic not found: ${epicId}`,
|
|
1414
|
+
data: { code: "not_found", id: epicId },
|
|
1415
|
+
error: {
|
|
1416
|
+
code: "not_found",
|
|
1417
|
+
message: `Epic not found: ${epicId}`,
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
const allTasks = domain.listTasks(epicId);
|
|
1423
|
+
let doneCount = 0;
|
|
1424
|
+
let inProgressCount = 0;
|
|
1425
|
+
let blockedCount = 0;
|
|
1426
|
+
let todoCount = 0;
|
|
1427
|
+
for (const t of allTasks) {
|
|
1428
|
+
if (t.status === "done") doneCount += 1;
|
|
1429
|
+
else if (t.status === "in_progress") inProgressCount += 1;
|
|
1430
|
+
else if (t.status === "blocked") blockedCount += 1;
|
|
1431
|
+
else if (t.status === "todo") todoCount += 1;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const readiness = buildTaskReadiness(domain, epicId);
|
|
1435
|
+
const readyCount = readiness.summary.readyCount;
|
|
1436
|
+
const nextCandidate = readiness.candidates[0] ?? null;
|
|
1437
|
+
|
|
1438
|
+
const nextTask = nextCandidate !== null
|
|
1439
|
+
? { id: nextCandidate.task.id, title: nextCandidate.task.title }
|
|
1440
|
+
: null;
|
|
1441
|
+
|
|
1442
|
+
const total = allTasks.length;
|
|
1443
|
+
let human = `Epic: ${epic.title}\n`;
|
|
1444
|
+
human += `Total: ${total}, Done: ${doneCount}, In Progress: ${inProgressCount}, Blocked: ${blockedCount}, Todo: ${todoCount}, Ready: ${readyCount}`;
|
|
1445
|
+
if (nextTask !== null) {
|
|
1446
|
+
human += `\nNext candidate: ${nextTask.id} | ${nextTask.title}`;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
return okResult({
|
|
1450
|
+
command: "epic.progress",
|
|
1451
|
+
human,
|
|
1452
|
+
data: {
|
|
1453
|
+
epicId: epic.id,
|
|
1454
|
+
title: epic.title,
|
|
1455
|
+
total,
|
|
1456
|
+
doneCount,
|
|
1457
|
+
inProgressCount,
|
|
1458
|
+
blockedCount,
|
|
1459
|
+
todoCount,
|
|
1460
|
+
readyCount,
|
|
1461
|
+
nextCandidate: nextTask,
|
|
1462
|
+
},
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1394
1465
|
case "delete": {
|
|
1395
1466
|
const epicId: string = parsed.positional[1] ?? "";
|
|
1396
1467
|
mutations.deleteEpic(epicId);
|
|
@@ -1404,7 +1475,7 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
1404
1475
|
default:
|
|
1405
1476
|
return failResult({
|
|
1406
1477
|
command: "epic",
|
|
1407
|
-
human: "Usage: trekoon epic <create|expand|list|show|search|replace|update|delete>",
|
|
1478
|
+
human: "Usage: trekoon epic <create|expand|list|show|search|replace|update|delete|progress>",
|
|
1408
1479
|
data: {
|
|
1409
1480
|
args: context.args,
|
|
1410
1481
|
},
|
package/src/commands/session.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { parseArgs, readOption } from "./arg-parser";
|
|
3
2
|
import { unexpectedFailureResult } from "./error-utils";
|
|
3
|
+
import { DEFAULT_SOURCE_BRANCH, resolveSyncStatus } from "./sync-helpers";
|
|
4
4
|
import { buildTaskReadiness, type DependencyBlocker } from "./task-readiness";
|
|
5
5
|
|
|
6
6
|
import { TrackerDomain } from "../domain/tracker-domain";
|
|
7
7
|
import { okResult } from "../io/output";
|
|
8
8
|
import { type CliContext, type CliResult } from "../runtime/command-types";
|
|
9
9
|
import { openTrekoonDatabase, type TrekoonDatabase } from "../storage/database";
|
|
10
|
-
import {
|
|
11
|
-
import { persistGitContext, resolveGitContext } from "../sync/git-context";
|
|
12
|
-
import { type GitContextSnapshot, type SyncStatusSummary } from "../sync/types";
|
|
13
|
-
|
|
14
|
-
const DEFAULT_SOURCE_BRANCH = "main";
|
|
10
|
+
import { type GitContextSnapshot } from "../sync/types";
|
|
15
11
|
|
|
16
12
|
interface SessionReadiness {
|
|
17
13
|
readonly readyCount: number;
|
|
@@ -50,73 +46,6 @@ interface SessionResult {
|
|
|
50
46
|
readonly readiness: SessionReadiness;
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
function countAheadLocal(db: Database, currentBranch: string | null, sourceBranch: string): number {
|
|
54
|
-
if (!currentBranch || currentBranch === sourceBranch) {
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const row = db
|
|
59
|
-
.query(
|
|
60
|
-
`
|
|
61
|
-
SELECT COUNT(*) AS count
|
|
62
|
-
FROM events
|
|
63
|
-
WHERE git_branch = @branch;
|
|
64
|
-
`,
|
|
65
|
-
)
|
|
66
|
-
.get({ "@branch": currentBranch }) as { count: number } | null;
|
|
67
|
-
|
|
68
|
-
return row?.count ?? 0;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function countPendingConflictsLocal(db: Database): number {
|
|
72
|
-
const row = db
|
|
73
|
-
.query("SELECT COUNT(*) AS count FROM sync_conflicts WHERE resolution = 'pending';")
|
|
74
|
-
.get() as { count: number } | null;
|
|
75
|
-
|
|
76
|
-
return row?.count ?? 0;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function loadCursorLocal(
|
|
80
|
-
db: Database,
|
|
81
|
-
worktreePath: string,
|
|
82
|
-
sourceBranch: string,
|
|
83
|
-
): { cursor_token: string } | null {
|
|
84
|
-
return db
|
|
85
|
-
.query(
|
|
86
|
-
`
|
|
87
|
-
SELECT cursor_token
|
|
88
|
-
FROM sync_cursors
|
|
89
|
-
WHERE owner_scope = 'worktree'
|
|
90
|
-
AND owner_worktree_path = ?
|
|
91
|
-
AND source_branch = ?
|
|
92
|
-
LIMIT 1;
|
|
93
|
-
`,
|
|
94
|
-
)
|
|
95
|
-
.get(worktreePath, sourceBranch) as { cursor_token: string } | null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function resolveSyncStatus(
|
|
99
|
-
database: TrekoonDatabase,
|
|
100
|
-
cwd: string,
|
|
101
|
-
sourceBranch: string,
|
|
102
|
-
): SyncStatusSummary {
|
|
103
|
-
const git: GitContextSnapshot = resolveGitContext(cwd);
|
|
104
|
-
persistGitContext(database.db, git);
|
|
105
|
-
|
|
106
|
-
const cursor = loadCursorLocal(database.db, git.worktreePath, sourceBranch);
|
|
107
|
-
const cursorToken: string = cursor?.cursor_token ?? "0:";
|
|
108
|
-
const onSourceBranch: boolean = git.branchName !== null && git.branchName === sourceBranch;
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
sourceBranch,
|
|
112
|
-
ahead: countAheadLocal(database.db, git.branchName, sourceBranch),
|
|
113
|
-
behind: onSourceBranch ? 0 : countBranchEventsSince(database.db, sourceBranch, cursorToken),
|
|
114
|
-
pendingConflicts: countPendingConflictsLocal(database.db),
|
|
115
|
-
sameBranch: onSourceBranch,
|
|
116
|
-
git,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
49
|
|
|
121
50
|
function formatSessionHuman(result: SessionResult): string {
|
|
122
51
|
const lines: string[] = [];
|
|
@@ -173,12 +102,15 @@ export async function runSession(context: CliContext): Promise<CliResult> {
|
|
|
173
102
|
let database: TrekoonDatabase | undefined;
|
|
174
103
|
|
|
175
104
|
try {
|
|
105
|
+
const parsed = parseArgs(context.args);
|
|
106
|
+
const epicId: string | undefined = readOption(parsed.options, "epic");
|
|
107
|
+
|
|
176
108
|
database = openTrekoonDatabase(context.cwd);
|
|
177
109
|
const diagnostics = database.diagnostics;
|
|
178
110
|
|
|
179
111
|
const syncSummary = resolveSyncStatus(database, context.cwd, DEFAULT_SOURCE_BRANCH);
|
|
180
112
|
const domain = new TrackerDomain(database.db);
|
|
181
|
-
const readiness = buildTaskReadiness(domain,
|
|
113
|
+
const readiness = buildTaskReadiness(domain, epicId);
|
|
182
114
|
const topCandidate = readiness.candidates[0] ?? null;
|
|
183
115
|
|
|
184
116
|
let nextTask: NextCandidate | null = null;
|
package/src/commands/subtask.ts
CHANGED
|
@@ -32,13 +32,13 @@ function formatSubtask(subtask: SubtaskRecord): string {
|
|
|
32
32
|
|
|
33
33
|
const VIEW_MODES = ["table", "compact"] as const;
|
|
34
34
|
const DEFAULT_SUBTASK_LIST_LIMIT = 10;
|
|
35
|
-
const DEFAULT_OPEN_SUBTASK_STATUSES = ["in_progress", "
|
|
35
|
+
const DEFAULT_OPEN_SUBTASK_STATUSES = ["in_progress", "todo"] as const;
|
|
36
36
|
const CREATE_OPTIONS = ["task", "t", "title", "description", "d", "status", "s"] as const;
|
|
37
37
|
const LIST_OPTIONS = ["task", "t", "status", "s", "limit", "l", "cursor", "all", "view"] as const;
|
|
38
38
|
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
|
-
const UPDATE_OPTIONS = ["all", "ids", "append", "description", "d", "status", "s", "title"] as const;
|
|
41
|
+
const UPDATE_OPTIONS = ["all", "ids", "append", "description", "d", "status", "s", "title", "owner"] as const;
|
|
42
42
|
const STATUS_CASCADE_UPDATE_STATUSES = ["done", "todo"] as const;
|
|
43
43
|
|
|
44
44
|
function parseIdsOption(rawIds: string | undefined): string[] {
|
|
@@ -113,7 +113,7 @@ function parseStatusCsv(rawStatuses: string | undefined): string[] | undefined {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
function subtaskStatusPriority(status: string): number {
|
|
116
|
-
if (status === "in_progress"
|
|
116
|
+
if (status === "in_progress") {
|
|
117
117
|
return 0;
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -788,7 +788,8 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
788
788
|
readMissingOptionValue(parsed.missingOptionValues, "ids") ??
|
|
789
789
|
readMissingOptionValue(parsed.missingOptionValues, "append") ??
|
|
790
790
|
readMissingOptionValue(parsed.missingOptionValues, "description", "d") ??
|
|
791
|
-
readMissingOptionValue(parsed.missingOptionValues, "status", "s")
|
|
791
|
+
readMissingOptionValue(parsed.missingOptionValues, "status", "s") ??
|
|
792
|
+
readMissingOptionValue(parsed.missingOptionValues, "owner");
|
|
792
793
|
if (missingUpdateOption !== undefined) {
|
|
793
794
|
return failMissingOptionValue("subtask.update", missingUpdateOption);
|
|
794
795
|
}
|
|
@@ -801,6 +802,7 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
801
802
|
const description: string | undefined = readOption(parsed.options, "description", "d");
|
|
802
803
|
const append: string | undefined = readOption(parsed.options, "append");
|
|
803
804
|
const status: string | undefined = readOption(parsed.options, "status", "s");
|
|
805
|
+
const owner: string | undefined = readOption(parsed.options, "owner");
|
|
804
806
|
|
|
805
807
|
if (updateAll && ids.length > 0) {
|
|
806
808
|
return failResult({
|
|
@@ -923,7 +925,7 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
923
925
|
append === undefined
|
|
924
926
|
? description
|
|
925
927
|
: appendLine(domain.getSubtaskOrThrow(subtaskId).description, append);
|
|
926
|
-
const subtask = mutations.updateSubtask(subtaskId, { title, description: nextDescription, status });
|
|
928
|
+
const subtask = mutations.updateSubtask(subtaskId, { title, description: nextDescription, status, owner });
|
|
927
929
|
|
|
928
930
|
return okResult({
|
|
929
931
|
command: "subtask.update",
|