@taskset/cli 4.0.0 → 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 +22 -0
  2. package/README.md +6 -2
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +510 -63
  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 +7 -5
  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 +611 -77
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
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
8
  taskset init [--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.
@@ -81,17 +84,51 @@ const DocumentInputSchema = z.strictObject({
81
84
  type: DocumentKindInputSchema,
82
85
  title: TrimmedStringSchema,
83
86
  status: DocumentStatusSchema.optional(),
87
+ priority: TaskPrioritySchema.optional(),
88
+ order: z.number().finite().nonnegative().optional(),
84
89
  labels: StringListSchema.optional(),
85
- related: StringListSchema.optional(),
90
+ dependsOn: TaskIdListSchema.optional(),
91
+ files: StringListSchema.optional(),
92
+ owner: TrimmedStringSchema.optional(),
93
+ assignees: StringListSchema.optional(),
94
+ reviewers: StringListSchema.optional(),
95
+ team: TrimmedStringSchema.optional(),
96
+ estimate: z.number().int().nonnegative().optional(),
97
+ effort: z.number().finite().nonnegative().optional(),
98
+ risk: TaskRiskSchema.optional(),
99
+ dueDate: DocumentTimestampSchema.optional(),
100
+ related: TaskIdListSchema.optional(),
101
+ duplicates: TaskIdListSchema.optional(),
102
+ parent: TaskIdSchema.optional(),
103
+ directories: StringListSchema.optional(),
104
+ projects: StringListSchema.optional(),
86
105
  body: z.string().optional(),
87
106
  });
88
- const DocumentUpdateSchema = z.strictObject({
107
+ const DocumentUpdateSchema = z
108
+ .strictObject({
89
109
  title: TrimmedStringSchema.optional(),
90
110
  status: DocumentStatusSchema.optional(),
111
+ priority: TaskPrioritySchema.nullable().optional(),
112
+ order: z.number().finite().nonnegative().nullable().optional(),
91
113
  labels: StringListSchema.optional(),
92
- related: 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(),
93
129
  body: z.string().optional(),
94
- });
130
+ })
131
+ .refine((input) => Object.keys(input).length > 0, 'Document update requires at least one field');
95
132
  const DocumentBatchSchema = z
96
133
  .array(z.discriminatedUnion('action', [
97
134
  z.strictObject({ action: z.literal('create'), input: DocumentInputSchema }),
@@ -121,6 +158,148 @@ const DocumentBatchSchema = z
121
158
  }),
122
159
  ]))
123
160
  .min(1);
161
+ const UPDATE_CLEAR_PAIRS = [
162
+ ['priority', 'clear-priority'],
163
+ ['order', 'clear-order'],
164
+ ['label', 'clear-labels'],
165
+ ['depends-on', 'clear-dependencies'],
166
+ ['file', 'clear-files'],
167
+ ['owner', 'clear-owner'],
168
+ ['assignee', 'clear-assignees'],
169
+ ['reviewer', 'clear-reviewers'],
170
+ ['team', 'clear-team'],
171
+ ['estimate', 'clear-estimate'],
172
+ ['effort', 'clear-effort'],
173
+ ['risk', 'clear-risk'],
174
+ ['due-date', 'clear-due-date'],
175
+ ['related', 'clear-related'],
176
+ ['duplicate', 'clear-duplicates'],
177
+ ['parent', 'clear-parent'],
178
+ ['directory', 'clear-directories'],
179
+ ['project', 'clear-projects'],
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
+ });
124
303
  const CreateValuesSchema = z.strictObject({
125
304
  title: TrimmedStringSchema,
126
305
  status: TaskStatusSchema.optional(),
@@ -146,26 +325,6 @@ const CreateValuesSchema = z.strictObject({
146
325
  cwd: CwdSchema,
147
326
  json: JsonSchema,
148
327
  });
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
328
  const UpdateValuesSchema = z
170
329
  .strictObject({
171
330
  title: TrimmedStringSchema.optional(),
@@ -328,6 +487,13 @@ function taskRecordJson(record, derived) {
328
487
  ...(derived ? { derived } : {}),
329
488
  };
330
489
  }
490
+ function documentRecordJson(record, derived) {
491
+ return {
492
+ relativePath: record.relativePath,
493
+ ...record.document.metadata,
494
+ ...(derived ? { derived } : {}),
495
+ };
496
+ }
331
497
  function writeTaskRecord(record, json, stdout) {
332
498
  if (json) {
333
499
  stdout(`${JSON.stringify(taskRecordJson(record), null, 2)}\n`);
@@ -336,6 +502,14 @@ function writeTaskRecord(record, json, stdout) {
336
502
  stdout(`${record.task.metadata.id}\n`);
337
503
  }
338
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
+ }
339
513
  function warningWriter(stderr) {
340
514
  return (warning) => stderr(`warning: ${warning.message}\n`);
341
515
  }
@@ -439,6 +613,103 @@ function updateInputFromValues(repository, values) {
439
613
  ...(values.body !== undefined ? { body: values.body } : {}),
440
614
  };
441
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
+ }
442
713
  export async function runCli(args, context = {}) {
443
714
  const cwd = path.resolve(context.cwd ?? process.cwd());
444
715
  const stdout = context.stdout ?? ((value) => process.stdout.write(value));
@@ -605,35 +876,38 @@ export async function runCli(args, context = {}) {
605
876
  const parsed = parseArgs({
606
877
  args: subcommandArgs,
607
878
  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
- },
879
+ options: { ...metadataOptionDefinitions, ...commonOptionDefinitions },
616
880
  });
617
881
  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');
882
+ const values = parseSchema(DocumentCreateValuesSchema, parsed.values, 'document create options');
627
883
  const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
628
884
  const record = await createDocument(repository, {
629
885
  type: normalizeDocumentKind(parseSchema(z.string(), rawType, 'document type')),
630
886
  title: values.title,
631
887
  ...(values.status ? { status: values.status } : {}),
888
+ ...(values.priority ? { priority: values.priority } : {}),
889
+ ...(values.order !== undefined ? { order: values.order } : {}),
632
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'] } : {}),
633
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 } : {}),
634
908
  ...(values.body !== undefined ? { body: values.body } : {}),
635
- });
636
- stdout(values.json ? `${JSON.stringify(record, null, 2)}\n` : `${record.document.metadata.id}\n`);
909
+ }, { onWarning });
910
+ writeDocumentRecord(record, values.json, stdout);
637
911
  return 0;
638
912
  }
639
913
  if (subcommand === 'import') {
@@ -668,33 +942,206 @@ export async function runCli(args, context = {}) {
668
942
  const parsed = parseArgs({
669
943
  args: subcommandArgs,
670
944
  allowPositionals: true,
671
- options: commonOptionDefinitions,
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
+ },
672
979
  });
673
980
  if (parsed.positionals.length > 1)
674
981
  throw new CliUsageError('Expected at most one document type');
675
- const values = parseSchema(CommonValuesSchema, parsed.values, 'document list options');
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');
676
1022
  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`);
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
+ }
683
1044
  return 0;
684
1045
  }
685
1046
  if (subcommand === 'show') {
686
1047
  const parsed = parseArgs({
687
1048
  args: subcommandArgs,
688
1049
  allowPositionals: true,
689
- options: { ...commonOptionDefinitions, type: { type: 'string' } },
1050
+ options: {
1051
+ ...commonOptionDefinitions,
1052
+ type: { type: 'string' },
1053
+ 'include-derived': { type: 'boolean' },
1054
+ },
690
1055
  });
691
1056
  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');
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');
693
1062
  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));
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
+ }
698
1145
  return 0;
699
1146
  }
700
1147
  throw new CliUsageError(`Unknown document command "${subcommand ?? ''}"`);