@taskset/cli 3.0.3 → 4.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.
- package/CHANGELOG.md +17 -0
- package/README.md +3 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +222 -2
- package/package.json +6 -6
- package/src/cli.ts +300 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @taskset/cli
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 0fb519a: Add canonical stories, flows, decisions, research, and runbooks with typed
|
|
8
|
+
templates and Markdown import support. New tasks use sequential title-derived
|
|
9
|
+
IDs, and `task migrate-ids` atomically converts legacy task files and their
|
|
10
|
+
relationships.
|
|
11
|
+
Add paced batch create/import/update/export manifests with progress and a
|
|
12
|
+
one-command sync that repairs repository-wide references and generated views.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [0fb519a]
|
|
17
|
+
- @taskset/contracts@4.0.0
|
|
18
|
+
- @taskset/core@4.0.0
|
|
19
|
+
|
|
3
20
|
## 3.0.3
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -15,8 +15,9 @@ and exit-code mapping. Repository behavior is delegated to `@taskset/core`.
|
|
|
15
15
|
|
|
16
16
|
Supported command groups:
|
|
17
17
|
|
|
18
|
-
- `taskset init`, `config`, `doctor`, and `
|
|
19
|
-
- `taskset task create|list|show|update|status|delete`
|
|
18
|
+
- `taskset init`, `config`, `doctor`, `generate`, and `sync`
|
|
19
|
+
- `taskset task create|list|show|update|status|delete|migrate-ids`
|
|
20
|
+
- `taskset document create|import|batch|list|show` (`doc` is an alias)
|
|
20
21
|
- `taskset snapshot create|list|restore`
|
|
21
22
|
|
|
22
23
|
Use `task list --file <path> --impact` for direct and transitive code-impact
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAuFA,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACzC;AAycD,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAkqB/F"}
|
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 { 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';
|
|
5
6
|
import * as z from 'zod';
|
|
6
7
|
const USAGE = `Usage:
|
|
7
8
|
taskset init [--cwd <path>]
|
|
@@ -16,6 +17,13 @@ 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>
|
|
23
|
+
taskset document import <markdown-path> [--type <type>] [--move]
|
|
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>]
|
|
19
27
|
|
|
20
28
|
Metadata options:
|
|
21
29
|
--status --priority --order --owner --team --estimate --effort --risk --due-date
|
|
@@ -56,6 +64,63 @@ const CommonValuesSchema = z.strictObject({
|
|
|
56
64
|
cwd: CwdSchema,
|
|
57
65
|
json: JsonSchema,
|
|
58
66
|
});
|
|
67
|
+
const ConcurrencySchema = z.coerce.number().int().min(1).max(32).optional();
|
|
68
|
+
const DocumentKindInputSchema = TrimmedStringSchema.transform((value, context) => {
|
|
69
|
+
try {
|
|
70
|
+
return normalizeDocumentKind(value);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
context.addIssue({
|
|
74
|
+
code: 'custom',
|
|
75
|
+
message: error instanceof Error ? error.message : 'Invalid document type',
|
|
76
|
+
});
|
|
77
|
+
return z.NEVER;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const DocumentInputSchema = z.strictObject({
|
|
81
|
+
type: DocumentKindInputSchema,
|
|
82
|
+
title: TrimmedStringSchema,
|
|
83
|
+
status: DocumentStatusSchema.optional(),
|
|
84
|
+
labels: StringListSchema.optional(),
|
|
85
|
+
related: StringListSchema.optional(),
|
|
86
|
+
body: z.string().optional(),
|
|
87
|
+
});
|
|
88
|
+
const DocumentUpdateSchema = z.strictObject({
|
|
89
|
+
title: TrimmedStringSchema.optional(),
|
|
90
|
+
status: DocumentStatusSchema.optional(),
|
|
91
|
+
labels: StringListSchema.optional(),
|
|
92
|
+
related: StringListSchema.optional(),
|
|
93
|
+
body: z.string().optional(),
|
|
94
|
+
});
|
|
95
|
+
const DocumentBatchSchema = z
|
|
96
|
+
.array(z.discriminatedUnion('action', [
|
|
97
|
+
z.strictObject({ action: z.literal('create'), input: DocumentInputSchema }),
|
|
98
|
+
z.strictObject({
|
|
99
|
+
action: z.literal('import'),
|
|
100
|
+
sourcePath: TrimmedStringSchema,
|
|
101
|
+
options: z
|
|
102
|
+
.strictObject({
|
|
103
|
+
type: DocumentKindInputSchema.optional(),
|
|
104
|
+
title: TrimmedStringSchema.optional(),
|
|
105
|
+
move: z.boolean().optional(),
|
|
106
|
+
})
|
|
107
|
+
.optional(),
|
|
108
|
+
}),
|
|
109
|
+
z.strictObject({
|
|
110
|
+
action: z.literal('update'),
|
|
111
|
+
id: TrimmedStringSchema,
|
|
112
|
+
type: DocumentKindInputSchema.optional(),
|
|
113
|
+
input: DocumentUpdateSchema,
|
|
114
|
+
}),
|
|
115
|
+
z.strictObject({
|
|
116
|
+
action: z.literal('export'),
|
|
117
|
+
id: TrimmedStringSchema,
|
|
118
|
+
type: DocumentKindInputSchema.optional(),
|
|
119
|
+
targetPath: TrimmedStringSchema,
|
|
120
|
+
overwrite: z.boolean().optional(),
|
|
121
|
+
}),
|
|
122
|
+
]))
|
|
123
|
+
.min(1);
|
|
59
124
|
const CreateValuesSchema = z.strictObject({
|
|
60
125
|
title: TrimmedStringSchema,
|
|
61
126
|
status: TaskStatusSchema.optional(),
|
|
@@ -443,6 +508,23 @@ export async function runCli(args, context = {}) {
|
|
|
443
508
|
: `Generated ${result.files.length} views in ${result.directory}\n`);
|
|
444
509
|
return 0;
|
|
445
510
|
}
|
|
511
|
+
if (command === 'sync') {
|
|
512
|
+
const parsed = parseArgs({
|
|
513
|
+
args: commandArgs,
|
|
514
|
+
allowPositionals: false,
|
|
515
|
+
options: { ...commonOptionDefinitions, concurrency: { type: 'string' } },
|
|
516
|
+
});
|
|
517
|
+
const values = parseSchema(z.strictObject({ ...CommonValuesSchema.shape, concurrency: ConcurrencySchema }), parsed.values, 'sync options');
|
|
518
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
519
|
+
const result = await syncRepository(repository, {
|
|
520
|
+
concurrency: values.concurrency,
|
|
521
|
+
onProgress: (progress) => stderr(`sync ${progress.phase}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`),
|
|
522
|
+
});
|
|
523
|
+
stdout(values.json
|
|
524
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
525
|
+
: `Synced ${result.migrations.length} migrations and generated views\n`);
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
446
528
|
if (command === 'snapshot') {
|
|
447
529
|
if (subcommand === 'create' || subcommand === 'list') {
|
|
448
530
|
const parsed = parseArgs({
|
|
@@ -497,9 +579,147 @@ export async function runCli(args, context = {}) {
|
|
|
497
579
|
}
|
|
498
580
|
throw new CliUsageError(`Unknown snapshot command "${subcommand ?? ''}"`);
|
|
499
581
|
}
|
|
582
|
+
if (command === 'document' || command === 'doc') {
|
|
583
|
+
if (subcommand === 'batch') {
|
|
584
|
+
const parsed = parseArgs({
|
|
585
|
+
args: subcommandArgs,
|
|
586
|
+
allowPositionals: true,
|
|
587
|
+
options: { ...commonOptionDefinitions, concurrency: { type: 'string' } },
|
|
588
|
+
});
|
|
589
|
+
const [manifestPath] = requirePositionals(parsed.positionals, 1, 'exactly one batch manifest path');
|
|
590
|
+
const values = parseSchema(z.strictObject({ ...CommonValuesSchema.shape, concurrency: ConcurrencySchema }), parsed.values, 'document batch options');
|
|
591
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
592
|
+
const validatedManifestPath = parseSchema(z.string(), manifestPath, 'document batch manifest path');
|
|
593
|
+
const raw = JSON.parse(await readFile(path.resolve(repository.rootDirectory, validatedManifestPath), 'utf8'));
|
|
594
|
+
const operations = parseSchema(DocumentBatchSchema, raw, 'document batch manifest');
|
|
595
|
+
const results = await executeDocumentBatch(repository, operations, {
|
|
596
|
+
concurrency: values.concurrency,
|
|
597
|
+
onProgress: (progress) => stderr(`document ${progress.action}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`),
|
|
598
|
+
});
|
|
599
|
+
stdout(values.json
|
|
600
|
+
? `${JSON.stringify(results, null, 2)}\n`
|
|
601
|
+
: `${results.map((result) => result.relativePath).join('\n')}\n`);
|
|
602
|
+
return 0;
|
|
603
|
+
}
|
|
604
|
+
if (subcommand === 'create') {
|
|
605
|
+
const parsed = parseArgs({
|
|
606
|
+
args: subcommandArgs,
|
|
607
|
+
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
|
+
},
|
|
616
|
+
});
|
|
617
|
+
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');
|
|
627
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
628
|
+
const record = await createDocument(repository, {
|
|
629
|
+
type: normalizeDocumentKind(parseSchema(z.string(), rawType, 'document type')),
|
|
630
|
+
title: values.title,
|
|
631
|
+
...(values.status ? { status: values.status } : {}),
|
|
632
|
+
...(values.label ? { labels: values.label } : {}),
|
|
633
|
+
...(values.related ? { related: values.related } : {}),
|
|
634
|
+
...(values.body !== undefined ? { body: values.body } : {}),
|
|
635
|
+
});
|
|
636
|
+
stdout(values.json ? `${JSON.stringify(record, null, 2)}\n` : `${record.document.metadata.id}\n`);
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
if (subcommand === 'import') {
|
|
640
|
+
const parsed = parseArgs({
|
|
641
|
+
args: subcommandArgs,
|
|
642
|
+
allowPositionals: true,
|
|
643
|
+
options: {
|
|
644
|
+
...commonOptionDefinitions,
|
|
645
|
+
type: { type: 'string' },
|
|
646
|
+
title: { type: 'string' },
|
|
647
|
+
move: { type: 'boolean' },
|
|
648
|
+
},
|
|
649
|
+
});
|
|
650
|
+
const [sourcePath] = requirePositionals(parsed.positionals, 1, 'exactly one Markdown path');
|
|
651
|
+
const values = parseSchema(z.strictObject({
|
|
652
|
+
type: TrimmedStringSchema.optional(),
|
|
653
|
+
title: TrimmedStringSchema.optional(),
|
|
654
|
+
move: z.boolean().optional(),
|
|
655
|
+
cwd: CwdSchema,
|
|
656
|
+
json: JsonSchema,
|
|
657
|
+
}), parsed.values, 'document import options');
|
|
658
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
659
|
+
const record = await importDocument(repository, parseSchema(z.string(), sourcePath, 'document import path'), {
|
|
660
|
+
...(values.type ? { type: normalizeDocumentKind(values.type) } : {}),
|
|
661
|
+
...(values.title ? { title: values.title } : {}),
|
|
662
|
+
move: values.move,
|
|
663
|
+
});
|
|
664
|
+
stdout(values.json ? `${JSON.stringify(record, null, 2)}\n` : `${record.relativePath}\n`);
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
if (subcommand === 'list') {
|
|
668
|
+
const parsed = parseArgs({
|
|
669
|
+
args: subcommandArgs,
|
|
670
|
+
allowPositionals: true,
|
|
671
|
+
options: commonOptionDefinitions,
|
|
672
|
+
});
|
|
673
|
+
if (parsed.positionals.length > 1)
|
|
674
|
+
throw new CliUsageError('Expected at most one document type');
|
|
675
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'document list options');
|
|
676
|
+
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`);
|
|
683
|
+
return 0;
|
|
684
|
+
}
|
|
685
|
+
if (subcommand === 'show') {
|
|
686
|
+
const parsed = parseArgs({
|
|
687
|
+
args: subcommandArgs,
|
|
688
|
+
allowPositionals: true,
|
|
689
|
+
options: { ...commonOptionDefinitions, type: { type: 'string' } },
|
|
690
|
+
});
|
|
691
|
+
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');
|
|
693
|
+
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));
|
|
698
|
+
return 0;
|
|
699
|
+
}
|
|
700
|
+
throw new CliUsageError(`Unknown document command "${subcommand ?? ''}"`);
|
|
701
|
+
}
|
|
500
702
|
if (command !== 'task') {
|
|
501
703
|
throw new CliUsageError(`Unknown command "${command}"`);
|
|
502
704
|
}
|
|
705
|
+
if (subcommand === 'migrate-ids') {
|
|
706
|
+
const parsed = parseArgs({
|
|
707
|
+
args: subcommandArgs,
|
|
708
|
+
allowPositionals: false,
|
|
709
|
+
options: commonOptionDefinitions,
|
|
710
|
+
});
|
|
711
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'task migrate-ids options');
|
|
712
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
713
|
+
const migrations = await migrateTaskIds(repository, {
|
|
714
|
+
onProgress: (progress) => stderr(`migration ${progress.phase}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`),
|
|
715
|
+
});
|
|
716
|
+
if (values.json)
|
|
717
|
+
stdout(`${JSON.stringify(migrations, null, 2)}\n`);
|
|
718
|
+
else
|
|
719
|
+
for (const migration of migrations)
|
|
720
|
+
stdout(`${migration.from}\t${migration.to}\n`);
|
|
721
|
+
return 0;
|
|
722
|
+
}
|
|
503
723
|
if (subcommand === 'create') {
|
|
504
724
|
const parsed = parseArgs({
|
|
505
725
|
args: subcommandArgs,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@taskset/cli",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "4.0.0",
|
|
6
6
|
"description": "The scriptable command-line interface for Taskset.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": {
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
"node": ">=24.16.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"zod": "4.5
|
|
47
|
-
"@taskset/
|
|
48
|
-
"@taskset/
|
|
46
|
+
"zod": "4.6.5",
|
|
47
|
+
"@taskset/contracts": "4.0.0",
|
|
48
|
+
"@taskset/core": "4.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^26.
|
|
51
|
+
"@types/node": "^26.6.3",
|
|
52
52
|
"typescript": "^7.0.2",
|
|
53
|
-
"vitest": "^5.0.
|
|
53
|
+
"vitest": "^5.0.2",
|
|
54
54
|
"@taskset/configs": "0.1.1"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
1
2
|
import path from 'node:path'
|
|
2
3
|
import { parseArgs } from 'node:util'
|
|
3
4
|
import {
|
|
5
|
+
DocumentStatusSchema,
|
|
4
6
|
TaskIdSchema,
|
|
5
7
|
TaskPrioritySchema,
|
|
6
8
|
TaskRiskSchema,
|
|
@@ -9,21 +11,31 @@ import {
|
|
|
9
11
|
} from '@taskset/contracts'
|
|
10
12
|
import {
|
|
11
13
|
buildTaskIndex,
|
|
14
|
+
createDocument,
|
|
12
15
|
createSnapshot,
|
|
13
16
|
createTask,
|
|
14
17
|
type DerivedTaskRelationships,
|
|
18
|
+
type DocumentBatchOperation,
|
|
15
19
|
deleteTask,
|
|
16
20
|
diagnoseRepository,
|
|
17
21
|
discoverRepository,
|
|
22
|
+
executeDocumentBatch,
|
|
18
23
|
generateViews,
|
|
24
|
+
importDocument,
|
|
19
25
|
initializeRepository,
|
|
26
|
+
listDocuments,
|
|
20
27
|
listSnapshots,
|
|
28
|
+
migrateTaskIds,
|
|
29
|
+
normalizeDocumentKind,
|
|
21
30
|
normalizeRepositoryPath,
|
|
22
31
|
queryTasks,
|
|
23
32
|
RepositoryPathError,
|
|
33
|
+
readDocument,
|
|
24
34
|
readTask,
|
|
25
35
|
restoreSnapshot,
|
|
36
|
+
serializeDocumentFile,
|
|
26
37
|
serializeTaskFile,
|
|
38
|
+
syncRepository,
|
|
27
39
|
TASK_SORT_DIRECTIONS,
|
|
28
40
|
TASK_SORT_KEYS,
|
|
29
41
|
type TaskQuery,
|
|
@@ -47,6 +59,13 @@ const USAGE = `Usage:
|
|
|
47
59
|
taskset task update <task-id> [metadata options]
|
|
48
60
|
taskset task status <task-id> <status> [--json] [--cwd <path>]
|
|
49
61
|
taskset task delete <task-id> [--remove-dependencies] [--json] [--cwd <path>]
|
|
62
|
+
taskset task migrate-ids [--json] [--cwd <path>]
|
|
63
|
+
taskset sync [--concurrency <count>] [--json] [--cwd <path>]
|
|
64
|
+
taskset document create <story|flow|decision|adr|dr|research|runbook> --title <title>
|
|
65
|
+
taskset document import <markdown-path> [--type <type>] [--move]
|
|
66
|
+
taskset document batch <manifest.json> [--concurrency <count>] [--json]
|
|
67
|
+
taskset document list [type] [--json] [--cwd <path>]
|
|
68
|
+
taskset document show <document-id> [--type <type>] [--json] [--cwd <path>]
|
|
50
69
|
|
|
51
70
|
Metadata options:
|
|
52
71
|
--status --priority --order --owner --team --estimate --effort --risk --due-date
|
|
@@ -104,6 +123,65 @@ const CommonValuesSchema = z.strictObject({
|
|
|
104
123
|
json: JsonSchema,
|
|
105
124
|
})
|
|
106
125
|
|
|
126
|
+
const ConcurrencySchema = z.coerce.number().int().min(1).max(32).optional()
|
|
127
|
+
const DocumentKindInputSchema = TrimmedStringSchema.transform((value, context) => {
|
|
128
|
+
try {
|
|
129
|
+
return normalizeDocumentKind(value)
|
|
130
|
+
} catch (error) {
|
|
131
|
+
context.addIssue({
|
|
132
|
+
code: 'custom',
|
|
133
|
+
message: error instanceof Error ? error.message : 'Invalid document type',
|
|
134
|
+
})
|
|
135
|
+
return z.NEVER
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
const DocumentInputSchema = z.strictObject({
|
|
139
|
+
type: DocumentKindInputSchema,
|
|
140
|
+
title: TrimmedStringSchema,
|
|
141
|
+
status: DocumentStatusSchema.optional(),
|
|
142
|
+
labels: StringListSchema.optional(),
|
|
143
|
+
related: StringListSchema.optional(),
|
|
144
|
+
body: z.string().optional(),
|
|
145
|
+
})
|
|
146
|
+
const DocumentUpdateSchema = z.strictObject({
|
|
147
|
+
title: TrimmedStringSchema.optional(),
|
|
148
|
+
status: DocumentStatusSchema.optional(),
|
|
149
|
+
labels: StringListSchema.optional(),
|
|
150
|
+
related: StringListSchema.optional(),
|
|
151
|
+
body: z.string().optional(),
|
|
152
|
+
})
|
|
153
|
+
const DocumentBatchSchema = z
|
|
154
|
+
.array(
|
|
155
|
+
z.discriminatedUnion('action', [
|
|
156
|
+
z.strictObject({ action: z.literal('create'), input: DocumentInputSchema }),
|
|
157
|
+
z.strictObject({
|
|
158
|
+
action: z.literal('import'),
|
|
159
|
+
sourcePath: TrimmedStringSchema,
|
|
160
|
+
options: z
|
|
161
|
+
.strictObject({
|
|
162
|
+
type: DocumentKindInputSchema.optional(),
|
|
163
|
+
title: TrimmedStringSchema.optional(),
|
|
164
|
+
move: z.boolean().optional(),
|
|
165
|
+
})
|
|
166
|
+
.optional(),
|
|
167
|
+
}),
|
|
168
|
+
z.strictObject({
|
|
169
|
+
action: z.literal('update'),
|
|
170
|
+
id: TrimmedStringSchema,
|
|
171
|
+
type: DocumentKindInputSchema.optional(),
|
|
172
|
+
input: DocumentUpdateSchema,
|
|
173
|
+
}),
|
|
174
|
+
z.strictObject({
|
|
175
|
+
action: z.literal('export'),
|
|
176
|
+
id: TrimmedStringSchema,
|
|
177
|
+
type: DocumentKindInputSchema.optional(),
|
|
178
|
+
targetPath: TrimmedStringSchema,
|
|
179
|
+
overwrite: z.boolean().optional(),
|
|
180
|
+
}),
|
|
181
|
+
]),
|
|
182
|
+
)
|
|
183
|
+
.min(1)
|
|
184
|
+
|
|
107
185
|
const CreateValuesSchema = z.strictObject({
|
|
108
186
|
title: TrimmedStringSchema,
|
|
109
187
|
status: TaskStatusSchema.optional(),
|
|
@@ -555,6 +633,33 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
|
|
|
555
633
|
return 0
|
|
556
634
|
}
|
|
557
635
|
|
|
636
|
+
if (command === 'sync') {
|
|
637
|
+
const parsed = parseArgs({
|
|
638
|
+
args: commandArgs,
|
|
639
|
+
allowPositionals: false,
|
|
640
|
+
options: { ...commonOptionDefinitions, concurrency: { type: 'string' } },
|
|
641
|
+
})
|
|
642
|
+
const values = parseSchema(
|
|
643
|
+
z.strictObject({ ...CommonValuesSchema.shape, concurrency: ConcurrencySchema }),
|
|
644
|
+
parsed.values,
|
|
645
|
+
'sync options',
|
|
646
|
+
)
|
|
647
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
648
|
+
const result = await syncRepository(repository, {
|
|
649
|
+
concurrency: values.concurrency,
|
|
650
|
+
onProgress: (progress) =>
|
|
651
|
+
stderr(
|
|
652
|
+
`sync ${progress.phase}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`,
|
|
653
|
+
),
|
|
654
|
+
})
|
|
655
|
+
stdout(
|
|
656
|
+
values.json
|
|
657
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
658
|
+
: `Synced ${result.migrations.length} migrations and generated views\n`,
|
|
659
|
+
)
|
|
660
|
+
return 0
|
|
661
|
+
}
|
|
662
|
+
|
|
558
663
|
if (command === 'snapshot') {
|
|
559
664
|
if (subcommand === 'create' || subcommand === 'list') {
|
|
560
665
|
const parsed = parseArgs({
|
|
@@ -625,10 +730,205 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
|
|
|
625
730
|
throw new CliUsageError(`Unknown snapshot command "${subcommand ?? ''}"`)
|
|
626
731
|
}
|
|
627
732
|
|
|
733
|
+
if (command === 'document' || command === 'doc') {
|
|
734
|
+
if (subcommand === 'batch') {
|
|
735
|
+
const parsed = parseArgs({
|
|
736
|
+
args: subcommandArgs,
|
|
737
|
+
allowPositionals: true,
|
|
738
|
+
options: { ...commonOptionDefinitions, concurrency: { type: 'string' } },
|
|
739
|
+
})
|
|
740
|
+
const [manifestPath] = requirePositionals(
|
|
741
|
+
parsed.positionals,
|
|
742
|
+
1,
|
|
743
|
+
'exactly one batch manifest path',
|
|
744
|
+
)
|
|
745
|
+
const values = parseSchema(
|
|
746
|
+
z.strictObject({ ...CommonValuesSchema.shape, concurrency: ConcurrencySchema }),
|
|
747
|
+
parsed.values,
|
|
748
|
+
'document batch options',
|
|
749
|
+
)
|
|
750
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
751
|
+
const validatedManifestPath = parseSchema(
|
|
752
|
+
z.string(),
|
|
753
|
+
manifestPath,
|
|
754
|
+
'document batch manifest path',
|
|
755
|
+
)
|
|
756
|
+
const raw = JSON.parse(
|
|
757
|
+
await readFile(path.resolve(repository.rootDirectory, validatedManifestPath), 'utf8'),
|
|
758
|
+
) as unknown
|
|
759
|
+
const operations = parseSchema(
|
|
760
|
+
DocumentBatchSchema,
|
|
761
|
+
raw,
|
|
762
|
+
'document batch manifest',
|
|
763
|
+
) as readonly DocumentBatchOperation[]
|
|
764
|
+
const results = await executeDocumentBatch(repository, operations, {
|
|
765
|
+
concurrency: values.concurrency,
|
|
766
|
+
onProgress: (progress) =>
|
|
767
|
+
stderr(
|
|
768
|
+
`document ${progress.action}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`,
|
|
769
|
+
),
|
|
770
|
+
})
|
|
771
|
+
stdout(
|
|
772
|
+
values.json
|
|
773
|
+
? `${JSON.stringify(results, null, 2)}\n`
|
|
774
|
+
: `${results.map((result) => result.relativePath).join('\n')}\n`,
|
|
775
|
+
)
|
|
776
|
+
return 0
|
|
777
|
+
}
|
|
778
|
+
if (subcommand === 'create') {
|
|
779
|
+
const parsed = parseArgs({
|
|
780
|
+
args: subcommandArgs,
|
|
781
|
+
allowPositionals: true,
|
|
782
|
+
options: {
|
|
783
|
+
...commonOptionDefinitions,
|
|
784
|
+
title: { type: 'string' },
|
|
785
|
+
status: { type: 'string' },
|
|
786
|
+
label: { type: 'string', multiple: true },
|
|
787
|
+
related: { type: 'string', multiple: true },
|
|
788
|
+
body: { type: 'string' },
|
|
789
|
+
},
|
|
790
|
+
})
|
|
791
|
+
const [rawType] = requirePositionals(parsed.positionals, 1, 'exactly one document type')
|
|
792
|
+
const values = parseSchema(
|
|
793
|
+
z.strictObject({
|
|
794
|
+
title: TrimmedStringSchema,
|
|
795
|
+
status: DocumentStatusSchema.optional(),
|
|
796
|
+
label: StringListSchema.optional(),
|
|
797
|
+
related: StringListSchema.optional(),
|
|
798
|
+
body: z.string().optional(),
|
|
799
|
+
cwd: CwdSchema,
|
|
800
|
+
json: JsonSchema,
|
|
801
|
+
}),
|
|
802
|
+
parsed.values,
|
|
803
|
+
'document create options',
|
|
804
|
+
)
|
|
805
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
806
|
+
const record = await createDocument(repository, {
|
|
807
|
+
type: normalizeDocumentKind(parseSchema(z.string(), rawType, 'document type')),
|
|
808
|
+
title: values.title,
|
|
809
|
+
...(values.status ? { status: values.status } : {}),
|
|
810
|
+
...(values.label ? { labels: values.label } : {}),
|
|
811
|
+
...(values.related ? { related: values.related } : {}),
|
|
812
|
+
...(values.body !== undefined ? { body: values.body } : {}),
|
|
813
|
+
})
|
|
814
|
+
stdout(
|
|
815
|
+
values.json ? `${JSON.stringify(record, null, 2)}\n` : `${record.document.metadata.id}\n`,
|
|
816
|
+
)
|
|
817
|
+
return 0
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (subcommand === 'import') {
|
|
821
|
+
const parsed = parseArgs({
|
|
822
|
+
args: subcommandArgs,
|
|
823
|
+
allowPositionals: true,
|
|
824
|
+
options: {
|
|
825
|
+
...commonOptionDefinitions,
|
|
826
|
+
type: { type: 'string' },
|
|
827
|
+
title: { type: 'string' },
|
|
828
|
+
move: { type: 'boolean' },
|
|
829
|
+
},
|
|
830
|
+
})
|
|
831
|
+
const [sourcePath] = requirePositionals(parsed.positionals, 1, 'exactly one Markdown path')
|
|
832
|
+
const values = parseSchema(
|
|
833
|
+
z.strictObject({
|
|
834
|
+
type: TrimmedStringSchema.optional(),
|
|
835
|
+
title: TrimmedStringSchema.optional(),
|
|
836
|
+
move: z.boolean().optional(),
|
|
837
|
+
cwd: CwdSchema,
|
|
838
|
+
json: JsonSchema,
|
|
839
|
+
}),
|
|
840
|
+
parsed.values,
|
|
841
|
+
'document import options',
|
|
842
|
+
)
|
|
843
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
844
|
+
const record = await importDocument(
|
|
845
|
+
repository,
|
|
846
|
+
parseSchema(z.string(), sourcePath, 'document import path'),
|
|
847
|
+
{
|
|
848
|
+
...(values.type ? { type: normalizeDocumentKind(values.type) } : {}),
|
|
849
|
+
...(values.title ? { title: values.title } : {}),
|
|
850
|
+
move: values.move,
|
|
851
|
+
},
|
|
852
|
+
)
|
|
853
|
+
stdout(values.json ? `${JSON.stringify(record, null, 2)}\n` : `${record.relativePath}\n`)
|
|
854
|
+
return 0
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (subcommand === 'list') {
|
|
858
|
+
const parsed = parseArgs({
|
|
859
|
+
args: subcommandArgs,
|
|
860
|
+
allowPositionals: true,
|
|
861
|
+
options: commonOptionDefinitions,
|
|
862
|
+
})
|
|
863
|
+
if (parsed.positionals.length > 1)
|
|
864
|
+
throw new CliUsageError('Expected at most one document type')
|
|
865
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'document list options')
|
|
866
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
867
|
+
const records = await listDocuments(
|
|
868
|
+
repository,
|
|
869
|
+
parsed.positionals[0] ? normalizeDocumentKind(parsed.positionals[0]) : undefined,
|
|
870
|
+
)
|
|
871
|
+
if (values.json) stdout(`${JSON.stringify(records, null, 2)}\n`)
|
|
872
|
+
else
|
|
873
|
+
for (const record of records)
|
|
874
|
+
stdout(
|
|
875
|
+
`${record.document.metadata.id}\t${record.document.metadata.type}\t${record.document.metadata.status}\t${record.document.metadata.title}\n`,
|
|
876
|
+
)
|
|
877
|
+
return 0
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (subcommand === 'show') {
|
|
881
|
+
const parsed = parseArgs({
|
|
882
|
+
args: subcommandArgs,
|
|
883
|
+
allowPositionals: true,
|
|
884
|
+
options: { ...commonOptionDefinitions, type: { type: 'string' } },
|
|
885
|
+
})
|
|
886
|
+
const [id] = requirePositionals(parsed.positionals, 1, 'exactly one document ID')
|
|
887
|
+
const values = parseSchema(
|
|
888
|
+
z.strictObject({ ...CommonValuesSchema.shape, type: TrimmedStringSchema.optional() }),
|
|
889
|
+
parsed.values,
|
|
890
|
+
'document show options',
|
|
891
|
+
)
|
|
892
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
893
|
+
const record = await readDocument(
|
|
894
|
+
repository,
|
|
895
|
+
parseSchema(z.string(), id, 'document ID'),
|
|
896
|
+
values.type ? normalizeDocumentKind(values.type) : undefined,
|
|
897
|
+
)
|
|
898
|
+
stdout(
|
|
899
|
+
values.json
|
|
900
|
+
? `${JSON.stringify(record, null, 2)}\n`
|
|
901
|
+
: serializeDocumentFile(record.document),
|
|
902
|
+
)
|
|
903
|
+
return 0
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
throw new CliUsageError(`Unknown document command "${subcommand ?? ''}"`)
|
|
907
|
+
}
|
|
908
|
+
|
|
628
909
|
if (command !== 'task') {
|
|
629
910
|
throw new CliUsageError(`Unknown command "${command}"`)
|
|
630
911
|
}
|
|
631
912
|
|
|
913
|
+
if (subcommand === 'migrate-ids') {
|
|
914
|
+
const parsed = parseArgs({
|
|
915
|
+
args: subcommandArgs,
|
|
916
|
+
allowPositionals: false,
|
|
917
|
+
options: commonOptionDefinitions,
|
|
918
|
+
})
|
|
919
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'task migrate-ids options')
|
|
920
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
921
|
+
const migrations = await migrateTaskIds(repository, {
|
|
922
|
+
onProgress: (progress) =>
|
|
923
|
+
stderr(
|
|
924
|
+
`migration ${progress.phase}: ${progress.completed}/${progress.total} (${progress.percent}%)\n`,
|
|
925
|
+
),
|
|
926
|
+
})
|
|
927
|
+
if (values.json) stdout(`${JSON.stringify(migrations, null, 2)}\n`)
|
|
928
|
+
else for (const migration of migrations) stdout(`${migration.from}\t${migration.to}\n`)
|
|
929
|
+
return 0
|
|
930
|
+
}
|
|
931
|
+
|
|
632
932
|
if (subcommand === 'create') {
|
|
633
933
|
const parsed = parseArgs({
|
|
634
934
|
args: subcommandArgs,
|