trekoon 0.1.9 → 0.2.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/.agents/skills/trekoon/SKILL.md +75 -2
- package/README.md +87 -6
- package/package.json +1 -1
- package/src/commands/arg-parser.ts +82 -0
- package/src/commands/epic.ts +186 -2
- package/src/commands/help.ts +33 -3
- package/src/commands/subtask.ts +186 -2
- package/src/commands/task.ts +186 -2
- package/src/domain/mutation-service.ts +242 -1
- package/src/domain/tracker-domain.ts +171 -0
- package/src/domain/types.ts +27 -0
- package/src/sync/event-writes.ts +21 -1
package/src/commands/subtask.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SEARCH_REPLACE_FIELDS,
|
|
3
|
+
findUnknownOption,
|
|
2
4
|
hasFlag,
|
|
3
5
|
parseArgs,
|
|
6
|
+
parseCsvEnumOption,
|
|
4
7
|
parseStrictNonNegativeInt,
|
|
5
8
|
parseStrictPositiveInt,
|
|
6
9
|
readEnumOption,
|
|
7
10
|
readMissingOptionValue,
|
|
8
11
|
readOption,
|
|
12
|
+
resolvePreviewApplyMode,
|
|
13
|
+
suggestOptions,
|
|
9
14
|
} from "./arg-parser";
|
|
10
15
|
|
|
11
16
|
import { MutationService } from "../domain/mutation-service";
|
|
12
17
|
import { TrackerDomain } from "../domain/tracker-domain";
|
|
13
|
-
import { DomainError, type SubtaskRecord } from "../domain/types";
|
|
18
|
+
import { DomainError, type SearchEntityMatch, type SubtaskRecord } from "../domain/types";
|
|
14
19
|
import { formatHumanTable } from "../io/human-table";
|
|
15
20
|
import { failResult, okResult } from "../io/output";
|
|
16
21
|
import { type CliContext, type CliResult } from "../runtime/command-types";
|
|
@@ -23,6 +28,8 @@ function formatSubtask(subtask: SubtaskRecord): string {
|
|
|
23
28
|
const VIEW_MODES = ["table", "compact"] as const;
|
|
24
29
|
const DEFAULT_SUBTASK_LIST_LIMIT = 10;
|
|
25
30
|
const DEFAULT_OPEN_SUBTASK_STATUSES = ["in_progress", "in-progress", "todo"] as const;
|
|
31
|
+
const SEARCH_OPTIONS = ["fields", "preview"] as const;
|
|
32
|
+
const REPLACE_OPTIONS = ["search", "replace", "fields", "preview", "apply"] as const;
|
|
26
33
|
|
|
27
34
|
function parseIdsOption(rawIds: string | undefined): string[] {
|
|
28
35
|
if (rawIds === undefined) {
|
|
@@ -35,6 +42,55 @@ function parseIdsOption(rawIds: string | undefined): string[] {
|
|
|
35
42
|
.filter((value) => value.length > 0);
|
|
36
43
|
}
|
|
37
44
|
|
|
45
|
+
function prefixedOptions(options: readonly string[]): string[] {
|
|
46
|
+
return options.map((option) => `--${option}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function unknownOption(command: string, option: string, allowedOptions: readonly string[]): CliResult {
|
|
50
|
+
const suggestions = suggestOptions(option, allowedOptions).map((suggestion) => `--${suggestion}`);
|
|
51
|
+
const suggestionMessage = suggestions.length > 0 ? ` Did you mean ${suggestions.join(" or ")}?` : "";
|
|
52
|
+
return failResult({
|
|
53
|
+
command,
|
|
54
|
+
human: `Unknown option --${option}.${suggestionMessage}`,
|
|
55
|
+
data: {
|
|
56
|
+
option: `--${option}`,
|
|
57
|
+
allowedOptions: prefixedOptions(allowedOptions),
|
|
58
|
+
suggestions,
|
|
59
|
+
},
|
|
60
|
+
error: {
|
|
61
|
+
code: "unknown_option",
|
|
62
|
+
message: `Unknown option --${option}`,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function invalidSearchInput(command: string, human: string, message: string, data: Record<string, unknown>): CliResult {
|
|
68
|
+
return failResult({
|
|
69
|
+
command,
|
|
70
|
+
human,
|
|
71
|
+
data,
|
|
72
|
+
error: {
|
|
73
|
+
code: "invalid_input",
|
|
74
|
+
message,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function formatSearchHuman(matches: readonly SearchEntityMatch[], emptyMessage: string): string {
|
|
80
|
+
if (matches.length === 0) {
|
|
81
|
+
return emptyMessage;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return matches
|
|
85
|
+
.map(
|
|
86
|
+
(match) =>
|
|
87
|
+
`${match.kind} ${match.id}: ${match.fields
|
|
88
|
+
.map((field) => `${field.field}(${field.count}) "${field.snippet}"`)
|
|
89
|
+
.join(", ")}`,
|
|
90
|
+
)
|
|
91
|
+
.join("\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
38
94
|
function parseStatusCsv(rawStatuses: string | undefined): string[] | undefined {
|
|
39
95
|
if (rawStatuses === undefined) {
|
|
40
96
|
return undefined;
|
|
@@ -348,6 +404,134 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
348
404
|
}),
|
|
349
405
|
});
|
|
350
406
|
}
|
|
407
|
+
case "search": {
|
|
408
|
+
const searchUnknownOption = findUnknownOption(parsed, SEARCH_OPTIONS);
|
|
409
|
+
if (searchUnknownOption !== undefined) {
|
|
410
|
+
return unknownOption("subtask.search", searchUnknownOption, SEARCH_OPTIONS);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const missingSearchOption = readMissingOptionValue(parsed.missingOptionValues, "fields");
|
|
414
|
+
if (missingSearchOption !== undefined) {
|
|
415
|
+
return failMissingOptionValue("subtask.search", missingSearchOption);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const subtaskId: string = parsed.positional[1] ?? "";
|
|
419
|
+
const searchText: string = parsed.positional[2] ?? "";
|
|
420
|
+
if (subtaskId.length === 0 || searchText.trim().length === 0) {
|
|
421
|
+
return invalidSearchInput(
|
|
422
|
+
"subtask.search",
|
|
423
|
+
"Usage: trekoon subtask search <subtask-id> \"search text\" [--fields <csv>] [--preview]",
|
|
424
|
+
"Missing search target",
|
|
425
|
+
{
|
|
426
|
+
subtaskId,
|
|
427
|
+
},
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const parsedFields = parseCsvEnumOption(readOption(parsed.options, "fields"), SEARCH_REPLACE_FIELDS);
|
|
432
|
+
if (parsedFields.empty || parsedFields.invalidValues.length > 0) {
|
|
433
|
+
return invalidSearchInput("subtask.search", "Invalid --fields value. Use title, description, or title,description.", "Invalid --fields value", {
|
|
434
|
+
fields: readOption(parsed.options, "fields"),
|
|
435
|
+
invalidFields: parsedFields.invalidValues,
|
|
436
|
+
allowedFields: [...SEARCH_REPLACE_FIELDS],
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const { matches, summary } = domain.searchSubtaskScope(subtaskId, searchText, parsedFields.values);
|
|
441
|
+
|
|
442
|
+
return okResult({
|
|
443
|
+
command: "subtask.search",
|
|
444
|
+
human: formatSearchHuman(matches, "No matches found."),
|
|
445
|
+
data: {
|
|
446
|
+
scope: {
|
|
447
|
+
kind: "subtask",
|
|
448
|
+
id: subtaskId,
|
|
449
|
+
},
|
|
450
|
+
query: {
|
|
451
|
+
search: searchText,
|
|
452
|
+
fields: parsedFields.values,
|
|
453
|
+
mode: "preview",
|
|
454
|
+
},
|
|
455
|
+
summary,
|
|
456
|
+
matches,
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
case "replace": {
|
|
461
|
+
const replaceUnknownOption = findUnknownOption(parsed, REPLACE_OPTIONS);
|
|
462
|
+
if (replaceUnknownOption !== undefined) {
|
|
463
|
+
return unknownOption("subtask.replace", replaceUnknownOption, REPLACE_OPTIONS);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const missingReplaceOption =
|
|
467
|
+
readMissingOptionValue(parsed.missingOptionValues, "search") ??
|
|
468
|
+
readMissingOptionValue(parsed.missingOptionValues, "replace") ??
|
|
469
|
+
readMissingOptionValue(parsed.missingOptionValues, "fields");
|
|
470
|
+
if (missingReplaceOption !== undefined) {
|
|
471
|
+
return failMissingOptionValue("subtask.replace", missingReplaceOption);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const subtaskId: string = parsed.positional[1] ?? "";
|
|
475
|
+
const searchText = readOption(parsed.options, "search") ?? "";
|
|
476
|
+
const replacementText = readOption(parsed.options, "replace") ?? "";
|
|
477
|
+
if (subtaskId.length === 0 || searchText.trim().length === 0) {
|
|
478
|
+
return invalidSearchInput(
|
|
479
|
+
"subtask.replace",
|
|
480
|
+
"Usage: trekoon subtask replace <subtask-id> --search \"text\" --replace \"text\" [--fields <csv>] [--preview|--apply]",
|
|
481
|
+
"Missing replace target",
|
|
482
|
+
{
|
|
483
|
+
subtaskId,
|
|
484
|
+
search: searchText,
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const rawFields = readOption(parsed.options, "fields");
|
|
490
|
+
const parsedFields = parseCsvEnumOption(rawFields, SEARCH_REPLACE_FIELDS);
|
|
491
|
+
if (parsedFields.empty || parsedFields.invalidValues.length > 0) {
|
|
492
|
+
return invalidSearchInput("subtask.replace", "Invalid --fields value. Use title, description, or title,description.", "Invalid --fields value", {
|
|
493
|
+
fields: rawFields,
|
|
494
|
+
invalidFields: parsedFields.invalidValues,
|
|
495
|
+
allowedFields: [...SEARCH_REPLACE_FIELDS],
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const previewMode = resolvePreviewApplyMode(parsed.flags);
|
|
500
|
+
if (previewMode.conflict) {
|
|
501
|
+
return invalidSearchInput("subtask.replace", "Use either --preview or --apply, not both.", "Conflicting mode flags", {
|
|
502
|
+
flags: ["preview", "apply"],
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const replacementSummary = previewMode.mode === "apply"
|
|
507
|
+
? mutations.applySubtaskReplacement(subtaskId, searchText, replacementText, parsedFields.values)
|
|
508
|
+
: mutations.previewSubtaskReplacement(subtaskId, searchText, replacementText, parsedFields.values);
|
|
509
|
+
const { matches, summary: matchSummary } = replacementSummary;
|
|
510
|
+
|
|
511
|
+
const summary = {
|
|
512
|
+
...matchSummary,
|
|
513
|
+
mode: previewMode.mode,
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
return okResult({
|
|
517
|
+
command: "subtask.replace",
|
|
518
|
+
human: formatSearchHuman(matches, `No ${previewMode.mode === "apply" ? "replacements" : "matches"} found.`),
|
|
519
|
+
data: {
|
|
520
|
+
scope: {
|
|
521
|
+
kind: "subtask",
|
|
522
|
+
id: subtaskId,
|
|
523
|
+
},
|
|
524
|
+
query: {
|
|
525
|
+
search: searchText,
|
|
526
|
+
replace: replacementText,
|
|
527
|
+
fields: parsedFields.values,
|
|
528
|
+
mode: previewMode.mode,
|
|
529
|
+
},
|
|
530
|
+
summary,
|
|
531
|
+
matches,
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
}
|
|
351
535
|
case "update": {
|
|
352
536
|
const missingUpdateOption =
|
|
353
537
|
readMissingOptionValue(parsed.missingOptionValues, "ids") ??
|
|
@@ -485,7 +669,7 @@ export async function runSubtask(context: CliContext): Promise<CliResult> {
|
|
|
485
669
|
default:
|
|
486
670
|
return failResult({
|
|
487
671
|
command: "subtask",
|
|
488
|
-
human: "Usage: trekoon subtask <create|list|update|delete>",
|
|
672
|
+
human: "Usage: trekoon subtask <create|list|search|replace|update|delete>",
|
|
489
673
|
data: {
|
|
490
674
|
args: context.args,
|
|
491
675
|
},
|
package/src/commands/task.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SEARCH_REPLACE_FIELDS,
|
|
3
|
+
findUnknownOption,
|
|
2
4
|
hasFlag,
|
|
3
5
|
parseArgs,
|
|
6
|
+
parseCsvEnumOption,
|
|
4
7
|
parseStrictNonNegativeInt,
|
|
5
8
|
parseStrictPositiveInt,
|
|
6
9
|
readEnumOption,
|
|
7
10
|
readMissingOptionValue,
|
|
8
11
|
readOption,
|
|
12
|
+
resolvePreviewApplyMode,
|
|
13
|
+
suggestOptions,
|
|
9
14
|
} from "./arg-parser";
|
|
10
15
|
|
|
11
16
|
import { MutationService } from "../domain/mutation-service";
|
|
12
17
|
import { TrackerDomain } from "../domain/tracker-domain";
|
|
13
|
-
import { DomainError, type TaskRecord } from "../domain/types";
|
|
18
|
+
import { DomainError, type SearchEntityMatch, type TaskRecord } from "../domain/types";
|
|
14
19
|
import { formatHumanTable } from "../io/human-table";
|
|
15
20
|
import { failResult, okResult } from "../io/output";
|
|
16
21
|
import { type CliContext, type CliResult } from "../runtime/command-types";
|
|
@@ -26,6 +31,8 @@ const DEFAULT_TASK_LIST_LIMIT = 10;
|
|
|
26
31
|
const DEFAULT_OPEN_TASK_STATUSES = ["in_progress", "in-progress", "todo"] as const;
|
|
27
32
|
const READY_REASON_READY = "all_dependencies_done";
|
|
28
33
|
const READY_REASON_BLOCKED = "blocked_by_dependencies";
|
|
34
|
+
const SEARCH_OPTIONS = ["fields", "preview"] as const;
|
|
35
|
+
const REPLACE_OPTIONS = ["search", "replace", "fields", "preview", "apply"] as const;
|
|
29
36
|
|
|
30
37
|
interface DependencyBlocker {
|
|
31
38
|
readonly id: string;
|
|
@@ -81,6 +88,55 @@ function parseIdsOption(rawIds: string | undefined): string[] {
|
|
|
81
88
|
.filter((value) => value.length > 0);
|
|
82
89
|
}
|
|
83
90
|
|
|
91
|
+
function prefixedOptions(options: readonly string[]): string[] {
|
|
92
|
+
return options.map((option) => `--${option}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function unknownOption(command: string, option: string, allowedOptions: readonly string[]): CliResult {
|
|
96
|
+
const suggestions = suggestOptions(option, allowedOptions).map((suggestion) => `--${suggestion}`);
|
|
97
|
+
const suggestionMessage = suggestions.length > 0 ? ` Did you mean ${suggestions.join(" or ")}?` : "";
|
|
98
|
+
return failResult({
|
|
99
|
+
command,
|
|
100
|
+
human: `Unknown option --${option}.${suggestionMessage}`,
|
|
101
|
+
data: {
|
|
102
|
+
option: `--${option}`,
|
|
103
|
+
allowedOptions: prefixedOptions(allowedOptions),
|
|
104
|
+
suggestions,
|
|
105
|
+
},
|
|
106
|
+
error: {
|
|
107
|
+
code: "unknown_option",
|
|
108
|
+
message: `Unknown option --${option}`,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function invalidSearchInput(command: string, human: string, message: string, data: Record<string, unknown>): CliResult {
|
|
114
|
+
return failResult({
|
|
115
|
+
command,
|
|
116
|
+
human,
|
|
117
|
+
data,
|
|
118
|
+
error: {
|
|
119
|
+
code: "invalid_input",
|
|
120
|
+
message,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatSearchHuman(matches: readonly SearchEntityMatch[], emptyMessage: string): string {
|
|
126
|
+
if (matches.length === 0) {
|
|
127
|
+
return emptyMessage;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return matches
|
|
131
|
+
.map(
|
|
132
|
+
(match) =>
|
|
133
|
+
`${match.kind} ${match.id}: ${match.fields
|
|
134
|
+
.map((field) => `${field.field}(${field.count}) "${field.snippet}"`)
|
|
135
|
+
.join(", ")}`,
|
|
136
|
+
)
|
|
137
|
+
.join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
84
140
|
function parseStatusCsv(rawStatuses: string | undefined): string[] | undefined {
|
|
85
141
|
if (rawStatuses === undefined) {
|
|
86
142
|
return undefined;
|
|
@@ -763,6 +819,134 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
763
819
|
},
|
|
764
820
|
});
|
|
765
821
|
}
|
|
822
|
+
case "search": {
|
|
823
|
+
const searchUnknownOption = findUnknownOption(parsed, SEARCH_OPTIONS);
|
|
824
|
+
if (searchUnknownOption !== undefined) {
|
|
825
|
+
return unknownOption("task.search", searchUnknownOption, SEARCH_OPTIONS);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const missingSearchOption = readMissingOptionValue(parsed.missingOptionValues, "fields");
|
|
829
|
+
if (missingSearchOption !== undefined) {
|
|
830
|
+
return failMissingOptionValue("task.search", missingSearchOption);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const taskId: string = parsed.positional[1] ?? "";
|
|
834
|
+
const searchText: string = parsed.positional[2] ?? "";
|
|
835
|
+
if (taskId.length === 0 || searchText.trim().length === 0) {
|
|
836
|
+
return invalidSearchInput(
|
|
837
|
+
"task.search",
|
|
838
|
+
"Usage: trekoon task search <task-id> \"search text\" [--fields <csv>] [--preview]",
|
|
839
|
+
"Missing search target",
|
|
840
|
+
{
|
|
841
|
+
taskId,
|
|
842
|
+
},
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const parsedFields = parseCsvEnumOption(readOption(parsed.options, "fields"), SEARCH_REPLACE_FIELDS);
|
|
847
|
+
if (parsedFields.empty || parsedFields.invalidValues.length > 0) {
|
|
848
|
+
return invalidSearchInput("task.search", "Invalid --fields value. Use title, description, or title,description.", "Invalid --fields value", {
|
|
849
|
+
fields: readOption(parsed.options, "fields"),
|
|
850
|
+
invalidFields: parsedFields.invalidValues,
|
|
851
|
+
allowedFields: [...SEARCH_REPLACE_FIELDS],
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const { matches, summary } = domain.searchTaskScope(taskId, searchText, parsedFields.values);
|
|
856
|
+
|
|
857
|
+
return okResult({
|
|
858
|
+
command: "task.search",
|
|
859
|
+
human: formatSearchHuman(matches, "No matches found."),
|
|
860
|
+
data: {
|
|
861
|
+
scope: {
|
|
862
|
+
kind: "task",
|
|
863
|
+
id: taskId,
|
|
864
|
+
},
|
|
865
|
+
query: {
|
|
866
|
+
search: searchText,
|
|
867
|
+
fields: parsedFields.values,
|
|
868
|
+
mode: "preview",
|
|
869
|
+
},
|
|
870
|
+
summary,
|
|
871
|
+
matches,
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
case "replace": {
|
|
876
|
+
const replaceUnknownOption = findUnknownOption(parsed, REPLACE_OPTIONS);
|
|
877
|
+
if (replaceUnknownOption !== undefined) {
|
|
878
|
+
return unknownOption("task.replace", replaceUnknownOption, REPLACE_OPTIONS);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const missingReplaceOption =
|
|
882
|
+
readMissingOptionValue(parsed.missingOptionValues, "search") ??
|
|
883
|
+
readMissingOptionValue(parsed.missingOptionValues, "replace") ??
|
|
884
|
+
readMissingOptionValue(parsed.missingOptionValues, "fields");
|
|
885
|
+
if (missingReplaceOption !== undefined) {
|
|
886
|
+
return failMissingOptionValue("task.replace", missingReplaceOption);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const taskId: string = parsed.positional[1] ?? "";
|
|
890
|
+
const searchText = readOption(parsed.options, "search") ?? "";
|
|
891
|
+
const replacementText = readOption(parsed.options, "replace") ?? "";
|
|
892
|
+
if (taskId.length === 0 || searchText.trim().length === 0) {
|
|
893
|
+
return invalidSearchInput(
|
|
894
|
+
"task.replace",
|
|
895
|
+
"Usage: trekoon task replace <task-id> --search \"text\" --replace \"text\" [--fields <csv>] [--preview|--apply]",
|
|
896
|
+
"Missing replace target",
|
|
897
|
+
{
|
|
898
|
+
taskId,
|
|
899
|
+
search: searchText,
|
|
900
|
+
},
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const rawFields = readOption(parsed.options, "fields");
|
|
905
|
+
const parsedFields = parseCsvEnumOption(rawFields, SEARCH_REPLACE_FIELDS);
|
|
906
|
+
if (parsedFields.empty || parsedFields.invalidValues.length > 0) {
|
|
907
|
+
return invalidSearchInput("task.replace", "Invalid --fields value. Use title, description, or title,description.", "Invalid --fields value", {
|
|
908
|
+
fields: rawFields,
|
|
909
|
+
invalidFields: parsedFields.invalidValues,
|
|
910
|
+
allowedFields: [...SEARCH_REPLACE_FIELDS],
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const previewMode = resolvePreviewApplyMode(parsed.flags);
|
|
915
|
+
if (previewMode.conflict) {
|
|
916
|
+
return invalidSearchInput("task.replace", "Use either --preview or --apply, not both.", "Conflicting mode flags", {
|
|
917
|
+
flags: ["preview", "apply"],
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const replacementSummary = previewMode.mode === "apply"
|
|
922
|
+
? mutations.applyTaskReplacement(taskId, searchText, replacementText, parsedFields.values)
|
|
923
|
+
: mutations.previewTaskReplacement(taskId, searchText, replacementText, parsedFields.values);
|
|
924
|
+
const { matches, summary: matchSummary } = replacementSummary;
|
|
925
|
+
|
|
926
|
+
const summary = {
|
|
927
|
+
...matchSummary,
|
|
928
|
+
mode: previewMode.mode,
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
return okResult({
|
|
932
|
+
command: "task.replace",
|
|
933
|
+
human: formatSearchHuman(matches, `No ${previewMode.mode === "apply" ? "replacements" : "matches"} found.`),
|
|
934
|
+
data: {
|
|
935
|
+
scope: {
|
|
936
|
+
kind: "task",
|
|
937
|
+
id: taskId,
|
|
938
|
+
},
|
|
939
|
+
query: {
|
|
940
|
+
search: searchText,
|
|
941
|
+
replace: replacementText,
|
|
942
|
+
fields: parsedFields.values,
|
|
943
|
+
mode: previewMode.mode,
|
|
944
|
+
},
|
|
945
|
+
summary,
|
|
946
|
+
matches,
|
|
947
|
+
},
|
|
948
|
+
});
|
|
949
|
+
}
|
|
766
950
|
case "update": {
|
|
767
951
|
const missingUpdateOption =
|
|
768
952
|
readMissingOptionValue(parsed.missingOptionValues, "ids") ??
|
|
@@ -900,7 +1084,7 @@ export async function runTask(context: CliContext): Promise<CliResult> {
|
|
|
900
1084
|
default:
|
|
901
1085
|
return failResult({
|
|
902
1086
|
command: "task",
|
|
903
|
-
human: "Usage: trekoon task <create|list|show|ready|next|update|delete>",
|
|
1087
|
+
human: "Usage: trekoon task <create|list|show|ready|next|search|replace|update|delete>",
|
|
904
1088
|
data: {
|
|
905
1089
|
args: context.args,
|
|
906
1090
|
},
|