trekoon 0.1.7 → 0.1.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.
@@ -1,8 +1,10 @@
1
1
  import { okResult } from "../io/output";
2
2
  import { type CliContext, type CliResult } from "../runtime/command-types";
3
+ import { CLI_VERSION } from "../runtime/version";
3
4
 
4
5
  const ROOT_HELP = [
5
6
  "Trekoon - AI-first local issue tracker",
7
+ `Version: ${CLI_VERSION}`,
6
8
  "",
7
9
  "Usage:",
8
10
  " trekoon [global-options] <command> [command-options]",
@@ -15,7 +17,7 @@ const ROOT_HELP = [
15
17
  "",
16
18
  "Commands:",
17
19
  " init Initialize .trekoon storage and local DB",
18
- " quickstart Show workflow + where to see task descriptions",
20
+ " quickstart Show AI execution loop + task detail workflow",
19
21
  " wipe Remove local Trekoon state (requires --yes)",
20
22
  " epic Epic lifecycle commands",
21
23
  " task Task lifecycle commands",
@@ -24,25 +26,260 @@ const ROOT_HELP = [
24
26
  " events Event retention and cleanup commands",
25
27
  " migrate Migration status and rollback commands",
26
28
  " sync Cross-branch sync commands",
27
- " skills Project-local skill install/link commands",
29
+ " skills Project-local skill install/update/link",
30
+ ].join("\n");
31
+
32
+ const INIT_HELP = [
33
+ "Usage: trekoon init [--json|--toon]",
34
+ "",
35
+ "Purpose:",
36
+ " Initialize local Trekoon storage (.trekoon) and database.",
37
+ "",
38
+ "Examples:",
39
+ " trekoon init",
40
+ " trekoon --json init",
41
+ ].join("\n");
42
+
43
+ const QUICKSTART_HELP = [
44
+ "Usage: trekoon quickstart [--json|--toon]",
45
+ "",
46
+ "Purpose:",
47
+ " Show the canonical Trekoon AI execution loop and task-detail workflow.",
48
+ "",
49
+ "Flow:",
50
+ " 1) trekoon --toon sync status",
51
+ " 2) trekoon --toon task ready --limit 5",
52
+ " 3) trekoon --toon task next",
53
+ " 4) trekoon --toon dep reverse <task-or-subtask-id>",
54
+ " 5) trekoon --toon task update <task-id> --status in_progress",
55
+ "",
56
+ "Examples:",
57
+ " trekoon quickstart",
58
+ " trekoon --toon quickstart",
59
+ ].join("\n");
60
+
61
+ const WIPE_HELP = [
62
+ "Usage: trekoon wipe --yes [--json|--toon]",
63
+ "",
64
+ "Purpose:",
65
+ " Remove local Trekoon state for the current repository.",
66
+ "",
67
+ "Options:",
68
+ " --yes Required safety confirmation.",
69
+ "",
70
+ "Examples:",
71
+ " trekoon wipe --yes",
72
+ ].join("\n");
73
+
74
+ const EPIC_HELP = [
75
+ "Usage: trekoon epic <create|list|show|update|delete> [options]",
76
+ "",
77
+ "List behavior:",
78
+ " Defaults:",
79
+ " - Open statuses only: in_progress, in-progress, todo",
80
+ " - Limit: 10",
81
+ " Flags:",
82
+ " --status <csv> | --limit <n> | --cursor <n> | --all | --view table|compact",
83
+ " Pagination:",
84
+ " - --cursor is offset-like",
85
+ " - Machine modes expose meta.pagination.hasMore / nextCursor",
86
+ " Constraints:",
87
+ " - --all is mutually exclusive with --status, --limit, and --cursor",
88
+ "",
89
+ "Show behavior:",
90
+ " Views:",
91
+ " - compact: epic summary",
92
+ " - tree: hierarchy",
93
+ " - detail: descriptions",
94
+ " Machine default:",
95
+ " - With --all, machine modes default to detail",
96
+ "",
97
+ "Update behavior:",
98
+ " Bulk target flags:",
99
+ " --all | --ids <csv>",
100
+ " Bulk fields:",
101
+ " --append <text> and/or --status <status>",
102
+ ].join("\n");
103
+
104
+ const TASK_HELP = [
105
+ "Usage: trekoon task <create|list|show|ready|next|update|delete> [options]",
106
+ "",
107
+ "List behavior:",
108
+ " Defaults:",
109
+ " - Open statuses only: in_progress, in-progress, todo",
110
+ " - Limit: 10",
111
+ " Flags:",
112
+ " --status <csv> | --limit <n> | --cursor <n> | --all | --view table|compact",
113
+ " Pagination:",
114
+ " - --cursor is offset-like",
115
+ " - Machine modes expose meta.pagination.hasMore / nextCursor",
116
+ " Constraints:",
117
+ " - --all is mutually exclusive with --status, --limit, and --cursor",
118
+ "",
119
+ "Show behavior:",
120
+ " Views:",
121
+ " - compact: task summary",
122
+ " - tree: hierarchy",
123
+ " - detail: descriptions",
124
+ " Machine default:",
125
+ " - With --all, machine modes default to detail",
126
+ "",
127
+ "Ready/Next behavior:",
128
+ " ready:",
129
+ " - Returns deterministic unblocked candidates",
130
+ " - Sort order: status, blockers, createdAt, id",
131
+ " - Options: --limit <n>, --epic <id>",
132
+ " next:",
133
+ " - Returns top ready candidate",
134
+ " - Option: --epic <id>",
135
+ "",
136
+ "Update behavior:",
137
+ " Bulk target flags:",
138
+ " --all | --ids <csv>",
139
+ " Bulk fields:",
140
+ " --append <text> and/or --status <status>",
141
+ ].join("\n");
142
+
143
+ const SUBTASK_HELP = [
144
+ "Usage: trekoon subtask <create|list|update|delete> [options]",
145
+ "",
146
+ "List behavior:",
147
+ " Defaults:",
148
+ " - Open statuses only: in_progress, in-progress, todo",
149
+ " - Limit: 10",
150
+ " Flags:",
151
+ " --task <id> | --status <csv> | --limit <n> | --cursor <n> | --all | --view table|compact",
152
+ " Pagination:",
153
+ " - --cursor is offset-like",
154
+ " - Machine modes expose meta.pagination.hasMore / nextCursor",
155
+ " Constraints:",
156
+ " - --all is mutually exclusive with --status, --limit, and --cursor",
157
+ "",
158
+ "Update behavior:",
159
+ " Bulk target flags:",
160
+ " --all | --ids <csv>",
161
+ " Bulk fields:",
162
+ " --append <text> and/or --status <status>",
163
+ ].join("\n");
164
+
165
+ const DEP_HELP = [
166
+ "Usage: trekoon dep <add|remove|list|reverse> [options]",
167
+ "",
168
+ "Subcommands:",
169
+ " add <source-id> <depends-on-id>",
170
+ " Create dependency edge: source depends on depends-on.",
171
+ " remove <source-id> <depends-on-id>",
172
+ " Remove one dependency edge if it exists.",
173
+ " list <source-id>",
174
+ " Show direct dependencies for a node.",
175
+ " reverse <target-id>",
176
+ " Show downstream nodes blocked by target (with distance).",
177
+ "",
178
+ "Examples:",
179
+ " trekoon dep add <task-a> <task-b>",
180
+ " trekoon dep remove <task-a> <task-b>",
181
+ " trekoon dep list <task-a>",
182
+ " trekoon dep reverse <task-b>",
183
+ ].join("\n");
184
+
185
+ const EVENTS_HELP = [
186
+ "Usage: trekoon events prune [--dry-run] [--archive] [--retention-days <n>]",
187
+ "",
188
+ "Purpose:",
189
+ " Manage retention for internal sync event log rows.",
190
+ "",
191
+ "Options:",
192
+ " --dry-run Preview candidate/archive/delete counts only.",
193
+ " --archive Copy pruned rows to event_archive before delete.",
194
+ " --retention-days <n> Keep last n days (positive integer, default 90).",
195
+ "",
196
+ "Examples:",
197
+ " trekoon events prune --dry-run",
198
+ " trekoon events prune --retention-days 30",
199
+ " trekoon events prune --archive",
200
+ ].join("\n");
201
+
202
+ const MIGRATE_HELP = [
203
+ "Usage: trekoon migrate <status|rollback> [--to-version <n>]",
204
+ "",
205
+ "Subcommands:",
206
+ " status",
207
+ " Show current schema version, latest version, and pending count.",
208
+ " rollback [--to-version <n>]",
209
+ " Roll back migrations; default target is one version back.",
210
+ "",
211
+ "Examples:",
212
+ " trekoon migrate status",
213
+ " trekoon migrate rollback",
214
+ " trekoon migrate rollback --to-version 1",
215
+ ].join("\n");
216
+
217
+ const SYNC_HELP = [
218
+ "Usage: trekoon sync <status|pull|resolve|conflicts> [options]",
219
+ "",
220
+ "Subcommands:",
221
+ " status [--from <branch>]",
222
+ " Show ahead/behind counts and pending conflicts vs source branch (default: main).",
223
+ " pull --from <branch>",
224
+ " Pull and apply upstream tracker events from a source branch.",
225
+ " conflicts list [--mode pending|all]",
226
+ " List sync conflicts (default mode: pending).",
227
+ " conflicts show <conflict-id>",
228
+ " Show full details for one conflict.",
229
+ " resolve <conflict-id> --use ours|theirs",
230
+ " Resolve a pending conflict by selecting ours or theirs.",
231
+ "",
232
+ "Examples:",
233
+ " trekoon sync status",
234
+ " trekoon sync status --from main",
235
+ " trekoon sync pull --from main",
236
+ " trekoon sync conflicts list",
237
+ " trekoon sync conflicts list --mode all",
238
+ " trekoon sync conflicts show <conflict-id>",
239
+ " trekoon sync resolve <conflict-id> --use ours",
240
+ ].join("\n");
241
+
242
+ const SKILLS_HELP = [
243
+ "Usage:",
244
+ " trekoon skills install [--link --editor opencode|claude|pi] [--to <path>] [--allow-outside-repo]",
245
+ " trekoon skills update",
246
+ "",
247
+ "Purpose:",
248
+ " Install or refresh the project-local Trekoon skill asset.",
249
+ "",
250
+ "Install behavior:",
251
+ " - Always installs canonical file to:",
252
+ " <cwd>/.agents/skills/trekoon/SKILL.md",
253
+ " - Use --link to also create an editor symlink named 'trekoon'.",
254
+ " - --editor is required when --link is used (opencode|claude|pi).",
255
+ " - --to overrides the symlink root for --link only.",
256
+ " - Without --allow-outside-repo, link targets must resolve inside repo.",
257
+ " - --allow-outside-repo requires --link and disables that boundary check.",
258
+ "",
259
+ "Update behavior:",
260
+ " - Refreshes canonical SKILL file in the install path above.",
261
+ " - Reports default link states for opencode/claude/pi.",
262
+ "",
263
+ "Examples:",
264
+ " trekoon skills install",
265
+ " trekoon skills install --link --editor opencode",
266
+ " trekoon skills install --link --editor claude --to .claude/skills",
267
+ " trekoon skills install --link --editor pi --to ../shared/skills --allow-outside-repo",
268
+ " trekoon skills update",
28
269
  ].join("\n");
29
270
 
30
271
  const COMMAND_HELP: Record<string, string> = {
31
- init: "Usage: trekoon init [--json|--toon]",
32
- quickstart: "Usage: trekoon quickstart [--json|--toon]",
33
- wipe: "Usage: trekoon wipe --yes [--json|--toon]",
34
- epic:
35
- "Usage: trekoon epic <subcommand> [options] (list defaults: open statuses + limit 10; list flags: --status <csv> | --limit <n> | --all | --view table|compact; show: compact=epic summary, tree=hierarchy, detail=descriptions, and --all defaults to detail in machine modes; update bulk flags: --all | --ids <csv> with --append <text> and/or --status <status>)",
36
- task:
37
- "Usage: trekoon task <subcommand> [options] (list defaults: open statuses + limit 10; list flags: --status <csv> | --limit <n> | --all | --view table|compact; show: compact=task summary, tree=hierarchy, detail=descriptions, and --all defaults to detail in machine modes; update bulk flags: --all | --ids <csv> with --append <text> and/or --status <status>)",
38
- subtask:
39
- "Usage: trekoon subtask <subcommand> [options] (list defaults: open statuses + limit 10; list flags: --task <id> | --status <csv> | --limit <n> | --all | --view table|compact; update bulk flags: --all | --ids <csv> with --append <text> and/or --status <status>)",
40
- dep: "Usage: trekoon dep <subcommand> [options]",
41
- events: "Usage: trekoon events prune [--dry-run] [--archive] [--retention-days <n>]",
42
- migrate: "Usage: trekoon migrate <status|rollback> [--to-version <n>]",
43
- sync: "Usage: trekoon sync <subcommand> [options]",
44
- skills:
45
- "Usage: trekoon skills install [--link --editor opencode|claude|pi] [--to <path>] [--allow-outside-repo] | trekoon skills update (--to sets symlink root for --link only; install path always <cwd>/.agents/skills/trekoon/SKILL.md; links must resolve inside repo unless --allow-outside-repo is set; update refreshes canonical SKILL and reports default link states)",
272
+ init: INIT_HELP,
273
+ quickstart: QUICKSTART_HELP,
274
+ wipe: WIPE_HELP,
275
+ epic: EPIC_HELP,
276
+ task: TASK_HELP,
277
+ subtask: SUBTASK_HELP,
278
+ dep: DEP_HELP,
279
+ events: EVENTS_HELP,
280
+ migrate: MIGRATE_HELP,
281
+ sync: SYNC_HELP,
282
+ skills: SKILLS_HELP,
46
283
  help: "Usage: trekoon help [command] [--json|--toon]",
47
284
  };
48
285
 
@@ -64,6 +301,7 @@ export async function runHelp(context: CliContext): Promise<CliResult> {
64
301
  data: {
65
302
  topic,
66
303
  text,
304
+ version: CLI_VERSION,
67
305
  },
68
306
  });
69
307
  }
@@ -4,34 +4,69 @@ import { type CliContext, type CliResult } from "../runtime/command-types";
4
4
  const QUICKSTART_TEXT = [
5
5
  "Trekoon quickstart",
6
6
  "",
7
+ "This quickstart is aligned with .agents/skills/trekoon/SKILL.md.",
8
+ "For agents: always use --toon for every command.",
9
+ "",
7
10
  "1) Local DB and worktree model",
8
11
  "- Every worktree stores tracker state at .trekoon/trekoon.db.",
9
12
  "- This DB stays local; it is not merged by Git automatically.",
10
13
  "",
11
- "2) Pre-merge sync flow",
12
- "- Run: trekoon sync status",
13
- "- Pull upstream tracker events: trekoon sync pull --from main",
14
- "- Resolve conflicts if needed: trekoon sync resolve <id> --use ours",
15
- "- Run sync status again before opening or merging a PR.",
14
+ "2) Agent session-start checklist (verify setup + current state)",
15
+ "- 1. Verify tracker exists (or initialize once): trekoon --toon init",
16
+ "- 2. Check sync baseline: trekoon --toon sync status",
17
+ "- 3. Load active context: trekoon --toon epic list",
18
+ "- 4. Load active tasks: trekoon --toon task list",
19
+ "- 5. Load deterministic candidates: trekoon --toon task ready --limit 5",
20
+ "- 6. If blocked, inspect downstream impact: trekoon --toon dep reverse <task-or-subtask-id>",
21
+ "",
22
+ "3) AI execution loop (deterministic, dependency-aware)",
23
+ "- 1. Sync branch/worktree state: trekoon --toon sync status",
24
+ "- 2. Select ready work: trekoon --toon task ready --limit 5",
25
+ "- 3. Pick top candidate when needed: trekoon --toon task next",
26
+ "- 4. Check downstream blockers: trekoon --toon dep reverse <task-or-subtask-id>",
27
+ "- 5. Claim work and update status: trekoon --toon task update <task-id> --status in_progress",
28
+ "- 6. Complete with context: trekoon --toon task update <task-id> --append \"Completed implementation\" --status done",
29
+ "- 7. Or report block: trekoon --toon task update <task-id> --append \"Blocked by <reason>\" --status blocked",
16
30
  "",
17
- "3) Task details and description",
31
+ "4) Power-user command patterns (aligned with skill)",
32
+ "- Inspect full epic tree: trekoon --toon epic show <epic-id> --all",
33
+ "- Inspect full task payload: trekoon --toon task show <task-id> --all",
34
+ "- Check direct dependencies before starting: trekoon --toon dep list <task-id>",
35
+ "- Find what this item unblocks: trekoon --toon dep reverse <task-or-subtask-id>",
36
+ "- Filter list explicitly: trekoon --toon task list --status in_progress,todo --limit 20",
37
+ "- Paginate deterministically: trekoon --toon task list --cursor <n>",
38
+ "- Bulk append/status update: trekoon --toon task update --ids id1,id2 --append \"...\" --status in_progress",
39
+ "",
40
+ "5) Task details and description",
18
41
  "- Human list and show views default to table format.",
19
42
  "- Alternate list view: add --view compact.",
20
43
  "- task/epic/subtask list defaults: open work only (in_progress/in-progress, todo), max 10.",
21
44
  "- Filter list by status: --status in_progress,todo (CSV).",
22
45
  "- Change page size: --limit <n>. Show all statuses and all rows with --all.",
23
- "- --all cannot be combined with --status or --limit.",
46
+ "- Continue pagination with --cursor <n> (offset-like list position).",
47
+ "- --all cannot be combined with --status, --limit, or --cursor.",
24
48
  "- Bulk update: use --all or --ids <csv> with --append and/or --status.",
25
49
  "- Bulk update rejects positional id, and --all/--ids cannot be combined.",
26
- "- Full tree + descriptions: trekoon epic show <epic-id> --all --json",
27
- "- For full task payload (including description), use --json:",
28
- " trekoon task show <task-id> --all --json",
29
- "",
30
- "4) Machine output examples",
31
- "- trekoon quickstart --json",
32
- "- trekoon task show <task-id> --all --json",
33
- "- trekoon epic show <epic-id> --all --json",
34
- "- trekoon sync status --toon",
50
+ "- Ready queue: trekoon task ready [--limit <n>] [--epic <id>] (deterministic order).",
51
+ "- Next execution candidate: trekoon task next [--epic <id>]",
52
+ "- Full tree + descriptions (canonical): trekoon --toon epic show <epic-id> --all",
53
+ "- Full task payload (including description): trekoon --toon task show <task-id> --all",
54
+ "- Optional integration format: trekoon --json task show <task-id> --all",
55
+ "",
56
+ "6) Pre-merge sync flow",
57
+ "- Run: trekoon --toon sync status",
58
+ "- Pull upstream tracker events: trekoon --toon sync pull --from main",
59
+ "- Resolve conflicts if needed: trekoon --toon sync resolve <id> --use ours",
60
+ "- Run sync status again before opening or merging a PR.",
61
+ "",
62
+ "7) Machine output examples",
63
+ "- trekoon --toon quickstart",
64
+ "- trekoon --toon task show <task-id> --all",
65
+ "- trekoon --toon epic show <epic-id> --all",
66
+ "- trekoon --toon sync status",
67
+ "- trekoon --toon task ready --limit 5",
68
+ "- trekoon --toon task next",
69
+ "- trekoon --toon dep reverse <task-or-subtask-id>",
35
70
  ].join("\n");
36
71
 
37
72
  export async function runQuickstart(_: CliContext): Promise<CliResult> {
@@ -44,17 +79,46 @@ export async function runQuickstart(_: CliContext): Promise<CliResult> {
44
79
  databaseFile: ".trekoon/trekoon.db",
45
80
  mergeBehavior: "manual-sync",
46
81
  },
82
+ alignedSkill: ".agents/skills/trekoon/SKILL.md",
83
+ requiresToonForAgents: true,
84
+ agentStartupChecklist: [
85
+ "trekoon --toon init",
86
+ "trekoon --toon sync status",
87
+ "trekoon --toon epic list",
88
+ "trekoon --toon task list",
89
+ "trekoon --toon task ready --limit 5",
90
+ "trekoon --toon dep reverse <task-or-subtask-id>",
91
+ ],
47
92
  preMergeFlow: [
48
- "trekoon sync status",
49
- "trekoon sync pull --from main",
50
- "trekoon sync resolve <id> --use ours",
51
- "trekoon sync status",
93
+ "trekoon --toon sync status",
94
+ "trekoon --toon sync pull --from main",
95
+ "trekoon --toon sync resolve <id> --use ours",
96
+ "trekoon --toon sync status",
97
+ ],
98
+ executionLoop: [
99
+ "trekoon --toon sync status",
100
+ "trekoon --toon task ready --limit 5",
101
+ "trekoon --toon task next",
102
+ "trekoon --toon dep reverse <task-or-subtask-id>",
103
+ "trekoon --toon task update <task-id> --status in_progress",
104
+ ],
105
+ powerUserCommands: [
106
+ "trekoon --toon epic show <epic-id> --all",
107
+ "trekoon --toon task show <task-id> --all",
108
+ "trekoon --toon dep list <task-id>",
109
+ "trekoon --toon dep reverse <task-or-subtask-id>",
110
+ "trekoon --toon task list --status in_progress,todo --limit 20",
111
+ "trekoon --toon task list --cursor <n>",
112
+ "trekoon --toon task update --ids id1,id2 --append \"...\" --status in_progress",
52
113
  ],
53
114
  machineExamples: [
54
- "trekoon quickstart --json",
55
- "trekoon task show <task-id> --all --json",
56
- "trekoon epic show <epic-id> --all --json",
57
- "trekoon sync status --toon",
115
+ "trekoon --toon quickstart",
116
+ "trekoon --toon task show <task-id> --all",
117
+ "trekoon --toon epic show <epic-id> --all",
118
+ "trekoon --toon sync status",
119
+ "trekoon --toon task ready --limit 5",
120
+ "trekoon --toon task next",
121
+ "trekoon --toon dep reverse <task-or-subtask-id>",
58
122
  ],
59
123
  },
60
124
  });
@@ -1,4 +1,12 @@
1
- import { hasFlag, parseArgs, parseStrictPositiveInt, readEnumOption, readMissingOptionValue, readOption } from "./arg-parser";
1
+ import {
2
+ hasFlag,
3
+ parseArgs,
4
+ parseStrictNonNegativeInt,
5
+ parseStrictPositiveInt,
6
+ readEnumOption,
7
+ readMissingOptionValue,
8
+ readOption,
9
+ } from "./arg-parser";
2
10
 
3
11
  import { MutationService } from "../domain/mutation-service";
4
12
  import { TrackerDomain } from "../domain/tracker-domain";
@@ -54,16 +62,44 @@ function filterSortAndLimitSubtasks(
54
62
  subtasks: readonly SubtaskRecord[],
55
63
  statuses: readonly string[] | undefined,
56
64
  limit: number | undefined,
57
- ): SubtaskRecord[] {
65
+ cursor: number,
66
+ ): { subtasks: SubtaskRecord[]; pagination: { hasMore: boolean; nextCursor: string | null } } {
58
67
  const allowedStatuses = statuses === undefined ? undefined : new Set(statuses);
59
68
  const filtered = allowedStatuses === undefined ? [...subtasks] : subtasks.filter((subtask) => allowedStatuses.has(subtask.status));
60
- const sorted = [...filtered].sort((left, right) => subtaskStatusPriority(left.status) - subtaskStatusPriority(right.status));
69
+ const sorted = [...filtered].sort((left, right) => {
70
+ const byStatus = subtaskStatusPriority(left.status) - subtaskStatusPriority(right.status);
71
+ if (byStatus !== 0) {
72
+ return byStatus;
73
+ }
74
+
75
+ const byCreatedAt = left.createdAt - right.createdAt;
76
+ if (byCreatedAt !== 0) {
77
+ return byCreatedAt;
78
+ }
79
+
80
+ return left.id.localeCompare(right.id);
81
+ });
61
82
 
62
83
  if (limit === undefined) {
63
- return sorted;
84
+ return {
85
+ subtasks: sorted,
86
+ pagination: {
87
+ hasMore: false,
88
+ nextCursor: null,
89
+ },
90
+ };
64
91
  }
65
92
 
66
- return sorted.slice(0, limit);
93
+ const pagedSubtasks = sorted.slice(cursor, cursor + limit);
94
+ const nextIndex = cursor + pagedSubtasks.length;
95
+ const hasMore = nextIndex < sorted.length;
96
+ return {
97
+ subtasks: pagedSubtasks,
98
+ pagination: {
99
+ hasMore,
100
+ nextCursor: hasMore ? `${nextIndex}` : null,
101
+ },
102
+ };
67
103
  }
68
104
 
69
105
  function appendLine(existing: string, line: string): string {
@@ -161,6 +197,7 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
161
197
  readMissingOptionValue(parsed.missingOptionValues, "view") ??
162
198
  readMissingOptionValue(parsed.missingOptionValues, "status", "s") ??
163
199
  readMissingOptionValue(parsed.missingOptionValues, "limit", "l") ??
200
+ readMissingOptionValue(parsed.missingOptionValues, "cursor") ??
164
201
  readMissingOptionValue(parsed.missingOptionValues, "task", "t");
165
202
  if (missingListOption !== undefined) {
166
203
  return failMissingOptionValue("subtask.list", missingListOption);
@@ -171,6 +208,7 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
171
208
  const includeAll = hasFlag(parsed.flags, "all");
172
209
  const rawStatuses = readOption(parsed.options, "status", "s");
173
210
  const rawLimit = readOption(parsed.options, "limit", "l");
211
+ const rawCursor = readOption(parsed.options, "cursor");
174
212
 
175
213
  if (rawView !== undefined && view === undefined) {
176
214
  return failResult({
@@ -208,6 +246,18 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
208
246
  });
209
247
  }
210
248
 
249
+ if (includeAll && rawCursor !== undefined) {
250
+ return failResult({
251
+ command: "subtask.list",
252
+ human: "Use either --all or --cursor, not both.",
253
+ data: { code: "invalid_input", flags: ["all", "cursor"] },
254
+ error: {
255
+ code: "invalid_input",
256
+ message: "--all and --cursor are mutually exclusive",
257
+ },
258
+ });
259
+ }
260
+
211
261
  const statuses = parseStatusCsv(rawStatuses);
212
262
  if (rawStatuses !== undefined && statuses !== undefined && statuses.length === 0) {
213
263
  return failResult({
@@ -234,6 +284,19 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
234
284
  });
235
285
  }
236
286
 
287
+ const parsedCursor = parseStrictNonNegativeInt(rawCursor);
288
+ if (Number.isNaN(parsedCursor)) {
289
+ return failResult({
290
+ command: "subtask.list",
291
+ human: "Invalid --cursor value. Use an integer >= 0.",
292
+ data: { code: "invalid_input", cursor: rawCursor },
293
+ error: {
294
+ code: "invalid_input",
295
+ message: "Invalid --cursor value",
296
+ },
297
+ });
298
+ }
299
+
237
300
  const taskId: string | undefined = readOption(parsed.options, "task", "t") ?? parsed.positional[1];
238
301
  const selectedStatuses = includeAll
239
302
  ? undefined
@@ -241,7 +304,13 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
241
304
  const selectedLimit = includeAll
242
305
  ? undefined
243
306
  : parsedLimit ?? DEFAULT_SUBTASK_LIST_LIMIT;
244
- const subtasks = filterSortAndLimitSubtasks(domain.listSubtasks(taskId), selectedStatuses, selectedLimit);
307
+ const listed = filterSortAndLimitSubtasks(
308
+ domain.listSubtasks(taskId),
309
+ selectedStatuses,
310
+ selectedLimit,
311
+ parsedCursor ?? 0,
312
+ );
313
+ const subtasks = listed.subtasks;
245
314
  const listView = view ?? "table";
246
315
  const human =
247
316
  subtasks.length === 0
@@ -254,6 +323,7 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
254
323
  command: "subtask.list",
255
324
  human,
256
325
  data: { subtasks },
326
+ ...(context.mode === "human" ? {} : { meta: { pagination: listed.pagination } }),
257
327
  });
258
328
  }
259
329
  case "update": {