@taskset/cli 6.0.0 → 6.1.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/src/cli.ts CHANGED
@@ -2,9 +2,11 @@ import { readFile } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
  import { parseArgs } from 'node:util'
4
4
  import {
5
+ ConcernClassSchema,
5
6
  DocumentIdSchema,
6
7
  DocumentStatusSchema,
7
8
  DocumentTimestampSchema,
9
+ LessonSeveritySchema,
8
10
  TaskIdSchema,
9
11
  TaskPrioritySchema,
10
12
  TaskRiskSchema,
@@ -31,6 +33,7 @@ import {
31
33
  discoverRepository,
32
34
  executeDocumentBatch,
33
35
  generateViews,
36
+ getProgramRollup,
34
37
  importDocument,
35
38
  initializeRepository,
36
39
  listDocuments,
@@ -73,8 +76,9 @@ const USAGE = `Usage:
73
76
  taskset task status <task-id> <status> [--json] [--cwd <path>]
74
77
  taskset task delete <task-id> [--remove-dependencies] [--json] [--cwd <path>]
75
78
  taskset task migrate-ids [--json] [--cwd <path>]
79
+ taskset task program <parent-id> [--json] [--cwd <path>]
76
80
  taskset sync [--concurrency <count>] [--json] [--cwd <path>]
77
- taskset document create <story|flow|decision|adr|dr|research|runbook> --title <title> [metadata options]
81
+ taskset document create <story|flow|decision|adr|dr|research|runbook|lesson|antipattern|concern|audit> --title <title> [metadata options]
78
82
  taskset document import <markdown-path> [--type <type>] [--move]
79
83
  taskset document batch <manifest.json> [--concurrency <count>] [--json]
80
84
  taskset document list [type] [query options]
@@ -87,10 +91,12 @@ Metadata options:
87
91
  --status --priority --order --owner --team --estimate --effort --risk --due-date
88
92
  --label --assignee --reviewer --depends-on --related --duplicate
89
93
  --parent --file --directory --project --body
94
+ Lesson/concern options: --severity --related-skill --pack --class --cadence
90
95
  Repeat array options. Task and document updates clear arrays with --clear-dependencies,
91
96
  --clear-labels, --clear-assignees, --clear-reviewers, --clear-related,
92
- --clear-files, --clear-directories, or --clear-projects. Clear scalar
93
- relationships with --clear-parent or --clear-owner.
97
+ --clear-files, --clear-directories, --clear-projects, --clear-related-skills,
98
+ or --clear-packs. Clear scalar relationships with --clear-parent, --clear-owner,
99
+ --clear-severity, --clear-class, or --clear-cadence.
94
100
 
95
101
  List query options:
96
102
  --status --priority --label --owner --assignee --reviewer --team --risk
@@ -178,6 +184,11 @@ const DocumentInputSchema = z.strictObject({
178
184
  parent: TaskIdSchema.optional(),
179
185
  directories: StringListSchema.optional(),
180
186
  projects: StringListSchema.optional(),
187
+ severity: LessonSeveritySchema.optional(),
188
+ relatedSkills: StringListSchema.optional(),
189
+ packs: StringListSchema.optional(),
190
+ class: ConcernClassSchema.optional(),
191
+ cadence: TrimmedStringSchema.optional(),
181
192
  body: z.string().optional(),
182
193
  })
183
194
  const DocumentUpdateSchema = z
@@ -202,6 +213,11 @@ const DocumentUpdateSchema = z
202
213
  parent: TaskIdSchema.nullable().optional(),
203
214
  directories: StringListSchema.optional(),
204
215
  projects: StringListSchema.optional(),
216
+ severity: LessonSeveritySchema.nullable().optional(),
217
+ relatedSkills: StringListSchema.optional(),
218
+ packs: StringListSchema.optional(),
219
+ class: ConcernClassSchema.nullable().optional(),
220
+ cadence: TrimmedStringSchema.nullable().optional(),
205
221
  body: z.string().optional(),
206
222
  })
207
223
  .refine((input) => Object.keys(input).length > 0, 'Document update requires at least one field')
@@ -258,6 +274,15 @@ const UPDATE_CLEAR_PAIRS = [
258
274
  ['project', 'clear-projects'],
259
275
  ] as const
260
276
 
277
+ const DOCUMENT_UPDATE_CLEAR_PAIRS = [
278
+ ...UPDATE_CLEAR_PAIRS,
279
+ ['severity', 'clear-severity'],
280
+ ['related-skill', 'clear-related-skills'],
281
+ ['pack', 'clear-packs'],
282
+ ['class', 'clear-class'],
283
+ ['cadence', 'clear-cadence'],
284
+ ] as const
285
+
261
286
  const DocumentCreateValuesSchema = z.strictObject({
262
287
  title: TrimmedStringSchema,
263
288
  status: DocumentStatusSchema.optional(),
@@ -279,6 +304,11 @@ const DocumentCreateValuesSchema = z.strictObject({
279
304
  parent: TaskIdSchema.optional(),
280
305
  directory: StringListSchema.optional(),
281
306
  project: StringListSchema.optional(),
307
+ severity: LessonSeveritySchema.optional(),
308
+ 'related-skill': StringListSchema.optional(),
309
+ pack: StringListSchema.optional(),
310
+ class: ConcernClassSchema.optional(),
311
+ cadence: TrimmedStringSchema.optional(),
282
312
  body: z.string().optional(),
283
313
  cwd: CwdSchema,
284
314
  json: JsonSchema,
@@ -306,6 +336,11 @@ const DocumentUpdateValuesSchema = z
306
336
  parent: TaskIdSchema.optional(),
307
337
  directory: StringListSchema.optional(),
308
338
  project: StringListSchema.optional(),
339
+ severity: LessonSeveritySchema.optional(),
340
+ 'related-skill': StringListSchema.optional(),
341
+ pack: StringListSchema.optional(),
342
+ class: ConcernClassSchema.optional(),
343
+ cadence: TrimmedStringSchema.optional(),
309
344
  body: z.string().optional(),
310
345
  type: TrimmedStringSchema.optional(),
311
346
  'clear-priority': z.boolean().optional(),
@@ -326,11 +361,16 @@ const DocumentUpdateValuesSchema = z
326
361
  'clear-parent': z.boolean().optional(),
327
362
  'clear-directories': z.boolean().optional(),
328
363
  'clear-projects': z.boolean().optional(),
364
+ 'clear-severity': z.boolean().optional(),
365
+ 'clear-related-skills': z.boolean().optional(),
366
+ 'clear-packs': z.boolean().optional(),
367
+ 'clear-class': z.boolean().optional(),
368
+ 'clear-cadence': z.boolean().optional(),
329
369
  cwd: CwdSchema,
330
370
  json: JsonSchema,
331
371
  })
332
372
  .superRefine((values, context) => {
333
- for (const [valueKey, clearKey] of UPDATE_CLEAR_PAIRS) {
373
+ for (const [valueKey, clearKey] of DOCUMENT_UPDATE_CLEAR_PAIRS) {
334
374
  if (values[valueKey] !== undefined && values[clearKey]) {
335
375
  context.addIssue({
336
376
  code: 'custom',
@@ -362,6 +402,8 @@ const DocumentListValuesSchema = z.strictObject({
362
402
  team: StringListSchema.optional(),
363
403
  risk: z.array(TaskRiskSchema).optional(),
364
404
  project: StringListSchema.optional(),
405
+ class: z.array(ConcernClassSchema).optional(),
406
+ severity: z.array(LessonSeveritySchema).optional(),
365
407
  'depends-on': TaskIdSchema.optional(),
366
408
  related: TaskIdSchema.optional(),
367
409
  duplicate: TaskIdSchema.optional(),
@@ -537,6 +579,15 @@ const metadataOptionDefinitions = {
537
579
  project: { type: 'string', multiple: true },
538
580
  } as const
539
581
 
582
+ const documentMetadataOptionDefinitions = {
583
+ ...metadataOptionDefinitions,
584
+ severity: { type: 'string' },
585
+ 'related-skill': { type: 'string', multiple: true },
586
+ pack: { type: 'string', multiple: true },
587
+ class: { type: 'string' },
588
+ cadence: { type: 'string' },
589
+ } as const
590
+
540
591
  const commonOptionDefinitions = {
541
592
  cwd: { type: 'string' },
542
593
  json: { type: 'boolean' },
@@ -757,101 +808,59 @@ function documentUpdateInputFromValues(
757
808
  repository: Awaited<ReturnType<typeof discoverRepository>>,
758
809
  values: z.infer<typeof DocumentUpdateValuesSchema>,
759
810
  ): UpdateDocumentInput {
760
- return {
761
- ...(values.title !== undefined ? { title: values.title } : {}),
762
- ...(values.status !== undefined ? { status: values.status } : {}),
763
- ...(values['clear-priority']
764
- ? { priority: null }
765
- : values.priority !== undefined
766
- ? { priority: values.priority }
767
- : {}),
768
- ...(values['clear-order']
769
- ? { order: null }
770
- : values.order !== undefined
771
- ? { order: values.order }
772
- : {}),
773
- ...(values['clear-labels']
774
- ? { labels: [] }
775
- : values.label !== undefined
776
- ? { labels: values.label }
777
- : {}),
778
- ...(values['clear-dependencies']
779
- ? { dependsOn: [] }
780
- : values['depends-on'] !== undefined
781
- ? { dependsOn: values['depends-on'] }
782
- : {}),
783
- ...(values['clear-files']
784
- ? { files: [] }
785
- : values.file !== undefined
786
- ? { files: normalizeMutationPaths(repository, values.file) }
787
- : {}),
788
- ...(values['clear-owner']
789
- ? { owner: null }
790
- : values.owner !== undefined
791
- ? { owner: values.owner }
792
- : {}),
793
- ...(values['clear-assignees']
794
- ? { assignees: [] }
795
- : values.assignee !== undefined
796
- ? { assignees: values.assignee }
797
- : {}),
798
- ...(values['clear-reviewers']
799
- ? { reviewers: [] }
800
- : values.reviewer !== undefined
801
- ? { reviewers: values.reviewer }
802
- : {}),
803
- ...(values['clear-team']
804
- ? { team: null }
805
- : values.team !== undefined
806
- ? { team: values.team }
807
- : {}),
808
- ...(values['clear-estimate']
809
- ? { estimate: null }
810
- : values.estimate !== undefined
811
- ? { estimate: values.estimate }
812
- : {}),
813
- ...(values['clear-effort']
814
- ? { effort: null }
815
- : values.effort !== undefined
816
- ? { effort: values.effort }
817
- : {}),
818
- ...(values['clear-risk']
819
- ? { risk: null }
820
- : values.risk !== undefined
821
- ? { risk: values.risk }
822
- : {}),
823
- ...(values['clear-due-date']
824
- ? { dueDate: null }
825
- : values['due-date'] !== undefined
826
- ? { dueDate: values['due-date'] }
827
- : {}),
828
- ...(values['clear-related']
829
- ? { related: [] }
830
- : values.related !== undefined
831
- ? { related: values.related }
832
- : {}),
833
- ...(values['clear-duplicates']
834
- ? { duplicates: [] }
835
- : values.duplicate !== undefined
836
- ? { duplicates: values.duplicate }
837
- : {}),
838
- ...(values['clear-parent']
839
- ? { parent: null }
840
- : values.parent !== undefined
841
- ? { parent: values.parent }
842
- : {}),
843
- ...(values['clear-directories']
844
- ? { directories: [] }
845
- : values.directory !== undefined
846
- ? { directories: normalizeMutationPaths(repository, values.directory) }
847
- : {}),
848
- ...(values['clear-projects']
849
- ? { projects: [] }
850
- : values.project !== undefined
851
- ? { projects: values.project }
852
- : {}),
853
- ...(values.body !== undefined ? { body: values.body } : {}),
811
+ const input: Record<string, unknown> = {}
812
+ if (values.title !== undefined) input.title = values.title
813
+ if (values.status !== undefined) input.status = values.status
814
+ if (values['clear-priority']) input.priority = null
815
+ else if (values.priority !== undefined) input.priority = values.priority
816
+ if (values['clear-order']) input.order = null
817
+ else if (values.order !== undefined) input.order = values.order
818
+ if (values['clear-labels']) input.labels = []
819
+ else if (values.label !== undefined) input.labels = values.label
820
+ if (values['clear-dependencies']) input.dependsOn = []
821
+ else if (values['depends-on'] !== undefined) input.dependsOn = values['depends-on']
822
+ if (values['clear-files']) input.files = []
823
+ else if (values.file !== undefined) input.files = normalizeMutationPaths(repository, values.file)
824
+ if (values['clear-owner']) input.owner = null
825
+ else if (values.owner !== undefined) input.owner = values.owner
826
+ if (values['clear-assignees']) input.assignees = []
827
+ else if (values.assignee !== undefined) input.assignees = values.assignee
828
+ if (values['clear-reviewers']) input.reviewers = []
829
+ else if (values.reviewer !== undefined) input.reviewers = values.reviewer
830
+ if (values['clear-team']) input.team = null
831
+ else if (values.team !== undefined) input.team = values.team
832
+ if (values['clear-estimate']) input.estimate = null
833
+ else if (values.estimate !== undefined) input.estimate = values.estimate
834
+ if (values['clear-effort']) input.effort = null
835
+ else if (values.effort !== undefined) input.effort = values.effort
836
+ if (values['clear-risk']) input.risk = null
837
+ else if (values.risk !== undefined) input.risk = values.risk
838
+ if (values['clear-due-date']) input.dueDate = null
839
+ else if (values['due-date'] !== undefined) input.dueDate = values['due-date']
840
+ if (values['clear-related']) input.related = []
841
+ else if (values.related !== undefined) input.related = values.related
842
+ if (values['clear-duplicates']) input.duplicates = []
843
+ else if (values.duplicate !== undefined) input.duplicates = values.duplicate
844
+ if (values['clear-parent']) input.parent = null
845
+ else if (values.parent !== undefined) input.parent = values.parent
846
+ if (values['clear-directories']) input.directories = []
847
+ else if (values.directory !== undefined) {
848
+ input.directories = normalizeMutationPaths(repository, values.directory)
854
849
  }
850
+ if (values['clear-projects']) input.projects = []
851
+ else if (values.project !== undefined) input.projects = values.project
852
+ if (values['clear-severity']) input.severity = null
853
+ else if (values.severity !== undefined) input.severity = values.severity
854
+ if (values['clear-related-skills']) input.relatedSkills = []
855
+ else if (values['related-skill'] !== undefined) input.relatedSkills = values['related-skill']
856
+ if (values['clear-packs']) input.packs = []
857
+ else if (values.pack !== undefined) input.packs = values.pack
858
+ if (values['clear-class']) input.class = null
859
+ else if (values.class !== undefined) input.class = values.class
860
+ if (values['clear-cadence']) input.cadence = null
861
+ else if (values.cadence !== undefined) input.cadence = values.cadence
862
+ if (values.body !== undefined) input.body = values.body
863
+ return input as UpdateDocumentInput
855
864
  }
856
865
 
857
866
  export async function runCli(args: readonly string[], context: CliContext = {}): Promise<number> {
@@ -926,7 +935,9 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
926
935
  if (values.json) {
927
936
  stdout(`${JSON.stringify(result, null, 2)}\n`)
928
937
  } else if (result.valid) {
929
- stdout(`Taskset repository is valid (${result.taskCount} tasks)\n`)
938
+ stdout(
939
+ `Taskset repository is valid (${result.taskCount} tasks, ${result.documentCount} documents)\n`,
940
+ )
930
941
  } else {
931
942
  for (const diagnostic of result.diagnostics) {
932
943
  stdout(
@@ -1101,7 +1112,7 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
1101
1112
  const parsed = parseArgs({
1102
1113
  args: subcommandArgs,
1103
1114
  allowPositionals: true,
1104
- options: { ...metadataOptionDefinitions, ...commonOptionDefinitions },
1115
+ options: { ...documentMetadataOptionDefinitions, ...commonOptionDefinitions },
1105
1116
  })
1106
1117
  const [rawType] = requirePositionals(parsed.positionals, 1, 'exactly one document type')
1107
1118
  const values = parseSchema(
@@ -1136,6 +1147,11 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
1136
1147
  ? { directories: normalizeMutationPaths(repository, values.directory) }
1137
1148
  : {}),
1138
1149
  ...(values.project ? { projects: values.project } : {}),
1150
+ ...(values.severity ? { severity: values.severity } : {}),
1151
+ ...(values['related-skill'] ? { relatedSkills: values['related-skill'] } : {}),
1152
+ ...(values.pack ? { packs: values.pack } : {}),
1153
+ ...(values.class ? { class: values.class } : {}),
1154
+ ...(values.cadence ? { cadence: values.cadence } : {}),
1139
1155
  ...(values.body !== undefined ? { body: values.body } : {}),
1140
1156
  },
1141
1157
  { onWarning },
@@ -1197,6 +1213,8 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
1197
1213
  team: { type: 'string', multiple: true },
1198
1214
  risk: { type: 'string', multiple: true },
1199
1215
  project: { type: 'string', multiple: true },
1216
+ class: { type: 'string', multiple: true },
1217
+ severity: { type: 'string', multiple: true },
1200
1218
  'depends-on': { type: 'string' },
1201
1219
  related: { type: 'string' },
1202
1220
  duplicate: { type: 'string' },
@@ -1239,6 +1257,8 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
1239
1257
  ...(values.team ? { teams: values.team } : {}),
1240
1258
  ...(values.risk ? { risks: values.risk } : {}),
1241
1259
  ...(values.project ? { projects: values.project } : {}),
1260
+ ...(values.class ? { classes: values.class } : {}),
1261
+ ...(values.severity ? { severities: values.severity } : {}),
1242
1262
  ...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
1243
1263
  ...(values.related ? { related: values.related } : {}),
1244
1264
  ...(values.duplicate ? { duplicate: values.duplicate } : {}),
@@ -1354,13 +1374,13 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
1354
1374
 
1355
1375
  if (subcommand === 'update') {
1356
1376
  const clearDefinitions = Object.fromEntries(
1357
- UPDATE_CLEAR_PAIRS.map(([, clear]) => [clear, { type: 'boolean' as const }]),
1377
+ DOCUMENT_UPDATE_CLEAR_PAIRS.map(([, clear]) => [clear, { type: 'boolean' as const }]),
1358
1378
  )
1359
1379
  const parsed = parseArgs({
1360
1380
  args: subcommandArgs,
1361
1381
  allowPositionals: true,
1362
1382
  options: {
1363
- ...metadataOptionDefinitions,
1383
+ ...documentMetadataOptionDefinitions,
1364
1384
  ...clearDefinitions,
1365
1385
  ...commonOptionDefinitions,
1366
1386
  type: { type: 'string' },
@@ -1482,6 +1502,29 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
1482
1502
  return 0
1483
1503
  }
1484
1504
 
1505
+ if (subcommand === 'program') {
1506
+ const parsed = parseArgs({
1507
+ args: subcommandArgs,
1508
+ allowPositionals: true,
1509
+ options: commonOptionDefinitions,
1510
+ })
1511
+ const [parentId] = requirePositionals(parsed.positionals, 1, 'exactly one parent task ID')
1512
+ const values = parseSchema(CommonValuesSchema, parsed.values, 'task program options')
1513
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
1514
+ const rollup = await getProgramRollup(
1515
+ repository,
1516
+ parseSchema(TaskIdSchema, parentId, 'parent task ID'),
1517
+ )
1518
+ if (values.json) {
1519
+ stdout(`${JSON.stringify(rollup, null, 2)}\n`)
1520
+ } else {
1521
+ stdout(
1522
+ `${rollup.parentId}\t${rollup.status}\tchildren=${rollup.children.total}\topen=${rollup.children.openIds.length}\tconcerns=${rollup.relatedOpenConcerns.length}\tcloseoutReady=${rollup.closeoutReady}\t${rollup.title}\n`,
1523
+ )
1524
+ }
1525
+ return 0
1526
+ }
1527
+
1485
1528
  if (subcommand === 'create') {
1486
1529
  const parsed = parseArgs({
1487
1530
  args: subcommandArgs,