trekoon 0.1.2 → 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.
@@ -7,9 +7,158 @@ description: Use Trekoon to create issues/tasks, plan backlog and sprints, creat
7
7
 
8
8
  Trekoon is a local-first issue tracker for epics, tasks, and subtasks.
9
9
 
10
- Use long flags (`--status`, `--description`, etc.) and ALWAYS prefer `--toon` for machine-readable output.
10
+ ## CRITICAL: Always Use --toon Flag
11
+
12
+ **Every trekoon command MUST include `--toon` for machine-readable output.**
13
+
14
+ The `--toon` flag outputs structured YAML-like data that is easy to parse. Never run trekoon commands without it.
15
+
16
+ ### TOON Output Format
17
+
18
+ All `--toon` output follows this structure:
19
+
20
+ ```yaml
21
+ ok: true
22
+ command: task.list
23
+ data:
24
+ tasks[0]:
25
+ id: abc-123
26
+ epicId: epic-456
27
+ title: Implement feature X
28
+ status: todo
29
+ createdAt: 1700000000000
30
+ updatedAt: 1700000000000
31
+ tasks[1]:
32
+ id: def-789
33
+ epicId: epic-456
34
+ title: Write tests
35
+ status: in_progress
36
+ createdAt: 1700000001000
37
+ updatedAt: 1700000001000
38
+ ```
39
+
40
+ On error:
41
+
42
+ ```yaml
43
+ ok: false
44
+ command: task.show
45
+ data: {}
46
+ error:
47
+ code: not_found
48
+ message: task not found: invalid-id
49
+ ```
50
+
51
+ ### Key Fields
52
+
53
+ | Field | Meaning |
54
+ |-------|---------|
55
+ | `ok` | `true` if command succeeded, `false` on error |
56
+ | `command` | The command that was executed (e.g., `task.list`, `epic.create`) |
57
+ | `data` | The response payload (tasks, epics, dependencies, etc.) |
58
+ | `error` | Present only on failure, contains `code` and `message` |
59
+
60
+ Use long flags (`--status`, `--description`, etc.) and ALWAYS append `--toon` to every command.
61
+
62
+ ## 1) Status Management
63
+
64
+ ### Status values
65
+
66
+ Trekoon accepts any non-empty status string.
67
+
68
+ Recommended statuses for consistent workflows:
69
+
70
+ | Status | Meaning |
71
+ |--------|---------|
72
+ | `todo` | Work not started (default for new items) |
73
+ | `in_progress` | Actively being worked on |
74
+ | `done` | Completed successfully |
75
+
76
+ Note: `in-progress` (hyphenated) is treated the same as `in_progress` for default list ordering/filtering.
77
+
78
+ ### When to Change Status
79
+
80
+ | Transition | When to apply |
81
+ |------------|---------------|
82
+ | `todo → in_progress` | When you START working on a task/subtask/epic |
83
+ | `in_progress → done` | When you COMPLETE the work and it is ready |
84
+
85
+ ### Status Change Commands
86
+
87
+ ```bash
88
+ trekoon task update <task-id> --status in_progress --toon
89
+ trekoon task update <task-id> --status done --toon
90
+ trekoon subtask update <subtask-id> --status done --toon
91
+ trekoon epic update <epic-id> --status done --toon
92
+ ```
93
+
94
+ ## 2) Dependency Management
95
+
96
+ Dependencies define what must be completed before a task can start. A task/subtask can depend on other tasks/subtasks.
97
+
98
+ ### Commands
99
+
100
+ ```bash
101
+ trekoon dep add <source-id> <depends-on-id> --toon
102
+ trekoon dep list <source-id> --toon
103
+ trekoon dep remove <source-id> <depends-on-id> --toon
104
+ ```
11
105
 
12
- ## 1) Load existing work first
106
+ - `<source-id>`: The task/subtask that has the dependency
107
+ - `<depends-on-id>`: The task/subtask that must be completed first
108
+
109
+ ### Checking Dependencies
110
+
111
+ Before starting any task, always check its dependencies:
112
+
113
+ ```bash
114
+ trekoon dep list <task-id> --toon
115
+ ```
116
+
117
+ The response `data.dependencies` array contains entries with:
118
+ - `sourceId`: the task you're checking
119
+ - `dependsOnId`: what must be done first
120
+ - `dependsOnKind`: "task" or "subtask"
121
+
122
+ ### Dependency Rules
123
+
124
+ 1. A task with dependencies should only be marked `in_progress` when ALL dependencies have status `done`
125
+ 2. Dependencies can only exist between tasks and subtasks (not epics)
126
+ 3. Cycles are automatically detected and rejected
127
+
128
+ ## 3) Task Completion Flow
129
+
130
+ ### Before Starting a Task
131
+
132
+ 1. Check if task has unmet dependencies:
133
+ ```bash
134
+ trekoon dep list <task-id> --toon
135
+ ```
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
+
141
+ ### When Completing a Task
142
+
143
+ 1. Mark the task as done:
144
+ ```bash
145
+ trekoon task update <task-id> --status done --toon
146
+ ```
147
+
148
+ 2. To find the next task that was blocked by this one:
149
+ - List all tasks: `trekoon task list --all --toon`
150
+ - Check which tasks have dependencies on the completed task
151
+ - The task(s) with all dependencies now satisfied are ready to start
152
+
153
+ ### Finding Next Work
154
+
155
+ ```bash
156
+ trekoon task list --status todo --limit 20 --toon
157
+ ```
158
+
159
+ Tasks are sorted with `in_progress` first, then `todo`. Look for tasks with no dependencies or all dependencies satisfied.
160
+
161
+ ## 4) Load existing work first
13
162
 
14
163
  Before creating or changing anything, inspect current context:
15
164
 
@@ -20,7 +169,7 @@ trekoon epic show <id> --all --toon
20
169
  trekoon task show <id> --all --toon
21
170
  ```
22
171
 
23
- - `epic list` / `task list` defaults:
172
+ - `epic list` / `task list` / `subtask list` defaults:
24
173
  - open work only (`in_progress`, `in-progress`, `todo`)
25
174
  - prioritized as `in_progress`/`in-progress` first, then `todo`
26
175
  - default limit `10`
@@ -36,7 +185,14 @@ trekoon task list --all --toon
36
185
  - `epic show <id> --all --toon`: full epic tree (tasks + subtasks)
37
186
  - `task show <id> --all --toon`: task plus its subtasks
38
187
 
39
- ## 2) Create work (epic/task/subtask)
188
+ ### View Options
189
+
190
+ | Command | `--view` options |
191
+ |---------|------------------|
192
+ | `list` | `table` (default), `compact` |
193
+ | `show` | `table` (default), `compact`, `tree`, `detail` |
194
+
195
+ ## 5) Create work (epic/task/subtask)
40
196
 
41
197
  ```bash
42
198
  trekoon epic create --title "..." --description "..." --status todo --toon
@@ -47,8 +203,9 @@ trekoon subtask create --task <task-id> --title "..." --description "..." --stat
47
203
  Notes:
48
204
  - `description` is required for epic/task create and it must be well written.
49
205
  - `status` defaults to `todo` if omitted.
206
+ - `description` is optional for subtask create.
50
207
 
51
- ## 3) Update work
208
+ ## 6) Update work
52
209
 
53
210
  ### Single-item update
54
211
 
@@ -73,7 +230,7 @@ Rules:
73
230
  - In bulk mode, do not pass a positional ID.
74
231
  - Bulk update supports `--append` and/or `--status`.
75
232
 
76
- ## 4) Setup/install/init (if `trekoon` is unavailable)
233
+ ## 7) Setup/install/init (if `trekoon` is unavailable)
77
234
 
78
235
  1. Install Trekoon (or make sure it is on `PATH`).
79
236
  2. In the target repository/worktree, initialize tracker state:
@@ -81,11 +238,12 @@ Rules:
81
238
  ```bash
82
239
  trekoon init
83
240
  ```
241
+
84
242
  3. You can always run `trekoon quickstart` or `trekoon --help` to get more information.
85
243
 
86
244
  If `.trekoon/trekoon.db` is missing, initialize before any create/update commands.
87
245
 
88
- ## 5) Safety
246
+ ## 8) Safety
89
247
 
90
248
  - Never edit `.trekoon/trekoon.db` directly.
91
249
  - `trekoon wipe --yes` is prohibited unless the user explicitly confirms they want a destructive wipe.
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`
@@ -89,7 +89,7 @@ List defaults and filters (`epic list`, `task list`):
89
89
  - All rows and statuses: `--all`
90
90
  - `--all` is mutually exclusive with `--status` and `--limit`
91
91
 
92
- Bulk updates (`epic update`, `task update`):
92
+ Bulk updates (`epic update`, `task update`, `subtask update`):
93
93
 
94
94
  - Target all rows: `--all`
95
95
  - Target specific rows: `--ids <id1,id2,...>`
@@ -103,6 +103,8 @@ Examples:
103
103
  ```bash
104
104
  trekoon task update --all --status in_progress
105
105
  trekoon task update --ids <task-1>,<task-2> --append "\nFollow-up note"
106
+ trekoon subtask update --all --status done
107
+ trekoon subtask update --ids <subtask-1>,<subtask-2> --append "\nFollow-up note"
106
108
  trekoon epic update --ids <epic-1>,<epic-2> --status done
107
109
  ```
108
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trekoon",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "AI-first local issue tracker CLI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,7 +17,8 @@
17
17
  "scripts": {
18
18
  "run": "bun run ./src/index.ts",
19
19
  "build": "bun build ./src/index.ts --outdir ./dist --target bun",
20
- "test": "bun test ./tests"
20
+ "test": "bun test ./tests",
21
+ "lint": "bunx tsc --noEmit"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/bun": "^1.3.9",
@@ -0,0 +1,88 @@
1
+ import { hasFlag, parseArgs, parseStrictPositiveInt, readMissingOptionValue, readOption } from "./arg-parser";
2
+
3
+ import { failResult, okResult } from "../io/output";
4
+ import { type CliContext, type CliResult } from "../runtime/command-types";
5
+ import { openTrekoonDatabase } from "../storage/database";
6
+ import { DEFAULT_EVENT_RETENTION_DAYS, pruneEvents } from "../storage/events-retention";
7
+
8
+ const EVENTS_USAGE = "Usage: trekoon events prune [--dry-run] [--archive] [--retention-days <n>]";
9
+
10
+ function usage(message: string): CliResult {
11
+ return failResult({
12
+ command: "events",
13
+ human: `${message}\n${EVENTS_USAGE}`,
14
+ data: { message },
15
+ error: {
16
+ code: "invalid_args",
17
+ message,
18
+ },
19
+ });
20
+ }
21
+
22
+ function invalidInput(command: string, message: string, option: string): CliResult {
23
+ return failResult({
24
+ command,
25
+ human: message,
26
+ data: {
27
+ option,
28
+ },
29
+ error: {
30
+ code: "invalid_input",
31
+ message,
32
+ },
33
+ });
34
+ }
35
+
36
+ export async function runEvents(context: CliContext): Promise<CliResult> {
37
+ const parsed = parseArgs(context.args);
38
+ const subcommand: string | undefined = parsed.positional[0];
39
+
40
+ if (!subcommand) {
41
+ return usage("Missing events subcommand.");
42
+ }
43
+
44
+ if (subcommand !== "prune") {
45
+ return usage(`Unknown events subcommand '${subcommand}'.`);
46
+ }
47
+
48
+ if (parsed.positional.length > 1) {
49
+ return usage("Unexpected positional arguments for events prune.");
50
+ }
51
+
52
+ const missingOption: string | undefined = readMissingOptionValue(parsed.missingOptionValues, "retention-days");
53
+ if (missingOption !== undefined) {
54
+ return invalidInput("events.prune", `Option --${missingOption} requires a value.`, missingOption);
55
+ }
56
+
57
+ const parsedRetentionDays: number | undefined = parseStrictPositiveInt(readOption(parsed.options, "retention-days"));
58
+ if (Number.isNaN(parsedRetentionDays)) {
59
+ return invalidInput("events.prune", "--retention-days must be a positive integer.", "retention-days");
60
+ }
61
+
62
+ const retentionDays: number = parsedRetentionDays ?? DEFAULT_EVENT_RETENTION_DAYS;
63
+ const dryRun: boolean = hasFlag(parsed.flags, "dry-run");
64
+ const archive: boolean = hasFlag(parsed.flags, "archive");
65
+ const storage = openTrekoonDatabase(context.cwd);
66
+
67
+ try {
68
+ const summary = pruneEvents(storage.db, {
69
+ retentionDays,
70
+ dryRun,
71
+ archive,
72
+ });
73
+
74
+ return okResult({
75
+ command: "events.prune",
76
+ human: [
77
+ dryRun ? "Dry run complete." : "Prune complete.",
78
+ `Retention days: ${summary.retentionDays}`,
79
+ `Candidates: ${summary.candidateCount}`,
80
+ `Archived: ${summary.archivedCount}`,
81
+ `Deleted: ${summary.deletedCount}`,
82
+ ].join("\n"),
83
+ data: summary,
84
+ });
85
+ } finally {
86
+ storage.close();
87
+ }
88
+ }
@@ -21,6 +21,8 @@ const ROOT_HELP = [
21
21
  " task Task lifecycle commands",
22
22
  " subtask Subtask lifecycle commands",
23
23
  " dep Dependency graph commands",
24
+ " events Event retention and cleanup commands",
25
+ " migrate Migration status and rollback commands",
24
26
  " sync Cross-branch sync commands",
25
27
  " skills Project-local skill install/link commands",
26
28
  ].join("\n");
@@ -33,8 +35,11 @@ const COMMAND_HELP: Record<string, string> = {
33
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>)",
34
36
  task:
35
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>)",
36
- subtask: "Usage: trekoon subtask <subcommand> [options] (list supports --view table|compact)",
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>)",
37
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>]",
38
43
  sync: "Usage: trekoon sync <subcommand> [options]",
39
44
  skills:
40
45
  "Usage: trekoon skills install [--link --editor opencode|claude] [--to <path>] (--to sets symlink root for --link only; install path always <cwd>/.agents/skills/trekoon/SKILL.md)",
@@ -0,0 +1,123 @@
1
+ import { parseArgs, readMissingOptionValue, readOption } from "./arg-parser";
2
+
3
+ import { failResult, okResult } from "../io/output";
4
+ import { type CliContext, type CliResult } from "../runtime/command-types";
5
+ import { openTrekoonDatabase } from "../storage/database";
6
+ import { describeMigrations, rollbackDatabase } from "../storage/migrations";
7
+
8
+ const MIGRATE_USAGE = "Usage: trekoon migrate <status|rollback> [--to-version <n>]";
9
+
10
+ function usage(message: string): CliResult {
11
+ return failResult({
12
+ command: "migrate",
13
+ human: `${message}\n${MIGRATE_USAGE}`,
14
+ data: { message },
15
+ error: {
16
+ code: "invalid_args",
17
+ message,
18
+ },
19
+ });
20
+ }
21
+
22
+ function parseVersion(rawValue: string | undefined): number | null {
23
+ if (rawValue === undefined) {
24
+ return null;
25
+ }
26
+
27
+ if (!/^\d+$/.test(rawValue)) {
28
+ return Number.NaN;
29
+ }
30
+
31
+ return Number.parseInt(rawValue, 10);
32
+ }
33
+
34
+ export async function runMigrate(context: CliContext): Promise<CliResult> {
35
+ const parsed = parseArgs(context.args);
36
+ const subcommand: string | undefined = parsed.positional[0];
37
+
38
+ if (!subcommand) {
39
+ return usage("Missing migrate subcommand.");
40
+ }
41
+
42
+ const missingOption = readMissingOptionValue(parsed.missingOptionValues, "to-version");
43
+ if (missingOption !== undefined) {
44
+ return failResult({
45
+ command: "migrate",
46
+ human: `Option --${missingOption} requires a value.`,
47
+ data: {
48
+ option: missingOption,
49
+ },
50
+ error: {
51
+ code: "invalid_input",
52
+ message: `Option --${missingOption} requires a value.`,
53
+ },
54
+ });
55
+ }
56
+
57
+ const storage = openTrekoonDatabase(context.cwd, { autoMigrate: false });
58
+
59
+ try {
60
+ if (subcommand === "status") {
61
+ const status = describeMigrations(storage.db);
62
+
63
+ return okResult({
64
+ command: "migrate.status",
65
+ human: [
66
+ `Current version: ${status.currentVersion}`,
67
+ `Latest version: ${status.latestVersion}`,
68
+ `Pending migrations: ${status.pending.length}`,
69
+ ].join("\n"),
70
+ data: status,
71
+ });
72
+ }
73
+
74
+ if (subcommand === "rollback") {
75
+ const status = describeMigrations(storage.db);
76
+ const parsedVersion: number | null = parseVersion(readOption(parsed.options, "to-version"));
77
+
78
+ if (Number.isNaN(parsedVersion)) {
79
+ return failResult({
80
+ command: "migrate.rollback",
81
+ human: "--to-version must be a non-negative integer.",
82
+ data: {
83
+ option: "to-version",
84
+ },
85
+ error: {
86
+ code: "invalid_input",
87
+ message: "--to-version must be a non-negative integer.",
88
+ },
89
+ });
90
+ }
91
+
92
+ const targetVersion: number = parsedVersion ?? Math.max(0, status.currentVersion - 1);
93
+ const summary = rollbackDatabase(storage.db, targetVersion);
94
+
95
+ return okResult({
96
+ command: "migrate.rollback",
97
+ human: [
98
+ `Rolled back ${summary.rolledBack} migration(s).`,
99
+ `From version ${summary.fromVersion} to ${summary.toVersion}.`,
100
+ ].join("\n"),
101
+ data: summary,
102
+ });
103
+ }
104
+
105
+ return usage(`Unknown migrate subcommand '${subcommand}'.`);
106
+ } catch (error: unknown) {
107
+ const message = error instanceof Error ? error.message : "Unknown migration failure.";
108
+
109
+ return failResult({
110
+ command: "migrate",
111
+ human: message,
112
+ data: {
113
+ reason: "migrate_failed",
114
+ },
115
+ error: {
116
+ code: "migrate_failed",
117
+ message,
118
+ },
119
+ });
120
+ } finally {
121
+ storage.close();
122
+ }
123
+ }
@@ -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.",