trekoon 0.1.1 → 0.1.4

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,154 @@ 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
+ ### Valid Statuses
65
+
66
+ | Status | Meaning |
67
+ |--------|---------|
68
+ | `todo` | Work not started (default for new items) |
69
+ | `in_progress` | Actively being worked on |
70
+ | `done` | Completed successfully |
71
+
72
+ Note: `in-progress` (hyphenated) is equivalent to `in_progress`.
73
+
74
+ ### When to Change Status
75
+
76
+ | Transition | When to apply |
77
+ |------------|---------------|
78
+ | `todo → in_progress` | When you START working on a task/subtask/epic |
79
+ | `in_progress → done` | When you COMPLETE the work and it is ready |
80
+
81
+ ### Status Change Commands
82
+
83
+ ```bash
84
+ trekoon task update <task-id> --status in_progress --toon
85
+ trekoon task update <task-id> --status done --toon
86
+ trekoon subtask update <subtask-id> --status done --toon
87
+ trekoon epic update <epic-id> --status done --toon
88
+ ```
89
+
90
+ ## 2) Dependency Management
91
+
92
+ Dependencies define what must be completed before a task can start. A task/subtask can depend on other tasks/subtasks.
93
+
94
+ ### Commands
95
+
96
+ ```bash
97
+ trekoon dep add <source-id> <depends-on-id> --toon
98
+ trekoon dep list <source-id> --toon
99
+ trekoon dep remove <source-id> <depends-on-id> --toon
100
+ ```
101
+
102
+ - `<source-id>`: The task/subtask that has the dependency
103
+ - `<depends-on-id>`: The task/subtask that must be completed first
11
104
 
12
- ## 1) Load existing work first
105
+ ### Checking Dependencies
106
+
107
+ Before starting any task, always check its dependencies:
108
+
109
+ ```bash
110
+ trekoon dep list <task-id> --toon
111
+ ```
112
+
113
+ The response `data.dependencies` array contains entries with:
114
+ - `sourceId`: the task you're checking
115
+ - `dependsOnId`: what must be done first
116
+ - `dependsOnKind`: "task" or "subtask"
117
+
118
+ ### Dependency Rules
119
+
120
+ 1. A task with dependencies should only be marked `in_progress` when ALL dependencies have status `done`
121
+ 2. Dependencies can only exist between tasks and subtasks (not epics)
122
+ 3. Cycles are automatically detected and rejected
123
+
124
+ ## 3) Task Completion Flow
125
+
126
+ ### Before Starting a Task
127
+
128
+ 1. Check if task has unmet dependencies:
129
+ ```bash
130
+ trekoon dep list <task-id> --toon
131
+ ```
132
+
133
+ 2. If dependencies exist and are not `done`, complete those first
134
+
135
+ 3. Only mark `in_progress` when all dependencies are `done`
136
+
137
+ ### When Completing a Task
138
+
139
+ 1. Mark the task as done:
140
+ ```bash
141
+ trekoon task update <task-id> --status done --toon
142
+ ```
143
+
144
+ 2. To find the next task that was blocked by this one:
145
+ - List all tasks: `trekoon task list --all --toon`
146
+ - Check which tasks have dependencies on the completed task
147
+ - The task(s) with all dependencies now satisfied are ready to start
148
+
149
+ ### Finding Next Work
150
+
151
+ ```bash
152
+ trekoon task list --status todo --limit 20 --toon
153
+ ```
154
+
155
+ Tasks are sorted with `in_progress` first, then `todo`. Look for tasks with no dependencies or all dependencies satisfied.
156
+
157
+ ## 4) Load existing work first
13
158
 
14
159
  Before creating or changing anything, inspect current context:
15
160
 
@@ -36,7 +181,14 @@ trekoon task list --all --toon
36
181
  - `epic show <id> --all --toon`: full epic tree (tasks + subtasks)
37
182
  - `task show <id> --all --toon`: task plus its subtasks
38
183
 
39
- ## 2) Create work (epic/task/subtask)
184
+ ### View Options
185
+
186
+ | Command | `--view` options |
187
+ |---------|------------------|
188
+ | `list` | `table` (default), `compact` |
189
+ | `show` | `table` (default), `compact`, `tree`, `detail` |
190
+
191
+ ## 5) Create work (epic/task/subtask)
40
192
 
41
193
  ```bash
42
194
  trekoon epic create --title "..." --description "..." --status todo --toon
@@ -47,8 +199,9 @@ trekoon subtask create --task <task-id> --title "..." --description "..." --stat
47
199
  Notes:
48
200
  - `description` is required for epic/task create and it must be well written.
49
201
  - `status` defaults to `todo` if omitted.
202
+ - `description` is optional for subtask create.
50
203
 
51
- ## 3) Update work
204
+ ## 6) Update work
52
205
 
53
206
  ### Single-item update
54
207
 
@@ -73,7 +226,7 @@ Rules:
73
226
  - In bulk mode, do not pass a positional ID.
74
227
  - Bulk update supports `--append` and/or `--status`.
75
228
 
76
- ## 4) Setup/install/init (if `trekoon` is unavailable)
229
+ ## 7) Setup/install/init (if `trekoon` is unavailable)
77
230
 
78
231
  1. Install Trekoon (or make sure it is on `PATH`).
79
232
  2. In the target repository/worktree, initialize tracker state:
@@ -81,11 +234,12 @@ Rules:
81
234
  ```bash
82
235
  trekoon init
83
236
  ```
237
+
84
238
  3. You can always run `trekoon quickstart` or `trekoon --help` to get more information.
85
239
 
86
240
  If `.trekoon/trekoon.db` is missing, initialize before any create/update commands.
87
241
 
88
- ## 5) Safety
242
+ ## 8) Safety
89
243
 
90
244
  - Never edit `.trekoon/trekoon.db` directly.
91
245
  - `trekoon wipe --yes` is prohibited unless the user explicitly confirms they want a destructive wipe.
package/README.md CHANGED
@@ -52,6 +52,7 @@ npm i -g trekoon
52
52
  - `trekoon subtask <create|list|update|delete>`
53
53
  - `trekoon dep <add|remove|list>`
54
54
  - `trekoon sync <status|pull|resolve>`
55
+ - `trekoon skills install [--link --editor opencode|claude] [--to <path>]`
55
56
  - `trekoon wipe --yes`
56
57
 
57
58
  Global output modes:
@@ -88,7 +89,7 @@ List defaults and filters (`epic list`, `task list`):
88
89
  - All rows and statuses: `--all`
89
90
  - `--all` is mutually exclusive with `--status` and `--limit`
90
91
 
91
- Bulk updates (`epic update`, `task update`):
92
+ Bulk updates (`epic update`, `task update`, `subtask update`):
92
93
 
93
94
  - Target all rows: `--all`
94
95
  - Target specific rows: `--ids <id1,id2,...>`
@@ -102,6 +103,8 @@ Examples:
102
103
  ```bash
103
104
  trekoon task update --all --status in_progress
104
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"
105
108
  trekoon epic update --ids <epic-1>,<epic-2> --status done
106
109
  ```
107
110
 
@@ -157,7 +160,57 @@ trekoon sync pull --from main
157
160
  trekoon sync resolve <conflict-id> --use ours
158
161
  ```
159
162
 
160
- ### 6) Pre-merge checklist
163
+ ### 6) Install project-local Trekoon skill for agents
164
+
165
+ `trekoon skills install` always writes the bundled skill file into the current
166
+ repository at:
167
+
168
+ - `.agents/skills/trekoon/SKILL.md`
169
+
170
+ You can also create a project-local editor link:
171
+
172
+ ```bash
173
+ trekoon skills install
174
+ trekoon skills install --link --editor opencode
175
+ trekoon skills install --link --editor claude
176
+ trekoon skills install --link --editor opencode --to ./.custom-editor/skills
177
+ ```
178
+
179
+ Path behavior:
180
+
181
+ - Default opencode link path: `.opencode/skills/trekoon`
182
+ - Default claude link path: `.claude/skills/trekoon`
183
+ - `--to <path>` overrides the editor root for link creation only.
184
+ - `--to` does **not** move or copy `SKILL.md` to that path.
185
+ - Re-running install is idempotent: it refreshes `SKILL.md` and reuses/replaces
186
+ the same symlink target.
187
+ - If the link destination exists as a non-link path, install fails with an
188
+ actionable conflict error.
189
+
190
+ How `--to` works (step-by-step):
191
+
192
+ 1. Trekoon always installs/copies to:
193
+ - `<repo>/.agents/skills/trekoon/SKILL.md`
194
+ 2. If `--link` is present, Trekoon creates a `trekoon` symlink directory entry.
195
+ 3. `--to <path>` sets the symlink root directory.
196
+ 4. Final link path is:
197
+ - `<resolved-to-path>/trekoon -> <repo>/.agents/skills/trekoon`
198
+
199
+ Example:
200
+
201
+ ```bash
202
+ trekoon skills install --link --editor opencode --to ./.custom-editor/skills
203
+ ```
204
+
205
+ This produces:
206
+
207
+ - `<repo>/.agents/skills/trekoon/SKILL.md` (copied file)
208
+ - `<repo>/.custom-editor/skills/trekoon` (symlink)
209
+ - symlink target: `<repo>/.agents/skills/trekoon`
210
+
211
+ Trekoon does not mutate global editor config directories.
212
+
213
+ ### 7) Pre-merge checklist
161
214
 
162
215
  - [ ] `trekoon sync status` shows no unresolved conflicts
163
216
  - [ ] done tasks/subtasks are marked completed
package/package.json CHANGED
@@ -1,16 +1,24 @@
1
1
  {
2
2
  "name": "trekoon",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "AI-first local issue tracker CLI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "trekoon": "./bin/trekoon"
9
9
  },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ ".agents/skills/trekoon/SKILL.md",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
10
17
  "scripts": {
11
18
  "run": "bun run ./src/index.ts",
12
19
  "build": "bun build ./src/index.ts --outdir ./dist --target bun",
13
- "test": "bun test ./tests"
20
+ "test": "bun test ./tests",
21
+ "lint": "bunx tsc --noEmit"
14
22
  },
15
23
  "devDependencies": {
16
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,7 +21,10 @@ 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",
27
+ " skills Project-local skill install/link commands",
25
28
  ].join("\n");
26
29
 
27
30
  const COMMAND_HELP: Record<string, string> = {
@@ -32,9 +35,14 @@ const COMMAND_HELP: Record<string, string> = {
32
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>)",
33
36
  task:
34
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>)",
35
- subtask: "Usage: trekoon subtask <subcommand> [options] (list supports --view table|compact)",
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>)",
36
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>]",
37
43
  sync: "Usage: trekoon sync <subcommand> [options]",
44
+ skills:
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)",
38
46
  help: "Usage: trekoon help [command] [--json|--toon]",
39
47
  };
40
48
 
@@ -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
+ }