trekoon 0.2.0 → 0.2.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.
- package/.agents/skills/trekoon/SKILL.md +232 -297
- package/README.md +288 -16
- package/package.json +1 -1
- package/src/commands/arg-parser.ts +116 -0
- package/src/commands/dep.ts +197 -25
- package/src/commands/epic.ts +490 -28
- package/src/commands/error-utils.ts +111 -0
- package/src/commands/events.ts +23 -3
- package/src/commands/help.ts +83 -17
- package/src/commands/init.ts +115 -9
- package/src/commands/migrate.ts +11 -4
- package/src/commands/quickstart.ts +76 -30
- package/src/commands/session.ts +223 -0
- package/src/commands/skills.ts +100 -63
- package/src/commands/subtask.ts +224 -26
- package/src/commands/sync.ts +64 -17
- package/src/commands/task-readiness.ts +147 -0
- package/src/commands/task.ts +277 -168
- package/src/commands/wipe.ts +15 -5
- package/src/domain/mutation-service.ts +152 -0
- package/src/domain/tracker-domain.ts +503 -0
- package/src/domain/types.ts +80 -0
- package/src/runtime/cli-shell.ts +83 -5
- package/src/storage/database.ts +86 -0
- package/src/storage/migrations.ts +48 -0
- package/src/storage/path.ts +70 -21
- package/src/storage/schema.ts +9 -2
- package/src/storage/worktree-recovery.ts +376 -0
- package/src/sync/branch-db.ts +87 -35
- package/src/sync/git-context.ts +7 -2
- package/src/sync/service.ts +131 -95
- package/src/sync/types.ts +2 -0
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ npm i -g trekoon
|
|
|
40
40
|
|
|
41
41
|
1. Make issue tracking fast enough for daily terminal use.
|
|
42
42
|
2. Make issue data deterministic and machine-readable for AI automation.
|
|
43
|
-
3. Keep
|
|
43
|
+
3. Keep one repo-scoped state store that every worktree can coordinate through safely.
|
|
44
44
|
4. Stay minimal in code size while preserving robustness and clear boundaries.
|
|
45
45
|
|
|
46
46
|
## Command surface
|
|
@@ -48,10 +48,11 @@ npm i -g trekoon
|
|
|
48
48
|
- `trekoon init`
|
|
49
49
|
- `trekoon help [command]`
|
|
50
50
|
- `trekoon quickstart`
|
|
51
|
-
- `trekoon epic <create|list|show|search|replace|update|delete>`
|
|
52
|
-
- `trekoon
|
|
53
|
-
- `trekoon
|
|
54
|
-
- `trekoon
|
|
51
|
+
- `trekoon epic <create|expand|list|show|search|replace|update|delete>`
|
|
52
|
+
- `trekoon session`
|
|
53
|
+
- `trekoon task <create|create-many|list|show|ready|next|done|search|replace|update|delete>`
|
|
54
|
+
- `trekoon subtask <create|create-many|list|search|replace|update|delete>`
|
|
55
|
+
- `trekoon dep <add|add-many|remove|list|reverse>`
|
|
55
56
|
- `trekoon events prune [--dry-run] [--archive] [--retention-days <n>]`
|
|
56
57
|
- `trekoon migrate <status|rollback> [--to-version <n>]`
|
|
57
58
|
- `trekoon sync <status|pull|resolve|conflicts>`
|
|
@@ -117,15 +118,23 @@ trekoon epic update --ids <epic-1>,<epic-2> --status done
|
|
|
117
118
|
|
|
118
119
|
## Quickstart
|
|
119
120
|
|
|
120
|
-
Trekoon is local-first
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
Trekoon is local-first, but in git repos and worktrees it is **repo-shared**:
|
|
122
|
+
every worktree for the same repository resolves to one shared `.trekoon`
|
|
123
|
+
directory and one shared `.trekoon/trekoon.db`.
|
|
124
|
+
|
|
125
|
+
- `worktreeRoot` identifies the current checkout.
|
|
126
|
+
- `sharedStorageRoot` identifies the repository root that owns `.trekoon`.
|
|
127
|
+
- `databaseFile` points at the shared SQLite database.
|
|
128
|
+
- `.trekoon` stays gitignored on purpose because the DB is operational state,
|
|
129
|
+
not source code.
|
|
130
|
+
- Committing `.trekoon/trekoon.db` is the wrong fix for drift because it bakes
|
|
131
|
+
machine-local state and stale snapshots into Git.
|
|
123
132
|
|
|
124
133
|
Outside git repos, Trekoon falls back to the invocation cwd.
|
|
125
134
|
|
|
126
135
|
When machine output is enabled (`--json`/`--toon`) and a command resolves
|
|
127
136
|
storage from a non-canonical cwd, Trekoon emits
|
|
128
|
-
`meta.storageRootDiagnostics`
|
|
137
|
+
`meta.storageRootDiagnostics` so automation can verify the storage contract.
|
|
129
138
|
|
|
130
139
|
### 1) Initialize
|
|
131
140
|
|
|
@@ -134,6 +143,15 @@ trekoon init
|
|
|
134
143
|
trekoon --version
|
|
135
144
|
```
|
|
136
145
|
|
|
146
|
+
Bootstrap expectations:
|
|
147
|
+
|
|
148
|
+
- Run `trekoon --toon init` once per repository to create or re-bootstrap the
|
|
149
|
+
shared storage root.
|
|
150
|
+
- Run `trekoon --toon sync status` before agent work to inspect diagnostics.
|
|
151
|
+
- If diagnostics report `recoveryRequired`, a tracked/ignored mismatch, or an
|
|
152
|
+
ambiguous recovery path, stop and repair setup before continuing.
|
|
153
|
+
- Do **not** continue with task selection after broken bootstrap warnings.
|
|
154
|
+
|
|
137
155
|
### 2) Create epic → task → subtask
|
|
138
156
|
|
|
139
157
|
```bash
|
|
@@ -146,6 +164,42 @@ trekoon task list --limit 25
|
|
|
146
164
|
trekoon task list --all --view compact
|
|
147
165
|
```
|
|
148
166
|
|
|
167
|
+
### 2a) Preferred one-shot epic creation
|
|
168
|
+
|
|
169
|
+
When you already know the epic tree, create the epic, tasks, subtasks, and
|
|
170
|
+
dependencies in one invocation.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
trekoon epic create \
|
|
174
|
+
--title "Batch command rollout" \
|
|
175
|
+
--description "Ship one-shot planning workflows" \
|
|
176
|
+
--task "task-a|First task|First description|todo" \
|
|
177
|
+
--task "task-b|Second task|Second description|todo" \
|
|
178
|
+
--subtask "@task-a|sub-a|First subtask|Subtask description|todo" \
|
|
179
|
+
--dep "@task-b|@task-a" \
|
|
180
|
+
--dep "@sub-a|@task-a"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Use this when:
|
|
184
|
+
|
|
185
|
+
- the epic does not exist yet
|
|
186
|
+
- later records need to reference earlier created records via `@temp-key`
|
|
187
|
+
- you want one atomic create step and one machine response with mappings/counts
|
|
188
|
+
|
|
189
|
+
Compact machine output adds:
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
command: epic.create
|
|
193
|
+
data:
|
|
194
|
+
epic: created epic row
|
|
195
|
+
tasks[]: created tasks in input order
|
|
196
|
+
subtasks[]: created subtasks in input order
|
|
197
|
+
dependencies[]: created dependencies in input order
|
|
198
|
+
result:
|
|
199
|
+
mappings[]: { kind: task|subtask, tempKey, id }
|
|
200
|
+
counts: { tasks, subtasks, dependencies }
|
|
201
|
+
```
|
|
202
|
+
|
|
149
203
|
### 3) Add dependencies
|
|
150
204
|
|
|
151
205
|
```bash
|
|
@@ -153,25 +207,229 @@ trekoon dep add <task-id> <depends-on-id>
|
|
|
153
207
|
trekoon dep list <task-id>
|
|
154
208
|
```
|
|
155
209
|
|
|
210
|
+
### 3a) Batch planning commands
|
|
211
|
+
|
|
212
|
+
Use compact batch commands when one invocation needs to create or link multiple
|
|
213
|
+
items atomically. Use the single-item commands when you already have persisted
|
|
214
|
+
UUIDs and only need one mutation.
|
|
215
|
+
|
|
216
|
+
#### `task create-many`
|
|
217
|
+
|
|
218
|
+
Create multiple tasks under one epic in declared order.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
trekoon task create-many \
|
|
222
|
+
--epic <epic-id> \
|
|
223
|
+
--task "seed-api|Design API|Define batch grammar|todo" \
|
|
224
|
+
--task "seed-cli|Wire CLI|Hook parser and output|in_progress"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Compact spec:
|
|
228
|
+
|
|
229
|
+
- `--task <temp-key>|<title>|<description>|<status>`
|
|
230
|
+
- escape `\|`, `\\`, `\n`, `\r`, `\t`
|
|
231
|
+
- repeated `--task` flags are preserved in the exact order provided
|
|
232
|
+
- temp keys are local mapping labels, not persisted IDs
|
|
233
|
+
|
|
234
|
+
Rollback semantics:
|
|
235
|
+
|
|
236
|
+
- Trekoon validates the full batch before inserts
|
|
237
|
+
- duplicate temp keys, empty required fields, or invalid input fail the whole
|
|
238
|
+
command
|
|
239
|
+
- no partial task rows are kept on failure
|
|
240
|
+
|
|
241
|
+
Compact machine output:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
command: task.create-many
|
|
245
|
+
data:
|
|
246
|
+
epicId: <epic-id>
|
|
247
|
+
tasks[]: created task rows in input order
|
|
248
|
+
result:
|
|
249
|
+
mappings[]: { kind: task, tempKey, id }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### `subtask create-many`
|
|
253
|
+
|
|
254
|
+
Create multiple subtasks under one existing task.
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
trekoon subtask create-many <task-id> \
|
|
258
|
+
--subtask "seed-tests|Write tests|Cover happy path|todo" \
|
|
259
|
+
--subtask "seed-docs|Document flow|Add operator notes|todo"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Equivalent explicit parent form:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
trekoon subtask create-many \
|
|
266
|
+
--task <task-id> \
|
|
267
|
+
--subtask "seed-tests|Write tests|Cover happy path|todo"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Rules:
|
|
271
|
+
|
|
272
|
+
- positional `<task-id>` or `--task <task-id>` may be used
|
|
273
|
+
- if both are provided, they must be identical or the command fails
|
|
274
|
+
- repeated `--subtask` flags are applied in declared order
|
|
275
|
+
|
|
276
|
+
Rollback semantics:
|
|
277
|
+
|
|
278
|
+
- full batch prevalidation happens before inserts
|
|
279
|
+
- duplicate temp keys, conflicting task ids, or invalid specs abort the whole
|
|
280
|
+
command
|
|
281
|
+
- no partial subtasks are kept on failure
|
|
282
|
+
|
|
283
|
+
Compact machine output:
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
command: subtask.create-many
|
|
287
|
+
data:
|
|
288
|
+
taskId: <task-id>
|
|
289
|
+
subtasks[]: created subtask rows in input order
|
|
290
|
+
result:
|
|
291
|
+
mappings[]: { kind: subtask, tempKey, id }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### `dep add-many`
|
|
295
|
+
|
|
296
|
+
Create multiple dependency edges in one ordered, transactional operation.
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
trekoon dep add-many \
|
|
300
|
+
--dep "<task-b>|<task-a>" \
|
|
301
|
+
--dep "<subtask-c>|<task-b>"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Compact spec:
|
|
305
|
+
|
|
306
|
+
- `--dep <source-ref>|<depends-on-ref>`
|
|
307
|
+
- repeated `--dep` flags are applied in declared order
|
|
308
|
+
- standalone `dep add-many` resolves persisted IDs only
|
|
309
|
+
- `@temp-key` refs are **not** resolved from earlier commands; they are reserved
|
|
310
|
+
for same-invocation workflows such as `epic expand`
|
|
311
|
+
|
|
312
|
+
Rollback semantics:
|
|
313
|
+
|
|
314
|
+
- validation covers the full dependency set before insert
|
|
315
|
+
- missing ids, unresolved `@temp-key` refs, duplicates, or cycles fail the whole
|
|
316
|
+
batch
|
|
317
|
+
- no partial dependency edges are inserted on failure
|
|
318
|
+
|
|
319
|
+
Compact machine output:
|
|
320
|
+
|
|
321
|
+
```text
|
|
322
|
+
command: dep.add-many
|
|
323
|
+
data:
|
|
324
|
+
dependencies[]: created dependency rows in input order
|
|
325
|
+
result:
|
|
326
|
+
mappings[]: []
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### `epic expand`
|
|
330
|
+
|
|
331
|
+
Expand one existing epic by creating tasks, subtasks, and dependencies in one
|
|
332
|
+
transaction. Use this when the epic already exists and you want to add a linked
|
|
333
|
+
batch later.
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
trekoon epic expand <epic-id> \
|
|
337
|
+
--task "task-api|Design API|Define compact grammar|todo" \
|
|
338
|
+
--task "task-cli|Wire CLI|Hook parser and output|todo" \
|
|
339
|
+
--subtask "@task-api|sub-tests|Write tests|Cover parser cases|todo" \
|
|
340
|
+
--dep "@task-cli|@task-api" \
|
|
341
|
+
--dep "@sub-tests|@task-api"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Compact specs:
|
|
345
|
+
|
|
346
|
+
- `--task <temp-key>|<title>|<description>|<status>`
|
|
347
|
+
- `--subtask <parent-ref>|<temp-key>|<title>|<description>|<status>`
|
|
348
|
+
- `--dep <source-ref>|<depends-on-ref>`
|
|
349
|
+
- `@temp-key` refs may target tasks/subtasks declared earlier in the same
|
|
350
|
+
`epic expand` invocation
|
|
351
|
+
|
|
352
|
+
Background phases:
|
|
353
|
+
|
|
354
|
+
1. validate all compact specs and duplicate temp keys
|
|
355
|
+
2. create tasks transactionally
|
|
356
|
+
3. resolve subtask parent temp keys and create subtasks
|
|
357
|
+
4. resolve dependency refs and link dependencies
|
|
358
|
+
5. append task, subtask, then dependency events
|
|
359
|
+
6. roll back the full expansion if any phase fails
|
|
360
|
+
|
|
361
|
+
Compact machine output:
|
|
362
|
+
|
|
363
|
+
```text
|
|
364
|
+
command: epic.expand
|
|
365
|
+
data:
|
|
366
|
+
epicId: <epic-id>
|
|
367
|
+
tasks[]: created tasks in input order
|
|
368
|
+
subtasks[]: created subtasks in input order
|
|
369
|
+
dependencies[]: created dependencies in input order
|
|
370
|
+
result:
|
|
371
|
+
mappings[]: { kind: task|subtask, tempKey, id }
|
|
372
|
+
counts: { tasks, subtasks, dependencies }
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
When to choose which command:
|
|
376
|
+
|
|
377
|
+
- use `task create-many` for sibling tasks under one known epic
|
|
378
|
+
- use `subtask create-many` for sibling subtasks under one known task
|
|
379
|
+
- use `dep add-many` only when every endpoint already has a persisted ID
|
|
380
|
+
- use `epic create` with batch specs when the epic does not exist yet and the
|
|
381
|
+
whole graph is known up front
|
|
382
|
+
- use `epic expand` when the epic already exists and one batch must add linked
|
|
383
|
+
tasks/subtasks/dependencies with `@temp-key` references
|
|
384
|
+
|
|
156
385
|
### 4) AI execution loop for agents
|
|
157
386
|
|
|
158
|
-
|
|
387
|
+
The primary loop is: **session → work → task done → repeat**.
|
|
388
|
+
|
|
389
|
+
Orient with a single call that returns diagnostics, sync status, next ready
|
|
390
|
+
task with subtasks, blocker list, and readiness counts:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
trekoon --toon session
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Claim work, then finish or report a block:
|
|
159
397
|
|
|
160
398
|
```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
399
|
trekoon --toon task update <task-id> --status in_progress
|
|
400
|
+
trekoon --toon task done <task-id>
|
|
401
|
+
trekoon --toon task update <task-id> --append "Blocked by <reason>" --status blocked
|
|
166
402
|
```
|
|
167
403
|
|
|
168
|
-
|
|
404
|
+
`task done` marks the task done and returns the next ready task with
|
|
405
|
+
dependencies inline, replacing the old multi-step transition.
|
|
406
|
+
|
|
407
|
+
Fail-fast rules:
|
|
408
|
+
|
|
409
|
+
- Treat `meta.storageRootDiagnostics` as the source of truth for worktree
|
|
410
|
+
storage.
|
|
411
|
+
- In linked worktrees, `sharedStorageRoot` may differ from `worktreeRoot`; that
|
|
412
|
+
is expected.
|
|
413
|
+
- If `recoveryRequired` is `true`, stop and follow the reported bootstrap or
|
|
414
|
+
recovery action.
|
|
415
|
+
- Do not fall back to a separate per-worktree DB or continue after missing
|
|
416
|
+
shared storage.
|
|
417
|
+
|
|
418
|
+
<details>
|
|
419
|
+
<summary>Legacy manual bootstrap (use <code>session</code> instead)</summary>
|
|
169
420
|
|
|
170
421
|
```bash
|
|
422
|
+
trekoon --toon init
|
|
423
|
+
trekoon --toon sync status
|
|
424
|
+
trekoon --toon task ready --limit 5
|
|
425
|
+
trekoon --toon task next
|
|
426
|
+
trekoon --toon dep reverse <task-or-subtask-id>
|
|
427
|
+
trekoon --toon task update <task-id> --status in_progress
|
|
171
428
|
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
429
|
```
|
|
174
430
|
|
|
431
|
+
</details>
|
|
432
|
+
|
|
175
433
|
### 5) Use TOON output for agent workflows
|
|
176
434
|
|
|
177
435
|
```bash
|
|
@@ -290,6 +548,19 @@ react deterministically:
|
|
|
290
548
|
- `diagnostics.conflictEvents`
|
|
291
549
|
- `diagnostics.errorHints`
|
|
292
550
|
|
|
551
|
+
Worktree diagnostics and recovery:
|
|
552
|
+
|
|
553
|
+
- Inspect `storageMode`, `repoCommonDir`, `worktreeRoot`, `sharedStorageRoot`,
|
|
554
|
+
and `databaseFile` in machine output when debugging worktree behavior.
|
|
555
|
+
- If a worktree resolves shared storage outside its checkout, that is expected
|
|
556
|
+
for linked worktrees and should not be “fixed” by committing `.trekoon`.
|
|
557
|
+
- If Git contains a tracked `.trekoon/trekoon.db`, remove it from Git history or
|
|
558
|
+
the index as appropriate, keep `.trekoon` ignored, and re-run
|
|
559
|
+
`trekoon --toon init`.
|
|
560
|
+
- Use `trekoon wipe --yes` only for explicit destructive recovery; it deletes
|
|
561
|
+
the shared storage root for the entire repository, not just the current
|
|
562
|
+
worktree.
|
|
563
|
+
|
|
293
564
|
Compatibility mode for legacy sync command IDs:
|
|
294
565
|
|
|
295
566
|
```bash
|
|
@@ -379,6 +650,7 @@ Trekoon does not mutate global editor config directories.
|
|
|
379
650
|
- [ ] done tasks/subtasks are marked completed
|
|
380
651
|
- [ ] dependency graph has no stale blockers
|
|
381
652
|
- [ ] final AI check: `trekoon --toon epic show <epic-id>`
|
|
653
|
+
- [ ] no one tried to commit `.trekoon/trekoon.db` as a worktree fix
|
|
382
654
|
|
|
383
655
|
## Machine-contract recipes (--toon)
|
|
384
656
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMPACT_TEMP_KEY_PREFIX,
|
|
3
|
+
type CompactEntityRef,
|
|
4
|
+
type CompactEntityIdRef,
|
|
5
|
+
type CompactTempKey,
|
|
6
|
+
type CompactTempKeyRef,
|
|
7
|
+
} from "../domain/types";
|
|
8
|
+
|
|
1
9
|
export interface ParsedArgs {
|
|
2
10
|
readonly positional: readonly string[];
|
|
3
11
|
readonly options: ReadonlyMap<string, string>;
|
|
12
|
+
readonly optionEntries: readonly ParsedOptionEntry[];
|
|
4
13
|
readonly flags: ReadonlySet<string>;
|
|
5
14
|
readonly missingOptionValues: ReadonlySet<string>;
|
|
6
15
|
readonly providedOptions: readonly string[];
|
|
7
16
|
}
|
|
8
17
|
|
|
18
|
+
export interface ParsedOptionEntry {
|
|
19
|
+
readonly key: string;
|
|
20
|
+
readonly value: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
export const SEARCH_REPLACE_FIELDS = ["title", "description"] as const;
|
|
10
24
|
|
|
11
25
|
export type SearchReplaceField = (typeof SEARCH_REPLACE_FIELDS)[number];
|
|
@@ -21,11 +35,18 @@ export interface PreviewApplyModeSelection {
|
|
|
21
35
|
readonly conflict: boolean;
|
|
22
36
|
}
|
|
23
37
|
|
|
38
|
+
export interface ParsedCompactFields {
|
|
39
|
+
readonly fields: readonly string[];
|
|
40
|
+
readonly invalidEscape: string | null;
|
|
41
|
+
readonly hasDanglingEscape: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
const LONG_PREFIX = "--";
|
|
25
45
|
|
|
26
46
|
export function parseArgs(args: readonly string[]): ParsedArgs {
|
|
27
47
|
const positional: string[] = [];
|
|
28
48
|
const options = new Map<string, string>();
|
|
49
|
+
const optionEntries: ParsedOptionEntry[] = [];
|
|
29
50
|
const flags = new Set<string>();
|
|
30
51
|
const missingOptionValues = new Set<string>();
|
|
31
52
|
const providedOptions: string[] = [];
|
|
@@ -51,12 +72,14 @@ export function parseArgs(args: readonly string[]): ParsedArgs {
|
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
options.set(key, value);
|
|
75
|
+
optionEntries.push({ key, value });
|
|
54
76
|
index += 1;
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
return {
|
|
58
80
|
positional,
|
|
59
81
|
options,
|
|
82
|
+
optionEntries,
|
|
60
83
|
flags,
|
|
61
84
|
missingOptionValues,
|
|
62
85
|
providedOptions,
|
|
@@ -78,6 +101,11 @@ export function hasFlag(flags: ReadonlySet<string>, ...keys: string[]): boolean
|
|
|
78
101
|
return keys.some((key) => flags.has(key));
|
|
79
102
|
}
|
|
80
103
|
|
|
104
|
+
export function readOptions(optionEntries: readonly ParsedOptionEntry[], ...keys: string[]): string[] {
|
|
105
|
+
const allowedKeys = new Set<string>(keys);
|
|
106
|
+
return optionEntries.filter((entry) => allowedKeys.has(entry.key)).map((entry) => entry.value);
|
|
107
|
+
}
|
|
108
|
+
|
|
81
109
|
export function readMissingOptionValue(
|
|
82
110
|
missingOptionValues: ReadonlySet<string>,
|
|
83
111
|
...keys: string[]
|
|
@@ -178,6 +206,90 @@ export function resolvePreviewApplyMode(
|
|
|
178
206
|
};
|
|
179
207
|
}
|
|
180
208
|
|
|
209
|
+
export function isValidCompactTempKey(value: string): value is CompactTempKey {
|
|
210
|
+
return /^[A-Za-z][A-Za-z0-9._-]{0,63}$/u.test(value);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function parseCompactFields(rawValue: string): ParsedCompactFields {
|
|
214
|
+
const fields: string[] = [];
|
|
215
|
+
let current = "";
|
|
216
|
+
let escaping = false;
|
|
217
|
+
|
|
218
|
+
for (const character of rawValue) {
|
|
219
|
+
if (escaping) {
|
|
220
|
+
switch (character) {
|
|
221
|
+
case "|":
|
|
222
|
+
current += "|";
|
|
223
|
+
break;
|
|
224
|
+
case "\\":
|
|
225
|
+
current += "\\";
|
|
226
|
+
break;
|
|
227
|
+
case "n":
|
|
228
|
+
current += "\n";
|
|
229
|
+
break;
|
|
230
|
+
case "r":
|
|
231
|
+
current += "\r";
|
|
232
|
+
break;
|
|
233
|
+
case "t":
|
|
234
|
+
current += "\t";
|
|
235
|
+
break;
|
|
236
|
+
default:
|
|
237
|
+
return {
|
|
238
|
+
fields,
|
|
239
|
+
invalidEscape: `\\${character}`,
|
|
240
|
+
hasDanglingEscape: false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
escaping = false;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (character === "\\") {
|
|
249
|
+
escaping = true;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (character === "|") {
|
|
254
|
+
fields.push(current);
|
|
255
|
+
current = "";
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
current += character;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (escaping) {
|
|
263
|
+
return {
|
|
264
|
+
fields,
|
|
265
|
+
invalidEscape: null,
|
|
266
|
+
hasDanglingEscape: true,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
fields.push(current);
|
|
271
|
+
return {
|
|
272
|
+
fields,
|
|
273
|
+
invalidEscape: null,
|
|
274
|
+
hasDanglingEscape: false,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function parseCompactEntityRef(rawValue: string): CompactEntityRef {
|
|
279
|
+
if (rawValue.startsWith(COMPACT_TEMP_KEY_PREFIX)) {
|
|
280
|
+
const tempKey = rawValue.slice(COMPACT_TEMP_KEY_PREFIX.length);
|
|
281
|
+
return {
|
|
282
|
+
kind: "temp_key",
|
|
283
|
+
tempKey,
|
|
284
|
+
} satisfies CompactTempKeyRef;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
kind: "id",
|
|
289
|
+
id: rawValue,
|
|
290
|
+
} satisfies CompactEntityIdRef;
|
|
291
|
+
}
|
|
292
|
+
|
|
181
293
|
function levenshteinDistance(source: string, target: string): number {
|
|
182
294
|
const sourceLength = source.length;
|
|
183
295
|
const targetLength = target.length;
|
|
@@ -268,3 +380,7 @@ export function readEnumOption<const T extends readonly string[]>(
|
|
|
268
380
|
|
|
269
381
|
return allowed.includes(value) ? value : undefined;
|
|
270
382
|
}
|
|
383
|
+
|
|
384
|
+
export function readUnexpectedPositionals(parsed: ParsedArgs, expectedCount: number): readonly string[] {
|
|
385
|
+
return parsed.positional.slice(expectedCount);
|
|
386
|
+
}
|