@markjaquith/agency 2.17.0 → 2.19.0
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/README.md +29 -8
- package/cli.ts +94 -8
- package/package.json +1 -1
- package/skills/agency/SKILL.md +13 -6
- package/src/cli-parser.test.ts +111 -2
- package/src/cli-parser.ts +273 -36
- package/src/cli.test.ts +105 -5
- package/src/commands/context.ts +3 -0
- package/src/commands/epic.test.ts +22 -0
- package/src/commands/epic.ts +38 -2
- package/src/commands/init.ts +3 -1
- package/src/commands/phase.ts +52 -2
- package/src/commands/status.test.ts +44 -0
- package/src/commands/status.ts +51 -2
- package/src/commands/task-phase.test.ts +20 -0
- package/src/commands/task.ts +49 -2
- package/src/commands/validate.test.ts +2 -0
- package/src/commands/work.test.ts +1 -0
- package/src/commands/work.ts +15 -7
- package/src/commands/workbase.test.ts +63 -2
- package/src/commands/workbase.ts +77 -10
- package/src/services/WorkbaseService.test.ts +147 -4
- package/src/services/WorkbaseService.ts +214 -12
- package/src/utils/table.ts +19 -0
- package/src/work-view.test.ts +151 -0
- package/src/work-view.ts +253 -0
- package/src/workbase/schemas.test.ts +17 -6
- package/src/workbase/schemas.ts +16 -1
- package/src/workbase/workbase-choice.ts +2 -0
package/src/cli-parser.ts
CHANGED
|
@@ -25,6 +25,14 @@ const commonOptions = {
|
|
|
25
25
|
silent: { type: "boolean", short: "s" },
|
|
26
26
|
verbose: { type: "boolean", short: "v" },
|
|
27
27
|
"no-input": { type: "boolean" },
|
|
28
|
+
workbase: { type: "string" },
|
|
29
|
+
cwd: { type: "string" },
|
|
30
|
+
} satisfies OptionConfig
|
|
31
|
+
|
|
32
|
+
const entitySelectorOptions = {
|
|
33
|
+
epic: { type: "string" },
|
|
34
|
+
task: { type: "string" },
|
|
35
|
+
phase: { type: "string" },
|
|
28
36
|
} satisfies OptionConfig
|
|
29
37
|
|
|
30
38
|
const outputOptions = {
|
|
@@ -32,6 +40,29 @@ const outputOptions = {
|
|
|
32
40
|
json: { type: "boolean" },
|
|
33
41
|
} satisfies OptionConfig
|
|
34
42
|
|
|
43
|
+
const viewOptions = {
|
|
44
|
+
status: { type: "string", multiple: true },
|
|
45
|
+
repository: { type: "string", multiple: true },
|
|
46
|
+
ready: { type: "boolean" },
|
|
47
|
+
blocked: { type: "boolean" },
|
|
48
|
+
pr: { type: "boolean" },
|
|
49
|
+
"no-pr": { type: "boolean" },
|
|
50
|
+
} satisfies OptionConfig
|
|
51
|
+
|
|
52
|
+
const viewOptionNames = [
|
|
53
|
+
"status",
|
|
54
|
+
"repository",
|
|
55
|
+
"ready",
|
|
56
|
+
"blocked",
|
|
57
|
+
"pr",
|
|
58
|
+
"no-pr",
|
|
59
|
+
] as const
|
|
60
|
+
|
|
61
|
+
const viewConflicts = [
|
|
62
|
+
["ready", "blocked"],
|
|
63
|
+
["pr", "no-pr"],
|
|
64
|
+
] as const
|
|
65
|
+
|
|
35
66
|
const createOptions = {
|
|
36
67
|
...outputOptions,
|
|
37
68
|
"ticket-url": { type: "string" },
|
|
@@ -86,14 +117,18 @@ const commands = {
|
|
|
86
117
|
},
|
|
87
118
|
},
|
|
88
119
|
workbase: {
|
|
89
|
-
usage: "agency workbase <add|list>",
|
|
90
|
-
options:
|
|
120
|
+
usage: "agency workbase <add|list|remove|prune|default>",
|
|
121
|
+
options: {
|
|
122
|
+
...outputOptions,
|
|
123
|
+
name: { type: "string" },
|
|
124
|
+
clear: { type: "boolean" },
|
|
125
|
+
},
|
|
91
126
|
subcommands: {
|
|
92
127
|
add: {
|
|
93
128
|
usage: "agency workbase add <path> [--json]",
|
|
94
129
|
minArgs: 1,
|
|
95
130
|
maxArgs: 1,
|
|
96
|
-
options: ["json"],
|
|
131
|
+
options: ["json", "name"],
|
|
97
132
|
},
|
|
98
133
|
list: {
|
|
99
134
|
usage: "agency workbase list [--json]",
|
|
@@ -101,6 +136,25 @@ const commands = {
|
|
|
101
136
|
maxArgs: 0,
|
|
102
137
|
options: ["json"],
|
|
103
138
|
},
|
|
139
|
+
remove: {
|
|
140
|
+
usage: "agency workbase remove <selector> [--json]",
|
|
141
|
+
minArgs: 1,
|
|
142
|
+
maxArgs: 1,
|
|
143
|
+
options: ["json"],
|
|
144
|
+
},
|
|
145
|
+
prune: {
|
|
146
|
+
usage: "agency workbase prune [--json]",
|
|
147
|
+
minArgs: 0,
|
|
148
|
+
maxArgs: 0,
|
|
149
|
+
options: ["json"],
|
|
150
|
+
},
|
|
151
|
+
default: {
|
|
152
|
+
usage: "agency workbase default [selector | --clear] [--json]",
|
|
153
|
+
minArgs: 0,
|
|
154
|
+
maxArgs: 1,
|
|
155
|
+
options: ["json", "clear"],
|
|
156
|
+
conflicts: [["clear", "$positional"]],
|
|
157
|
+
},
|
|
104
158
|
},
|
|
105
159
|
},
|
|
106
160
|
integration: {
|
|
@@ -147,7 +201,7 @@ const commands = {
|
|
|
147
201
|
},
|
|
148
202
|
epic: {
|
|
149
203
|
usage: "agency epic <create|list|show>",
|
|
150
|
-
options: createOptions,
|
|
204
|
+
options: { ...createOptions, ...viewOptions, epic: { type: "string" } },
|
|
151
205
|
subcommands: {
|
|
152
206
|
create: {
|
|
153
207
|
usage:
|
|
@@ -159,22 +213,24 @@ const commands = {
|
|
|
159
213
|
repeatable: ["repo"],
|
|
160
214
|
},
|
|
161
215
|
list: {
|
|
162
|
-
usage: "agency epic list [--json]",
|
|
216
|
+
usage: "agency epic list [filters] [--json]",
|
|
163
217
|
minArgs: 0,
|
|
164
218
|
maxArgs: 0,
|
|
165
|
-
options: ["json"],
|
|
219
|
+
options: ["json", ...viewOptionNames],
|
|
220
|
+
repeatable: ["status", "repository"],
|
|
221
|
+
conflicts: viewConflicts,
|
|
166
222
|
},
|
|
167
223
|
show: {
|
|
168
224
|
usage: "agency epic show <id> [--json]",
|
|
169
225
|
minArgs: 1,
|
|
170
226
|
maxArgs: 1,
|
|
171
|
-
options: ["json"],
|
|
227
|
+
options: ["json", "epic"],
|
|
172
228
|
},
|
|
173
229
|
},
|
|
174
230
|
},
|
|
175
231
|
task: {
|
|
176
232
|
usage: "agency task <new|create|list|show|status>",
|
|
177
|
-
options: taskCreateOptions,
|
|
233
|
+
options: { ...taskCreateOptions, ...viewOptions, task: { type: "string" } },
|
|
178
234
|
subcommands: {
|
|
179
235
|
new: {
|
|
180
236
|
usage: "agency task new [id] [options]",
|
|
@@ -212,28 +268,35 @@ const commands = {
|
|
|
212
268
|
repeatable: ["reference"],
|
|
213
269
|
},
|
|
214
270
|
list: {
|
|
215
|
-
usage: "agency task list [--json]",
|
|
271
|
+
usage: "agency task list [filters] [--json]",
|
|
216
272
|
minArgs: 0,
|
|
217
273
|
maxArgs: 0,
|
|
218
|
-
options: ["json"],
|
|
274
|
+
options: ["json", ...viewOptionNames],
|
|
275
|
+
repeatable: ["status", "repository"],
|
|
276
|
+
conflicts: viewConflicts,
|
|
219
277
|
},
|
|
220
278
|
show: {
|
|
221
279
|
usage: "agency task show <id> [--json]",
|
|
222
280
|
minArgs: 1,
|
|
223
281
|
maxArgs: 1,
|
|
224
|
-
options: ["json"],
|
|
282
|
+
options: ["json", "task"],
|
|
225
283
|
},
|
|
226
284
|
status: {
|
|
227
285
|
usage: "agency task status <id> <status> [--json]",
|
|
228
286
|
minArgs: 2,
|
|
229
287
|
maxArgs: 2,
|
|
230
|
-
options: ["json"],
|
|
288
|
+
options: ["json", "task"],
|
|
231
289
|
},
|
|
232
290
|
},
|
|
233
291
|
},
|
|
234
292
|
phase: {
|
|
235
293
|
usage: "agency phase <create|list|show|status>",
|
|
236
|
-
options:
|
|
294
|
+
options: {
|
|
295
|
+
...phaseCreateOptions,
|
|
296
|
+
...viewOptions,
|
|
297
|
+
task: { type: "string" },
|
|
298
|
+
phase: { type: "string" },
|
|
299
|
+
},
|
|
237
300
|
subcommands: {
|
|
238
301
|
create: {
|
|
239
302
|
usage:
|
|
@@ -249,33 +312,41 @@ const commands = {
|
|
|
249
312
|
"depends-on",
|
|
250
313
|
"first-phase",
|
|
251
314
|
"json",
|
|
315
|
+
"task",
|
|
316
|
+
"phase",
|
|
252
317
|
],
|
|
253
318
|
required: ["repo", "branch", "base"],
|
|
254
319
|
repeatable: ["reference", "depends-on"],
|
|
255
320
|
},
|
|
256
321
|
list: {
|
|
257
|
-
usage: "agency phase list <task-id> [--json]",
|
|
322
|
+
usage: "agency phase list <task-id> [filters] [--json]",
|
|
258
323
|
minArgs: 1,
|
|
259
324
|
maxArgs: 1,
|
|
260
|
-
options: ["json"],
|
|
325
|
+
options: ["json", "task", ...viewOptionNames],
|
|
326
|
+
repeatable: ["status", "repository"],
|
|
327
|
+
conflicts: viewConflicts,
|
|
261
328
|
},
|
|
262
329
|
show: {
|
|
263
330
|
usage: "agency phase show <task-id> <phase-id> [--json]",
|
|
264
331
|
minArgs: 2,
|
|
265
332
|
maxArgs: 2,
|
|
266
|
-
options: ["json"],
|
|
333
|
+
options: ["json", "task", "phase"],
|
|
267
334
|
},
|
|
268
335
|
status: {
|
|
269
336
|
usage: "agency phase status <task-id> <phase-id> <status> [--json]",
|
|
270
337
|
minArgs: 3,
|
|
271
338
|
maxArgs: 3,
|
|
272
|
-
options: ["json"],
|
|
339
|
+
options: ["json", "task", "phase"],
|
|
273
340
|
},
|
|
274
341
|
},
|
|
275
342
|
},
|
|
276
343
|
claim: {
|
|
277
344
|
usage: "agency claim <task-id> [phase-id] [options]",
|
|
278
|
-
options:
|
|
345
|
+
options: {
|
|
346
|
+
...claimOptions,
|
|
347
|
+
task: { type: "string" },
|
|
348
|
+
phase: { type: "string" },
|
|
349
|
+
},
|
|
279
350
|
command: {
|
|
280
351
|
usage:
|
|
281
352
|
"agency claim <task-id> [phase-id] --claimant <id> --runner <id> --session-id <id> --revision <sha256> [--expires-at <timestamp>] [--json]",
|
|
@@ -288,19 +359,25 @@ const commands = {
|
|
|
288
359
|
"revision",
|
|
289
360
|
"expires-at",
|
|
290
361
|
"json",
|
|
362
|
+
"task",
|
|
363
|
+
"phase",
|
|
291
364
|
],
|
|
292
365
|
required: ["claimant", "runner", "session-id", "revision"],
|
|
293
366
|
},
|
|
294
367
|
},
|
|
295
368
|
release: {
|
|
296
369
|
usage: "agency release <task-id> [phase-id] [options]",
|
|
297
|
-
options:
|
|
370
|
+
options: {
|
|
371
|
+
...ownedClaimOptions,
|
|
372
|
+
task: { type: "string" },
|
|
373
|
+
phase: { type: "string" },
|
|
374
|
+
},
|
|
298
375
|
command: {
|
|
299
376
|
usage:
|
|
300
377
|
"agency release <task-id> [phase-id] --session-id <id> --revision <sha256> [--json]",
|
|
301
378
|
minArgs: 1,
|
|
302
379
|
maxArgs: 2,
|
|
303
|
-
options: ["session-id", "revision", "json"],
|
|
380
|
+
options: ["session-id", "revision", "json", "task", "phase"],
|
|
304
381
|
required: ["session-id", "revision"],
|
|
305
382
|
},
|
|
306
383
|
},
|
|
@@ -309,13 +386,15 @@ const commands = {
|
|
|
309
386
|
options: {
|
|
310
387
|
...ownedClaimOptions,
|
|
311
388
|
outcome: { type: "string" },
|
|
389
|
+
task: { type: "string" },
|
|
390
|
+
phase: { type: "string" },
|
|
312
391
|
},
|
|
313
392
|
command: {
|
|
314
393
|
usage:
|
|
315
394
|
"agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped> [--json]",
|
|
316
395
|
minArgs: 1,
|
|
317
396
|
maxArgs: 2,
|
|
318
|
-
options: ["session-id", "revision", "outcome", "json"],
|
|
397
|
+
options: ["session-id", "revision", "outcome", "json", "task", "phase"],
|
|
319
398
|
required: ["session-id", "revision", "outcome"],
|
|
320
399
|
},
|
|
321
400
|
},
|
|
@@ -336,25 +415,25 @@ const commands = {
|
|
|
336
415
|
},
|
|
337
416
|
archive: {
|
|
338
417
|
usage: "agency archive <epic|task|phase>",
|
|
339
|
-
options: outputOptions,
|
|
418
|
+
options: { ...outputOptions, ...entitySelectorOptions },
|
|
340
419
|
subcommands: {
|
|
341
420
|
epic: {
|
|
342
421
|
usage: "agency archive epic <epic-id> [--json]",
|
|
343
422
|
minArgs: 1,
|
|
344
423
|
maxArgs: 1,
|
|
345
|
-
options: ["json"],
|
|
424
|
+
options: ["json", "epic"],
|
|
346
425
|
},
|
|
347
426
|
task: {
|
|
348
427
|
usage: "agency archive task <task-id> [--json]",
|
|
349
428
|
minArgs: 1,
|
|
350
429
|
maxArgs: 1,
|
|
351
|
-
options: ["json"],
|
|
430
|
+
options: ["json", "task"],
|
|
352
431
|
},
|
|
353
432
|
phase: {
|
|
354
433
|
usage: "agency archive phase <task-id> <phase-id> [--json]",
|
|
355
434
|
minArgs: 2,
|
|
356
435
|
maxArgs: 2,
|
|
357
|
-
options: ["json"],
|
|
436
|
+
options: ["json", "task", "phase"],
|
|
358
437
|
},
|
|
359
438
|
},
|
|
360
439
|
},
|
|
@@ -363,9 +442,9 @@ const commands = {
|
|
|
363
442
|
"agency work [<directory-or-task-id> | --epic <epic-id>] | agency work prepare [target] [--dry-run] [--json]",
|
|
364
443
|
options: {
|
|
365
444
|
...commonOptions,
|
|
445
|
+
...entitySelectorOptions,
|
|
366
446
|
json: { type: "boolean" },
|
|
367
447
|
"dry-run": { type: "boolean" },
|
|
368
|
-
epic: { type: "string" },
|
|
369
448
|
opencode: { type: "boolean" },
|
|
370
449
|
claude: { type: "boolean" },
|
|
371
450
|
force: { type: "boolean" },
|
|
@@ -375,7 +454,16 @@ const commands = {
|
|
|
375
454
|
"agency work [<directory-or-task-id> | --epic <epic-id>] | agency work prepare [target] [--dry-run] [--json]",
|
|
376
455
|
minArgs: 0,
|
|
377
456
|
maxArgs: 2,
|
|
378
|
-
options: [
|
|
457
|
+
options: [
|
|
458
|
+
"json",
|
|
459
|
+
"dry-run",
|
|
460
|
+
"epic",
|
|
461
|
+
"task",
|
|
462
|
+
"phase",
|
|
463
|
+
"opencode",
|
|
464
|
+
"claude",
|
|
465
|
+
"force",
|
|
466
|
+
],
|
|
379
467
|
conflicts: [
|
|
380
468
|
["opencode", "claude"],
|
|
381
469
|
["epic", "$positional"],
|
|
@@ -388,6 +476,8 @@ const commands = {
|
|
|
388
476
|
...outputOptions,
|
|
389
477
|
draft: { type: "boolean" },
|
|
390
478
|
force: { type: "boolean" },
|
|
479
|
+
task: { type: "string" },
|
|
480
|
+
phase: { type: "string" },
|
|
391
481
|
},
|
|
392
482
|
subcommands: {
|
|
393
483
|
create: {
|
|
@@ -395,7 +485,7 @@ const commands = {
|
|
|
395
485
|
"agency pr create <task-id> [phase-id] [--draft] [--force] [--json]",
|
|
396
486
|
minArgs: 1,
|
|
397
487
|
maxArgs: 2,
|
|
398
|
-
options: ["draft", "force", "json"],
|
|
488
|
+
options: ["draft", "force", "json", "task", "phase"],
|
|
399
489
|
},
|
|
400
490
|
},
|
|
401
491
|
},
|
|
@@ -413,13 +503,15 @@ const commands = {
|
|
|
413
503
|
},
|
|
414
504
|
},
|
|
415
505
|
status: {
|
|
416
|
-
usage: "agency status [--json]",
|
|
417
|
-
options: outputOptions,
|
|
506
|
+
usage: "agency status [filters] [--json]",
|
|
507
|
+
options: { ...outputOptions, ...viewOptions },
|
|
418
508
|
command: {
|
|
419
|
-
usage: "agency status [--json]",
|
|
509
|
+
usage: "agency status [filters] [--json]",
|
|
420
510
|
minArgs: 0,
|
|
421
511
|
maxArgs: 0,
|
|
422
|
-
options: ["json"],
|
|
512
|
+
options: ["json", ...viewOptionNames],
|
|
513
|
+
repeatable: ["status", "repository"],
|
|
514
|
+
conflicts: viewConflicts,
|
|
423
515
|
},
|
|
424
516
|
},
|
|
425
517
|
validate: {
|
|
@@ -438,13 +530,14 @@ const commands = {
|
|
|
438
530
|
usage: "agency context [target] [--json] [--compact]",
|
|
439
531
|
options: {
|
|
440
532
|
...outputOptions,
|
|
533
|
+
...entitySelectorOptions,
|
|
441
534
|
compact: { type: "boolean" },
|
|
442
535
|
},
|
|
443
536
|
command: {
|
|
444
537
|
usage: "agency context [target] [--json] [--compact]",
|
|
445
538
|
minArgs: 0,
|
|
446
539
|
maxArgs: 1,
|
|
447
|
-
options: ["json", "compact"],
|
|
540
|
+
options: ["json", "compact", "epic", "task", "phase"],
|
|
448
541
|
},
|
|
449
542
|
},
|
|
450
543
|
graph: {
|
|
@@ -495,6 +588,7 @@ const preCommandOptions = new Set([
|
|
|
495
588
|
"-v",
|
|
496
589
|
"--no-input",
|
|
497
590
|
])
|
|
591
|
+
const preCommandValueOptions = new Set(["--workbase", "--cwd"])
|
|
498
592
|
|
|
499
593
|
export interface ParsedCli {
|
|
500
594
|
readonly commandName?: keyof typeof commands
|
|
@@ -523,8 +617,22 @@ const usageError = (message: string, usage: string) =>
|
|
|
523
617
|
const optionLabel = (name: string) => `--${name}`
|
|
524
618
|
|
|
525
619
|
function findCommandIndex(args: readonly string[]) {
|
|
526
|
-
for (
|
|
620
|
+
for (let index = 0; index < args.length; index++) {
|
|
621
|
+
const argument = args[index]!
|
|
527
622
|
if (!argument.startsWith("-")) return index
|
|
623
|
+
if (argument.startsWith("--workbase=") || argument.startsWith("--cwd=")) {
|
|
624
|
+
continue
|
|
625
|
+
}
|
|
626
|
+
if (preCommandValueOptions.has(argument)) {
|
|
627
|
+
if (args[index + 1] === undefined) {
|
|
628
|
+
throw usageError(
|
|
629
|
+
`Option '${argument}' expects a value.`,
|
|
630
|
+
"agency <command> [options]",
|
|
631
|
+
)
|
|
632
|
+
}
|
|
633
|
+
index++
|
|
634
|
+
continue
|
|
635
|
+
}
|
|
528
636
|
if (!preCommandOptions.has(argument) && !/^-[hVsv]+$/.test(argument)) {
|
|
529
637
|
throw usageError(
|
|
530
638
|
`Unknown option '${argument}'.`,
|
|
@@ -535,6 +643,87 @@ function findCommandIndex(args: readonly string[]) {
|
|
|
535
643
|
return -1
|
|
536
644
|
}
|
|
537
645
|
|
|
646
|
+
const targetSlots = (
|
|
647
|
+
commandName: string,
|
|
648
|
+
subcommand: string | undefined,
|
|
649
|
+
): readonly ("epic" | "task" | "phase")[] => {
|
|
650
|
+
if (commandName === "epic" && subcommand === "show") return ["epic"]
|
|
651
|
+
if (commandName === "task" && ["show", "status"].includes(subcommand ?? ""))
|
|
652
|
+
return ["task"]
|
|
653
|
+
if (commandName === "phase")
|
|
654
|
+
return subcommand === "list" ? ["task"] : ["task", "phase"]
|
|
655
|
+
if (["claim", "release", "finish"].includes(commandName))
|
|
656
|
+
return ["task", "phase"]
|
|
657
|
+
if (commandName === "archive")
|
|
658
|
+
return subcommand === "epic"
|
|
659
|
+
? ["epic"]
|
|
660
|
+
: subcommand === "task"
|
|
661
|
+
? ["task"]
|
|
662
|
+
: ["task", "phase"]
|
|
663
|
+
if (commandName === "pr" && subcommand === "create") return ["task", "phase"]
|
|
664
|
+
return []
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function applyEntitySelectors(
|
|
668
|
+
commandName: string,
|
|
669
|
+
subcommand: string | undefined,
|
|
670
|
+
positionals: readonly string[],
|
|
671
|
+
values: ParsedCli["values"],
|
|
672
|
+
spec: LeafCommand,
|
|
673
|
+
) {
|
|
674
|
+
const supplied = ["epic", "task", "phase"].filter(
|
|
675
|
+
(name) => values[name] !== undefined,
|
|
676
|
+
)
|
|
677
|
+
if (supplied.length === 0) return [...positionals]
|
|
678
|
+
if (values.phase !== undefined && values.task === undefined) {
|
|
679
|
+
throw usageError("Option '--phase' requires '--task'.", spec.usage)
|
|
680
|
+
}
|
|
681
|
+
if (values.epic !== undefined && (values.task || values.phase)) {
|
|
682
|
+
throw usageError(
|
|
683
|
+
"Option '--epic' cannot be combined with '--task' or '--phase'.",
|
|
684
|
+
spec.usage,
|
|
685
|
+
)
|
|
686
|
+
}
|
|
687
|
+
if (commandName === "work" || commandName === "context") {
|
|
688
|
+
if (
|
|
689
|
+
positionals.length >
|
|
690
|
+
(commandName === "work" && positionals[0] === "prepare" ? 1 : 0)
|
|
691
|
+
) {
|
|
692
|
+
throw usageError(
|
|
693
|
+
"Entity selector options cannot be combined with a positional target.",
|
|
694
|
+
spec.usage,
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
return [...positionals]
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const slots = targetSlots(commandName, subcommand)
|
|
701
|
+
const selectedSlots = slots.filter((slot) => values[slot] !== undefined)
|
|
702
|
+
if (selectedSlots.length === 0) return [...positionals]
|
|
703
|
+
const trailingCount = spec.maxArgs - slots.length
|
|
704
|
+
if (positionals.length > trailingCount) {
|
|
705
|
+
throw usageError(
|
|
706
|
+
"Entity selector options cannot be combined with positional target IDs.",
|
|
707
|
+
spec.usage,
|
|
708
|
+
)
|
|
709
|
+
}
|
|
710
|
+
const requiredSlots = slots.slice(0, spec.minArgs - trailingCount)
|
|
711
|
+
for (const slot of requiredSlots) {
|
|
712
|
+
if (values[slot] === undefined) {
|
|
713
|
+
throw usageError(
|
|
714
|
+
`Option '--${slot}' is required with explicit selectors.`,
|
|
715
|
+
spec.usage,
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return [
|
|
720
|
+
...slots.flatMap((slot) =>
|
|
721
|
+
typeof values[slot] === "string" ? [values[slot] as string] : [],
|
|
722
|
+
),
|
|
723
|
+
...positionals,
|
|
724
|
+
]
|
|
725
|
+
}
|
|
726
|
+
|
|
538
727
|
function assertNoDuplicateOptions(
|
|
539
728
|
tokens: readonly { readonly kind: string; readonly name?: string }[],
|
|
540
729
|
repeatable: ReadonlySet<string>,
|
|
@@ -615,6 +804,24 @@ function validateGraphOptions(values: ParsedCli["values"], spec: LeafCommand) {
|
|
|
615
804
|
}
|
|
616
805
|
}
|
|
617
806
|
|
|
807
|
+
function validateViewOptions(values: ParsedCli["values"], spec: LeafCommand) {
|
|
808
|
+
const supplied = values.status
|
|
809
|
+
const statuses = Array.isArray(supplied)
|
|
810
|
+
? supplied
|
|
811
|
+
: supplied === undefined
|
|
812
|
+
? []
|
|
813
|
+
: [supplied]
|
|
814
|
+
const accepted = new Set(["open", "working", "delegated", "done", "dropped"])
|
|
815
|
+
for (const status of statuses) {
|
|
816
|
+
if (typeof status !== "string" || !accepted.has(status)) {
|
|
817
|
+
throw usageError(
|
|
818
|
+
`Invalid '--status' value '${String(status)}'. Expected one of: ${[...accepted].join(", ")}.`,
|
|
819
|
+
spec.usage,
|
|
820
|
+
)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
618
825
|
export function parseCli(args: readonly string[]): ParsedCli {
|
|
619
826
|
const commandIndex = findCommandIndex(args)
|
|
620
827
|
if (commandIndex === -1) {
|
|
@@ -674,7 +881,7 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
674
881
|
throw usageError(message, definition.usage)
|
|
675
882
|
}
|
|
676
883
|
|
|
677
|
-
|
|
884
|
+
let commandPositionals = definition.subcommands
|
|
678
885
|
? parsed.positionals.slice(1)
|
|
679
886
|
: parsed.positionals
|
|
680
887
|
const allowed = new Set([...commonOptionNames, ...(spec.options ?? [])])
|
|
@@ -694,6 +901,20 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
694
901
|
spec.usage,
|
|
695
902
|
)
|
|
696
903
|
}
|
|
904
|
+
for (const selector of ["workbase", "cwd", "epic", "task", "phase"]) {
|
|
905
|
+
if (parsed.values[selector] === "") {
|
|
906
|
+
throw usageError(
|
|
907
|
+
`Option '--${selector}' requires a non-empty value.`,
|
|
908
|
+
spec.usage,
|
|
909
|
+
)
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (parsed.values.workbase && parsed.values.cwd) {
|
|
913
|
+
throw usageError(
|
|
914
|
+
"Options '--workbase' and '--cwd' cannot be combined.",
|
|
915
|
+
spec.usage,
|
|
916
|
+
)
|
|
917
|
+
}
|
|
697
918
|
if (parsed.values.version) {
|
|
698
919
|
return {
|
|
699
920
|
commandName: commandName as keyof typeof commands,
|
|
@@ -709,6 +930,14 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
709
930
|
}
|
|
710
931
|
}
|
|
711
932
|
|
|
933
|
+
commandPositionals = applyEntitySelectors(
|
|
934
|
+
commandName,
|
|
935
|
+
subcommand,
|
|
936
|
+
commandPositionals,
|
|
937
|
+
parsed.values,
|
|
938
|
+
spec,
|
|
939
|
+
)
|
|
940
|
+
|
|
712
941
|
if (
|
|
713
942
|
commandPositionals.length < spec.minArgs ||
|
|
714
943
|
commandPositionals.length > spec.maxArgs
|
|
@@ -745,6 +974,12 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
745
974
|
if (commandName === "graph") {
|
|
746
975
|
validateGraphOptions(parsed.values, spec)
|
|
747
976
|
}
|
|
977
|
+
if (
|
|
978
|
+
commandName === "status" ||
|
|
979
|
+
(["epic", "task", "phase"].includes(commandName) && subcommand === "list")
|
|
980
|
+
) {
|
|
981
|
+
validateViewOptions(parsed.values, spec)
|
|
982
|
+
}
|
|
748
983
|
if (
|
|
749
984
|
commandName === "finish" &&
|
|
750
985
|
parsed.values.outcome !== "done" &&
|
|
@@ -782,7 +1017,9 @@ export function parseCli(args: readonly string[]): ParsedCli {
|
|
|
782
1017
|
|
|
783
1018
|
return {
|
|
784
1019
|
commandName: commandName as keyof typeof commands,
|
|
785
|
-
args:
|
|
1020
|
+
args: definition.subcommands
|
|
1021
|
+
? [subcommand!, ...commandPositionals]
|
|
1022
|
+
: commandPositionals,
|
|
786
1023
|
values: parsed.values,
|
|
787
1024
|
}
|
|
788
1025
|
}
|