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.
- package/.agents/skills/trekoon/SKILL.md +39 -15
- package/README.md +110 -9
- package/package.json +1 -1
- package/src/commands/arg-parser.ts +13 -0
- package/src/commands/dep.ts +20 -1
- package/src/commands/epic.ts +72 -7
- package/src/commands/help.ts +255 -17
- package/src/commands/quickstart.ts +88 -24
- package/src/commands/subtask.ts +76 -6
- package/src/commands/task.ts +299 -7
- package/src/domain/tracker-domain.ts +113 -7
- package/src/domain/types.ts +7 -0
- package/src/runtime/cli-shell.ts +1 -2
- package/src/runtime/version.ts +20 -0
|
@@ -101,6 +101,7 @@ Dependencies define what must be completed before a task can start. A task/subta
|
|
|
101
101
|
trekoon dep add <source-id> <depends-on-id> --toon
|
|
102
102
|
trekoon dep list <source-id> --toon
|
|
103
103
|
trekoon dep remove <source-id> <depends-on-id> --toon
|
|
104
|
+
trekoon dep reverse <task-or-subtask-id> --toon
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
- `<source-id>`: The task/subtask that has the dependency
|
|
@@ -127,16 +128,32 @@ The response `data.dependencies` array contains entries with:
|
|
|
127
128
|
|
|
128
129
|
## 3) Task Completion Flow
|
|
129
130
|
|
|
130
|
-
###
|
|
131
|
+
### Canonical dependency-aware execution loop
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
Run this sequence every session:
|
|
134
|
+
|
|
135
|
+
1. Sync branch/worktree status:
|
|
133
136
|
```bash
|
|
134
|
-
trekoon
|
|
137
|
+
trekoon sync status --toon
|
|
138
|
+
```
|
|
139
|
+
2. Pull deterministic ready candidates (or next candidate):
|
|
140
|
+
```bash
|
|
141
|
+
trekoon task ready --limit 5 --toon
|
|
142
|
+
trekoon task next --toon
|
|
143
|
+
```
|
|
144
|
+
3. Inspect downstream impact before changes:
|
|
145
|
+
```bash
|
|
146
|
+
trekoon dep reverse <task-or-subtask-id> --toon
|
|
147
|
+
```
|
|
148
|
+
4. Start work with explicit status updates:
|
|
149
|
+
```bash
|
|
150
|
+
trekoon task update <task-id> --status in_progress --toon
|
|
151
|
+
```
|
|
152
|
+
5. Finish or block with appended context + final status:
|
|
153
|
+
```bash
|
|
154
|
+
trekoon task update <task-id> --append "Completed implementation" --status done --toon
|
|
155
|
+
trekoon task update <task-id> --append "Blocked by <reason>" --status blocked --toon
|
|
135
156
|
```
|
|
136
|
-
|
|
137
|
-
2. If dependencies exist and are not `done`, complete those first
|
|
138
|
-
|
|
139
|
-
3. Only mark `in_progress` when all dependencies are `done`
|
|
140
157
|
|
|
141
158
|
### When Completing a Task
|
|
142
159
|
|
|
@@ -146,17 +163,19 @@ The response `data.dependencies` array contains entries with:
|
|
|
146
163
|
```
|
|
147
164
|
|
|
148
165
|
2. To find the next task that was blocked by this one:
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
166
|
+
- Inspect downstream nodes: `trekoon dep reverse <task-id> --toon`
|
|
167
|
+
- Pull ready queue: `trekoon task ready --limit 5 --toon`
|
|
168
|
+
- Pick one deterministically: `trekoon task next --toon`
|
|
152
169
|
|
|
153
170
|
### Finding Next Work
|
|
154
171
|
|
|
155
172
|
```bash
|
|
156
|
-
trekoon task
|
|
173
|
+
trekoon task ready --limit 5 --toon
|
|
174
|
+
trekoon task next --toon
|
|
175
|
+
trekoon dep reverse <task-or-subtask-id> --toon
|
|
157
176
|
```
|
|
158
177
|
|
|
159
|
-
|
|
178
|
+
Use `task ready` for ranked candidates and `task next` for the top deterministic pick.
|
|
160
179
|
|
|
161
180
|
## 4) Load existing work first
|
|
162
181
|
|
|
@@ -173,6 +192,7 @@ trekoon task show <id> --all --toon
|
|
|
173
192
|
- open work only (`in_progress`, `in-progress`, `todo`)
|
|
174
193
|
- prioritized as `in_progress`/`in-progress` first, then `todo`
|
|
175
194
|
- default limit `10`
|
|
195
|
+
- `--cursor <n>` is offset-like pagination for list endpoints
|
|
176
196
|
- Filter list explicitly when needed:
|
|
177
197
|
|
|
178
198
|
```bash
|
|
@@ -182,6 +202,9 @@ trekoon task list --all --toon
|
|
|
182
202
|
```
|
|
183
203
|
|
|
184
204
|
- `--all` cannot be combined with `--status` or `--limit`.
|
|
205
|
+
- `--all` cannot be combined with `--cursor`.
|
|
206
|
+
- Machine pagination contract is in `meta.pagination.hasMore` and
|
|
207
|
+
`meta.pagination.nextCursor`.
|
|
185
208
|
- `epic show <id> --all --toon`: full epic tree (tasks + subtasks)
|
|
186
209
|
- `task show <id> --all --toon`: task plus its subtasks
|
|
187
210
|
|
|
@@ -236,14 +259,15 @@ Rules:
|
|
|
236
259
|
2. In the target repository/worktree, initialize tracker state:
|
|
237
260
|
|
|
238
261
|
```bash
|
|
239
|
-
trekoon init
|
|
262
|
+
trekoon init --toon
|
|
240
263
|
```
|
|
241
264
|
|
|
242
|
-
3. You can always run `trekoon quickstart` or `trekoon --help` to
|
|
265
|
+
3. You can always run `trekoon quickstart --toon` or `trekoon --help --toon` to
|
|
266
|
+
get more information.
|
|
243
267
|
|
|
244
268
|
If `.trekoon/trekoon.db` is missing, initialize before any create/update commands.
|
|
245
269
|
|
|
246
270
|
## 8) Safety
|
|
247
271
|
|
|
248
272
|
- Never edit `.trekoon/trekoon.db` directly.
|
|
249
|
-
- `trekoon wipe --yes` is prohibited unless the user explicitly confirms they want a destructive wipe.
|
|
273
|
+
- `trekoon wipe --yes --toon` is prohibited unless the user explicitly confirms they want a destructive wipe.
|
package/README.md
CHANGED
|
@@ -48,9 +48,9 @@ npm i -g trekoon
|
|
|
48
48
|
- `trekoon init`
|
|
49
49
|
- `trekoon quickstart`
|
|
50
50
|
- `trekoon epic <create|list|show|update|delete>`
|
|
51
|
-
- `trekoon task <create|list|show|update|delete>`
|
|
51
|
+
- `trekoon task <create|list|show|ready|next|update|delete>`
|
|
52
52
|
- `trekoon subtask <create|list|update|delete>`
|
|
53
|
-
- `trekoon dep <add|remove|list>`
|
|
53
|
+
- `trekoon dep <add|remove|list|reverse>`
|
|
54
54
|
- `trekoon sync <status|pull|resolve>`
|
|
55
55
|
- `trekoon skills install [--link --editor opencode|claude|pi] [--to <path>] [--allow-outside-repo]`
|
|
56
56
|
- `trekoon skills update`
|
|
@@ -87,8 +87,9 @@ List defaults and filters (`epic list`, `task list`, `subtask list`):
|
|
|
87
87
|
- Default limit: `10`
|
|
88
88
|
- Status filter: `--status in_progress,todo` (CSV)
|
|
89
89
|
- Custom limit: `--limit <n>`
|
|
90
|
+
- Cursor pagination: `--cursor <n>` (offset-like start index for next page)
|
|
90
91
|
- All rows and statuses: `--all`
|
|
91
|
-
- `--all` is mutually exclusive with `--status
|
|
92
|
+
- `--all` is mutually exclusive with `--status`, `--limit`, and `--cursor`
|
|
92
93
|
|
|
93
94
|
Bulk updates (`epic update`, `task update`, `subtask update`):
|
|
94
95
|
|
|
@@ -140,16 +141,40 @@ trekoon dep add <task-id> <depends-on-id>
|
|
|
140
141
|
trekoon dep list <task-id>
|
|
141
142
|
```
|
|
142
143
|
|
|
143
|
-
### 4)
|
|
144
|
+
### 4) AI execution loop for agents
|
|
145
|
+
|
|
146
|
+
Run this loop each session to pick next work deterministically:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
trekoon --toon sync status
|
|
150
|
+
trekoon --toon task ready --limit 5
|
|
151
|
+
trekoon --toon task next
|
|
152
|
+
trekoon --toon dep reverse <task-or-subtask-id>
|
|
153
|
+
trekoon --toon task update <task-id> --status in_progress
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
When done or blocked, append context and update final status:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
trekoon --toon task update <task-id> --append "Completed implementation and checks" --status done
|
|
160
|
+
trekoon --toon task update <task-id> --append "Blocked by <reason>" --status blocked
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 5) Use TOON output for agent workflows
|
|
144
164
|
|
|
145
165
|
```bash
|
|
146
|
-
trekoon --json epic show <epic-id>
|
|
147
|
-
trekoon --json task show <task-id>
|
|
148
166
|
trekoon --toon epic show <epic-id>
|
|
149
167
|
trekoon --toon task show <task-id>
|
|
150
168
|
```
|
|
151
169
|
|
|
152
|
-
|
|
170
|
+
Optional alternative for integrations that explicitly require JSON:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
trekoon --json epic show <epic-id>
|
|
174
|
+
trekoon --json task show <task-id>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 6) Sync workflow for worktrees
|
|
153
178
|
|
|
154
179
|
- Run `trekoon sync status` at session start and before PR/merge.
|
|
155
180
|
- Run `trekoon sync pull --from main` before merge to align tracker state.
|
|
@@ -170,7 +195,7 @@ react deterministically:
|
|
|
170
195
|
- `diagnostics.conflictEvents`
|
|
171
196
|
- `diagnostics.errorHints`
|
|
172
197
|
|
|
173
|
-
###
|
|
198
|
+
### 7) Install project-local Trekoon skill for agents
|
|
174
199
|
|
|
175
200
|
`trekoon skills install` always writes the bundled skill file into the current
|
|
176
201
|
repository at:
|
|
@@ -232,13 +257,89 @@ This produces:
|
|
|
232
257
|
|
|
233
258
|
Trekoon does not mutate global editor config directories.
|
|
234
259
|
|
|
235
|
-
###
|
|
260
|
+
### 8) Pre-merge checklist
|
|
236
261
|
|
|
237
262
|
- [ ] `trekoon sync status` shows no unresolved conflicts
|
|
238
263
|
- [ ] done tasks/subtasks are marked completed
|
|
239
264
|
- [ ] dependency graph has no stale blockers
|
|
240
265
|
- [ ] final AI check: `trekoon --toon epic show <epic-id>`
|
|
241
266
|
|
|
267
|
+
## Machine-contract recipes (--toon)
|
|
268
|
+
|
|
269
|
+
Use `--toon` for production agent loops. The examples below show command +
|
|
270
|
+
expected envelope fields.
|
|
271
|
+
|
|
272
|
+
### Ready queue (deterministic candidates)
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
trekoon --toon task ready --limit 3
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Payload fields:
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
ok: true
|
|
282
|
+
command: task.ready
|
|
283
|
+
data:
|
|
284
|
+
candidates[]:
|
|
285
|
+
task: { id, epicId, title, status, ... }
|
|
286
|
+
readiness: { isReady, reason }
|
|
287
|
+
blockerSummary: { blockedByCount, totalDependencies, blockedBy[] }
|
|
288
|
+
ranking: { rank, blockerCount, statusPriority }
|
|
289
|
+
blocked[]: (same shape, non-ready items)
|
|
290
|
+
summary: {
|
|
291
|
+
totalOpenTasks,
|
|
292
|
+
readyCount,
|
|
293
|
+
returnedCount,
|
|
294
|
+
appliedLimit,
|
|
295
|
+
blockedCount,
|
|
296
|
+
unresolvedDependencyCount,
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Reverse dependency walk (blocker impact)
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
trekoon --toon dep reverse <task-or-subtask-id>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Payload fields:
|
|
307
|
+
|
|
308
|
+
```text
|
|
309
|
+
ok: true
|
|
310
|
+
command: dep.reverse
|
|
311
|
+
data:
|
|
312
|
+
targetId: <id>
|
|
313
|
+
targetKind: task|subtask
|
|
314
|
+
blockedNodes[]: { id, kind, distance, isDirect }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Pagination contract for machine list calls
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
trekoon --toon task list --status todo --limit 2
|
|
321
|
+
trekoon --toon task list --status todo --limit 2 --cursor 2
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Cursor semantics:
|
|
325
|
+
|
|
326
|
+
- `--cursor <n>` is offset-like pagination for list endpoints (`epic list`,
|
|
327
|
+
`task list`, `subtask list`).
|
|
328
|
+
- Do not combine `--all` with `--cursor`.
|
|
329
|
+
- Machine consumers should page using `meta.pagination.hasMore` and
|
|
330
|
+
`meta.pagination.nextCursor`.
|
|
331
|
+
|
|
332
|
+
Payload fields:
|
|
333
|
+
|
|
334
|
+
```text
|
|
335
|
+
ok: true
|
|
336
|
+
command: task.list
|
|
337
|
+
data:
|
|
338
|
+
tasks[]: ...
|
|
339
|
+
meta:
|
|
340
|
+
pagination: { hasMore, nextCursor }
|
|
341
|
+
```
|
|
342
|
+
|
|
242
343
|
## Implementation principles
|
|
243
344
|
|
|
244
345
|
- Minimal, composable modules
|
package/package.json
CHANGED
|
@@ -79,6 +79,19 @@ export function parseStrictPositiveInt(rawValue: string | undefined): number | u
|
|
|
79
79
|
return parsed;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
export function parseStrictNonNegativeInt(rawValue: string | undefined): number | undefined {
|
|
83
|
+
if (rawValue === undefined) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
88
|
+
if (!Number.isInteger(parsed) || parsed < 0 || `${parsed}` !== rawValue.trim()) {
|
|
89
|
+
return Number.NaN;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
|
|
82
95
|
export function readEnumOption<const T extends readonly string[]>(
|
|
83
96
|
options: ReadonlyMap<string, string>,
|
|
84
97
|
allowed: T,
|
package/src/commands/dep.ts
CHANGED
|
@@ -86,10 +86,29 @@ export async function runDep(context: CliContext): Promise<CliResult> {
|
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
+
case "reverse": {
|
|
90
|
+
const targetKind = domain.resolveNodeKind(sourceId);
|
|
91
|
+
const blockedNodes = domain.listReverseDependencies(sourceId);
|
|
92
|
+
|
|
93
|
+
return okResult({
|
|
94
|
+
command: "dep.reverse",
|
|
95
|
+
human:
|
|
96
|
+
blockedNodes.length === 0
|
|
97
|
+
? `No downstream blockers for ${sourceId}`
|
|
98
|
+
: blockedNodes
|
|
99
|
+
.map((item) => `${item.id} (${item.kind}, distance=${item.distance})`)
|
|
100
|
+
.join("\n"),
|
|
101
|
+
data: {
|
|
102
|
+
targetId: sourceId,
|
|
103
|
+
targetKind,
|
|
104
|
+
blockedNodes,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
89
108
|
default:
|
|
90
109
|
return failResult({
|
|
91
110
|
command: "dep",
|
|
92
|
-
human: "Usage: trekoon dep <add|remove|list>",
|
|
111
|
+
human: "Usage: trekoon dep <add|remove|list|reverse>",
|
|
93
112
|
data: {
|
|
94
113
|
args: context.args,
|
|
95
114
|
},
|
package/src/commands/epic.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
@@ -41,21 +49,58 @@ function getStatusPriority(status: string): number {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
function sortByStatusPriority(epics: readonly EpicRecord[]): EpicRecord[] {
|
|
44
|
-
return [...epics].sort((left, right) =>
|
|
52
|
+
return [...epics].sort((left, right) => {
|
|
53
|
+
const byStatus = getStatusPriority(left.status) - getStatusPriority(right.status);
|
|
54
|
+
if (byStatus !== 0) {
|
|
55
|
+
return byStatus;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const byCreatedAt = left.createdAt - right.createdAt;
|
|
59
|
+
if (byCreatedAt !== 0) {
|
|
60
|
+
return byCreatedAt;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return left.id.localeCompare(right.id);
|
|
64
|
+
});
|
|
45
65
|
}
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
interface PaginationMeta {
|
|
68
|
+
readonly hasMore: boolean;
|
|
69
|
+
readonly nextCursor: string | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function filterSortAndLimitEpics(epics: readonly EpicRecord[], options: {
|
|
73
|
+
includeAll: boolean;
|
|
74
|
+
statuses: readonly string[] | undefined;
|
|
75
|
+
limit: number | undefined;
|
|
76
|
+
cursor: number;
|
|
77
|
+
}): { epics: EpicRecord[]; pagination: PaginationMeta } {
|
|
78
|
+
const { includeAll, statuses, limit, cursor } = options;
|
|
49
79
|
const selectedStatuses = includeAll ? undefined : (statuses ?? DEFAULT_OPEN_STATUSES);
|
|
50
80
|
const selectedEpics = selectedStatuses === undefined ? [...epics] : epics.filter((epic) => selectedStatuses.includes(epic.status));
|
|
51
81
|
const sortedEpics = sortByStatusPriority(selectedEpics);
|
|
52
82
|
|
|
53
83
|
if (includeAll) {
|
|
54
|
-
return
|
|
84
|
+
return {
|
|
85
|
+
epics: sortedEpics,
|
|
86
|
+
pagination: {
|
|
87
|
+
hasMore: false,
|
|
88
|
+
nextCursor: null,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
55
91
|
}
|
|
56
92
|
|
|
57
93
|
const effectiveLimit = limit ?? DEFAULT_LIST_LIMIT;
|
|
58
|
-
|
|
94
|
+
const pagedEpics = sortedEpics.slice(cursor, cursor + effectiveLimit);
|
|
95
|
+
const nextIndex = cursor + pagedEpics.length;
|
|
96
|
+
const hasMore = nextIndex < sortedEpics.length;
|
|
97
|
+
return {
|
|
98
|
+
epics: pagedEpics,
|
|
99
|
+
pagination: {
|
|
100
|
+
hasMore,
|
|
101
|
+
nextCursor: hasMore ? `${nextIndex}` : null,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
59
104
|
}
|
|
60
105
|
|
|
61
106
|
function invalidEpicListInput(human: string, message: string, data: Record<string, unknown>): CliResult {
|
|
@@ -266,6 +311,7 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
266
311
|
const missingListOption =
|
|
267
312
|
readMissingOptionValue(parsed.missingOptionValues, "status", "s") ??
|
|
268
313
|
readMissingOptionValue(parsed.missingOptionValues, "limit", "l") ??
|
|
314
|
+
readMissingOptionValue(parsed.missingOptionValues, "cursor") ??
|
|
269
315
|
readMissingOptionValue(parsed.missingOptionValues, "view");
|
|
270
316
|
if (missingListOption !== undefined) {
|
|
271
317
|
return failMissingOptionValue("epic.list", missingListOption);
|
|
@@ -274,6 +320,7 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
274
320
|
const includeAll: boolean = hasFlag(parsed.flags, "all");
|
|
275
321
|
const rawStatuses: string | undefined = readOption(parsed.options, "status");
|
|
276
322
|
const rawLimit: string | undefined = readOption(parsed.options, "limit");
|
|
323
|
+
const rawCursor: string | undefined = readOption(parsed.options, "cursor");
|
|
277
324
|
const rawView: string | undefined = readOption(parsed.options, "view");
|
|
278
325
|
const view = readEnumOption(parsed.options, VIEW_MODES, "view");
|
|
279
326
|
if (rawView !== undefined && view === undefined) {
|
|
@@ -320,11 +367,28 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
320
367
|
});
|
|
321
368
|
}
|
|
322
369
|
|
|
323
|
-
const
|
|
370
|
+
const cursor = parseStrictNonNegativeInt(rawCursor) ?? 0;
|
|
371
|
+
if (Number.isNaN(cursor)) {
|
|
372
|
+
return invalidEpicListInput("Invalid --cursor value. Use an integer >= 0.", "Invalid --cursor value", {
|
|
373
|
+
code: "invalid_input",
|
|
374
|
+
cursor: rawCursor,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (includeAll && rawCursor !== undefined) {
|
|
379
|
+
return invalidEpicListInput("Use either --all or --cursor, not both.", "--all and --cursor are mutually exclusive", {
|
|
380
|
+
code: "invalid_input",
|
|
381
|
+
flags: ["all", "cursor"],
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const listed = filterSortAndLimitEpics(domain.listEpics(), {
|
|
324
386
|
includeAll,
|
|
325
387
|
statuses,
|
|
326
388
|
limit,
|
|
389
|
+
cursor,
|
|
327
390
|
});
|
|
391
|
+
const epics = listed.epics;
|
|
328
392
|
const listView = view ?? "table";
|
|
329
393
|
const human = epics.length === 0 ? "No epics found." : listView === "compact" ? epics.map(formatEpic).join("\n") : formatEpicListTable(epics);
|
|
330
394
|
|
|
@@ -332,6 +396,7 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
332
396
|
command: "epic.list",
|
|
333
397
|
human,
|
|
334
398
|
data: { epics },
|
|
399
|
+
...(context.mode === "human" ? {} : { meta: { pagination: listed.pagination } }),
|
|
335
400
|
});
|
|
336
401
|
}
|
|
337
402
|
case "show": {
|