trekoon 0.1.7 → 0.1.9
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 +81 -15
- package/README.md +181 -21
- package/package.json +1 -1
- package/src/commands/arg-parser.ts +95 -0
- package/src/commands/dep.ts +20 -1
- package/src/commands/epic.ts +141 -7
- package/src/commands/help.ts +266 -17
- package/src/commands/quickstart.ts +88 -24
- package/src/commands/subtask.ts +98 -6
- package/src/commands/sync.ts +130 -52
- package/src/commands/task.ts +369 -7
- package/src/domain/tracker-domain.ts +113 -7
- package/src/domain/types.ts +7 -0
- package/src/index.ts +1 -1
- package/src/io/output.ts +98 -5
- package/src/runtime/cli-shell.ts +160 -24
- package/src/runtime/command-types.ts +18 -0
- package/src/runtime/version.ts +20 -0
- package/src/storage/path.ts +58 -1
|
@@ -35,6 +35,9 @@ data:
|
|
|
35
35
|
status: in_progress
|
|
36
36
|
createdAt: 1700000001000
|
|
37
37
|
updatedAt: 1700000001000
|
|
38
|
+
metadata:
|
|
39
|
+
contractVersion: 1.0.0
|
|
40
|
+
requestId: req-abc12345
|
|
38
41
|
```
|
|
39
42
|
|
|
40
43
|
On error:
|
|
@@ -43,6 +46,9 @@ On error:
|
|
|
43
46
|
ok: false
|
|
44
47
|
command: task.show
|
|
45
48
|
data: {}
|
|
49
|
+
metadata:
|
|
50
|
+
contractVersion: 1.0.0
|
|
51
|
+
requestId: req-def67890
|
|
46
52
|
error:
|
|
47
53
|
code: not_found
|
|
48
54
|
message: task not found: invalid-id
|
|
@@ -55,10 +61,34 @@ error:
|
|
|
55
61
|
| `ok` | `true` if command succeeded, `false` on error |
|
|
56
62
|
| `command` | The command that was executed (e.g., `task.list`, `epic.create`) |
|
|
57
63
|
| `data` | The response payload (tasks, epics, dependencies, etc.) |
|
|
64
|
+
| `metadata` | Contract metadata (`contractVersion`, `requestId`) |
|
|
65
|
+
| `meta` | Optional command-specific metadata (pagination/defaults/filters/diagnostics) |
|
|
58
66
|
| `error` | Present only on failure, contains `code` and `message` |
|
|
59
67
|
|
|
60
68
|
Use long flags (`--status`, `--description`, etc.) and ALWAYS append `--toon` to every command.
|
|
61
69
|
|
|
70
|
+
### Contract details to rely on
|
|
71
|
+
|
|
72
|
+
- Machine responses include `metadata.contractVersion` and `metadata.requestId`.
|
|
73
|
+
- Command IDs are stable and typically dot namespaced (`task.list`, `sync.status`).
|
|
74
|
+
- Some root commands use single-token IDs (`help`, `init`, `quickstart`, `wipe`, `version`).
|
|
75
|
+
- Unknown options fail fast with deterministic `unknown_option` errors and may include:
|
|
76
|
+
- `data.option`
|
|
77
|
+
- `data.allowedOptions`
|
|
78
|
+
- `data.suggestions`
|
|
79
|
+
|
|
80
|
+
### Compatibility mode (legacy sync consumers)
|
|
81
|
+
|
|
82
|
+
Default behavior is strict canonical IDs (for example `sync.status`).
|
|
83
|
+
|
|
84
|
+
If a legacy consumer still expects underscore sync IDs, compatibility mode can be used:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
trekoon --toon --compat legacy-sync-command-ids sync status
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When enabled, output includes `metadata.compatibility` with migration/deprecation details.
|
|
91
|
+
|
|
62
92
|
## 1) Status Management
|
|
63
93
|
|
|
64
94
|
### Status values
|
|
@@ -101,6 +131,7 @@ Dependencies define what must be completed before a task can start. A task/subta
|
|
|
101
131
|
trekoon dep add <source-id> <depends-on-id> --toon
|
|
102
132
|
trekoon dep list <source-id> --toon
|
|
103
133
|
trekoon dep remove <source-id> <depends-on-id> --toon
|
|
134
|
+
trekoon dep reverse <task-or-subtask-id> --toon
|
|
104
135
|
```
|
|
105
136
|
|
|
106
137
|
- `<source-id>`: The task/subtask that has the dependency
|
|
@@ -127,16 +158,32 @@ The response `data.dependencies` array contains entries with:
|
|
|
127
158
|
|
|
128
159
|
## 3) Task Completion Flow
|
|
129
160
|
|
|
130
|
-
###
|
|
161
|
+
### Canonical dependency-aware execution loop
|
|
131
162
|
|
|
132
|
-
|
|
163
|
+
Run this sequence every session:
|
|
164
|
+
|
|
165
|
+
1. Sync branch/worktree status:
|
|
133
166
|
```bash
|
|
134
|
-
trekoon
|
|
167
|
+
trekoon sync status --toon
|
|
168
|
+
```
|
|
169
|
+
2. Pull deterministic ready candidates (or next candidate):
|
|
170
|
+
```bash
|
|
171
|
+
trekoon task ready --limit 5 --toon
|
|
172
|
+
trekoon task next --toon
|
|
173
|
+
```
|
|
174
|
+
3. Inspect downstream impact before changes:
|
|
175
|
+
```bash
|
|
176
|
+
trekoon dep reverse <task-or-subtask-id> --toon
|
|
177
|
+
```
|
|
178
|
+
4. Start work with explicit status updates:
|
|
179
|
+
```bash
|
|
180
|
+
trekoon task update <task-id> --status in_progress --toon
|
|
181
|
+
```
|
|
182
|
+
5. Finish or block with appended context + final status:
|
|
183
|
+
```bash
|
|
184
|
+
trekoon task update <task-id> --append "Completed implementation" --status done --toon
|
|
185
|
+
trekoon task update <task-id> --append "Blocked by <reason>" --status blocked --toon
|
|
135
186
|
```
|
|
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
187
|
|
|
141
188
|
### When Completing a Task
|
|
142
189
|
|
|
@@ -146,17 +193,19 @@ The response `data.dependencies` array contains entries with:
|
|
|
146
193
|
```
|
|
147
194
|
|
|
148
195
|
2. To find the next task that was blocked by this one:
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
196
|
+
- Inspect downstream nodes: `trekoon dep reverse <task-id> --toon`
|
|
197
|
+
- Pull ready queue: `trekoon task ready --limit 5 --toon`
|
|
198
|
+
- Pick one deterministically: `trekoon task next --toon`
|
|
152
199
|
|
|
153
200
|
### Finding Next Work
|
|
154
201
|
|
|
155
202
|
```bash
|
|
156
|
-
trekoon task
|
|
203
|
+
trekoon task ready --limit 5 --toon
|
|
204
|
+
trekoon task next --toon
|
|
205
|
+
trekoon dep reverse <task-or-subtask-id> --toon
|
|
157
206
|
```
|
|
158
207
|
|
|
159
|
-
|
|
208
|
+
Use `task ready` for ranked candidates and `task next` for the top deterministic pick.
|
|
160
209
|
|
|
161
210
|
## 4) Load existing work first
|
|
162
211
|
|
|
@@ -173,6 +222,7 @@ trekoon task show <id> --all --toon
|
|
|
173
222
|
- open work only (`in_progress`, `in-progress`, `todo`)
|
|
174
223
|
- prioritized as `in_progress`/`in-progress` first, then `todo`
|
|
175
224
|
- default limit `10`
|
|
225
|
+
- `--cursor <n>` is offset-like pagination for list endpoints
|
|
176
226
|
- Filter list explicitly when needed:
|
|
177
227
|
|
|
178
228
|
```bash
|
|
@@ -182,9 +232,24 @@ trekoon task list --all --toon
|
|
|
182
232
|
```
|
|
183
233
|
|
|
184
234
|
- `--all` cannot be combined with `--status` or `--limit`.
|
|
235
|
+
- `--all` cannot be combined with `--cursor`.
|
|
236
|
+
- Machine pagination contract is in `meta.pagination.hasMore` and
|
|
237
|
+
`meta.pagination.nextCursor`.
|
|
238
|
+
- Machine list/show responses may also include:
|
|
239
|
+
- `meta.defaults`
|
|
240
|
+
- `meta.filters`
|
|
241
|
+
- `meta.truncation`
|
|
185
242
|
- `epic show <id> --all --toon`: full epic tree (tasks + subtasks)
|
|
186
243
|
- `task show <id> --all --toon`: task plus its subtasks
|
|
187
244
|
|
|
245
|
+
### Canonical storage root behavior
|
|
246
|
+
|
|
247
|
+
- In git repos/worktrees, Trekoon resolves storage from repository top-level so
|
|
248
|
+
nested cwd invocations use one canonical `.trekoon/trekoon.db`.
|
|
249
|
+
- In non-git directories, Trekoon falls back to invocation cwd.
|
|
250
|
+
- If invocation cwd differs from canonical root, machine output may include
|
|
251
|
+
`meta.storageRootDiagnostics`.
|
|
252
|
+
|
|
188
253
|
### View Options
|
|
189
254
|
|
|
190
255
|
| Command | `--view` options |
|
|
@@ -236,14 +301,15 @@ Rules:
|
|
|
236
301
|
2. In the target repository/worktree, initialize tracker state:
|
|
237
302
|
|
|
238
303
|
```bash
|
|
239
|
-
trekoon init
|
|
304
|
+
trekoon init --toon
|
|
240
305
|
```
|
|
241
306
|
|
|
242
|
-
3. You can always run `trekoon quickstart` or `trekoon --help` to
|
|
307
|
+
3. You can always run `trekoon quickstart --toon` or `trekoon --help --toon` to
|
|
308
|
+
get more information.
|
|
243
309
|
|
|
244
310
|
If `.trekoon/trekoon.db` is missing, initialize before any create/update commands.
|
|
245
311
|
|
|
246
312
|
## 8) Safety
|
|
247
313
|
|
|
248
314
|
- Never edit `.trekoon/trekoon.db` directly.
|
|
249
|
-
- `trekoon wipe --yes` is prohibited unless the user explicitly confirms they want a destructive wipe.
|
|
315
|
+
- `trekoon wipe --yes --toon` is prohibited unless the user explicitly confirms they want a destructive wipe.
|
package/README.md
CHANGED
|
@@ -46,12 +46,15 @@ npm i -g trekoon
|
|
|
46
46
|
## Command surface
|
|
47
47
|
|
|
48
48
|
- `trekoon init`
|
|
49
|
+
- `trekoon help [command]`
|
|
49
50
|
- `trekoon quickstart`
|
|
50
51
|
- `trekoon epic <create|list|show|update|delete>`
|
|
51
|
-
- `trekoon task <create|list|show|update|delete>`
|
|
52
|
+
- `trekoon task <create|list|show|ready|next|update|delete>`
|
|
52
53
|
- `trekoon subtask <create|list|update|delete>`
|
|
53
|
-
- `trekoon dep <add|remove|list>`
|
|
54
|
-
- `trekoon
|
|
54
|
+
- `trekoon dep <add|remove|list|reverse>`
|
|
55
|
+
- `trekoon events prune [--dry-run] [--archive] [--retention-days <n>]`
|
|
56
|
+
- `trekoon migrate <status|rollback> [--to-version <n>]`
|
|
57
|
+
- `trekoon sync <status|pull|resolve|conflicts>`
|
|
55
58
|
- `trekoon skills install [--link --editor opencode|claude|pi] [--to <path>] [--allow-outside-repo]`
|
|
56
59
|
- `trekoon skills update`
|
|
57
60
|
- `trekoon wipe --yes`
|
|
@@ -60,6 +63,7 @@ Global output modes:
|
|
|
60
63
|
|
|
61
64
|
- `--json` for structured JSON output
|
|
62
65
|
- `--toon` for true TOON-encoded output (not JSON text)
|
|
66
|
+
- `--compat <mode>` for explicit machine compatibility behavior
|
|
63
67
|
- `--help` for root and command help
|
|
64
68
|
- `--version` for CLI version
|
|
65
69
|
|
|
@@ -72,7 +76,8 @@ trekoon --json quickstart
|
|
|
72
76
|
trekoon quickstart --json
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
Trekoon
|
|
79
|
+
Trekoon options use long form (`--option`) for command/subcommand flags.
|
|
80
|
+
Root help/version aliases `-h` and `-v` are also supported.
|
|
76
81
|
|
|
77
82
|
Human view options:
|
|
78
83
|
|
|
@@ -87,8 +92,9 @@ List defaults and filters (`epic list`, `task list`, `subtask list`):
|
|
|
87
92
|
- Default limit: `10`
|
|
88
93
|
- Status filter: `--status in_progress,todo` (CSV)
|
|
89
94
|
- Custom limit: `--limit <n>`
|
|
95
|
+
- Cursor pagination: `--cursor <n>` (offset-like start index for next page)
|
|
90
96
|
- All rows and statuses: `--all`
|
|
91
|
-
- `--all` is mutually exclusive with `--status
|
|
97
|
+
- `--all` is mutually exclusive with `--status`, `--limit`, and `--cursor`
|
|
92
98
|
|
|
93
99
|
Bulk updates (`epic update`, `task update`, `subtask update`):
|
|
94
100
|
|
|
@@ -111,8 +117,15 @@ trekoon epic update --ids <epic-1>,<epic-2> --status done
|
|
|
111
117
|
|
|
112
118
|
## Quickstart
|
|
113
119
|
|
|
114
|
-
Trekoon is local-first:
|
|
115
|
-
|
|
120
|
+
Trekoon is local-first: in git repos/worktrees, Trekoon resolves state to one
|
|
121
|
+
canonical repository root (`git rev-parse --show-toplevel`) so nested
|
|
122
|
+
invocations share the same `.trekoon/trekoon.db`.
|
|
123
|
+
|
|
124
|
+
Outside git repos, Trekoon falls back to the invocation cwd.
|
|
125
|
+
|
|
126
|
+
When machine output is enabled (`--json`/`--toon`) and a command resolves
|
|
127
|
+
storage from a non-canonical cwd, Trekoon emits
|
|
128
|
+
`meta.storageRootDiagnostics` to make the divergence explicit for automation.
|
|
116
129
|
|
|
117
130
|
### 1) Initialize
|
|
118
131
|
|
|
@@ -140,16 +153,40 @@ trekoon dep add <task-id> <depends-on-id>
|
|
|
140
153
|
trekoon dep list <task-id>
|
|
141
154
|
```
|
|
142
155
|
|
|
143
|
-
### 4)
|
|
156
|
+
### 4) AI execution loop for agents
|
|
157
|
+
|
|
158
|
+
Run this loop each session to pick next work deterministically:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
trekoon --toon sync status
|
|
162
|
+
trekoon --toon task ready --limit 5
|
|
163
|
+
trekoon --toon task next
|
|
164
|
+
trekoon --toon dep reverse <task-or-subtask-id>
|
|
165
|
+
trekoon --toon task update <task-id> --status in_progress
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
When done or blocked, append context and update final status:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
trekoon --toon task update <task-id> --append "Completed implementation and checks" --status done
|
|
172
|
+
trekoon --toon task update <task-id> --append "Blocked by <reason>" --status blocked
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 5) Use TOON output for agent workflows
|
|
144
176
|
|
|
145
177
|
```bash
|
|
146
|
-
trekoon --json epic show <epic-id>
|
|
147
|
-
trekoon --json task show <task-id>
|
|
148
178
|
trekoon --toon epic show <epic-id>
|
|
149
179
|
trekoon --toon task show <task-id>
|
|
150
180
|
```
|
|
151
181
|
|
|
152
|
-
|
|
182
|
+
Optional alternative for integrations that explicitly require JSON:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
trekoon --json epic show <epic-id>
|
|
186
|
+
trekoon --json task show <task-id>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 6) Sync workflow for worktrees
|
|
153
190
|
|
|
154
191
|
- Run `trekoon sync status` at session start and before PR/merge.
|
|
155
192
|
- Run `trekoon sync pull --from main` before merge to align tracker state.
|
|
@@ -158,6 +195,8 @@ trekoon --toon task show <task-id>
|
|
|
158
195
|
```bash
|
|
159
196
|
trekoon sync status
|
|
160
197
|
trekoon sync pull --from main
|
|
198
|
+
trekoon sync conflicts list
|
|
199
|
+
trekoon sync conflicts show <conflict-id>
|
|
161
200
|
trekoon sync resolve <conflict-id> --use ours
|
|
162
201
|
```
|
|
163
202
|
|
|
@@ -170,10 +209,31 @@ react deterministically:
|
|
|
170
209
|
- `diagnostics.conflictEvents`
|
|
171
210
|
- `diagnostics.errorHints`
|
|
172
211
|
|
|
173
|
-
|
|
212
|
+
Compatibility mode for legacy sync command IDs:
|
|
174
213
|
|
|
175
|
-
|
|
176
|
-
|
|
214
|
+
```bash
|
|
215
|
+
trekoon --json --compat legacy-sync-command-ids sync status
|
|
216
|
+
trekoon --toon --compat legacy-sync-command-ids sync pull --from main
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Behavior:
|
|
220
|
+
|
|
221
|
+
- Default remains strict canonical IDs (`sync.status`, `sync.pull`, ...).
|
|
222
|
+
- Compatibility mode rewrites sync command IDs to legacy forms
|
|
223
|
+
(`sync_status`, `sync_pull`, ...).
|
|
224
|
+
- Compatibility mode is machine-only and valid only for `sync` commands.
|
|
225
|
+
- Machine output includes `metadata.compatibility` with:
|
|
226
|
+
- deprecation warning code
|
|
227
|
+
- migration guidance
|
|
228
|
+
- canonical + compatibility command IDs
|
|
229
|
+
- removal window (`removalAfter: 2026-09-30`)
|
|
230
|
+
- Migration path: remove `--compat legacy-sync-command-ids` and consume dotted
|
|
231
|
+
command IDs directly.
|
|
232
|
+
|
|
233
|
+
### 7) Install project-local Trekoon skill for agents
|
|
234
|
+
|
|
235
|
+
`trekoon skills install` always writes the bundled skill file under the current
|
|
236
|
+
working directory at:
|
|
177
237
|
|
|
178
238
|
- `.agents/skills/trekoon/SKILL.md`
|
|
179
239
|
|
|
@@ -195,7 +255,7 @@ Path behavior:
|
|
|
195
255
|
- Default pi link path: `.pi/skills/trekoon`
|
|
196
256
|
- `--to <path>` overrides the editor root for link creation only.
|
|
197
257
|
- `--to` does **not** move or copy `SKILL.md` to that path.
|
|
198
|
-
- By default, link targets must resolve inside the
|
|
258
|
+
- By default, link targets must resolve inside the current working directory root.
|
|
199
259
|
- Use `--allow-outside-repo` only for intentional external links.
|
|
200
260
|
- When override is used, install prints a warning and includes confirmation
|
|
201
261
|
fields in machine output.
|
|
@@ -212,11 +272,11 @@ Path behavior:
|
|
|
212
272
|
How `--to` works (step-by-step):
|
|
213
273
|
|
|
214
274
|
1. Trekoon always installs/copies to:
|
|
215
|
-
- `<
|
|
275
|
+
- `<cwd>/.agents/skills/trekoon/SKILL.md`
|
|
216
276
|
2. If `--link` is present, Trekoon creates a `trekoon` symlink directory entry.
|
|
217
277
|
3. `--to <path>` sets the symlink root directory.
|
|
218
278
|
4. Final link path is:
|
|
219
|
-
- `<resolved-to-path>/trekoon -> <
|
|
279
|
+
- `<resolved-to-path>/trekoon -> <cwd>/.agents/skills/trekoon`
|
|
220
280
|
|
|
221
281
|
Example:
|
|
222
282
|
|
|
@@ -226,19 +286,119 @@ trekoon skills install --link --editor opencode --to ./.custom-editor/skills
|
|
|
226
286
|
|
|
227
287
|
This produces:
|
|
228
288
|
|
|
229
|
-
- `<
|
|
230
|
-
- `<
|
|
231
|
-
- symlink target: `<
|
|
289
|
+
- `<cwd>/.agents/skills/trekoon/SKILL.md` (copied file)
|
|
290
|
+
- `<cwd>/.custom-editor/skills/trekoon` (symlink)
|
|
291
|
+
- symlink target: `<cwd>/.agents/skills/trekoon`
|
|
232
292
|
|
|
233
293
|
Trekoon does not mutate global editor config directories.
|
|
234
294
|
|
|
235
|
-
###
|
|
295
|
+
### 8) Pre-merge checklist
|
|
236
296
|
|
|
237
297
|
- [ ] `trekoon sync status` shows no unresolved conflicts
|
|
238
298
|
- [ ] done tasks/subtasks are marked completed
|
|
239
299
|
- [ ] dependency graph has no stale blockers
|
|
240
300
|
- [ ] final AI check: `trekoon --toon epic show <epic-id>`
|
|
241
301
|
|
|
302
|
+
## Machine-contract recipes (--toon)
|
|
303
|
+
|
|
304
|
+
Use `--toon` for production agent loops. The examples below show command +
|
|
305
|
+
expected envelope fields.
|
|
306
|
+
|
|
307
|
+
Base envelope fields (all machine responses):
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
ok: true|false
|
|
311
|
+
command: <stable command id>
|
|
312
|
+
data: <payload>
|
|
313
|
+
metadata:
|
|
314
|
+
contractVersion: "1.0.0"
|
|
315
|
+
requestId: req-<stable-id>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Most subcommand identifiers are dot-namespaced (`task.list`, `sync.pull`,
|
|
319
|
+
`epic.show`). Root-level commands may use single-token IDs (`help`, `init`,
|
|
320
|
+
`quickstart`, `wipe`, `version`).
|
|
321
|
+
|
|
322
|
+
Additional metadata can appear when relevant:
|
|
323
|
+
|
|
324
|
+
- `metadata.compatibility` when `--compat` mode is active
|
|
325
|
+
- `meta.storageRootDiagnostics` when a machine-readable command resolves
|
|
326
|
+
storage from a non-canonical cwd
|
|
327
|
+
|
|
328
|
+
### Ready queue (deterministic candidates)
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
trekoon --toon task ready --limit 3
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Payload fields:
|
|
335
|
+
|
|
336
|
+
```text
|
|
337
|
+
ok: true
|
|
338
|
+
command: task.ready
|
|
339
|
+
data:
|
|
340
|
+
candidates[]:
|
|
341
|
+
task: { id, epicId, title, status, ... }
|
|
342
|
+
readiness: { isReady, reason }
|
|
343
|
+
blockerSummary: { blockedByCount, totalDependencies, blockedBy[] }
|
|
344
|
+
ranking: { rank, blockerCount, statusPriority }
|
|
345
|
+
blocked[]: (same shape, non-ready items)
|
|
346
|
+
summary: {
|
|
347
|
+
totalOpenTasks,
|
|
348
|
+
readyCount,
|
|
349
|
+
returnedCount,
|
|
350
|
+
appliedLimit,
|
|
351
|
+
blockedCount,
|
|
352
|
+
unresolvedDependencyCount,
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Reverse dependency walk (blocker impact)
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
trekoon --toon dep reverse <task-or-subtask-id>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Payload fields:
|
|
363
|
+
|
|
364
|
+
```text
|
|
365
|
+
ok: true
|
|
366
|
+
command: dep.reverse
|
|
367
|
+
data:
|
|
368
|
+
targetId: <id>
|
|
369
|
+
targetKind: task|subtask
|
|
370
|
+
blockedNodes[]: { id, kind, distance, isDirect }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Pagination contract for machine list calls
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
trekoon --toon task list --status todo --limit 2
|
|
377
|
+
trekoon --toon task list --status todo --limit 2 --cursor 2
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Cursor semantics:
|
|
381
|
+
|
|
382
|
+
- `--cursor <n>` is offset-like pagination for list endpoints (`epic list`,
|
|
383
|
+
`task list`, `subtask list`).
|
|
384
|
+
- Do not combine `--all` with `--cursor`.
|
|
385
|
+
- Machine consumers should page using `meta.pagination.hasMore` and
|
|
386
|
+
`meta.pagination.nextCursor`.
|
|
387
|
+
|
|
388
|
+
Payload fields:
|
|
389
|
+
|
|
390
|
+
```text
|
|
391
|
+
ok: true
|
|
392
|
+
command: task.list
|
|
393
|
+
data:
|
|
394
|
+
tasks[]: ...
|
|
395
|
+
metadata:
|
|
396
|
+
contractVersion: "1.0.0"
|
|
397
|
+
requestId: req-<stable-id>
|
|
398
|
+
meta:
|
|
399
|
+
pagination: { hasMore, nextCursor }
|
|
400
|
+
```
|
|
401
|
+
|
|
242
402
|
## Implementation principles
|
|
243
403
|
|
|
244
404
|
- Minimal, composable modules
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ export interface ParsedArgs {
|
|
|
3
3
|
readonly options: ReadonlyMap<string, string>;
|
|
4
4
|
readonly flags: ReadonlySet<string>;
|
|
5
5
|
readonly missingOptionValues: ReadonlySet<string>;
|
|
6
|
+
readonly providedOptions: readonly string[];
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
const LONG_PREFIX = "--";
|
|
@@ -12,6 +13,7 @@ export function parseArgs(args: readonly string[]): ParsedArgs {
|
|
|
12
13
|
const options = new Map<string, string>();
|
|
13
14
|
const flags = new Set<string>();
|
|
14
15
|
const missingOptionValues = new Set<string>();
|
|
16
|
+
const providedOptions: string[] = [];
|
|
15
17
|
|
|
16
18
|
for (let index = 0; index < args.length; index += 1) {
|
|
17
19
|
const token: string | undefined = args[index];
|
|
@@ -25,6 +27,7 @@ export function parseArgs(args: readonly string[]): ParsedArgs {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const key = token.slice(LONG_PREFIX.length);
|
|
30
|
+
providedOptions.push(key);
|
|
28
31
|
const value = args[index + 1];
|
|
29
32
|
if (!value || value.startsWith(LONG_PREFIX)) {
|
|
30
33
|
flags.add(key);
|
|
@@ -41,6 +44,7 @@ export function parseArgs(args: readonly string[]): ParsedArgs {
|
|
|
41
44
|
options,
|
|
42
45
|
flags,
|
|
43
46
|
missingOptionValues,
|
|
47
|
+
providedOptions,
|
|
44
48
|
};
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -79,6 +83,97 @@ export function parseStrictPositiveInt(rawValue: string | undefined): number | u
|
|
|
79
83
|
return parsed;
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
export function parseStrictNonNegativeInt(rawValue: string | undefined): number | undefined {
|
|
87
|
+
if (rawValue === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
92
|
+
if (!Number.isInteger(parsed) || parsed < 0 || `${parsed}` !== rawValue.trim()) {
|
|
93
|
+
return Number.NaN;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function levenshteinDistance(source: string, target: string): number {
|
|
100
|
+
const sourceLength = source.length;
|
|
101
|
+
const targetLength = target.length;
|
|
102
|
+
if (sourceLength === 0) {
|
|
103
|
+
return targetLength;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (targetLength === 0) {
|
|
107
|
+
return sourceLength;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const previous: number[] = Array.from({ length: targetLength + 1 }, (_, index) => index);
|
|
111
|
+
const current: number[] = new Array<number>(targetLength + 1).fill(0);
|
|
112
|
+
|
|
113
|
+
for (let sourceIndex = 1; sourceIndex <= sourceLength; sourceIndex += 1) {
|
|
114
|
+
current[0] = sourceIndex;
|
|
115
|
+
for (let targetIndex = 1; targetIndex <= targetLength; targetIndex += 1) {
|
|
116
|
+
const replacementCost = source[sourceIndex - 1] === target[targetIndex - 1] ? 0 : 1;
|
|
117
|
+
const insertCost = (current[targetIndex - 1] ?? 0) + 1;
|
|
118
|
+
const deleteCost = (previous[targetIndex] ?? 0) + 1;
|
|
119
|
+
const replaceCost = (previous[targetIndex - 1] ?? 0) + replacementCost;
|
|
120
|
+
current[targetIndex] = Math.min(
|
|
121
|
+
insertCost,
|
|
122
|
+
deleteCost,
|
|
123
|
+
replaceCost,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (let targetIndex = 0; targetIndex <= targetLength; targetIndex += 1) {
|
|
128
|
+
previous[targetIndex] = current[targetIndex] ?? previous[targetIndex] ?? 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return previous[targetLength] ?? 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeOption(option: string): string {
|
|
136
|
+
return option.startsWith(LONG_PREFIX) ? option.slice(LONG_PREFIX.length) : option;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function findUnknownOption(parsed: ParsedArgs, allowedOptions: readonly string[]): string | undefined {
|
|
140
|
+
const allowed = new Set<string>(allowedOptions.map(normalizeOption));
|
|
141
|
+
for (const option of parsed.providedOptions) {
|
|
142
|
+
if (!allowed.has(option)) {
|
|
143
|
+
return option;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function suggestOptions(option: string, allowedOptions: readonly string[], limit = 3): string[] {
|
|
151
|
+
const normalizedOption = normalizeOption(option);
|
|
152
|
+
const normalizedAllowed = allowedOptions.map(normalizeOption);
|
|
153
|
+
return normalizedAllowed
|
|
154
|
+
.map((candidate) => {
|
|
155
|
+
const distance =
|
|
156
|
+
candidate.startsWith(normalizedOption) || normalizedOption.startsWith(candidate)
|
|
157
|
+
? 0
|
|
158
|
+
: levenshteinDistance(normalizedOption, candidate);
|
|
159
|
+
return {
|
|
160
|
+
candidate,
|
|
161
|
+
distance,
|
|
162
|
+
};
|
|
163
|
+
})
|
|
164
|
+
.sort((left, right) => {
|
|
165
|
+
const byDistance = left.distance - right.distance;
|
|
166
|
+
if (byDistance !== 0) {
|
|
167
|
+
return byDistance;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return left.candidate.localeCompare(right.candidate);
|
|
171
|
+
})
|
|
172
|
+
.filter((item) => item.distance <= Math.max(2, Math.floor(normalizedOption.length / 2)))
|
|
173
|
+
.slice(0, limit)
|
|
174
|
+
.map((item) => item.candidate);
|
|
175
|
+
}
|
|
176
|
+
|
|
82
177
|
export function readEnumOption<const T extends readonly string[]>(
|
|
83
178
|
options: ReadonlyMap<string, string>,
|
|
84
179
|
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
|
},
|