@taskset/cli 3.0.3 → 5.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +8 -3
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +686 -19
  5. package/docs/_meta.ts +8 -0
  6. package/docs/cli-reference.md +461 -0
  7. package/docs/configuration.md +61 -0
  8. package/docs/document-types.md +100 -0
  9. package/docs/getting-started.md +99 -0
  10. package/docs/index.md +37 -0
  11. package/docs/maintainers/_meta.ts +7 -0
  12. package/docs/maintainers/architecture/_meta.ts +5 -0
  13. package/docs/maintainers/architecture/decisions/0001-documentation-platform.md +70 -0
  14. package/docs/maintainers/architecture/decisions/0002-code-architecture.md +31 -0
  15. package/docs/maintainers/architecture/decisions/0003-snapshot-policy.md +31 -0
  16. package/docs/maintainers/architecture/decisions/_meta.ts +5 -0
  17. package/docs/maintainers/architecture/overview.md +111 -0
  18. package/docs/maintainers/architecture/synchronization.md +56 -0
  19. package/docs/maintainers/development/_meta.ts +6 -0
  20. package/docs/maintainers/development/contributing.md +59 -0
  21. package/docs/maintainers/development/documentation.md +69 -0
  22. package/docs/maintainers/development/engineering.md +59 -0
  23. package/docs/maintainers/development/testing.md +89 -0
  24. package/docs/maintainers/index.md +22 -0
  25. package/docs/maintainers/product/_meta.ts +3 -0
  26. package/docs/maintainers/product/vision.md +76 -0
  27. package/docs/maintainers/technology.md +55 -0
  28. package/docs/task-files.md +174 -0
  29. package/package.json +10 -8
  30. package/skills/taskset/SKILL.md +227 -0
  31. package/skills/taskset/references/changesets-examples.md +97 -0
  32. package/skills/taskset/references/document-modeling-examples.md +63 -0
  33. package/skills/taskset/references/monorepo-task-modeling.md +125 -0
  34. package/skills/taskset/references/task-modeling-examples.md +249 -0
  35. package/skills/taskset-implement/SKILL.md +234 -0
  36. package/skills/taskset-implement/agents/openai.yaml +4 -0
  37. package/skills/taskset-implement/references/architecture/client-and-server.md +69 -0
  38. package/skills/taskset-implement/references/architecture/documentation-and-generated.md +70 -0
  39. package/skills/taskset-implement/references/architecture/ownership-and-dependencies.md +107 -0
  40. package/skills/taskset-implement/references/architecture/product-and-source.md +80 -0
  41. package/skills/taskset-implement/references/architecture/storage-and-snapshots.md +45 -0
  42. package/skills/taskset-implement/references/architecture.md +34 -0
  43. package/skills/taskset-implement/references/conventions/backend-and-tooling.md +33 -0
  44. package/skills/taskset-implement/references/conventions/design.md +38 -0
  45. package/skills/taskset-implement/references/conventions/interfaces-and-ui.md +50 -0
  46. package/skills/taskset-implement/references/conventions/naming-and-packages.md +64 -0
  47. package/skills/taskset-implement/references/conventions/task-files.md +89 -0
  48. package/skills/taskset-implement/references/conventions/tests-and-docs.md +55 -0
  49. package/skills/taskset-implement/references/conventions/typescript-and-exports.md +41 -0
  50. package/skills/taskset-implement/references/conventions.md +40 -0
  51. package/skills/taskset-implement/references/release.md +135 -0
  52. package/skills/taskset-implement/references/workflows/dependencies-and-docs-site.md +54 -0
  53. package/skills/taskset-implement/references/workflows/environment-and-pnpm.md +108 -0
  54. package/skills/taskset-implement/references/workflows/persisted-data-and-git.md +30 -0
  55. package/skills/taskset-implement/references/workflows/validation.md +34 -0
  56. package/skills/taskset-implement/references/workflows/vitest-and-test-strategy.md +82 -0
  57. package/skills/taskset-implement/references/workflows.md +33 -0
  58. package/src/cli.ts +851 -17
package/dist/cli.js CHANGED
@@ -1,7 +1,8 @@
1
+ import { readFile } from 'node:fs/promises';
1
2
  import path from 'node:path';
2
3
  import { parseArgs } from 'node:util';
3
- import { TaskIdSchema, TaskPrioritySchema, TaskRiskSchema, TaskStatusSchema, TaskTimestampSchema, } from '@taskset/contracts';
4
- import { buildTaskIndex, createSnapshot, createTask, deleteTask, diagnoseRepository, discoverRepository, generateViews, initializeRepository, listSnapshots, normalizeRepositoryPath, queryTasks, RepositoryPathError, readTask, restoreSnapshot, serializeTaskFile, 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';
5
6
  import * as z from 'zod';
6
7
  const USAGE = `Usage:
7
8
  taskset init [--cwd <path>]
@@ -16,12 +17,22 @@ const USAGE = `Usage:
16
17
  taskset task update <task-id> [metadata options]
17
18
  taskset task status <task-id> <status> [--json] [--cwd <path>]
18
19
  taskset task delete <task-id> [--remove-dependencies] [--json] [--cwd <path>]
20
+ taskset task migrate-ids [--json] [--cwd <path>]
21
+ taskset sync [--concurrency <count>] [--json] [--cwd <path>]
22
+ taskset document create <story|flow|decision|adr|dr|research|runbook> --title <title> [metadata options]
23
+ taskset document import <markdown-path> [--type <type>] [--move]
24
+ taskset document batch <manifest.json> [--concurrency <count>] [--json]
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]
19
30
 
20
31
  Metadata options:
21
32
  --status --priority --order --owner --team --estimate --effort --risk --due-date
22
33
  --label --assignee --reviewer --depends-on --related --duplicate
23
34
  --parent --file --directory --project --body
24
- Repeat array options. Task updates clear arrays with --clear-dependencies,
35
+ Repeat array options. Task and document updates clear arrays with --clear-dependencies,
25
36
  --clear-labels, --clear-assignees, --clear-reviewers, --clear-related,
26
37
  --clear-files, --clear-directories, or --clear-projects. Clear scalar
27
38
  relationships with --clear-parent or --clear-owner.
@@ -56,31 +67,97 @@ const CommonValuesSchema = z.strictObject({
56
67
  cwd: CwdSchema,
57
68
  json: JsonSchema,
58
69
  });
59
- const CreateValuesSchema = z.strictObject({
70
+ const ConcurrencySchema = z.coerce.number().int().min(1).max(32).optional();
71
+ const DocumentKindInputSchema = TrimmedStringSchema.transform((value, context) => {
72
+ try {
73
+ return normalizeDocumentKind(value);
74
+ }
75
+ catch (error) {
76
+ context.addIssue({
77
+ code: 'custom',
78
+ message: error instanceof Error ? error.message : 'Invalid document type',
79
+ });
80
+ return z.NEVER;
81
+ }
82
+ });
83
+ const DocumentInputSchema = z.strictObject({
84
+ type: DocumentKindInputSchema,
60
85
  title: TrimmedStringSchema,
61
- status: TaskStatusSchema.optional(),
86
+ status: DocumentStatusSchema.optional(),
62
87
  priority: TaskPrioritySchema.optional(),
63
- order: z.coerce.number().finite().nonnegative().optional(),
64
- label: StringListSchema.optional(),
65
- 'depends-on': TaskIdListSchema.optional(),
66
- file: StringListSchema.optional(),
88
+ order: z.number().finite().nonnegative().optional(),
89
+ labels: StringListSchema.optional(),
90
+ dependsOn: TaskIdListSchema.optional(),
91
+ files: StringListSchema.optional(),
67
92
  owner: TrimmedStringSchema.optional(),
68
- assignee: StringListSchema.optional(),
69
- reviewer: StringListSchema.optional(),
93
+ assignees: StringListSchema.optional(),
94
+ reviewers: StringListSchema.optional(),
70
95
  team: TrimmedStringSchema.optional(),
71
- estimate: z.coerce.number().int().nonnegative().optional(),
72
- effort: z.coerce.number().finite().nonnegative().optional(),
96
+ estimate: z.number().int().nonnegative().optional(),
97
+ effort: z.number().finite().nonnegative().optional(),
73
98
  risk: TaskRiskSchema.optional(),
74
- 'due-date': TaskTimestampSchema.optional(),
99
+ dueDate: DocumentTimestampSchema.optional(),
75
100
  related: TaskIdListSchema.optional(),
76
- duplicate: TaskIdListSchema.optional(),
101
+ duplicates: TaskIdListSchema.optional(),
77
102
  parent: TaskIdSchema.optional(),
78
- directory: StringListSchema.optional(),
79
- project: StringListSchema.optional(),
103
+ directories: StringListSchema.optional(),
104
+ projects: StringListSchema.optional(),
80
105
  body: z.string().optional(),
81
- cwd: CwdSchema,
82
- json: JsonSchema,
83
106
  });
107
+ const DocumentUpdateSchema = z
108
+ .strictObject({
109
+ title: TrimmedStringSchema.optional(),
110
+ status: DocumentStatusSchema.optional(),
111
+ priority: TaskPrioritySchema.nullable().optional(),
112
+ order: z.number().finite().nonnegative().nullable().optional(),
113
+ labels: StringListSchema.optional(),
114
+ dependsOn: TaskIdListSchema.optional(),
115
+ files: StringListSchema.optional(),
116
+ owner: TrimmedStringSchema.nullable().optional(),
117
+ assignees: StringListSchema.optional(),
118
+ reviewers: StringListSchema.optional(),
119
+ team: TrimmedStringSchema.nullable().optional(),
120
+ estimate: z.number().int().nonnegative().nullable().optional(),
121
+ effort: z.number().finite().nonnegative().nullable().optional(),
122
+ risk: TaskRiskSchema.nullable().optional(),
123
+ dueDate: DocumentTimestampSchema.nullable().optional(),
124
+ related: TaskIdListSchema.optional(),
125
+ duplicates: TaskIdListSchema.optional(),
126
+ parent: TaskIdSchema.nullable().optional(),
127
+ directories: StringListSchema.optional(),
128
+ projects: StringListSchema.optional(),
129
+ body: z.string().optional(),
130
+ })
131
+ .refine((input) => Object.keys(input).length > 0, 'Document update requires at least one field');
132
+ const DocumentBatchSchema = z
133
+ .array(z.discriminatedUnion('action', [
134
+ z.strictObject({ action: z.literal('create'), input: DocumentInputSchema }),
135
+ z.strictObject({
136
+ action: z.literal('import'),
137
+ sourcePath: TrimmedStringSchema,
138
+ options: z
139
+ .strictObject({
140
+ type: DocumentKindInputSchema.optional(),
141
+ title: TrimmedStringSchema.optional(),
142
+ move: z.boolean().optional(),
143
+ })
144
+ .optional(),
145
+ }),
146
+ z.strictObject({
147
+ action: z.literal('update'),
148
+ id: TrimmedStringSchema,
149
+ type: DocumentKindInputSchema.optional(),
150
+ input: DocumentUpdateSchema,
151
+ }),
152
+ z.strictObject({
153
+ action: z.literal('export'),
154
+ id: TrimmedStringSchema,
155
+ type: DocumentKindInputSchema.optional(),
156
+ targetPath: TrimmedStringSchema,
157
+ overwrite: z.boolean().optional(),
158
+ }),
159
+ ]))
160
+ .min(1);
84
161
  const UPDATE_CLEAR_PAIRS = [
85
162
  ['priority', 'clear-priority'],
86
163
  ['order', 'clear-order'],
@@ -101,6 +178,153 @@ const UPDATE_CLEAR_PAIRS = [
101
178
  ['directory', 'clear-directories'],
102
179
  ['project', 'clear-projects'],
103
180
  ];
181
+ const DocumentCreateValuesSchema = z.strictObject({
182
+ title: TrimmedStringSchema,
183
+ status: DocumentStatusSchema.optional(),
184
+ priority: TaskPrioritySchema.optional(),
185
+ order: z.coerce.number().finite().nonnegative().optional(),
186
+ label: StringListSchema.optional(),
187
+ 'depends-on': TaskIdListSchema.optional(),
188
+ file: StringListSchema.optional(),
189
+ owner: TrimmedStringSchema.optional(),
190
+ assignee: StringListSchema.optional(),
191
+ reviewer: StringListSchema.optional(),
192
+ team: TrimmedStringSchema.optional(),
193
+ estimate: z.coerce.number().int().nonnegative().optional(),
194
+ effort: z.coerce.number().finite().nonnegative().optional(),
195
+ risk: TaskRiskSchema.optional(),
196
+ 'due-date': DocumentTimestampSchema.optional(),
197
+ related: TaskIdListSchema.optional(),
198
+ duplicate: TaskIdListSchema.optional(),
199
+ parent: TaskIdSchema.optional(),
200
+ directory: StringListSchema.optional(),
201
+ project: StringListSchema.optional(),
202
+ body: z.string().optional(),
203
+ cwd: CwdSchema,
204
+ json: JsonSchema,
205
+ });
206
+ const DocumentUpdateValuesSchema = z
207
+ .strictObject({
208
+ title: TrimmedStringSchema.optional(),
209
+ status: DocumentStatusSchema.optional(),
210
+ priority: TaskPrioritySchema.optional(),
211
+ order: z.coerce.number().finite().nonnegative().optional(),
212
+ label: StringListSchema.optional(),
213
+ 'depends-on': TaskIdListSchema.optional(),
214
+ file: StringListSchema.optional(),
215
+ owner: TrimmedStringSchema.optional(),
216
+ assignee: StringListSchema.optional(),
217
+ reviewer: StringListSchema.optional(),
218
+ team: TrimmedStringSchema.optional(),
219
+ estimate: z.coerce.number().int().nonnegative().optional(),
220
+ effort: z.coerce.number().finite().nonnegative().optional(),
221
+ risk: TaskRiskSchema.optional(),
222
+ 'due-date': DocumentTimestampSchema.optional(),
223
+ related: TaskIdListSchema.optional(),
224
+ duplicate: TaskIdListSchema.optional(),
225
+ parent: TaskIdSchema.optional(),
226
+ directory: StringListSchema.optional(),
227
+ project: StringListSchema.optional(),
228
+ body: z.string().optional(),
229
+ type: TrimmedStringSchema.optional(),
230
+ 'clear-priority': z.boolean().optional(),
231
+ 'clear-order': z.boolean().optional(),
232
+ 'clear-labels': z.boolean().optional(),
233
+ 'clear-dependencies': z.boolean().optional(),
234
+ 'clear-files': z.boolean().optional(),
235
+ 'clear-owner': z.boolean().optional(),
236
+ 'clear-assignees': z.boolean().optional(),
237
+ 'clear-reviewers': z.boolean().optional(),
238
+ 'clear-team': z.boolean().optional(),
239
+ 'clear-estimate': z.boolean().optional(),
240
+ 'clear-effort': z.boolean().optional(),
241
+ 'clear-risk': z.boolean().optional(),
242
+ 'clear-due-date': z.boolean().optional(),
243
+ 'clear-related': z.boolean().optional(),
244
+ 'clear-duplicates': z.boolean().optional(),
245
+ 'clear-parent': z.boolean().optional(),
246
+ 'clear-directories': z.boolean().optional(),
247
+ 'clear-projects': z.boolean().optional(),
248
+ cwd: CwdSchema,
249
+ json: JsonSchema,
250
+ })
251
+ .superRefine((values, context) => {
252
+ for (const [valueKey, clearKey] of UPDATE_CLEAR_PAIRS) {
253
+ if (values[valueKey] !== undefined && values[clearKey]) {
254
+ context.addIssue({
255
+ code: 'custom',
256
+ path: [clearKey],
257
+ message: `Cannot set and clear ${valueKey} in the same update`,
258
+ });
259
+ }
260
+ }
261
+ const nonControlKeys = Object.keys(values).filter((key) => key !== 'cwd' && key !== 'json' && key !== 'type');
262
+ if (nonControlKeys.length === 0) {
263
+ context.addIssue({
264
+ code: 'custom',
265
+ message: 'Document update requires at least one field option',
266
+ });
267
+ }
268
+ });
269
+ const DocumentListValuesSchema = z.strictObject({
270
+ status: z.array(DocumentStatusSchema).optional(),
271
+ priority: z.array(TaskPrioritySchema).optional(),
272
+ label: StringListSchema.optional(),
273
+ owner: StringListSchema.optional(),
274
+ assignee: StringListSchema.optional(),
275
+ reviewer: StringListSchema.optional(),
276
+ team: StringListSchema.optional(),
277
+ risk: z.array(TaskRiskSchema).optional(),
278
+ project: StringListSchema.optional(),
279
+ 'depends-on': TaskIdSchema.optional(),
280
+ related: TaskIdSchema.optional(),
281
+ duplicate: TaskIdSchema.optional(),
282
+ parent: TaskIdSchema.optional(),
283
+ file: StringListSchema.optional(),
284
+ directory: StringListSchema.optional(),
285
+ 'estimate-min': z.coerce.number().finite().nonnegative().optional(),
286
+ 'estimate-max': z.coerce.number().finite().nonnegative().optional(),
287
+ 'effort-min': z.coerce.number().finite().nonnegative().optional(),
288
+ 'effort-max': z.coerce.number().finite().nonnegative().optional(),
289
+ 'due-before': DocumentTimestampSchema.optional(),
290
+ 'due-after': DocumentTimestampSchema.optional(),
291
+ 'created-before': DocumentTimestampSchema.optional(),
292
+ 'created-after': DocumentTimestampSchema.optional(),
293
+ 'updated-before': DocumentTimestampSchema.optional(),
294
+ 'updated-after': DocumentTimestampSchema.optional(),
295
+ search: z.string().optional(),
296
+ sort: z.enum(DOCUMENT_SORT_KEYS).optional(),
297
+ direction: z.enum(DOCUMENT_SORT_DIRECTIONS).optional(),
298
+ impact: z.boolean().optional(),
299
+ 'include-derived': z.boolean().optional(),
300
+ cwd: CwdSchema,
301
+ json: JsonSchema,
302
+ });
303
+ const CreateValuesSchema = z.strictObject({
304
+ title: TrimmedStringSchema,
305
+ status: TaskStatusSchema.optional(),
306
+ priority: TaskPrioritySchema.optional(),
307
+ order: z.coerce.number().finite().nonnegative().optional(),
308
+ label: StringListSchema.optional(),
309
+ 'depends-on': TaskIdListSchema.optional(),
310
+ file: StringListSchema.optional(),
311
+ owner: TrimmedStringSchema.optional(),
312
+ assignee: StringListSchema.optional(),
313
+ reviewer: StringListSchema.optional(),
314
+ team: TrimmedStringSchema.optional(),
315
+ estimate: z.coerce.number().int().nonnegative().optional(),
316
+ effort: z.coerce.number().finite().nonnegative().optional(),
317
+ risk: TaskRiskSchema.optional(),
318
+ 'due-date': TaskTimestampSchema.optional(),
319
+ related: TaskIdListSchema.optional(),
320
+ duplicate: TaskIdListSchema.optional(),
321
+ parent: TaskIdSchema.optional(),
322
+ directory: StringListSchema.optional(),
323
+ project: StringListSchema.optional(),
324
+ body: z.string().optional(),
325
+ cwd: CwdSchema,
326
+ json: JsonSchema,
327
+ });
104
328
  const UpdateValuesSchema = z
105
329
  .strictObject({
106
330
  title: TrimmedStringSchema.optional(),
@@ -263,6 +487,13 @@ function taskRecordJson(record, derived) {
263
487
  ...(derived ? { derived } : {}),
264
488
  };
265
489
  }
490
+ function documentRecordJson(record, derived) {
491
+ return {
492
+ relativePath: record.relativePath,
493
+ ...record.document.metadata,
494
+ ...(derived ? { derived } : {}),
495
+ };
496
+ }
266
497
  function writeTaskRecord(record, json, stdout) {
267
498
  if (json) {
268
499
  stdout(`${JSON.stringify(taskRecordJson(record), null, 2)}\n`);
@@ -271,6 +502,14 @@ function writeTaskRecord(record, json, stdout) {
271
502
  stdout(`${record.task.metadata.id}\n`);
272
503
  }
273
504
  }
505
+ function writeDocumentRecord(record, json, stdout) {
506
+ if (json) {
507
+ stdout(`${JSON.stringify(documentRecordJson(record), null, 2)}\n`);
508
+ }
509
+ else {
510
+ stdout(`${record.document.metadata.id}\n`);
511
+ }
512
+ }
274
513
  function warningWriter(stderr) {
275
514
  return (warning) => stderr(`warning: ${warning.message}\n`);
276
515
  }
@@ -374,6 +613,103 @@ function updateInputFromValues(repository, values) {
374
613
  ...(values.body !== undefined ? { body: values.body } : {}),
375
614
  };
376
615
  }
616
+ function documentUpdateInputFromValues(repository, values) {
617
+ return {
618
+ ...(values.title !== undefined ? { title: values.title } : {}),
619
+ ...(values.status !== undefined ? { status: values.status } : {}),
620
+ ...(values['clear-priority']
621
+ ? { priority: null }
622
+ : values.priority !== undefined
623
+ ? { priority: values.priority }
624
+ : {}),
625
+ ...(values['clear-order']
626
+ ? { order: null }
627
+ : values.order !== undefined
628
+ ? { order: values.order }
629
+ : {}),
630
+ ...(values['clear-labels']
631
+ ? { labels: [] }
632
+ : values.label !== undefined
633
+ ? { labels: values.label }
634
+ : {}),
635
+ ...(values['clear-dependencies']
636
+ ? { dependsOn: [] }
637
+ : values['depends-on'] !== undefined
638
+ ? { dependsOn: values['depends-on'] }
639
+ : {}),
640
+ ...(values['clear-files']
641
+ ? { files: [] }
642
+ : values.file !== undefined
643
+ ? { files: normalizeMutationPaths(repository, values.file) }
644
+ : {}),
645
+ ...(values['clear-owner']
646
+ ? { owner: null }
647
+ : values.owner !== undefined
648
+ ? { owner: values.owner }
649
+ : {}),
650
+ ...(values['clear-assignees']
651
+ ? { assignees: [] }
652
+ : values.assignee !== undefined
653
+ ? { assignees: values.assignee }
654
+ : {}),
655
+ ...(values['clear-reviewers']
656
+ ? { reviewers: [] }
657
+ : values.reviewer !== undefined
658
+ ? { reviewers: values.reviewer }
659
+ : {}),
660
+ ...(values['clear-team']
661
+ ? { team: null }
662
+ : values.team !== undefined
663
+ ? { team: values.team }
664
+ : {}),
665
+ ...(values['clear-estimate']
666
+ ? { estimate: null }
667
+ : values.estimate !== undefined
668
+ ? { estimate: values.estimate }
669
+ : {}),
670
+ ...(values['clear-effort']
671
+ ? { effort: null }
672
+ : values.effort !== undefined
673
+ ? { effort: values.effort }
674
+ : {}),
675
+ ...(values['clear-risk']
676
+ ? { risk: null }
677
+ : values.risk !== undefined
678
+ ? { risk: values.risk }
679
+ : {}),
680
+ ...(values['clear-due-date']
681
+ ? { dueDate: null }
682
+ : values['due-date'] !== undefined
683
+ ? { dueDate: values['due-date'] }
684
+ : {}),
685
+ ...(values['clear-related']
686
+ ? { related: [] }
687
+ : values.related !== undefined
688
+ ? { related: values.related }
689
+ : {}),
690
+ ...(values['clear-duplicates']
691
+ ? { duplicates: [] }
692
+ : values.duplicate !== undefined
693
+ ? { duplicates: values.duplicate }
694
+ : {}),
695
+ ...(values['clear-parent']
696
+ ? { parent: null }
697
+ : values.parent !== undefined
698
+ ? { parent: values.parent }
699
+ : {}),
700
+ ...(values['clear-directories']
701
+ ? { directories: [] }
702
+ : values.directory !== undefined
703
+ ? { directories: normalizeMutationPaths(repository, values.directory) }
704
+ : {}),
705
+ ...(values['clear-projects']
706
+ ? { projects: [] }
707
+ : values.project !== undefined
708
+ ? { projects: values.project }
709
+ : {}),
710
+ ...(values.body !== undefined ? { body: values.body } : {}),
711
+ };
712
+ }
377
713
  export async function runCli(args, context = {}) {
378
714
  const cwd = path.resolve(context.cwd ?? process.cwd());
379
715
  const stdout = context.stdout ?? ((value) => process.stdout.write(value));
@@ -443,6 +779,23 @@ export async function runCli(args, context = {}) {
443
779
  : `Generated ${result.files.length} views in ${result.directory}\n`);
444
780
  return 0;
445
781
  }
782
+ if (command === 'sync') {
783
+ const parsed = parseArgs({
784
+ args: commandArgs,
785
+ allowPositionals: false,
786
+ options: { ...commonOptionDefinitions, concurrency: { type: 'string' } },
787
+ });
788
+ const values = parseSchema(z.strictObject({ ...CommonValuesSchema.shape, concurrency: ConcurrencySchema }), parsed.values, 'sync options');
789
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
790
+ const result = await syncRepository(repository, {
791
+ concurrency: values.concurrency,
792
+ onProgress: (progress) => stderr(`sync ${progress.phase}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`),
793
+ });
794
+ stdout(values.json
795
+ ? `${JSON.stringify(result, null, 2)}\n`
796
+ : `Synced ${result.migrations.length} migrations and generated views\n`);
797
+ return 0;
798
+ }
446
799
  if (command === 'snapshot') {
447
800
  if (subcommand === 'create' || subcommand === 'list') {
448
801
  const parsed = parseArgs({
@@ -497,9 +850,323 @@ export async function runCli(args, context = {}) {
497
850
  }
498
851
  throw new CliUsageError(`Unknown snapshot command "${subcommand ?? ''}"`);
499
852
  }
853
+ if (command === 'document' || command === 'doc') {
854
+ if (subcommand === 'batch') {
855
+ const parsed = parseArgs({
856
+ args: subcommandArgs,
857
+ allowPositionals: true,
858
+ options: { ...commonOptionDefinitions, concurrency: { type: 'string' } },
859
+ });
860
+ const [manifestPath] = requirePositionals(parsed.positionals, 1, 'exactly one batch manifest path');
861
+ const values = parseSchema(z.strictObject({ ...CommonValuesSchema.shape, concurrency: ConcurrencySchema }), parsed.values, 'document batch options');
862
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
863
+ const validatedManifestPath = parseSchema(z.string(), manifestPath, 'document batch manifest path');
864
+ const raw = JSON.parse(await readFile(path.resolve(repository.rootDirectory, validatedManifestPath), 'utf8'));
865
+ const operations = parseSchema(DocumentBatchSchema, raw, 'document batch manifest');
866
+ const results = await executeDocumentBatch(repository, operations, {
867
+ concurrency: values.concurrency,
868
+ onProgress: (progress) => stderr(`document ${progress.action}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`),
869
+ });
870
+ stdout(values.json
871
+ ? `${JSON.stringify(results, null, 2)}\n`
872
+ : `${results.map((result) => result.relativePath).join('\n')}\n`);
873
+ return 0;
874
+ }
875
+ if (subcommand === 'create') {
876
+ const parsed = parseArgs({
877
+ args: subcommandArgs,
878
+ allowPositionals: true,
879
+ options: { ...metadataOptionDefinitions, ...commonOptionDefinitions },
880
+ });
881
+ const [rawType] = requirePositionals(parsed.positionals, 1, 'exactly one document type');
882
+ const values = parseSchema(DocumentCreateValuesSchema, parsed.values, 'document create options');
883
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
884
+ const record = await createDocument(repository, {
885
+ type: normalizeDocumentKind(parseSchema(z.string(), rawType, 'document type')),
886
+ title: values.title,
887
+ ...(values.status ? { status: values.status } : {}),
888
+ ...(values.priority ? { priority: values.priority } : {}),
889
+ ...(values.order !== undefined ? { order: values.order } : {}),
890
+ ...(values.label ? { labels: values.label } : {}),
891
+ ...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
892
+ ...(values.file ? { files: normalizeMutationPaths(repository, values.file) } : {}),
893
+ ...(values.owner ? { owner: values.owner } : {}),
894
+ ...(values.assignee ? { assignees: values.assignee } : {}),
895
+ ...(values.reviewer ? { reviewers: values.reviewer } : {}),
896
+ ...(values.team ? { team: values.team } : {}),
897
+ ...(values.estimate !== undefined ? { estimate: values.estimate } : {}),
898
+ ...(values.effort !== undefined ? { effort: values.effort } : {}),
899
+ ...(values.risk ? { risk: values.risk } : {}),
900
+ ...(values['due-date'] ? { dueDate: values['due-date'] } : {}),
901
+ ...(values.related ? { related: values.related } : {}),
902
+ ...(values.duplicate ? { duplicates: values.duplicate } : {}),
903
+ ...(values.parent ? { parent: values.parent } : {}),
904
+ ...(values.directory
905
+ ? { directories: normalizeMutationPaths(repository, values.directory) }
906
+ : {}),
907
+ ...(values.project ? { projects: values.project } : {}),
908
+ ...(values.body !== undefined ? { body: values.body } : {}),
909
+ }, { onWarning });
910
+ writeDocumentRecord(record, values.json, stdout);
911
+ return 0;
912
+ }
913
+ if (subcommand === 'import') {
914
+ const parsed = parseArgs({
915
+ args: subcommandArgs,
916
+ allowPositionals: true,
917
+ options: {
918
+ ...commonOptionDefinitions,
919
+ type: { type: 'string' },
920
+ title: { type: 'string' },
921
+ move: { type: 'boolean' },
922
+ },
923
+ });
924
+ const [sourcePath] = requirePositionals(parsed.positionals, 1, 'exactly one Markdown path');
925
+ const values = parseSchema(z.strictObject({
926
+ type: TrimmedStringSchema.optional(),
927
+ title: TrimmedStringSchema.optional(),
928
+ move: z.boolean().optional(),
929
+ cwd: CwdSchema,
930
+ json: JsonSchema,
931
+ }), parsed.values, 'document import options');
932
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
933
+ const record = await importDocument(repository, parseSchema(z.string(), sourcePath, 'document import path'), {
934
+ ...(values.type ? { type: normalizeDocumentKind(values.type) } : {}),
935
+ ...(values.title ? { title: values.title } : {}),
936
+ move: values.move,
937
+ });
938
+ stdout(values.json ? `${JSON.stringify(record, null, 2)}\n` : `${record.relativePath}\n`);
939
+ return 0;
940
+ }
941
+ if (subcommand === 'list') {
942
+ const parsed = parseArgs({
943
+ args: subcommandArgs,
944
+ allowPositionals: true,
945
+ options: {
946
+ cwd: { type: 'string' },
947
+ json: { type: 'boolean' },
948
+ status: { type: 'string', multiple: true },
949
+ priority: { type: 'string', multiple: true },
950
+ label: { type: 'string', multiple: true },
951
+ owner: { type: 'string', multiple: true },
952
+ assignee: { type: 'string', multiple: true },
953
+ reviewer: { type: 'string', multiple: true },
954
+ team: { type: 'string', multiple: true },
955
+ risk: { type: 'string', multiple: true },
956
+ project: { type: 'string', multiple: true },
957
+ 'depends-on': { type: 'string' },
958
+ related: { type: 'string' },
959
+ duplicate: { type: 'string' },
960
+ parent: { type: 'string' },
961
+ file: { type: 'string', multiple: true },
962
+ directory: { type: 'string', multiple: true },
963
+ 'estimate-min': { type: 'string' },
964
+ 'estimate-max': { type: 'string' },
965
+ 'effort-min': { type: 'string' },
966
+ 'effort-max': { type: 'string' },
967
+ 'due-before': { type: 'string' },
968
+ 'due-after': { type: 'string' },
969
+ 'created-before': { type: 'string' },
970
+ 'created-after': { type: 'string' },
971
+ 'updated-before': { type: 'string' },
972
+ 'updated-after': { type: 'string' },
973
+ search: { type: 'string' },
974
+ sort: { type: 'string' },
975
+ direction: { type: 'string' },
976
+ impact: { type: 'boolean' },
977
+ 'include-derived': { type: 'boolean' },
978
+ },
979
+ });
980
+ if (parsed.positionals.length > 1)
981
+ throw new CliUsageError('Expected at most one document type');
982
+ const values = parseSchema(DocumentListValuesSchema, parsed.values, 'document list options');
983
+ const typeFilter = parsed.positionals[0]
984
+ ? normalizeDocumentKind(parsed.positionals[0])
985
+ : undefined;
986
+ const query = parseSchema(DocumentQuerySchema, {
987
+ ...(typeFilter ? { types: [typeFilter] } : {}),
988
+ ...(values.status ? { statuses: values.status } : {}),
989
+ ...(values.priority ? { priorities: values.priority } : {}),
990
+ ...(values.label ? { labels: values.label } : {}),
991
+ ...(values.owner ? { owners: values.owner } : {}),
992
+ ...(values.assignee ? { assignees: values.assignee } : {}),
993
+ ...(values.reviewer ? { reviewers: values.reviewer } : {}),
994
+ ...(values.team ? { teams: values.team } : {}),
995
+ ...(values.risk ? { risks: values.risk } : {}),
996
+ ...(values.project ? { projects: values.project } : {}),
997
+ ...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
998
+ ...(values.related ? { related: values.related } : {}),
999
+ ...(values.duplicate ? { duplicate: values.duplicate } : {}),
1000
+ ...(values.parent ? { parent: values.parent } : {}),
1001
+ ...(values.file ? { files: values.file } : {}),
1002
+ ...(values.directory ? { directories: values.directory } : {}),
1003
+ ...(values['estimate-min'] !== undefined
1004
+ ? { estimateMin: values['estimate-min'] }
1005
+ : {}),
1006
+ ...(values['estimate-max'] !== undefined
1007
+ ? { estimateMax: values['estimate-max'] }
1008
+ : {}),
1009
+ ...(values['effort-min'] !== undefined ? { effortMin: values['effort-min'] } : {}),
1010
+ ...(values['effort-max'] !== undefined ? { effortMax: values['effort-max'] } : {}),
1011
+ ...(values['due-before'] ? { dueBefore: values['due-before'] } : {}),
1012
+ ...(values['due-after'] ? { dueAfter: values['due-after'] } : {}),
1013
+ ...(values['created-before'] ? { createdBefore: values['created-before'] } : {}),
1014
+ ...(values['created-after'] ? { createdAfter: values['created-after'] } : {}),
1015
+ ...(values['updated-before'] ? { updatedBefore: values['updated-before'] } : {}),
1016
+ ...(values['updated-after'] ? { updatedAfter: values['updated-after'] } : {}),
1017
+ ...(values.search !== undefined ? { text: values.search } : {}),
1018
+ ...(values.sort ? { sortBy: values.sort } : {}),
1019
+ ...(values.direction ? { direction: values.direction } : {}),
1020
+ ...(values.impact ? { impact: true } : {}),
1021
+ }, 'document list query');
1022
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1023
+ const result = await queryDocuments(repository, query);
1024
+ const derivedGraph = values['include-derived']
1025
+ ? buildDocumentGraph(await listDocuments(repository))
1026
+ : undefined;
1027
+ const serialize = (record) => documentRecordJson(record, derivedGraph?.derive(record.document.metadata.id));
1028
+ if (values.json) {
1029
+ stdout(`${JSON.stringify(values.impact
1030
+ ? {
1031
+ direct: result.direct.map(serialize),
1032
+ impacted: result.impacted.map(serialize),
1033
+ }
1034
+ : result.direct.map(serialize), null, 2)}\n`);
1035
+ }
1036
+ else {
1037
+ for (const record of result.direct) {
1038
+ 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`);
1039
+ }
1040
+ for (const record of result.impacted) {
1041
+ stdout(`impact\t${record.document.metadata.id}\t${record.document.metadata.type}\t${record.document.metadata.status}\t${record.document.metadata.title}\n`);
1042
+ }
1043
+ }
1044
+ return 0;
1045
+ }
1046
+ if (subcommand === 'show') {
1047
+ const parsed = parseArgs({
1048
+ args: subcommandArgs,
1049
+ allowPositionals: true,
1050
+ options: {
1051
+ ...commonOptionDefinitions,
1052
+ type: { type: 'string' },
1053
+ 'include-derived': { type: 'boolean' },
1054
+ },
1055
+ });
1056
+ const [id] = requirePositionals(parsed.positionals, 1, 'exactly one document ID');
1057
+ const values = parseSchema(z.strictObject({
1058
+ ...CommonValuesSchema.shape,
1059
+ type: TrimmedStringSchema.optional(),
1060
+ 'include-derived': z.boolean().optional(),
1061
+ }), parsed.values, 'document show options');
1062
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1063
+ const validatedId = parseSchema(DocumentIdSchema, id, 'document ID');
1064
+ const record = await readDocument(repository, validatedId, values.type ? normalizeDocumentKind(values.type) : undefined);
1065
+ if (values.json) {
1066
+ const derived = values['include-derived']
1067
+ ? buildDocumentGraph(await listDocuments(repository)).derive(validatedId)
1068
+ : undefined;
1069
+ stdout(`${JSON.stringify({
1070
+ relativePath: record.relativePath,
1071
+ metadata: record.document.metadata,
1072
+ body: record.document.body,
1073
+ ...(derived ? { derived } : {}),
1074
+ }, null, 2)}\n`);
1075
+ }
1076
+ else {
1077
+ stdout(serializeDocumentFile(record.document));
1078
+ }
1079
+ return 0;
1080
+ }
1081
+ if (subcommand === 'update') {
1082
+ const clearDefinitions = Object.fromEntries(UPDATE_CLEAR_PAIRS.map(([, clear]) => [clear, { type: 'boolean' }]));
1083
+ const parsed = parseArgs({
1084
+ args: subcommandArgs,
1085
+ allowPositionals: true,
1086
+ options: {
1087
+ ...metadataOptionDefinitions,
1088
+ ...clearDefinitions,
1089
+ ...commonOptionDefinitions,
1090
+ type: { type: 'string' },
1091
+ },
1092
+ });
1093
+ const [documentId] = requirePositionals(parsed.positionals, 1, 'exactly one document ID');
1094
+ const validatedDocumentId = parseSchema(DocumentIdSchema, documentId, 'document ID');
1095
+ const values = parseSchema(DocumentUpdateValuesSchema, parsed.values, 'document update options');
1096
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1097
+ const record = await updateDocument(repository, validatedDocumentId, documentUpdateInputFromValues(repository, values), values.type ? normalizeDocumentKind(values.type) : undefined, { onWarning });
1098
+ writeDocumentRecord(record, values.json, stdout);
1099
+ return 0;
1100
+ }
1101
+ if (subcommand === 'status') {
1102
+ const parsed = parseArgs({
1103
+ args: subcommandArgs,
1104
+ allowPositionals: true,
1105
+ options: { ...commonOptionDefinitions, type: { type: 'string' } },
1106
+ });
1107
+ const [documentId, status] = requirePositionals(parsed.positionals, 2, 'a document ID and status');
1108
+ const validatedDocumentId = parseSchema(DocumentIdSchema, documentId, 'document ID');
1109
+ const validatedStatus = parseSchema(DocumentStatusSchema, status, 'document status');
1110
+ const values = parseSchema(z.strictObject({ ...CommonValuesSchema.shape, type: TrimmedStringSchema.optional() }), parsed.values, 'document status options');
1111
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1112
+ const record = await updateDocument(repository, validatedDocumentId, { status: validatedStatus }, values.type ? normalizeDocumentKind(values.type) : undefined, { onWarning });
1113
+ writeDocumentRecord(record, values.json, stdout);
1114
+ return 0;
1115
+ }
1116
+ if (subcommand === 'delete') {
1117
+ const parsed = parseArgs({
1118
+ args: subcommandArgs,
1119
+ allowPositionals: true,
1120
+ options: {
1121
+ ...commonOptionDefinitions,
1122
+ type: { type: 'string' },
1123
+ 'remove-dependencies': { type: 'boolean' },
1124
+ },
1125
+ });
1126
+ const [documentId] = requirePositionals(parsed.positionals, 1, 'exactly one document ID');
1127
+ const validatedDocumentId = parseSchema(DocumentIdSchema, documentId, 'document ID');
1128
+ const values = parseSchema(z.strictObject({
1129
+ cwd: CwdSchema,
1130
+ json: JsonSchema,
1131
+ type: TrimmedStringSchema.optional(),
1132
+ 'remove-dependencies': z.boolean().optional(),
1133
+ }), parsed.values, 'document delete options');
1134
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1135
+ const record = await deleteDocument(repository, validatedDocumentId, values.type ? normalizeDocumentKind(values.type) : undefined, {
1136
+ removeDependencies: values['remove-dependencies'],
1137
+ onWarning,
1138
+ });
1139
+ if (values.json) {
1140
+ stdout(`${JSON.stringify({ deleted: true, ...documentRecordJson(record) }, null, 2)}\n`);
1141
+ }
1142
+ else {
1143
+ stdout(`${validatedDocumentId}\n`);
1144
+ }
1145
+ return 0;
1146
+ }
1147
+ throw new CliUsageError(`Unknown document command "${subcommand ?? ''}"`);
1148
+ }
500
1149
  if (command !== 'task') {
501
1150
  throw new CliUsageError(`Unknown command "${command}"`);
502
1151
  }
1152
+ if (subcommand === 'migrate-ids') {
1153
+ const parsed = parseArgs({
1154
+ args: subcommandArgs,
1155
+ allowPositionals: false,
1156
+ options: commonOptionDefinitions,
1157
+ });
1158
+ const values = parseSchema(CommonValuesSchema, parsed.values, 'task migrate-ids options');
1159
+ const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
1160
+ const migrations = await migrateTaskIds(repository, {
1161
+ onProgress: (progress) => stderr(`migration ${progress.phase}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`),
1162
+ });
1163
+ if (values.json)
1164
+ stdout(`${JSON.stringify(migrations, null, 2)}\n`);
1165
+ else
1166
+ for (const migration of migrations)
1167
+ stdout(`${migration.from}\t${migration.to}\n`);
1168
+ return 0;
1169
+ }
503
1170
  if (subcommand === 'create') {
504
1171
  const parsed = parseArgs({
505
1172
  args: subcommandArgs,