trekoon 0.1.4 → 0.1.5

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.
@@ -61,7 +61,11 @@ Use long flags (`--status`, `--description`, etc.) and ALWAYS append `--toon` to
61
61
 
62
62
  ## 1) Status Management
63
63
 
64
- ### Valid Statuses
64
+ ### Status values
65
+
66
+ Trekoon accepts any non-empty status string.
67
+
68
+ Recommended statuses for consistent workflows:
65
69
 
66
70
  | Status | Meaning |
67
71
  |--------|---------|
@@ -69,7 +73,7 @@ Use long flags (`--status`, `--description`, etc.) and ALWAYS append `--toon` to
69
73
  | `in_progress` | Actively being worked on |
70
74
  | `done` | Completed successfully |
71
75
 
72
- Note: `in-progress` (hyphenated) is equivalent to `in_progress`.
76
+ Note: `in-progress` (hyphenated) is treated the same as `in_progress` for default list ordering/filtering.
73
77
 
74
78
  ### When to Change Status
75
79
 
@@ -165,7 +169,7 @@ trekoon epic show <id> --all --toon
165
169
  trekoon task show <id> --all --toon
166
170
  ```
167
171
 
168
- - `epic list` / `task list` defaults:
172
+ - `epic list` / `task list` / `subtask list` defaults:
169
173
  - open work only (`in_progress`, `in-progress`, `todo`)
170
174
  - prioritized as `in_progress`/`in-progress` first, then `todo`
171
175
  - default limit `10`
package/README.md CHANGED
@@ -77,10 +77,10 @@ Human view options:
77
77
 
78
78
  - List and show commands default to table output in human mode.
79
79
  - Use `--view compact` to restore compact pipe output.
80
- - `epic list` and `task list` support `--view table|compact`.
80
+ - `epic list`, `task list`, and `subtask list` support `--view table|compact`.
81
81
  - `epic show` and `task show` support `--view table|compact|tree|detail`.
82
82
 
83
- List defaults and filters (`epic list`, `task list`):
83
+ List defaults and filters (`epic list`, `task list`, `subtask list`):
84
84
 
85
85
  - Default scope: open work only (`in_progress`, `in-progress`, `todo`)
86
86
  - Default limit: `10`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trekoon",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "AI-first local issue tracker CLI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,7 +36,7 @@ const COMMAND_HELP: Record<string, string> = {
36
36
  task:
37
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
38
  subtask:
39
- "Usage: trekoon subtask <subcommand> [options] (list supports --view table|compact; update bulk flags: --all | --ids <csv> with --append <text> and/or --status <status>)",
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
40
  dep: "Usage: trekoon dep <subcommand> [options]",
41
41
  events: "Usage: trekoon events prune [--dry-run] [--archive] [--retention-days <n>]",
42
42
  migrate: "Usage: trekoon migrate <status|rollback> [--to-version <n>]",
@@ -17,7 +17,7 @@ const QUICKSTART_TEXT = [
17
17
  "3) Task details and description",
18
18
  "- Human list and show views default to table format.",
19
19
  "- Alternate list view: add --view compact.",
20
- "- task/epic list defaults: open work only (in_progress/in-progress, todo), max 10.",
20
+ "- task/epic/subtask list defaults: open work only (in_progress/in-progress, todo), max 10.",
21
21
  "- Filter list by status: --status in_progress,todo (CSV).",
22
22
  "- Change page size: --limit <n>. Show all statuses and all rows with --all.",
23
23
  "- --all cannot be combined with --status or --limit.",
@@ -1,4 +1,4 @@
1
- import { hasFlag, parseArgs, readEnumOption, readMissingOptionValue, readOption } from "./arg-parser";
1
+ import { hasFlag, parseArgs, parseStrictPositiveInt, readEnumOption, readMissingOptionValue, readOption } from "./arg-parser";
2
2
 
3
3
  import { DomainError, type SubtaskRecord } from "../domain/types";
4
4
  import { TrackerDomain } from "../domain/tracker-domain";
@@ -12,6 +12,8 @@ function formatSubtask(subtask: SubtaskRecord): string {
12
12
  }
13
13
 
14
14
  const VIEW_MODES = ["table", "compact"] as const;
15
+ const DEFAULT_SUBTASK_LIST_LIMIT = 10;
16
+ const DEFAULT_OPEN_SUBTASK_STATUSES = ["in_progress", "in-progress", "todo"] as const;
15
17
 
16
18
  function parseIdsOption(rawIds: string | undefined): string[] {
17
19
  if (rawIds === undefined) {
@@ -24,6 +26,45 @@ function parseIdsOption(rawIds: string | undefined): string[] {
24
26
  .filter((value) => value.length > 0);
25
27
  }
26
28
 
29
+ function parseStatusCsv(rawStatuses: string | undefined): string[] | undefined {
30
+ if (rawStatuses === undefined) {
31
+ return undefined;
32
+ }
33
+
34
+ return rawStatuses
35
+ .split(",")
36
+ .map((value) => value.trim())
37
+ .filter((value) => value.length > 0);
38
+ }
39
+
40
+ function subtaskStatusPriority(status: string): number {
41
+ if (status === "in_progress" || status === "in-progress") {
42
+ return 0;
43
+ }
44
+
45
+ if (status === "todo") {
46
+ return 1;
47
+ }
48
+
49
+ return 2;
50
+ }
51
+
52
+ function filterSortAndLimitSubtasks(
53
+ subtasks: readonly SubtaskRecord[],
54
+ statuses: readonly string[] | undefined,
55
+ limit: number | undefined,
56
+ ): SubtaskRecord[] {
57
+ const allowedStatuses = statuses === undefined ? undefined : new Set(statuses);
58
+ const filtered = allowedStatuses === undefined ? [...subtasks] : subtasks.filter((subtask) => allowedStatuses.has(subtask.status));
59
+ const sorted = [...filtered].sort((left, right) => subtaskStatusPriority(left.status) - subtaskStatusPriority(right.status));
60
+
61
+ if (limit === undefined) {
62
+ return sorted;
63
+ }
64
+
65
+ return sorted.slice(0, limit);
66
+ }
67
+
27
68
  function appendLine(existing: string, line: string): string {
28
69
  return existing.length > 0 ? `${existing}\n${line}` : line;
29
70
  }
@@ -116,6 +157,8 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
116
157
  case "list": {
117
158
  const missingListOption =
118
159
  readMissingOptionValue(parsed.missingOptionValues, "view") ??
160
+ readMissingOptionValue(parsed.missingOptionValues, "status", "s") ??
161
+ readMissingOptionValue(parsed.missingOptionValues, "limit", "l") ??
119
162
  readMissingOptionValue(parsed.missingOptionValues, "task", "t");
120
163
  if (missingListOption !== undefined) {
121
164
  return failMissingOptionValue("subtask.list", missingListOption);
@@ -123,6 +166,10 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
123
166
 
124
167
  const rawView: string | undefined = readOption(parsed.options, "view");
125
168
  const view = readEnumOption(parsed.options, VIEW_MODES, "view");
169
+ const includeAll = hasFlag(parsed.flags, "all");
170
+ const rawStatuses = readOption(parsed.options, "status", "s");
171
+ const rawLimit = readOption(parsed.options, "limit", "l");
172
+
126
173
  if (rawView !== undefined && view === undefined) {
127
174
  return failResult({
128
175
  command: "subtask.list",
@@ -135,8 +182,64 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
135
182
  });
136
183
  }
137
184
 
185
+ if (includeAll && rawStatuses !== undefined) {
186
+ return failResult({
187
+ command: "subtask.list",
188
+ human: "Use either --all or --status, not both.",
189
+ data: { code: "invalid_input", flags: ["all", "status"] },
190
+ error: {
191
+ code: "invalid_input",
192
+ message: "--all and --status are mutually exclusive",
193
+ },
194
+ });
195
+ }
196
+
197
+ if (includeAll && rawLimit !== undefined) {
198
+ return failResult({
199
+ command: "subtask.list",
200
+ human: "Use either --all or --limit, not both.",
201
+ data: { code: "invalid_input", flags: ["all", "limit"] },
202
+ error: {
203
+ code: "invalid_input",
204
+ message: "--all and --limit are mutually exclusive",
205
+ },
206
+ });
207
+ }
208
+
209
+ const statuses = parseStatusCsv(rawStatuses);
210
+ if (rawStatuses !== undefined && statuses !== undefined && statuses.length === 0) {
211
+ return failResult({
212
+ command: "subtask.list",
213
+ human: "Provide at least one status with --status.",
214
+ data: { code: "invalid_input", status: rawStatuses },
215
+ error: {
216
+ code: "invalid_input",
217
+ message: "Invalid --status value",
218
+ },
219
+ });
220
+ }
221
+
222
+ const parsedLimit = parseStrictPositiveInt(rawLimit);
223
+ if (Number.isNaN(parsedLimit)) {
224
+ return failResult({
225
+ command: "subtask.list",
226
+ human: "Invalid --limit value. Use an integer >= 1.",
227
+ data: { code: "invalid_input", limit: rawLimit },
228
+ error: {
229
+ code: "invalid_input",
230
+ message: "Invalid --limit value",
231
+ },
232
+ });
233
+ }
234
+
138
235
  const taskId: string | undefined = readOption(parsed.options, "task", "t") ?? parsed.positional[1];
139
- const subtasks = domain.listSubtasks(taskId);
236
+ const selectedStatuses = includeAll
237
+ ? undefined
238
+ : statuses ?? [...DEFAULT_OPEN_SUBTASK_STATUSES];
239
+ const selectedLimit = includeAll
240
+ ? undefined
241
+ : parsedLimit ?? DEFAULT_SUBTASK_LIST_LIMIT;
242
+ const subtasks = filterSortAndLimitSubtasks(domain.listSubtasks(taskId), selectedStatuses, selectedLimit);
140
243
  const listView = view ?? "table";
141
244
  const human =
142
245
  subtasks.length === 0