@taskset/cli 4.0.0 → 6.0.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +23 -20
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +538 -71
  5. package/docs/_meta.ts +9 -0
  6. package/docs/agents/_meta.ts +5 -0
  7. package/docs/agents/commands.md +70 -0
  8. package/docs/agents/index.md +99 -0
  9. package/docs/agents/llms.txt +28 -0
  10. package/docs/agents/workflows.md +50 -0
  11. package/docs/cli-reference.md +461 -0
  12. package/docs/configuration.md +63 -0
  13. package/docs/document-types.md +103 -0
  14. package/docs/getting-started.md +106 -0
  15. package/docs/index.md +43 -0
  16. package/docs/maintainers/_meta.ts +7 -0
  17. package/docs/maintainers/architecture/_meta.ts +5 -0
  18. package/docs/maintainers/architecture/decisions/0001-documentation-platform.md +44 -0
  19. package/docs/maintainers/architecture/decisions/0002-code-architecture.md +31 -0
  20. package/docs/maintainers/architecture/decisions/0003-snapshot-policy.md +31 -0
  21. package/docs/maintainers/architecture/decisions/_meta.ts +5 -0
  22. package/docs/maintainers/architecture/overview.md +111 -0
  23. package/docs/maintainers/architecture/synchronization.md +56 -0
  24. package/docs/maintainers/development/_meta.ts +6 -0
  25. package/docs/maintainers/development/contributing.md +58 -0
  26. package/docs/maintainers/development/documentation.md +52 -0
  27. package/docs/maintainers/development/engineering.md +59 -0
  28. package/docs/maintainers/development/testing.md +89 -0
  29. package/docs/maintainers/index.md +20 -0
  30. package/docs/maintainers/product/_meta.ts +3 -0
  31. package/docs/maintainers/product/vision.md +68 -0
  32. package/docs/maintainers/technology.md +55 -0
  33. package/docs/task-files.md +180 -0
  34. package/package.json +8 -6
  35. package/skills/taskset/SKILL.md +240 -0
  36. package/skills/taskset/references/changesets-examples.md +97 -0
  37. package/skills/taskset/references/document-modeling-examples.md +63 -0
  38. package/skills/taskset/references/monorepo-task-modeling.md +125 -0
  39. package/skills/taskset/references/task-modeling-examples.md +249 -0
  40. package/skills/taskset-implement/SKILL.md +240 -0
  41. package/skills/taskset-implement/agents/openai.yaml +4 -0
  42. package/skills/taskset-implement/references/architecture/client-and-server.md +69 -0
  43. package/skills/taskset-implement/references/architecture/documentation-and-generated.md +72 -0
  44. package/skills/taskset-implement/references/architecture/ownership-and-dependencies.md +108 -0
  45. package/skills/taskset-implement/references/architecture/product-and-source.md +81 -0
  46. package/skills/taskset-implement/references/architecture/storage-and-snapshots.md +45 -0
  47. package/skills/taskset-implement/references/architecture.md +34 -0
  48. package/skills/taskset-implement/references/conventions/backend-and-tooling.md +33 -0
  49. package/skills/taskset-implement/references/conventions/design.md +38 -0
  50. package/skills/taskset-implement/references/conventions/interfaces-and-ui.md +50 -0
  51. package/skills/taskset-implement/references/conventions/naming-and-packages.md +64 -0
  52. package/skills/taskset-implement/references/conventions/task-files.md +94 -0
  53. package/skills/taskset-implement/references/conventions/tests-and-docs.md +55 -0
  54. package/skills/taskset-implement/references/conventions/typescript-and-exports.md +41 -0
  55. package/skills/taskset-implement/references/conventions.md +40 -0
  56. package/skills/taskset-implement/references/release.md +135 -0
  57. package/skills/taskset-implement/references/workflows/dependencies-and-docs-site.md +54 -0
  58. package/skills/taskset-implement/references/workflows/environment-and-pnpm.md +109 -0
  59. package/skills/taskset-implement/references/workflows/persisted-data-and-git.md +30 -0
  60. package/skills/taskset-implement/references/workflows/validation.md +34 -0
  61. package/skills/taskset-implement/references/workflows/vitest-and-test-strategy.md +82 -0
  62. package/skills/taskset-implement/references/workflows.md +33 -0
  63. package/src/cli.ts +640 -87
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { parseArgs } from 'node:util';
4
- import { DocumentStatusSchema, TaskIdSchema, TaskPrioritySchema, TaskRiskSchema, TaskStatusSchema, TaskTimestampSchema, } from '@taskset/contracts';
5
- import { buildTaskIndex, createDocument, createSnapshot, createTask, deleteTask, diagnoseRepository, discoverRepository, executeDocumentBatch, generateViews, importDocument, initializeRepository, listDocuments, listSnapshots, migrateTaskIds, normalizeDocumentKind, normalizeRepositoryPath, queryTasks, RepositoryPathError, readDocument, readTask, restoreSnapshot, serializeDocumentFile, serializeTaskFile, syncRepository, TASK_SORT_DIRECTIONS, TASK_SORT_KEYS, TaskQuerySchema, updateTask, } from '@taskset/core';
4
+ import { DocumentIdSchema, DocumentStatusSchema, DocumentTimestampSchema, TaskIdSchema, TaskPrioritySchema, TaskRiskSchema, TaskStatusSchema, TaskTimestampSchema, } from '@taskset/contracts';
5
+ import { buildDocumentGraph, buildTaskIndex, createDocument, createSnapshot, createTask, DOCUMENT_SORT_DIRECTIONS, DOCUMENT_SORT_KEYS, DocumentQuerySchema, deleteDocument, deleteTask, diagnoseRepository, discoverRepository, executeDocumentBatch, generateViews, importDocument, initializeRepository, listDocuments, listSnapshots, migrateTaskIds, normalizeDocumentKind, normalizeRepositoryPath, queryDocuments, queryTasks, RepositoryPathError, readDocument, readTask, restoreSnapshot, serializeDocumentFile, serializeTaskFile, syncRepository, TASK_SORT_DIRECTIONS, TASK_SORT_KEYS, TaskQuerySchema, updateDocument, updateTask, } from '@taskset/core';
6
6
  import * as z from 'zod';
7
7
  const USAGE = `Usage:
8
- taskset init [--cwd <path>]
8
+ taskset init [--config] [--cwd <path>]
9
9
  taskset config [--json] [--cwd <path>]
10
10
  taskset doctor [--json] [--cwd <path>]
11
11
  taskset generate [--json] [--cwd <path>]
@@ -19,17 +19,20 @@ const USAGE = `Usage:
19
19
  taskset task delete <task-id> [--remove-dependencies] [--json] [--cwd <path>]
20
20
  taskset task migrate-ids [--json] [--cwd <path>]
21
21
  taskset sync [--concurrency <count>] [--json] [--cwd <path>]
22
- taskset document create <story|flow|decision|adr|dr|research|runbook> --title <title>
22
+ taskset document create <story|flow|decision|adr|dr|research|runbook> --title <title> [metadata options]
23
23
  taskset document import <markdown-path> [--type <type>] [--move]
24
24
  taskset document batch <manifest.json> [--concurrency <count>] [--json]
25
- taskset document list [type] [--json] [--cwd <path>]
26
- taskset document show <document-id> [--type <type>] [--json] [--cwd <path>]
25
+ taskset document list [type] [query options]
26
+ taskset document show <document-id> [--type <type>] [--include-derived] [--json]
27
+ taskset document update <document-id> [metadata options] [--type <type>]
28
+ taskset document status <document-id> <status> [--type <type>] [--json]
29
+ taskset document delete <document-id> [--type <type>] [--remove-dependencies] [--json]
27
30
 
28
31
  Metadata options:
29
32
  --status --priority --order --owner --team --estimate --effort --risk --due-date
30
33
  --label --assignee --reviewer --depends-on --related --duplicate
31
34
  --parent --file --directory --project --body
32
- Repeat array options. Task updates clear arrays with --clear-dependencies,
35
+ Repeat array options. Task and document updates clear arrays with --clear-dependencies,
33
36
  --clear-labels, --clear-assignees, --clear-reviewers, --clear-related,
34
37
  --clear-files, --clear-directories, or --clear-projects. Clear scalar
35
38
  relationships with --clear-parent or --clear-owner.
@@ -64,6 +67,10 @@ const CommonValuesSchema = z.strictObject({
64
67
  cwd: CwdSchema,
65
68
  json: JsonSchema,
66
69
  });
70
+ const InitValuesSchema = z.strictObject({
71
+ cwd: CwdSchema,
72
+ config: z.boolean().optional(),
73
+ });
67
74
  const ConcurrencySchema = z.coerce.number().int().min(1).max(32).optional();
68
75
  const DocumentKindInputSchema = TrimmedStringSchema.transform((value, context) => {
69
76
  try {
@@ -81,17 +88,51 @@ const DocumentInputSchema = z.strictObject({
81
88
  type: DocumentKindInputSchema,
82
89
  title: TrimmedStringSchema,
83
90
  status: DocumentStatusSchema.optional(),
91
+ priority: TaskPrioritySchema.optional(),
92
+ order: z.number().finite().nonnegative().optional(),
84
93
  labels: StringListSchema.optional(),
85
- related: StringListSchema.optional(),
94
+ dependsOn: TaskIdListSchema.optional(),
95
+ files: StringListSchema.optional(),
96
+ owner: TrimmedStringSchema.optional(),
97
+ assignees: StringListSchema.optional(),
98
+ reviewers: StringListSchema.optional(),
99
+ team: TrimmedStringSchema.optional(),
100
+ estimate: z.number().int().nonnegative().optional(),
101
+ effort: z.number().finite().nonnegative().optional(),
102
+ risk: TaskRiskSchema.optional(),
103
+ dueDate: DocumentTimestampSchema.optional(),
104
+ related: TaskIdListSchema.optional(),
105
+ duplicates: TaskIdListSchema.optional(),
106
+ parent: TaskIdSchema.optional(),
107
+ directories: StringListSchema.optional(),
108
+ projects: StringListSchema.optional(),
86
109
  body: z.string().optional(),
87
110
  });
88
- const DocumentUpdateSchema = z.strictObject({
111
+ const DocumentUpdateSchema = z
112
+ .strictObject({
89
113
  title: TrimmedStringSchema.optional(),
90
114
  status: DocumentStatusSchema.optional(),
115
+ priority: TaskPrioritySchema.nullable().optional(),
116
+ order: z.number().finite().nonnegative().nullable().optional(),
91
117
  labels: StringListSchema.optional(),
92
- related: StringListSchema.optional(),
118
+ dependsOn: TaskIdListSchema.optional(),
119
+ files: StringListSchema.optional(),
120
+ owner: TrimmedStringSchema.nullable().optional(),
121
+ assignees: StringListSchema.optional(),
122
+ reviewers: StringListSchema.optional(),
123
+ team: TrimmedStringSchema.nullable().optional(),
124
+ estimate: z.number().int().nonnegative().nullable().optional(),
125
+ effort: z.number().finite().nonnegative().nullable().optional(),
126
+ risk: TaskRiskSchema.nullable().optional(),
127
+ dueDate: DocumentTimestampSchema.nullable().optional(),
128
+ related: TaskIdListSchema.optional(),
129
+ duplicates: TaskIdListSchema.optional(),
130
+ parent: TaskIdSchema.nullable().optional(),
131
+ directories: StringListSchema.optional(),
132
+ projects: StringListSchema.optional(),
93
133
  body: z.string().optional(),
94
- });
134
+ })
135
+ .refine((input) => Object.keys(input).length > 0, 'Document update requires at least one field');
95
136
  const DocumentBatchSchema = z
96
137
  .array(z.discriminatedUnion('action', [
97
138
  z.strictObject({ action: z.literal('create'), input: DocumentInputSchema }),
@@ -121,6 +162,148 @@ const DocumentBatchSchema = z
121
162
  }),
122
163
  ]))
123
164
  .min(1);
165
+ const UPDATE_CLEAR_PAIRS = [
166
+ ['priority', 'clear-priority'],
167
+ ['order', 'clear-order'],
168
+ ['label', 'clear-labels'],
169
+ ['depends-on', 'clear-dependencies'],
170
+ ['file', 'clear-files'],
171
+ ['owner', 'clear-owner'],
172
+ ['assignee', 'clear-assignees'],
173
+ ['reviewer', 'clear-reviewers'],
174
+ ['team', 'clear-team'],
175
+ ['estimate', 'clear-estimate'],
176
+ ['effort', 'clear-effort'],
177
+ ['risk', 'clear-risk'],
178
+ ['due-date', 'clear-due-date'],
179
+ ['related', 'clear-related'],
180
+ ['duplicate', 'clear-duplicates'],
181
+ ['parent', 'clear-parent'],
182
+ ['directory', 'clear-directories'],
183
+ ['project', 'clear-projects'],
184
+ ];
185
+ const DocumentCreateValuesSchema = z.strictObject({
186
+ title: TrimmedStringSchema,
187
+ status: DocumentStatusSchema.optional(),
188
+ priority: TaskPrioritySchema.optional(),
189
+ order: z.coerce.number().finite().nonnegative().optional(),
190
+ label: StringListSchema.optional(),
191
+ 'depends-on': TaskIdListSchema.optional(),
192
+ file: StringListSchema.optional(),
193
+ owner: TrimmedStringSchema.optional(),
194
+ assignee: StringListSchema.optional(),
195
+ reviewer: StringListSchema.optional(),
196
+ team: TrimmedStringSchema.optional(),
197
+ estimate: z.coerce.number().int().nonnegative().optional(),
198
+ effort: z.coerce.number().finite().nonnegative().optional(),
199
+ risk: TaskRiskSchema.optional(),
200
+ 'due-date': DocumentTimestampSchema.optional(),
201
+ related: TaskIdListSchema.optional(),
202
+ duplicate: TaskIdListSchema.optional(),
203
+ parent: TaskIdSchema.optional(),
204
+ directory: StringListSchema.optional(),
205
+ project: StringListSchema.optional(),
206
+ body: z.string().optional(),
207
+ cwd: CwdSchema,
208
+ json: JsonSchema,
209
+ });
210
+ const DocumentUpdateValuesSchema = z
211
+ .strictObject({
212
+ title: TrimmedStringSchema.optional(),
213
+ status: DocumentStatusSchema.optional(),
214
+ priority: TaskPrioritySchema.optional(),
215
+ order: z.coerce.number().finite().nonnegative().optional(),
216
+ label: StringListSchema.optional(),
217
+ 'depends-on': TaskIdListSchema.optional(),
218
+ file: StringListSchema.optional(),
219
+ owner: TrimmedStringSchema.optional(),
220
+ assignee: StringListSchema.optional(),
221
+ reviewer: StringListSchema.optional(),
222
+ team: TrimmedStringSchema.optional(),
223
+ estimate: z.coerce.number().int().nonnegative().optional(),
224
+ effort: z.coerce.number().finite().nonnegative().optional(),
225
+ risk: TaskRiskSchema.optional(),
226
+ 'due-date': DocumentTimestampSchema.optional(),
227
+ related: TaskIdListSchema.optional(),
228
+ duplicate: TaskIdListSchema.optional(),
229
+ parent: TaskIdSchema.optional(),
230
+ directory: StringListSchema.optional(),
231
+ project: StringListSchema.optional(),
232
+ body: z.string().optional(),
233
+ type: TrimmedStringSchema.optional(),
234
+ 'clear-priority': z.boolean().optional(),
235
+ 'clear-order': z.boolean().optional(),
236
+ 'clear-labels': z.boolean().optional(),
237
+ 'clear-dependencies': z.boolean().optional(),
238
+ 'clear-files': z.boolean().optional(),
239
+ 'clear-owner': z.boolean().optional(),
240
+ 'clear-assignees': z.boolean().optional(),
241
+ 'clear-reviewers': z.boolean().optional(),
242
+ 'clear-team': z.boolean().optional(),
243
+ 'clear-estimate': z.boolean().optional(),
244
+ 'clear-effort': z.boolean().optional(),
245
+ 'clear-risk': z.boolean().optional(),
246
+ 'clear-due-date': z.boolean().optional(),
247
+ 'clear-related': z.boolean().optional(),
248
+ 'clear-duplicates': z.boolean().optional(),
249
+ 'clear-parent': z.boolean().optional(),
250
+ 'clear-directories': z.boolean().optional(),
251
+ 'clear-projects': z.boolean().optional(),
252
+ cwd: CwdSchema,
253
+ json: JsonSchema,
254
+ })
255
+ .superRefine((values, context) => {
256
+ for (const [valueKey, clearKey] of UPDATE_CLEAR_PAIRS) {
257
+ if (values[valueKey] !== undefined && values[clearKey]) {
258
+ context.addIssue({
259
+ code: 'custom',
260
+ path: [clearKey],
261
+ message: `Cannot set and clear ${valueKey} in the same update`,
262
+ });
263
+ }
264
+ }
265
+ const nonControlKeys = Object.keys(values).filter((key) => key !== 'cwd' && key !== 'json' && key !== 'type');
266
+ if (nonControlKeys.length === 0) {
267
+ context.addIssue({
268
+ code: 'custom',
269
+ message: 'Document update requires at least one field option',
270
+ });
271
+ }
272
+ });
273
+ const DocumentListValuesSchema = z.strictObject({
274
+ status: z.array(DocumentStatusSchema).optional(),
275
+ priority: z.array(TaskPrioritySchema).optional(),
276
+ label: StringListSchema.optional(),
277
+ owner: StringListSchema.optional(),
278
+ assignee: StringListSchema.optional(),
279
+ reviewer: StringListSchema.optional(),
280
+ team: StringListSchema.optional(),
281
+ risk: z.array(TaskRiskSchema).optional(),
282
+ project: StringListSchema.optional(),
283
+ 'depends-on': TaskIdSchema.optional(),
284
+ related: TaskIdSchema.optional(),
285
+ duplicate: TaskIdSchema.optional(),
286
+ parent: TaskIdSchema.optional(),
287
+ file: StringListSchema.optional(),
288
+ directory: StringListSchema.optional(),
289
+ 'estimate-min': z.coerce.number().finite().nonnegative().optional(),
290
+ 'estimate-max': z.coerce.number().finite().nonnegative().optional(),
291
+ 'effort-min': z.coerce.number().finite().nonnegative().optional(),
292
+ 'effort-max': z.coerce.number().finite().nonnegative().optional(),
293
+ 'due-before': DocumentTimestampSchema.optional(),
294
+ 'due-after': DocumentTimestampSchema.optional(),
295
+ 'created-before': DocumentTimestampSchema.optional(),
296
+ 'created-after': DocumentTimestampSchema.optional(),
297
+ 'updated-before': DocumentTimestampSchema.optional(),
298
+ 'updated-after': DocumentTimestampSchema.optional(),
299
+ search: z.string().optional(),
300
+ sort: z.enum(DOCUMENT_SORT_KEYS).optional(),
301
+ direction: z.enum(DOCUMENT_SORT_DIRECTIONS).optional(),
302
+ impact: z.boolean().optional(),
303
+ 'include-derived': z.boolean().optional(),
304
+ cwd: CwdSchema,
305
+ json: JsonSchema,
306
+ });
124
307
  const CreateValuesSchema = z.strictObject({
125
308
  title: TrimmedStringSchema,
126
309
  status: TaskStatusSchema.optional(),
@@ -146,26 +329,6 @@ const CreateValuesSchema = z.strictObject({
146
329
  cwd: CwdSchema,
147
330
  json: JsonSchema,
148
331
  });
149
- const UPDATE_CLEAR_PAIRS = [
150
- ['priority', 'clear-priority'],
151
- ['order', 'clear-order'],
152
- ['label', 'clear-labels'],
153
- ['depends-on', 'clear-dependencies'],
154
- ['file', 'clear-files'],
155
- ['owner', 'clear-owner'],
156
- ['assignee', 'clear-assignees'],
157
- ['reviewer', 'clear-reviewers'],
158
- ['team', 'clear-team'],
159
- ['estimate', 'clear-estimate'],
160
- ['effort', 'clear-effort'],
161
- ['risk', 'clear-risk'],
162
- ['due-date', 'clear-due-date'],
163
- ['related', 'clear-related'],
164
- ['duplicate', 'clear-duplicates'],
165
- ['parent', 'clear-parent'],
166
- ['directory', 'clear-directories'],
167
- ['project', 'clear-projects'],
168
- ];
169
332
  const UpdateValuesSchema = z
170
333
  .strictObject({
171
334
  title: TrimmedStringSchema.optional(),
@@ -328,6 +491,13 @@ function taskRecordJson(record, derived) {
328
491
  ...(derived ? { derived } : {}),
329
492
  };
330
493
  }
494
+ function documentRecordJson(record, derived) {
495
+ return {
496
+ relativePath: record.relativePath,
497
+ ...record.document.metadata,
498
+ ...(derived ? { derived } : {}),
499
+ };
500
+ }
331
501
  function writeTaskRecord(record, json, stdout) {
332
502
  if (json) {
333
503
  stdout(`${JSON.stringify(taskRecordJson(record), null, 2)}\n`);
@@ -336,6 +506,14 @@ function writeTaskRecord(record, json, stdout) {
336
506
  stdout(`${record.task.metadata.id}\n`);
337
507
  }
338
508
  }
509
+ function writeDocumentRecord(record, json, stdout) {
510
+ if (json) {
511
+ stdout(`${JSON.stringify(documentRecordJson(record), null, 2)}\n`);
512
+ }
513
+ else {
514
+ stdout(`${record.document.metadata.id}\n`);
515
+ }
516
+ }
339
517
  function warningWriter(stderr) {
340
518
  return (warning) => stderr(`warning: ${warning.message}\n`);
341
519
  }
@@ -439,6 +617,103 @@ function updateInputFromValues(repository, values) {
439
617
  ...(values.body !== undefined ? { body: values.body } : {}),
440
618
  };
441
619
  }
620
+ function documentUpdateInputFromValues(repository, values) {
621
+ return {
622
+ ...(values.title !== undefined ? { title: values.title } : {}),
623
+ ...(values.status !== undefined ? { status: values.status } : {}),
624
+ ...(values['clear-priority']
625
+ ? { priority: null }
626
+ : values.priority !== undefined
627
+ ? { priority: values.priority }
628
+ : {}),
629
+ ...(values['clear-order']
630
+ ? { order: null }
631
+ : values.order !== undefined
632
+ ? { order: values.order }
633
+ : {}),
634
+ ...(values['clear-labels']
635
+ ? { labels: [] }
636
+ : values.label !== undefined
637
+ ? { labels: values.label }
638
+ : {}),
639
+ ...(values['clear-dependencies']
640
+ ? { dependsOn: [] }
641
+ : values['depends-on'] !== undefined
642
+ ? { dependsOn: values['depends-on'] }
643
+ : {}),
644
+ ...(values['clear-files']
645
+ ? { files: [] }
646
+ : values.file !== undefined
647
+ ? { files: normalizeMutationPaths(repository, values.file) }
648
+ : {}),
649
+ ...(values['clear-owner']
650
+ ? { owner: null }
651
+ : values.owner !== undefined
652
+ ? { owner: values.owner }
653
+ : {}),
654
+ ...(values['clear-assignees']
655
+ ? { assignees: [] }
656
+ : values.assignee !== undefined
657
+ ? { assignees: values.assignee }
658
+ : {}),
659
+ ...(values['clear-reviewers']
660
+ ? { reviewers: [] }
661
+ : values.reviewer !== undefined
662
+ ? { reviewers: values.reviewer }
663
+ : {}),
664
+ ...(values['clear-team']
665
+ ? { team: null }
666
+ : values.team !== undefined
667
+ ? { team: values.team }
668
+ : {}),
669
+ ...(values['clear-estimate']
670
+ ? { estimate: null }
671
+ : values.estimate !== undefined
672
+ ? { estimate: values.estimate }
673
+ : {}),
674
+ ...(values['clear-effort']
675
+ ? { effort: null }
676
+ : values.effort !== undefined
677
+ ? { effort: values.effort }
678
+ : {}),
679
+ ...(values['clear-risk']
680
+ ? { risk: null }
681
+ : values.risk !== undefined
682
+ ? { risk: values.risk }
683
+ : {}),
684
+ ...(values['clear-due-date']
685
+ ? { dueDate: null }
686
+ : values['due-date'] !== undefined
687
+ ? { dueDate: values['due-date'] }
688
+ : {}),
689
+ ...(values['clear-related']
690
+ ? { related: [] }
691
+ : values.related !== undefined
692
+ ? { related: values.related }
693
+ : {}),
694
+ ...(values['clear-duplicates']
695
+ ? { duplicates: [] }
696
+ : values.duplicate !== undefined
697
+ ? { duplicates: values.duplicate }
698
+ : {}),
699
+ ...(values['clear-parent']
700
+ ? { parent: null }
701
+ : values.parent !== undefined
702
+ ? { parent: values.parent }
703
+ : {}),
704
+ ...(values['clear-directories']
705
+ ? { directories: [] }
706
+ : values.directory !== undefined
707
+ ? { directories: normalizeMutationPaths(repository, values.directory) }
708
+ : {}),
709
+ ...(values['clear-projects']
710
+ ? { projects: [] }
711
+ : values.project !== undefined
712
+ ? { projects: values.project }
713
+ : {}),
714
+ ...(values.body !== undefined ? { body: values.body } : {}),
715
+ };
716
+ }
442
717
  export async function runCli(args, context = {}) {
443
718
  const cwd = path.resolve(context.cwd ?? process.cwd());
444
719
  const stdout = context.stdout ?? ((value) => process.stdout.write(value));
@@ -453,6 +728,23 @@ export async function runCli(args, context = {}) {
453
728
  return 0;
454
729
  }
455
730
  if (command === 'init' || command === 'config' || command === 'doctor') {
731
+ if (command === 'init') {
732
+ const parsed = parseArgs({
733
+ args: commandArgs,
734
+ allowPositionals: false,
735
+ options: {
736
+ cwd: { type: 'string' },
737
+ config: { type: 'boolean' },
738
+ },
739
+ });
740
+ const values = parseSchema(InitValuesSchema, parsed.values, 'init options');
741
+ const commandCwd = resolveCommandCwd(cwd, values.cwd);
742
+ const repository = await initializeRepository(commandCwd, {
743
+ writeConfig: values.config === true,
744
+ });
745
+ stdout(`Initialized Taskset in ${repository.rootDirectory}\n`);
746
+ return 0;
747
+ }
456
748
  const parsed = parseArgs({
457
749
  args: commandArgs,
458
750
  allowPositionals: false,
@@ -460,24 +752,23 @@ export async function runCli(args, context = {}) {
460
752
  });
461
753
  const values = parseSchema(CommonValuesSchema, parsed.values, `${command} options`);
462
754
  const commandCwd = resolveCommandCwd(cwd, values.cwd);
463
- if (command === 'init') {
464
- const repository = await initializeRepository(commandCwd);
465
- stdout(`Initialized Taskset in ${repository.rootDirectory}\n`);
466
- return 0;
467
- }
468
755
  const repository = await discoverRepository(commandCwd);
469
756
  if (command === 'config') {
470
757
  if (values.json) {
471
758
  stdout(`${JSON.stringify({
472
759
  rootDirectory: repository.rootDirectory,
473
760
  configPath: repository.configPath,
761
+ hasConfig: repository.hasConfig,
474
762
  dataDirectory: repository.dataDirectory,
475
763
  config: repository.config,
476
764
  }, null, 2)}\n`);
477
765
  }
478
- else {
766
+ else if (repository.hasConfig) {
479
767
  stdout(`${repository.configPath}\n`);
480
768
  }
769
+ else {
770
+ stdout(`defaults (${repository.rootDirectory})\n`);
771
+ }
481
772
  return 0;
482
773
  }
483
774
  const result = await diagnoseRepository(repository);
@@ -522,7 +813,7 @@ export async function runCli(args, context = {}) {
522
813
  });
523
814
  stdout(values.json
524
815
  ? `${JSON.stringify(result, null, 2)}\n`
525
- : `Synced ${result.migrations.length} migrations and generated views\n`);
816
+ : `Synced ${result.migrations.length + result.documentMigrations.length} migrations and generated views\n`);
526
817
  return 0;
527
818
  }
528
819
  if (command === 'snapshot') {
@@ -605,35 +896,38 @@ export async function runCli(args, context = {}) {
605
896
  const parsed = parseArgs({
606
897
  args: subcommandArgs,
607
898
  allowPositionals: true,
608
- options: {
609
- ...commonOptionDefinitions,
610
- title: { type: 'string' },
611
- status: { type: 'string' },
612
- label: { type: 'string', multiple: true },
613
- related: { type: 'string', multiple: true },
614
- body: { type: 'string' },
615
- },
899
+ options: { ...metadataOptionDefinitions, ...commonOptionDefinitions },
616
900
  });
617
901
  const [rawType] = requirePositionals(parsed.positionals, 1, 'exactly one document type');
618
- const values = parseSchema(z.strictObject({
619
- title: TrimmedStringSchema,
620
- status: DocumentStatusSchema.optional(),
621
- label: StringListSchema.optional(),
622
- related: StringListSchema.optional(),
623
- body: z.string().optional(),
624
- cwd: CwdSchema,
625
- json: JsonSchema,
626
- }), parsed.values, 'document create options');
902
+ const values = parseSchema(DocumentCreateValuesSchema, parsed.values, 'document create options');
627
903
  const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
628
904
  const record = await createDocument(repository, {
629
905
  type: normalizeDocumentKind(parseSchema(z.string(), rawType, 'document type')),
630
906
  title: values.title,
631
907
  ...(values.status ? { status: values.status } : {}),
908
+ ...(values.priority ? { priority: values.priority } : {}),
909
+ ...(values.order !== undefined ? { order: values.order } : {}),
632
910
  ...(values.label ? { labels: values.label } : {}),
911
+ ...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
912
+ ...(values.file ? { files: normalizeMutationPaths(repository, values.file) } : {}),
913
+ ...(values.owner ? { owner: values.owner } : {}),
914
+ ...(values.assignee ? { assignees: values.assignee } : {}),
915
+ ...(values.reviewer ? { reviewers: values.reviewer } : {}),
916
+ ...(values.team ? { team: values.team } : {}),
917
+ ...(values.estimate !== undefined ? { estimate: values.estimate } : {}),
918
+ ...(values.effort !== undefined ? { effort: values.effort } : {}),
919
+ ...(values.risk ? { risk: values.risk } : {}),
920
+ ...(values['due-date'] ? { dueDate: values['due-date'] } : {}),
633
921
  ...(values.related ? { related: values.related } : {}),
922
+ ...(values.duplicate ? { duplicates: values.duplicate } : {}),
923
+ ...(values.parent ? { parent: values.parent } : {}),
924
+ ...(values.directory
925
+ ? { directories: normalizeMutationPaths(repository, values.directory) }
926
+ : {}),
927
+ ...(values.project ? { projects: values.project } : {}),
634
928
  ...(values.body !== undefined ? { body: values.body } : {}),
635
- });
636
- stdout(values.json ? `${JSON.stringify(record, null, 2)}\n` : `${record.document.metadata.id}\n`);
929
+ }, { onWarning });
930
+ writeDocumentRecord(record, values.json, stdout);
637
931
  return 0;
638
932
  }
639
933
  if (subcommand === 'import') {
@@ -668,33 +962,206 @@ export async function runCli(args, context = {}) {
668
962
  const parsed = parseArgs({
669
963
  args: subcommandArgs,
670
964
  allowPositionals: true,
671
- options: commonOptionDefinitions,
965
+ options: {
966
+ cwd: { type: 'string' },
967
+ json: { type: 'boolean' },
968
+ status: { type: 'string', multiple: true },
969
+ priority: { type: 'string', multiple: true },
970
+ label: { type: 'string', multiple: true },
971
+ owner: { type: 'string', multiple: true },
972
+ assignee: { type: 'string', multiple: true },
973
+ reviewer: { type: 'string', multiple: true },
974
+ team: { type: 'string', multiple: true },
975
+ risk: { type: 'string', multiple: true },
976
+ project: { type: 'string', multiple: true },
977
+ 'depends-on': { type: 'string' },
978
+ related: { type: 'string' },
979
+ duplicate: { type: 'string' },
980
+ parent: { type: 'string' },
981
+ file: { type: 'string', multiple: true },
982
+ directory: { type: 'string', multiple: true },
983
+ 'estimate-min': { type: 'string' },
984
+ 'estimate-max': { type: 'string' },
985
+ 'effort-min': { type: 'string' },
986
+ 'effort-max': { type: 'string' },
987
+ 'due-before': { type: 'string' },
988
+ 'due-after': { type: 'string' },
989
+ 'created-before': { type: 'string' },
990
+ 'created-after': { type: 'string' },
991
+ 'updated-before': { type: 'string' },
992
+ 'updated-after': { type: 'string' },
993
+ search: { type: 'string' },
994
+ sort: { type: 'string' },
995
+ direction: { type: 'string' },
996
+ impact: { type: 'boolean' },
997
+ 'include-derived': { type: 'boolean' },
998
+ },
672
999
  });
673
1000
  if (parsed.positionals.length > 1)
674
1001
  throw new CliUsageError('Expected at most one document type');
675
- const values = parseSchema(CommonValuesSchema, parsed.values, 'document list options');
1002
+ const values = parseSchema(DocumentListValuesSchema, parsed.values, 'document list options');
1003
+ const typeFilter = parsed.positionals[0]
1004
+ ? normalizeDocumentKind(parsed.positionals[0])
1005
+ : undefined;
1006
+ const query = parseSchema(DocumentQuerySchema, {
1007
+ ...(typeFilter ? { types: [typeFilter] } : {}),
1008
+ ...(values.status ? { statuses: values.status } : {}),
1009
+ ...(values.priority ? { priorities: values.priority } : {}),
1010
+ ...(values.label ? { labels: values.label } : {}),
1011
+ ...(values.owner ? { owners: values.owner } : {}),
1012
+ ...(values.assignee ? { assignees: values.assignee } : {}),
1013
+ ...(values.reviewer ? { reviewers: values.reviewer } : {}),
1014
+ ...(values.team ? { teams: values.team } : {}),
1015
+ ...(values.risk ? { risks: values.risk } : {}),
1016
+ ...(values.project ? { projects: values.project } : {}),
1017
+ ...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
1018
+ ...(values.related ? { related: values.related } : {}),
1019
+ ...(values.duplicate ? { duplicate: values.duplicate } : {}),
1020
+ ...(values.parent ? { parent: values.parent } : {}),
1021
+ ...(values.file ? { files: values.file } : {}),
1022
+ ...(values.directory ? { directories: values.directory } : {}),
1023
+ ...(values['estimate-min'] !== undefined
1024
+ ? { estimateMin: values['estimate-min'] }
1025
+ : {}),
1026
+ ...(values['estimate-max'] !== undefined
1027
+ ? { estimateMax: values['estimate-max'] }
1028
+ : {}),
1029
+ ...(values['effort-min'] !== undefined ? { effortMin: values['effort-min'] } : {}),
1030
+ ...(values['effort-max'] !== undefined ? { effortMax: values['effort-max'] } : {}),
1031
+ ...(values['due-before'] ? { dueBefore: values['due-before'] } : {}),
1032
+ ...(values['due-after'] ? { dueAfter: values['due-after'] } : {}),
1033
+ ...(values['created-before'] ? { createdBefore: values['created-before'] } : {}),
1034
+ ...(values['created-after'] ? { createdAfter: values['created-after'] } : {}),
1035
+ ...(values['updated-before'] ? { updatedBefore: values['updated-before'] } : {}),
1036
+ ...(values['updated-after'] ? { updatedAfter: values['updated-after'] } : {}),
1037
+ ...(values.search !== undefined ? { text: values.search } : {}),
1038
+ ...(values.sort ? { sortBy: values.sort } : {}),
1039
+ ...(values.direction ? { direction: values.direction } : {}),
1040
+ ...(values.impact ? { impact: true } : {}),
1041
+ }, 'document list query');
676
1042
  const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
677
- const records = await listDocuments(repository, parsed.positionals[0] ? normalizeDocumentKind(parsed.positionals[0]) : undefined);
678
- if (values.json)
679
- stdout(`${JSON.stringify(records, null, 2)}\n`);
680
- else
681
- for (const record of records)
682
- stdout(`${record.document.metadata.id}\t${record.document.metadata.type}\t${record.document.metadata.status}\t${record.document.metadata.title}\n`);
1043
+ const result = await queryDocuments(repository, query);
1044
+ const derivedGraph = values['include-derived']
1045
+ ? buildDocumentGraph(await listDocuments(repository))
1046
+ : undefined;
1047
+ const serialize = (record) => documentRecordJson(record, derivedGraph?.derive(record.document.metadata.id));
1048
+ if (values.json) {
1049
+ stdout(`${JSON.stringify(values.impact
1050
+ ? {
1051
+ direct: result.direct.map(serialize),
1052
+ impacted: result.impacted.map(serialize),
1053
+ }
1054
+ : result.direct.map(serialize), null, 2)}\n`);
1055
+ }
1056
+ else {
1057
+ for (const record of result.direct) {
1058
+ stdout(`${values.impact ? 'direct\t' : ''}${record.document.metadata.id}\t${record.document.metadata.type}\t${record.document.metadata.status}\t${record.document.metadata.title}\n`);
1059
+ }
1060
+ for (const record of result.impacted) {
1061
+ stdout(`impact\t${record.document.metadata.id}\t${record.document.metadata.type}\t${record.document.metadata.status}\t${record.document.metadata.title}\n`);
1062
+ }
1063
+ }
683
1064
  return 0;
684
1065
  }
685
1066
  if (subcommand === 'show') {
686
1067
  const parsed = parseArgs({
687
1068
  args: subcommandArgs,
688
1069
  allowPositionals: true,
689
- options: { ...commonOptionDefinitions, type: { type: 'string' } },
1070
+ options: {
1071
+ ...commonOptionDefinitions,
1072
+ type: { type: 'string' },
1073
+ 'include-derived': { type: 'boolean' },
1074
+ },
690
1075
  });
691
1076
  const [id] = requirePositionals(parsed.positionals, 1, 'exactly one document ID');
692
- const values = parseSchema(z.strictObject({ ...CommonValuesSchema.shape, type: TrimmedStringSchema.optional() }), parsed.values, 'document show options');
1077
+ const values = parseSchema(z.strictObject({
1078
+ ...CommonValuesSchema.shape,
1079
+ type: TrimmedStringSchema.optional(),
1080
+ 'include-derived': z.boolean().optional(),
1081
+ }), parsed.values, 'document show options');
693
1082
  const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
694
- const record = await readDocument(repository, parseSchema(z.string(), id, 'document ID'), values.type ? normalizeDocumentKind(values.type) : undefined);
695
- stdout(values.json
696
- ? `${JSON.stringify(record, null, 2)}\n`
697
- : serializeDocumentFile(record.document));
1083
+ const validatedId = parseSchema(DocumentIdSchema, id, 'document ID');
1084
+ const record = await readDocument(repository, validatedId, values.type ? normalizeDocumentKind(values.type) : undefined);
1085
+ if (values.json) {
1086
+ const derived = values['include-derived']
1087
+ ? buildDocumentGraph(await listDocuments(repository)).derive(validatedId)
1088
+ : undefined;
1089
+ stdout(`${JSON.stringify({
1090
+ relativePath: record.relativePath,
1091
+ metadata: record.document.metadata,
1092
+ body: record.document.body,
1093
+ ...(derived ? { derived } : {}),
1094
+ }, null, 2)}\n`);
1095
+ }
1096
+ else {
1097
+ stdout(serializeDocumentFile(record.document));
1098
+ }
1099
+ return 0;
1100
+ }
1101
+ if (subcommand === 'update') {
1102
+ const clearDefinitions = Object.fromEntries(UPDATE_CLEAR_PAIRS.map(([, clear]) => [clear, { type: 'boolean' }]));
1103
+ const parsed = parseArgs({
1104
+ args: subcommandArgs,
1105
+ allowPositionals: true,
1106
+ options: {
1107
+ ...metadataOptionDefinitions,
1108
+ ...clearDefinitions,
1109
+ ...commonOptionDefinitions,
1110
+ type: { type: 'string' },
1111
+ },
1112
+ });
1113
+ const [documentId] = requirePositionals(parsed.positionals, 1, 'exactly one document ID');
1114
+ const validatedDocumentId = parseSchema(DocumentIdSchema, documentId, 'document ID');
1115
+ const values = parseSchema(DocumentUpdateValuesSchema, parsed.values, 'document update options');
1116
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1117
+ const record = await updateDocument(repository, validatedDocumentId, documentUpdateInputFromValues(repository, values), values.type ? normalizeDocumentKind(values.type) : undefined, { onWarning });
1118
+ writeDocumentRecord(record, values.json, stdout);
1119
+ return 0;
1120
+ }
1121
+ if (subcommand === 'status') {
1122
+ const parsed = parseArgs({
1123
+ args: subcommandArgs,
1124
+ allowPositionals: true,
1125
+ options: { ...commonOptionDefinitions, type: { type: 'string' } },
1126
+ });
1127
+ const [documentId, status] = requirePositionals(parsed.positionals, 2, 'a document ID and status');
1128
+ const validatedDocumentId = parseSchema(DocumentIdSchema, documentId, 'document ID');
1129
+ const validatedStatus = parseSchema(DocumentStatusSchema, status, 'document status');
1130
+ const values = parseSchema(z.strictObject({ ...CommonValuesSchema.shape, type: TrimmedStringSchema.optional() }), parsed.values, 'document status options');
1131
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1132
+ const record = await updateDocument(repository, validatedDocumentId, { status: validatedStatus }, values.type ? normalizeDocumentKind(values.type) : undefined, { onWarning });
1133
+ writeDocumentRecord(record, values.json, stdout);
1134
+ return 0;
1135
+ }
1136
+ if (subcommand === 'delete') {
1137
+ const parsed = parseArgs({
1138
+ args: subcommandArgs,
1139
+ allowPositionals: true,
1140
+ options: {
1141
+ ...commonOptionDefinitions,
1142
+ type: { type: 'string' },
1143
+ 'remove-dependencies': { type: 'boolean' },
1144
+ },
1145
+ });
1146
+ const [documentId] = requirePositionals(parsed.positionals, 1, 'exactly one document ID');
1147
+ const validatedDocumentId = parseSchema(DocumentIdSchema, documentId, 'document ID');
1148
+ const values = parseSchema(z.strictObject({
1149
+ cwd: CwdSchema,
1150
+ json: JsonSchema,
1151
+ type: TrimmedStringSchema.optional(),
1152
+ 'remove-dependencies': z.boolean().optional(),
1153
+ }), parsed.values, 'document delete options');
1154
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1155
+ const record = await deleteDocument(repository, validatedDocumentId, values.type ? normalizeDocumentKind(values.type) : undefined, {
1156
+ removeDependencies: values['remove-dependencies'],
1157
+ onWarning,
1158
+ });
1159
+ if (values.json) {
1160
+ stdout(`${JSON.stringify({ deleted: true, ...documentRecordJson(record) }, null, 2)}\n`);
1161
+ }
1162
+ else {
1163
+ stdout(`${validatedDocumentId}\n`);
1164
+ }
698
1165
  return 0;
699
1166
  }
700
1167
  throw new CliUsageError(`Unknown document command "${subcommand ?? ''}"`);