ralphctl 0.1.2 → 0.1.4
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/dist/{add-HGJCLWED.mjs → add-7LBVENXM.mjs} +6 -4
- package/dist/{add-MRGCS3US.mjs → add-DVEYDCTR.mjs} +6 -4
- package/dist/{chunk-NTWO2LXB.mjs → chunk-7LZ6GOGN.mjs} +13 -12
- package/dist/{chunk-JON4GCLR.mjs → chunk-DZ6HHTM5.mjs} +1 -1
- package/dist/chunk-EDJX7TT6.mjs +148 -0
- package/dist/{chunk-MNMQC36F.mjs → chunk-F2MMCTB5.mjs} +71 -77
- package/dist/{chunk-EKMZZRWI.mjs → chunk-LFDW6MWF.mjs} +65 -70
- package/dist/{chunk-LOR7QBXX.mjs → chunk-M7JV6MKD.mjs} +270 -349
- package/dist/chunk-OEUJDSHY.mjs +27 -0
- package/dist/{chunk-WGHJI3OI.mjs → chunk-PDI6HBZ7.mjs} +32 -37
- package/dist/{chunk-6PYTKGB5.mjs → chunk-W3TY22IS.mjs} +45 -39
- package/dist/{chunk-MRKOFVTM.mjs → chunk-YIB7QYU4.mjs} +102 -100
- package/dist/cli.mjs +761 -739
- package/dist/create-MQ4OHZAX.mjs +12 -0
- package/dist/{handle-UG5M2OON.mjs → handle-K2AZLTKU.mjs} +1 -1
- package/dist/{project-NT3L4FTB.mjs → project-Q4LKML42.mjs} +6 -4
- package/dist/{resolver-WSFWKACM.mjs → resolver-NH34HTB6.mjs} +27 -17
- package/dist/{sprint-4VHDLGFN.mjs → sprint-UHYXSEBJ.mjs} +8 -5
- package/dist/{wizard-LRELAN2J.mjs → wizard-MCDDXLGE.mjs} +45 -48
- package/package.json +2 -1
- package/dist/create-MG7E7PLQ.mjs +0 -10
package/dist/cli.mjs
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
addCheckScriptToRepository,
|
|
4
4
|
projectAddCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YIB7QYU4.mjs";
|
|
6
6
|
import {
|
|
7
|
-
TaskNotFoundError,
|
|
8
7
|
addTask,
|
|
9
8
|
areAllTasksDone,
|
|
10
9
|
branchExists,
|
|
@@ -50,16 +49,14 @@ import {
|
|
|
50
49
|
sprintStartCommand,
|
|
51
50
|
updateTaskStatus,
|
|
52
51
|
validateImportTasks
|
|
53
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-M7JV6MKD.mjs";
|
|
54
53
|
import {
|
|
55
54
|
escapableSelect
|
|
56
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-7LZ6GOGN.mjs";
|
|
57
56
|
import {
|
|
58
57
|
sprintCreateCommand
|
|
59
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-DZ6HHTM5.mjs";
|
|
60
59
|
import {
|
|
61
|
-
IssueFetchError,
|
|
62
|
-
TicketNotFoundError,
|
|
63
60
|
addTicket,
|
|
64
61
|
allRequirementsApproved,
|
|
65
62
|
editorInput,
|
|
@@ -73,23 +70,19 @@ import {
|
|
|
73
70
|
removeTicket,
|
|
74
71
|
ticketAddCommand,
|
|
75
72
|
updateTicket
|
|
76
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-F2MMCTB5.mjs";
|
|
77
74
|
import {
|
|
78
75
|
EXIT_ERROR,
|
|
79
76
|
exitWithCode
|
|
80
77
|
} from "./chunk-7TG3EAQ2.mjs";
|
|
81
78
|
import {
|
|
82
|
-
ProjectNotFoundError,
|
|
83
79
|
addProjectRepo,
|
|
84
80
|
getProject,
|
|
85
81
|
listProjects,
|
|
86
82
|
removeProject,
|
|
87
83
|
removeProjectRepo
|
|
88
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-PDI6HBZ7.mjs";
|
|
89
85
|
import {
|
|
90
|
-
NoCurrentSprintError,
|
|
91
|
-
SprintNotFoundError,
|
|
92
|
-
SprintStatusError,
|
|
93
86
|
assertSprintStatus,
|
|
94
87
|
closeSprint,
|
|
95
88
|
deleteSprint,
|
|
@@ -108,7 +101,11 @@ import {
|
|
|
108
101
|
setCurrentSprint,
|
|
109
102
|
setEditor,
|
|
110
103
|
withFileLock
|
|
111
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-LFDW6MWF.mjs";
|
|
105
|
+
import {
|
|
106
|
+
ensureError,
|
|
107
|
+
wrapAsync
|
|
108
|
+
} from "./chunk-OEUJDSHY.mjs";
|
|
112
109
|
import {
|
|
113
110
|
AiProviderSchema,
|
|
114
111
|
IdeateOutputSchema,
|
|
@@ -130,7 +127,16 @@ import {
|
|
|
130
127
|
getTasksFilePath,
|
|
131
128
|
readValidatedJson,
|
|
132
129
|
validateProjectPath
|
|
133
|
-
} from "./chunk-
|
|
130
|
+
} from "./chunk-W3TY22IS.mjs";
|
|
131
|
+
import {
|
|
132
|
+
DomainError,
|
|
133
|
+
NoCurrentSprintError,
|
|
134
|
+
ProjectNotFoundError,
|
|
135
|
+
SprintNotFoundError,
|
|
136
|
+
SprintStatusError,
|
|
137
|
+
TaskNotFoundError,
|
|
138
|
+
TicketNotFoundError
|
|
139
|
+
} from "./chunk-EDJX7TT6.mjs";
|
|
134
140
|
import {
|
|
135
141
|
DETAIL_LABEL_WIDTH,
|
|
136
142
|
badge,
|
|
@@ -433,7 +439,7 @@ function buildSubMenu(group, ctx) {
|
|
|
433
439
|
async function loadDashboardData() {
|
|
434
440
|
const sprintId = await getCurrentSprint();
|
|
435
441
|
if (!sprintId) return null;
|
|
436
|
-
|
|
442
|
+
const r = await wrapAsync(async () => {
|
|
437
443
|
const sprint = await getSprint(sprintId);
|
|
438
444
|
const tasks = await getTasks(sprintId);
|
|
439
445
|
const pendingTickets = getPendingRequirements(sprint.tickets);
|
|
@@ -447,9 +453,8 @@ async function loadDashboardData() {
|
|
|
447
453
|
const plannedTicketCount = sprint.tickets.filter((t) => ticketIdsWithTasks.has(t.id)).length;
|
|
448
454
|
const aiProvider = await getAiProvider();
|
|
449
455
|
return { sprint, tasks, approvedCount, pendingCount, blockedCount, plannedTicketCount, aiProvider };
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
}
|
|
456
|
+
}, ensureError);
|
|
457
|
+
return r.ok ? r.value : null;
|
|
453
458
|
}
|
|
454
459
|
function getNextAction(data) {
|
|
455
460
|
const { sprint, tasks, pendingCount, approvedCount } = data;
|
|
@@ -640,34 +645,35 @@ async function projectShowCommand(args) {
|
|
|
640
645
|
if (!selected) return;
|
|
641
646
|
projectName = selected;
|
|
642
647
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
infoLines.push(labelValue("Description", project.description));
|
|
648
|
-
}
|
|
649
|
-
infoLines.push(labelValue("Repositories", String(project.repositories.length)));
|
|
650
|
-
log.newline();
|
|
651
|
-
console.log(renderCard(`${icons.project} ${project.displayName}`, infoLines));
|
|
652
|
-
for (const repo of project.repositories) {
|
|
648
|
+
const projectR = await wrapAsync(() => getProject(projectName), ensureError);
|
|
649
|
+
if (!projectR.ok) {
|
|
650
|
+
if (projectR.error instanceof ProjectNotFoundError) {
|
|
651
|
+
showError(`Project not found: ${projectName}`);
|
|
653
652
|
log.newline();
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
repoLines.push(labelValue("Check", colors.info(repo.checkScript)));
|
|
657
|
-
} else {
|
|
658
|
-
repoLines.push(muted("No check script configured"));
|
|
659
|
-
}
|
|
660
|
-
console.log(renderCard(` ${repo.name}`, repoLines));
|
|
653
|
+
} else {
|
|
654
|
+
throw projectR.error;
|
|
661
655
|
}
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const project = projectR.value;
|
|
659
|
+
const infoLines = [labelValue("Name", project.name), labelValue("Display Name", project.displayName)];
|
|
660
|
+
if (project.description) {
|
|
661
|
+
infoLines.push(labelValue("Description", project.description));
|
|
662
|
+
}
|
|
663
|
+
infoLines.push(labelValue("Repositories", String(project.repositories.length)));
|
|
664
|
+
log.newline();
|
|
665
|
+
console.log(renderCard(`${icons.project} ${project.displayName}`, infoLines));
|
|
666
|
+
for (const repo of project.repositories) {
|
|
662
667
|
log.newline();
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
|
|
666
|
-
log.newline();
|
|
668
|
+
const repoLines = [labelValue("Path", repo.path)];
|
|
669
|
+
if (repo.checkScript) {
|
|
670
|
+
repoLines.push(labelValue("Check", colors.info(repo.checkScript)));
|
|
667
671
|
} else {
|
|
668
|
-
|
|
672
|
+
repoLines.push(muted("No check script configured"));
|
|
669
673
|
}
|
|
674
|
+
console.log(renderCard(` ${repo.name}`, repoLines));
|
|
670
675
|
}
|
|
676
|
+
log.newline();
|
|
671
677
|
}
|
|
672
678
|
|
|
673
679
|
// src/commands/project/remove.ts
|
|
@@ -680,29 +686,30 @@ async function projectRemoveCommand(args) {
|
|
|
680
686
|
if (!selected) return;
|
|
681
687
|
projectName = selected;
|
|
682
688
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (
|
|
686
|
-
const confirmed = await confirm({
|
|
687
|
-
message: `Remove project "${project.displayName}" (${project.name})?`,
|
|
688
|
-
default: false
|
|
689
|
-
});
|
|
690
|
-
if (!confirmed) {
|
|
691
|
-
console.log(muted("\nProject removal cancelled.\n"));
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
await removeProject(projectName);
|
|
696
|
-
showSuccess("Project removed", [["Name", projectName]]);
|
|
697
|
-
console.log("");
|
|
698
|
-
} catch (err) {
|
|
699
|
-
if (err instanceof ProjectNotFoundError) {
|
|
689
|
+
const projectR = await wrapAsync(() => getProject(projectName), ensureError);
|
|
690
|
+
if (!projectR.ok) {
|
|
691
|
+
if (projectR.error instanceof ProjectNotFoundError) {
|
|
700
692
|
showError(`Project not found: ${projectName}`);
|
|
701
693
|
console.log("");
|
|
702
694
|
} else {
|
|
703
|
-
throw
|
|
695
|
+
throw projectR.error;
|
|
696
|
+
}
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const project = projectR.value;
|
|
700
|
+
if (!skipConfirm) {
|
|
701
|
+
const confirmed = await confirm({
|
|
702
|
+
message: `Remove project "${project.displayName}" (${project.name})?`,
|
|
703
|
+
default: false
|
|
704
|
+
});
|
|
705
|
+
if (!confirmed) {
|
|
706
|
+
console.log(muted("\nProject removal cancelled.\n"));
|
|
707
|
+
return;
|
|
704
708
|
}
|
|
705
709
|
}
|
|
710
|
+
await removeProject(projectName);
|
|
711
|
+
showSuccess("Project removed", [["Name", projectName]]);
|
|
712
|
+
console.log("");
|
|
706
713
|
}
|
|
707
714
|
|
|
708
715
|
// src/commands/project/repo.ts
|
|
@@ -720,31 +727,28 @@ async function projectRepoAddCommand(args) {
|
|
|
720
727
|
message: `${emoji.donut} Repository path to add:`,
|
|
721
728
|
validate: (v) => v.trim().length > 0 ? true : "Path is required"
|
|
722
729
|
});
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
log.info(`
|
|
730
|
+
const resolvedPath = resolve(expandTilde(path));
|
|
731
|
+
const bareRepo = { name: basename(resolvedPath), path: resolvedPath };
|
|
732
|
+
log.info(`
|
|
727
733
|
Configuring: ${bareRepo.name}`);
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
log.info("Current repositories:");
|
|
733
|
-
for (const repo of project.repositories) {
|
|
734
|
-
log.item(`${repo.name} \u2192 ${repo.path}`);
|
|
735
|
-
}
|
|
736
|
-
log.newline();
|
|
737
|
-
} catch (err) {
|
|
738
|
-
if (err instanceof ProjectNotFoundError) {
|
|
734
|
+
const repoWithScripts = await addCheckScriptToRepository(bareRepo);
|
|
735
|
+
const addR = await wrapAsync(() => addProjectRepo(projectName, repoWithScripts), ensureError);
|
|
736
|
+
if (!addR.ok) {
|
|
737
|
+
if (addR.error instanceof ProjectNotFoundError) {
|
|
739
738
|
showError(`Project not found: ${projectName}`);
|
|
740
|
-
log.newline();
|
|
741
|
-
} else if (err instanceof Error) {
|
|
742
|
-
showError(err.message);
|
|
743
|
-
log.newline();
|
|
744
739
|
} else {
|
|
745
|
-
|
|
740
|
+
showError(addR.error.message);
|
|
746
741
|
}
|
|
742
|
+
log.newline();
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
showSuccess("Repository added", [["Project", projectName]]);
|
|
746
|
+
log.newline();
|
|
747
|
+
log.info("Current repositories:");
|
|
748
|
+
for (const repo of addR.value.repositories) {
|
|
749
|
+
log.item(`${repo.name} \u2192 ${repo.path}`);
|
|
747
750
|
}
|
|
751
|
+
log.newline();
|
|
748
752
|
}
|
|
749
753
|
async function projectRepoRemoveCommand(args) {
|
|
750
754
|
const skipConfirm = args.includes("-y") || args.includes("--yes");
|
|
@@ -756,50 +760,57 @@ async function projectRepoRemoveCommand(args) {
|
|
|
756
760
|
if (!selected) return;
|
|
757
761
|
projectName = selected;
|
|
758
762
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
if (
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
path = await select({
|
|
767
|
-
message: `${emoji.donut} Select repository to remove:`,
|
|
768
|
-
choices: project.repositories.map((r) => ({
|
|
769
|
-
name: `${r.name} (${r.path})`,
|
|
770
|
-
value: r.path
|
|
771
|
-
}))
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
if (!skipConfirm) {
|
|
775
|
-
const confirmed = await confirm2({
|
|
776
|
-
message: `Remove repository "${path}" from project "${project.displayName}"?`,
|
|
777
|
-
default: false
|
|
778
|
-
});
|
|
779
|
-
if (!confirmed) {
|
|
780
|
-
console.log(muted("\nRepository removal cancelled.\n"));
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
763
|
+
const projectR = await wrapAsync(() => getProject(projectName), ensureError);
|
|
764
|
+
if (!projectR.ok) {
|
|
765
|
+
if (projectR.error instanceof ProjectNotFoundError) {
|
|
766
|
+
showError(`Project not found: ${projectName}`);
|
|
767
|
+
} else {
|
|
768
|
+
showError(projectR.error.message);
|
|
783
769
|
}
|
|
784
|
-
const updatedProject = await removeProjectRepo(projectName, path);
|
|
785
|
-
showSuccess("Repository removed", [["Project", projectName]]);
|
|
786
770
|
log.newline();
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const project = projectR.value;
|
|
774
|
+
if (!path) {
|
|
775
|
+
if (project.repositories.length === 0) {
|
|
776
|
+
console.log(muted("\nNo repositories to remove.\n"));
|
|
777
|
+
return;
|
|
790
778
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
779
|
+
path = await select({
|
|
780
|
+
message: `${emoji.donut} Select repository to remove:`,
|
|
781
|
+
choices: project.repositories.map((r) => ({
|
|
782
|
+
name: `${r.name} (${r.path})`,
|
|
783
|
+
value: r.path
|
|
784
|
+
}))
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (!skipConfirm) {
|
|
788
|
+
const confirmed = await confirm2({
|
|
789
|
+
message: `Remove repository "${path}" from project "${project.displayName}"?`,
|
|
790
|
+
default: false
|
|
791
|
+
});
|
|
792
|
+
if (!confirmed) {
|
|
793
|
+
console.log(muted("\nRepository removal cancelled.\n"));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
const removeR = await wrapAsync(() => removeProjectRepo(projectName, path), ensureError);
|
|
798
|
+
if (!removeR.ok) {
|
|
799
|
+
if (removeR.error instanceof ProjectNotFoundError) {
|
|
794
800
|
showError(`Project not found: ${projectName}`);
|
|
795
|
-
log.newline();
|
|
796
|
-
} else if (err instanceof Error) {
|
|
797
|
-
showError(err.message);
|
|
798
|
-
log.newline();
|
|
799
801
|
} else {
|
|
800
|
-
|
|
802
|
+
showError(removeR.error.message);
|
|
801
803
|
}
|
|
804
|
+
log.newline();
|
|
805
|
+
return;
|
|
802
806
|
}
|
|
807
|
+
showSuccess("Repository removed", [["Project", projectName]]);
|
|
808
|
+
log.newline();
|
|
809
|
+
log.info("Remaining repositories:");
|
|
810
|
+
for (const repo of removeR.value.repositories) {
|
|
811
|
+
log.item(`${repo.name} \u2192 ${repo.path}`);
|
|
812
|
+
}
|
|
813
|
+
log.newline();
|
|
803
814
|
}
|
|
804
815
|
|
|
805
816
|
// src/commands/sprint/list.ts
|
|
@@ -864,12 +875,13 @@ async function sprintListCommand(args = []) {
|
|
|
864
875
|
async function sprintShowCommand(args) {
|
|
865
876
|
const sprintId = args[0];
|
|
866
877
|
let id;
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
} catch {
|
|
878
|
+
const idR = await wrapAsync(() => resolveSprintId(sprintId), ensureError);
|
|
879
|
+
if (!idR.ok) {
|
|
870
880
|
const selected = await selectSprint("Select sprint to show:");
|
|
871
881
|
if (!selected) return;
|
|
872
882
|
id = selected;
|
|
883
|
+
} else {
|
|
884
|
+
id = idR.value;
|
|
873
885
|
}
|
|
874
886
|
const sprint = await getSprint(id);
|
|
875
887
|
const tasks = await listTasks(id);
|
|
@@ -961,9 +973,8 @@ async function sprintShowCommand(args) {
|
|
|
961
973
|
async function sprintContextCommand(args) {
|
|
962
974
|
const sprintId = args[0];
|
|
963
975
|
let id;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
} catch {
|
|
976
|
+
const idR = await wrapAsync(() => resolveSprintId(sprintId), ensureError);
|
|
977
|
+
if (!idR.ok) {
|
|
967
978
|
const selected = await selectSprint("Select sprint to show context for:");
|
|
968
979
|
if (!selected) {
|
|
969
980
|
showWarning("No sprints available.");
|
|
@@ -972,6 +983,8 @@ async function sprintContextCommand(args) {
|
|
|
972
983
|
return;
|
|
973
984
|
}
|
|
974
985
|
id = selected;
|
|
986
|
+
} else {
|
|
987
|
+
id = idR.value;
|
|
975
988
|
}
|
|
976
989
|
const sprint = await getSprint(id);
|
|
977
990
|
const tasks = await listTasks(id);
|
|
@@ -987,11 +1000,11 @@ async function sprintContextCommand(args) {
|
|
|
987
1000
|
const ticketsByProject = groupTicketsByProject(sprint.tickets);
|
|
988
1001
|
for (const [projectName, tickets] of ticketsByProject) {
|
|
989
1002
|
console.log(`### Project: ${projectName}`);
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
const repoPaths =
|
|
1003
|
+
const projectR = await wrapAsync(() => getProject(projectName), ensureError);
|
|
1004
|
+
if (projectR.ok) {
|
|
1005
|
+
const repoPaths = projectR.value.repositories.map((r) => `${r.name} (${r.path})`);
|
|
993
1006
|
console.log(`Repositories: ${repoPaths.join(", ")}`);
|
|
994
|
-
}
|
|
1007
|
+
} else {
|
|
995
1008
|
console.log("Repositories: (project not found)");
|
|
996
1009
|
}
|
|
997
1010
|
console.log("");
|
|
@@ -1055,14 +1068,14 @@ async function sprintCurrentCommand(args) {
|
|
|
1055
1068
|
log.newline();
|
|
1056
1069
|
return;
|
|
1057
1070
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1071
|
+
const sprintR = await wrapAsync(() => getSprint(currentSprintId), ensureError);
|
|
1072
|
+
if (sprintR.ok) {
|
|
1060
1073
|
printHeader("Current Sprint");
|
|
1061
|
-
console.log(field("ID",
|
|
1062
|
-
console.log(field("Name",
|
|
1063
|
-
console.log(field("Status", formatSprintStatus(
|
|
1074
|
+
console.log(field("ID", sprintR.value.id));
|
|
1075
|
+
console.log(field("Name", sprintR.value.name));
|
|
1076
|
+
console.log(field("Status", formatSprintStatus(sprintR.value.status)));
|
|
1064
1077
|
log.newline();
|
|
1065
|
-
}
|
|
1078
|
+
} else {
|
|
1066
1079
|
showWarning(`Current sprint "${currentSprintId}" no longer exists.`);
|
|
1067
1080
|
showNextStep("ralphctl sprint current -", "select a different sprint");
|
|
1068
1081
|
log.newline();
|
|
@@ -1080,22 +1093,23 @@ async function sprintCurrentCommand(args) {
|
|
|
1080
1093
|
]);
|
|
1081
1094
|
log.newline();
|
|
1082
1095
|
} else {
|
|
1083
|
-
|
|
1096
|
+
const setR = await wrapAsync(async () => {
|
|
1084
1097
|
const sprint = await getSprint(sprintId);
|
|
1085
1098
|
await setCurrentSprint(sprintId);
|
|
1099
|
+
return sprint;
|
|
1100
|
+
}, ensureError);
|
|
1101
|
+
if (setR.ok) {
|
|
1086
1102
|
showSuccess("Current sprint set!", [
|
|
1087
|
-
["ID",
|
|
1088
|
-
["Name",
|
|
1103
|
+
["ID", setR.value.id],
|
|
1104
|
+
["Name", setR.value.name]
|
|
1089
1105
|
]);
|
|
1090
1106
|
log.newline();
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
throw err;
|
|
1098
|
-
}
|
|
1107
|
+
} else if (setR.error instanceof SprintNotFoundError) {
|
|
1108
|
+
showError(`Sprint not found: ${sprintId}`);
|
|
1109
|
+
showNextStep("ralphctl sprint list", "see available sprints");
|
|
1110
|
+
log.newline();
|
|
1111
|
+
} else {
|
|
1112
|
+
throw setR.error;
|
|
1099
1113
|
}
|
|
1100
1114
|
}
|
|
1101
1115
|
}
|
|
@@ -1104,6 +1118,7 @@ async function sprintCurrentCommand(args) {
|
|
|
1104
1118
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
1105
1119
|
import { join } from "path";
|
|
1106
1120
|
import { input as input2, select as select2 } from "@inquirer/prompts";
|
|
1121
|
+
import { Result } from "typescript-result";
|
|
1107
1122
|
function parseArgs(args) {
|
|
1108
1123
|
const options = {
|
|
1109
1124
|
auto: false,
|
|
@@ -1163,13 +1178,11 @@ async function invokeAiAuto(prompt, repoPaths, ideateDir) {
|
|
|
1163
1178
|
}
|
|
1164
1179
|
function parseIdeateOutput(output) {
|
|
1165
1180
|
const jsonStr = extractJsonObject(output);
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
} catch (err) {
|
|
1170
|
-
throw new Error(`Invalid JSON: ${err instanceof Error ? err.message : "parse error"}`, { cause: err });
|
|
1181
|
+
const parseR = Result.try(() => JSON.parse(jsonStr));
|
|
1182
|
+
if (!parseR.ok) {
|
|
1183
|
+
throw new Error(`Invalid JSON: ${parseR.error.message}`, { cause: parseR.error });
|
|
1171
1184
|
}
|
|
1172
|
-
const result = IdeateOutputSchema.safeParse(
|
|
1185
|
+
const result = IdeateOutputSchema.safeParse(parseR.value);
|
|
1173
1186
|
if (!result.success) {
|
|
1174
1187
|
const issues = result.error.issues.map((issue) => {
|
|
1175
1188
|
const path = issue.path.length > 0 ? `[${issue.path.join(".")}]` : "";
|
|
@@ -1182,23 +1195,20 @@ ${issues}`);
|
|
|
1182
1195
|
}
|
|
1183
1196
|
async function sprintIdeateCommand(args) {
|
|
1184
1197
|
const { sprintId, options } = parseArgs(args);
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
id = await resolveSprintId(sprintId);
|
|
1188
|
-
} catch {
|
|
1198
|
+
const idR = await wrapAsync(() => resolveSprintId(sprintId), ensureError);
|
|
1199
|
+
if (!idR.ok) {
|
|
1189
1200
|
showWarning("No sprint specified and no current sprint set.");
|
|
1190
1201
|
showNextStep("ralphctl sprint create", "create a new sprint");
|
|
1191
1202
|
log.newline();
|
|
1192
1203
|
return;
|
|
1193
1204
|
}
|
|
1205
|
+
const id = idR.value;
|
|
1194
1206
|
const sprint = await getSprint(id);
|
|
1195
1207
|
try {
|
|
1196
1208
|
assertSprintStatus(sprint, ["draft"], "ideate");
|
|
1197
1209
|
} catch (err) {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
log.newline();
|
|
1201
|
-
}
|
|
1210
|
+
showError(err instanceof Error ? err.message : String(err));
|
|
1211
|
+
log.newline();
|
|
1202
1212
|
return;
|
|
1203
1213
|
}
|
|
1204
1214
|
const projects = await listProjects();
|
|
@@ -1230,21 +1240,26 @@ async function sprintIdeateCommand(args) {
|
|
|
1230
1240
|
log.newline();
|
|
1231
1241
|
return;
|
|
1232
1242
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
project = await getProject(projectName);
|
|
1236
|
-
} catch {
|
|
1243
|
+
const projectR = await wrapAsync(() => getProject(projectName), ensureError);
|
|
1244
|
+
if (!projectR.ok) {
|
|
1237
1245
|
showError(`Project '${projectName}' not found.`);
|
|
1238
1246
|
log.newline();
|
|
1239
1247
|
return;
|
|
1240
1248
|
}
|
|
1249
|
+
const project = projectR.value;
|
|
1241
1250
|
const ideaTitle = await input2({
|
|
1242
1251
|
message: "Idea title (short summary):",
|
|
1243
1252
|
validate: (value) => value.trim().length > 0 ? true : "Title is required"
|
|
1244
1253
|
});
|
|
1245
|
-
const
|
|
1254
|
+
const editorR = await editorInput({
|
|
1246
1255
|
message: "Idea description (what you want to build):"
|
|
1247
1256
|
});
|
|
1257
|
+
if (!editorR.ok) {
|
|
1258
|
+
showError(`Editor input failed: ${editorR.error.message}`);
|
|
1259
|
+
log.newline();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const ideaDescription = editorR.value;
|
|
1248
1263
|
if (!ideaDescription.trim()) {
|
|
1249
1264
|
showError("Description is required.");
|
|
1250
1265
|
log.newline();
|
|
@@ -1291,19 +1306,16 @@ async function sprintIdeateCommand(args) {
|
|
|
1291
1306
|
const prompt = buildIdeateAutoPrompt(ideaTitle, ideaDescription, projectName, repositoriesText, schema);
|
|
1292
1307
|
const spinner = createSpinner(`${providerName} is refining idea and planning tasks...`);
|
|
1293
1308
|
spinner.start();
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
output = await invokeAiAuto(prompt, selectedPaths, ideateDir);
|
|
1297
|
-
spinner.succeed(`${providerName} finished`);
|
|
1298
|
-
} catch (err) {
|
|
1309
|
+
const outputR = await wrapAsync(() => invokeAiAuto(prompt, selectedPaths, ideateDir), ensureError);
|
|
1310
|
+
if (!outputR.ok) {
|
|
1299
1311
|
spinner.fail(`${providerName} session failed`);
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
log.newline();
|
|
1304
|
-
}
|
|
1312
|
+
showError(`Failed to invoke ${providerName}: ${outputR.error.message}`);
|
|
1313
|
+
showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
|
|
1314
|
+
log.newline();
|
|
1305
1315
|
return;
|
|
1306
1316
|
}
|
|
1317
|
+
spinner.succeed(`${providerName} finished`);
|
|
1318
|
+
const output = outputR.value;
|
|
1307
1319
|
const blockedReason = parsePlanningBlocked(output);
|
|
1308
1320
|
if (blockedReason) {
|
|
1309
1321
|
showWarning(`Planning blocked: ${blockedReason}`);
|
|
@@ -1311,18 +1323,15 @@ async function sprintIdeateCommand(args) {
|
|
|
1311
1323
|
return;
|
|
1312
1324
|
}
|
|
1313
1325
|
log.dim("Parsing response...");
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
log.dim("Raw output:");
|
|
1321
|
-
console.log(output);
|
|
1322
|
-
log.newline();
|
|
1323
|
-
}
|
|
1326
|
+
const ideateR = Result.try(() => parseIdeateOutput(output));
|
|
1327
|
+
if (!ideateR.ok) {
|
|
1328
|
+
showError(`Failed to parse ${providerName} output: ${ideateR.error.message}`);
|
|
1329
|
+
log.dim("Raw output:");
|
|
1330
|
+
console.log(output);
|
|
1331
|
+
log.newline();
|
|
1324
1332
|
return;
|
|
1325
1333
|
}
|
|
1334
|
+
const ideateOutput = ideateR.value;
|
|
1326
1335
|
const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticket.id);
|
|
1327
1336
|
const ticketToUpdate = sprint.tickets[ticketIdx];
|
|
1328
1337
|
if (ticketIdx !== -1 && ticketToUpdate) {
|
|
@@ -1332,16 +1341,13 @@ async function sprintIdeateCommand(args) {
|
|
|
1332
1341
|
await saveSprint(sprint);
|
|
1333
1342
|
showSuccess("Requirements approved and saved!");
|
|
1334
1343
|
log.newline();
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
if (err instanceof Error) {
|
|
1340
|
-
showError(`Failed to parse tasks: ${err.message}`);
|
|
1341
|
-
log.newline();
|
|
1342
|
-
}
|
|
1344
|
+
const parsedTasksR = Result.try(() => parseTasksJson(JSON.stringify(ideateOutput.tasks)));
|
|
1345
|
+
if (!parsedTasksR.ok) {
|
|
1346
|
+
showError(`Failed to parse tasks: ${parsedTasksR.error.message}`);
|
|
1347
|
+
log.newline();
|
|
1343
1348
|
return;
|
|
1344
1349
|
}
|
|
1350
|
+
const parsedTasks = parsedTasksR.value;
|
|
1345
1351
|
if (parsedTasks.length === 0) {
|
|
1346
1352
|
showWarning("No tasks generated.");
|
|
1347
1353
|
log.newline();
|
|
@@ -1376,37 +1382,29 @@ async function sprintIdeateCommand(args) {
|
|
|
1376
1382
|
${providerName} will guide you through requirements refinement and task planning.`));
|
|
1377
1383
|
console.log(muted(` When done, ask ${providerName} to write the output to: ${outputFile}
|
|
1378
1384
|
`));
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
|
|
1385
|
-
log.newline();
|
|
1386
|
-
}
|
|
1385
|
+
const interactiveR = await wrapAsync(() => invokeAiInteractive(prompt, selectedPaths, ideateDir), ensureError);
|
|
1386
|
+
if (!interactiveR.ok) {
|
|
1387
|
+
showError(`Failed to invoke ${providerName}: ${interactiveR.error.message}`);
|
|
1388
|
+
showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
|
|
1389
|
+
log.newline();
|
|
1387
1390
|
return;
|
|
1388
1391
|
}
|
|
1389
1392
|
console.log("");
|
|
1390
1393
|
if (await fileExists(outputFile)) {
|
|
1391
1394
|
showInfo("Output file found. Processing...");
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
content = await readFile(outputFile, "utf-8");
|
|
1395
|
-
} catch {
|
|
1395
|
+
const contentR = await wrapAsync(() => readFile(outputFile, "utf-8"), ensureError);
|
|
1396
|
+
if (!contentR.ok) {
|
|
1396
1397
|
showError(`Failed to read output file: ${outputFile}`);
|
|
1397
1398
|
log.newline();
|
|
1398
1399
|
return;
|
|
1399
1400
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
if (err instanceof Error) {
|
|
1405
|
-
showError(`Failed to parse output file: ${err.message}`);
|
|
1406
|
-
log.newline();
|
|
1407
|
-
}
|
|
1401
|
+
const ideateR = Result.try(() => parseIdeateOutput(contentR.value));
|
|
1402
|
+
if (!ideateR.ok) {
|
|
1403
|
+
showError(`Failed to parse output file: ${ideateR.error.message}`);
|
|
1404
|
+
log.newline();
|
|
1408
1405
|
return;
|
|
1409
1406
|
}
|
|
1407
|
+
const ideateOutput = ideateR.value;
|
|
1410
1408
|
const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticket.id);
|
|
1411
1409
|
const ticketToUpdate = sprint.tickets[ticketIdx];
|
|
1412
1410
|
if (ticketIdx !== -1 && ticketToUpdate) {
|
|
@@ -1416,16 +1414,13 @@ async function sprintIdeateCommand(args) {
|
|
|
1416
1414
|
await saveSprint(sprint);
|
|
1417
1415
|
showSuccess("Requirements approved and saved!");
|
|
1418
1416
|
log.newline();
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
if (err instanceof Error) {
|
|
1424
|
-
showError(`Failed to parse tasks: ${err.message}`);
|
|
1425
|
-
log.newline();
|
|
1426
|
-
}
|
|
1417
|
+
const parsedTasksR = Result.try(() => parseTasksJson(JSON.stringify(ideateOutput.tasks)));
|
|
1418
|
+
if (!parsedTasksR.ok) {
|
|
1419
|
+
showError(`Failed to parse tasks: ${parsedTasksR.error.message}`);
|
|
1420
|
+
log.newline();
|
|
1427
1421
|
return;
|
|
1428
1422
|
}
|
|
1423
|
+
const parsedTasks = parsedTasksR.value;
|
|
1429
1424
|
if (parsedTasks.length === 0) {
|
|
1430
1425
|
showWarning("No tasks in file.");
|
|
1431
1426
|
log.newline();
|
|
@@ -1463,6 +1458,7 @@ async function sprintIdeateCommand(args) {
|
|
|
1463
1458
|
// src/commands/sprint/close.ts
|
|
1464
1459
|
import { spawnSync } from "child_process";
|
|
1465
1460
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1461
|
+
import { Result as Result2 } from "typescript-result";
|
|
1466
1462
|
async function sprintCloseCommand(args) {
|
|
1467
1463
|
let sprintId;
|
|
1468
1464
|
let createPr = false;
|
|
@@ -1510,23 +1506,13 @@ async function sprintCloseCommand(args) {
|
|
|
1510
1506
|
return;
|
|
1511
1507
|
}
|
|
1512
1508
|
}
|
|
1513
|
-
|
|
1514
|
-
const
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
]);
|
|
1521
|
-
showRandomQuote();
|
|
1522
|
-
log.newline();
|
|
1523
|
-
if (createPr && sprintBeforeClose.branch) {
|
|
1524
|
-
await createPullRequests(sprintId, sprintBeforeClose.branch, sprint.name);
|
|
1525
|
-
} else if (createPr && !sprintBeforeClose.branch) {
|
|
1526
|
-
log.dim("No sprint branch set \u2014 skipping PR creation.");
|
|
1527
|
-
log.newline();
|
|
1528
|
-
}
|
|
1529
|
-
} catch (err) {
|
|
1509
|
+
const closeR = await wrapAsync(async () => {
|
|
1510
|
+
const sprintBeforeClose2 = await getSprint(sprintId);
|
|
1511
|
+
const sprint2 = await closeSprint(sprintId);
|
|
1512
|
+
return { sprintBeforeClose: sprintBeforeClose2, sprint: sprint2 };
|
|
1513
|
+
}, ensureError);
|
|
1514
|
+
if (!closeR.ok) {
|
|
1515
|
+
const err = closeR.error;
|
|
1530
1516
|
if (err instanceof SprintNotFoundError) {
|
|
1531
1517
|
showError(`Sprint not found: ${sprintId}`);
|
|
1532
1518
|
log.newline();
|
|
@@ -1536,6 +1522,21 @@ async function sprintCloseCommand(args) {
|
|
|
1536
1522
|
} else {
|
|
1537
1523
|
throw err;
|
|
1538
1524
|
}
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
const { sprintBeforeClose, sprint } = closeR.value;
|
|
1528
|
+
showSuccess("Sprint closed!", [
|
|
1529
|
+
["ID", sprint.id],
|
|
1530
|
+
["Name", sprint.name],
|
|
1531
|
+
["Status", formatSprintStatus(sprint.status)]
|
|
1532
|
+
]);
|
|
1533
|
+
showRandomQuote();
|
|
1534
|
+
log.newline();
|
|
1535
|
+
if (createPr && sprintBeforeClose.branch) {
|
|
1536
|
+
await createPullRequests(sprintId, sprintBeforeClose.branch, sprint.name);
|
|
1537
|
+
} else if (createPr && !sprintBeforeClose.branch) {
|
|
1538
|
+
log.dim("No sprint branch set \u2014 skipping PR creation.");
|
|
1539
|
+
log.newline();
|
|
1539
1540
|
}
|
|
1540
1541
|
}
|
|
1541
1542
|
async function createPullRequests(sprintId, branchName, sprintName) {
|
|
@@ -1548,59 +1549,62 @@ async function createPullRequests(sprintId, branchName, sprintName) {
|
|
|
1548
1549
|
const tasks = await listTasks(sprintId);
|
|
1549
1550
|
const uniquePaths = [...new Set(tasks.map((t) => t.projectPath))];
|
|
1550
1551
|
for (const projectPath of uniquePaths) {
|
|
1551
|
-
try {
|
|
1552
|
+
const prR = Result2.try(() => {
|
|
1552
1553
|
assertSafeCwd(projectPath);
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1554
|
+
return { branchExists: branchExists(projectPath, branchName) };
|
|
1555
|
+
});
|
|
1556
|
+
if (!prR.ok) {
|
|
1557
|
+
showWarning(`Error creating PR for ${projectPath}: ${prR.error.message}`);
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
if (!prR.value.branchExists) {
|
|
1561
|
+
log.dim(`Branch '${branchName}' not found in ${projectPath} \u2014 skipping`);
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
const baseBranch = getDefaultBranch(projectPath);
|
|
1565
|
+
const title = `Sprint: ${sprintName}`;
|
|
1566
|
+
log.info(`Creating PR in ${projectPath}...`);
|
|
1567
|
+
const pushResult = spawnSync("git", ["push", "-u", "origin", branchName], {
|
|
1568
|
+
cwd: projectPath,
|
|
1569
|
+
encoding: "utf-8",
|
|
1570
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1571
|
+
});
|
|
1572
|
+
if (pushResult.status !== 0) {
|
|
1573
|
+
showWarning(`Failed to push branch in ${projectPath}: ${pushResult.stderr.trim()}`);
|
|
1574
|
+
log.dim(
|
|
1575
|
+
` Manual: cd ${projectPath} && git push -u origin ${branchName} && gh pr create --base ${baseBranch} --head ${branchName} --title "${title}"`
|
|
1576
|
+
);
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
const result = spawnSync(
|
|
1580
|
+
"gh",
|
|
1581
|
+
[
|
|
1582
|
+
"pr",
|
|
1583
|
+
"create",
|
|
1584
|
+
"--base",
|
|
1585
|
+
baseBranch,
|
|
1586
|
+
"--head",
|
|
1587
|
+
branchName,
|
|
1588
|
+
"--title",
|
|
1589
|
+
title,
|
|
1590
|
+
"--body",
|
|
1591
|
+
`Sprint: ${sprintName}
|
|
1592
|
+
ID: ${sprintId}`
|
|
1593
|
+
],
|
|
1594
|
+
{
|
|
1561
1595
|
cwd: projectPath,
|
|
1562
1596
|
encoding: "utf-8",
|
|
1563
1597
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1564
|
-
});
|
|
1565
|
-
if (pushResult.status !== 0) {
|
|
1566
|
-
showWarning(`Failed to push branch in ${projectPath}: ${pushResult.stderr.trim()}`);
|
|
1567
|
-
log.dim(
|
|
1568
|
-
` Manual: cd ${projectPath} && git push -u origin ${branchName} && gh pr create --base ${baseBranch} --head ${branchName} --title "${title}"`
|
|
1569
|
-
);
|
|
1570
|
-
continue;
|
|
1571
1598
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
branchName,
|
|
1581
|
-
"--title",
|
|
1582
|
-
title,
|
|
1583
|
-
"--body",
|
|
1584
|
-
`Sprint: ${sprintName}
|
|
1585
|
-
ID: ${sprintId}`
|
|
1586
|
-
],
|
|
1587
|
-
{
|
|
1588
|
-
cwd: projectPath,
|
|
1589
|
-
encoding: "utf-8",
|
|
1590
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1591
|
-
}
|
|
1599
|
+
);
|
|
1600
|
+
if (result.status === 0) {
|
|
1601
|
+
const prUrl = result.stdout.trim();
|
|
1602
|
+
showSuccess(`PR created: ${prUrl}`);
|
|
1603
|
+
} else {
|
|
1604
|
+
showWarning(`Failed to create PR in ${projectPath}: ${result.stderr.trim()}`);
|
|
1605
|
+
log.dim(
|
|
1606
|
+
` Manual: cd ${projectPath} && gh pr create --base ${baseBranch} --head ${branchName} --title "${title}"`
|
|
1592
1607
|
);
|
|
1593
|
-
if (result.status === 0) {
|
|
1594
|
-
const prUrl = result.stdout.trim();
|
|
1595
|
-
showSuccess(`PR created: ${prUrl}`);
|
|
1596
|
-
} else {
|
|
1597
|
-
showWarning(`Failed to create PR in ${projectPath}: ${result.stderr.trim()}`);
|
|
1598
|
-
log.dim(
|
|
1599
|
-
` Manual: cd ${projectPath} && gh pr create --base ${baseBranch} --head ${branchName} --title "${title}"`
|
|
1600
|
-
);
|
|
1601
|
-
}
|
|
1602
|
-
} catch (err) {
|
|
1603
|
-
showWarning(`Error creating PR for ${projectPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1604
1608
|
}
|
|
1605
1609
|
}
|
|
1606
1610
|
log.newline();
|
|
@@ -1616,51 +1620,51 @@ async function sprintDeleteCommand(args) {
|
|
|
1616
1620
|
if (!selected) return;
|
|
1617
1621
|
sprintId = selected;
|
|
1618
1622
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
try {
|
|
1623
|
-
const tasks = await listTasks(sprintId);
|
|
1624
|
-
taskCount = tasks.length;
|
|
1625
|
-
} catch {
|
|
1626
|
-
}
|
|
1627
|
-
if (!skipConfirm) {
|
|
1628
|
-
log.newline();
|
|
1629
|
-
log.warn("This will permanently delete the sprint and all its data.");
|
|
1630
|
-
log.item(`Name: ${sprint.name}`);
|
|
1631
|
-
log.item(`Status: ${formatSprintStatus(sprint.status)}`);
|
|
1632
|
-
log.item(`Tickets: ${String(sprint.tickets.length)}`);
|
|
1633
|
-
log.item(`Tasks: ${String(taskCount)}`);
|
|
1634
|
-
log.newline();
|
|
1635
|
-
const confirmed = await confirm4({
|
|
1636
|
-
message: `Delete sprint "${sprint.name}"?`,
|
|
1637
|
-
default: false
|
|
1638
|
-
});
|
|
1639
|
-
if (!confirmed) {
|
|
1640
|
-
console.log(muted("\nSprint deletion cancelled.\n"));
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
const currentSprintId = await getCurrentSprint();
|
|
1645
|
-
await deleteSprint(sprintId);
|
|
1646
|
-
if (currentSprintId === sprintId) {
|
|
1647
|
-
await setCurrentSprint(null);
|
|
1648
|
-
showTip('Current sprint was cleared. Use "ralphctl sprint current" to set a new one.');
|
|
1649
|
-
}
|
|
1650
|
-
showSuccess("Sprint deleted", [
|
|
1651
|
-
["Name", sprint.name],
|
|
1652
|
-
["ID", sprint.id]
|
|
1653
|
-
]);
|
|
1654
|
-
showRandomQuote();
|
|
1655
|
-
log.newline();
|
|
1656
|
-
} catch (err) {
|
|
1657
|
-
if (err instanceof SprintNotFoundError) {
|
|
1623
|
+
const sprintR = await wrapAsync(() => getSprint(sprintId), ensureError);
|
|
1624
|
+
if (!sprintR.ok) {
|
|
1625
|
+
if (sprintR.error instanceof SprintNotFoundError) {
|
|
1658
1626
|
showError(`Sprint not found: ${sprintId}`);
|
|
1659
1627
|
log.newline();
|
|
1660
1628
|
} else {
|
|
1661
|
-
throw
|
|
1629
|
+
throw sprintR.error;
|
|
1630
|
+
}
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
const sprint = sprintR.value;
|
|
1634
|
+
let taskCount = 0;
|
|
1635
|
+
const tasksR = await wrapAsync(() => listTasks(sprintId), ensureError);
|
|
1636
|
+
if (tasksR.ok) {
|
|
1637
|
+
taskCount = tasksR.value.length;
|
|
1638
|
+
}
|
|
1639
|
+
if (!skipConfirm) {
|
|
1640
|
+
log.newline();
|
|
1641
|
+
log.warn("This will permanently delete the sprint and all its data.");
|
|
1642
|
+
log.item(`Name: ${sprint.name}`);
|
|
1643
|
+
log.item(`Status: ${formatSprintStatus(sprint.status)}`);
|
|
1644
|
+
log.item(`Tickets: ${String(sprint.tickets.length)}`);
|
|
1645
|
+
log.item(`Tasks: ${String(taskCount)}`);
|
|
1646
|
+
log.newline();
|
|
1647
|
+
const confirmed = await confirm4({
|
|
1648
|
+
message: `Delete sprint "${sprint.name}"?`,
|
|
1649
|
+
default: false
|
|
1650
|
+
});
|
|
1651
|
+
if (!confirmed) {
|
|
1652
|
+
console.log(muted("\nSprint deletion cancelled.\n"));
|
|
1653
|
+
return;
|
|
1662
1654
|
}
|
|
1663
1655
|
}
|
|
1656
|
+
const currentSprintId = await getCurrentSprint();
|
|
1657
|
+
await deleteSprint(sprintId);
|
|
1658
|
+
if (currentSprintId === sprintId) {
|
|
1659
|
+
await setCurrentSprint(null);
|
|
1660
|
+
showTip('Current sprint was cleared. Use "ralphctl sprint current" to set a new one.');
|
|
1661
|
+
}
|
|
1662
|
+
showSuccess("Sprint deleted", [
|
|
1663
|
+
["Name", sprint.name],
|
|
1664
|
+
["ID", sprint.id]
|
|
1665
|
+
]);
|
|
1666
|
+
showRandomQuote();
|
|
1667
|
+
log.newline();
|
|
1664
1668
|
}
|
|
1665
1669
|
|
|
1666
1670
|
// src/commands/sprint/requirements.ts
|
|
@@ -1668,12 +1672,13 @@ import { join as join2 } from "path";
|
|
|
1668
1672
|
async function sprintRequirementsCommand(args = []) {
|
|
1669
1673
|
const sprintId = args.find((a) => !a.startsWith("-"));
|
|
1670
1674
|
let id;
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
} catch {
|
|
1675
|
+
const idR = await wrapAsync(() => resolveSprintId(sprintId), ensureError);
|
|
1676
|
+
if (!idR.ok) {
|
|
1674
1677
|
const selected = await selectSprint("Select sprint to export requirements from:");
|
|
1675
1678
|
if (!selected) return;
|
|
1676
1679
|
id = selected;
|
|
1680
|
+
} else {
|
|
1681
|
+
id = idR.value;
|
|
1677
1682
|
}
|
|
1678
1683
|
const sprint = await getSprint(id);
|
|
1679
1684
|
if (sprint.tickets.length === 0) {
|
|
@@ -1693,22 +1698,18 @@ async function sprintRequirementsCommand(args = []) {
|
|
|
1693
1698
|
log.newline();
|
|
1694
1699
|
const sprintDir = getSprintDir(id);
|
|
1695
1700
|
const outputPath = join2(sprintDir, "requirements.md");
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
log.item(outputPath);
|
|
1700
|
-
} catch (err) {
|
|
1701
|
-
if (err instanceof Error) {
|
|
1702
|
-
showError(`Failed to write requirements: ${err.message}`);
|
|
1703
|
-
} else {
|
|
1704
|
-
showError("Failed to write requirements: Unknown error");
|
|
1705
|
-
}
|
|
1701
|
+
const exportR = await wrapAsync(() => exportRequirementsToMarkdown(sprint, outputPath), ensureError);
|
|
1702
|
+
if (!exportR.ok) {
|
|
1703
|
+
showError(`Failed to write requirements: ${exportR.error.message}`);
|
|
1706
1704
|
return;
|
|
1707
1705
|
}
|
|
1706
|
+
showSuccess("Requirements written to:");
|
|
1707
|
+
log.item(outputPath);
|
|
1708
1708
|
log.newline();
|
|
1709
1709
|
}
|
|
1710
1710
|
|
|
1711
1711
|
// src/commands/sprint/health.ts
|
|
1712
|
+
import { Result as Result3 } from "typescript-result";
|
|
1712
1713
|
function checkBlockers(tasks) {
|
|
1713
1714
|
const doneTasks = new Set(tasks.filter((t) => t.status === "done").map((t) => t.id));
|
|
1714
1715
|
const allTaskIds = new Set(tasks.map((t) => t.id));
|
|
@@ -1799,13 +1800,11 @@ function checkBranchConsistency(sprint, tasks) {
|
|
|
1799
1800
|
const uniquePaths = [...new Set(remainingTasks.map((t) => t.projectPath))];
|
|
1800
1801
|
const items = [];
|
|
1801
1802
|
for (const projectPath of uniquePaths) {
|
|
1802
|
-
try
|
|
1803
|
-
|
|
1804
|
-
if (current !== sprint.branch) {
|
|
1805
|
-
items.push(`${projectPath} \u2014 on '${current}', expected '${sprint.branch}'`);
|
|
1806
|
-
}
|
|
1807
|
-
} catch {
|
|
1803
|
+
const branchR = Result3.try(() => getCurrentBranch(projectPath));
|
|
1804
|
+
if (!branchR.ok) {
|
|
1808
1805
|
items.push(`${projectPath} \u2014 unable to determine branch`);
|
|
1806
|
+
} else if (branchR.value !== sprint.branch) {
|
|
1807
|
+
items.push(`${projectPath} \u2014 on '${branchR.value}', expected '${sprint.branch}'`);
|
|
1809
1808
|
}
|
|
1810
1809
|
}
|
|
1811
1810
|
return {
|
|
@@ -1837,17 +1836,12 @@ function renderCheckCard(check) {
|
|
|
1837
1836
|
return renderCard(check.name, lines, { colorFn });
|
|
1838
1837
|
}
|
|
1839
1838
|
async function sprintHealthCommand() {
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
} catch (err) {
|
|
1844
|
-
if (err instanceof Error) {
|
|
1845
|
-
showError(err.message);
|
|
1846
|
-
} else {
|
|
1847
|
-
showError("Unknown error");
|
|
1848
|
-
}
|
|
1839
|
+
const sprintR = await wrapAsync(() => getCurrentSprintOrThrow(), ensureError);
|
|
1840
|
+
if (!sprintR.ok) {
|
|
1841
|
+
showError(sprintR.error.message);
|
|
1849
1842
|
return;
|
|
1850
1843
|
}
|
|
1844
|
+
const sprint = sprintR.value;
|
|
1851
1845
|
const tasks = await getTasks(sprint.id);
|
|
1852
1846
|
printHeader(`Sprint Health: ${sprint.name}`, icons.sprint);
|
|
1853
1847
|
const checks = [
|
|
@@ -1899,18 +1893,17 @@ async function ticketEditCommand(ticketId, options = {}) {
|
|
|
1899
1893
|
}
|
|
1900
1894
|
resolvedId = selected;
|
|
1901
1895
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
} catch (err) {
|
|
1906
|
-
if (err instanceof TicketNotFoundError) {
|
|
1896
|
+
const ticketR = await wrapAsync(() => getTicket(resolvedId), ensureError);
|
|
1897
|
+
if (!ticketR.ok) {
|
|
1898
|
+
if (ticketR.error instanceof TicketNotFoundError) {
|
|
1907
1899
|
showError(`Ticket not found: ${resolvedId}`);
|
|
1908
1900
|
showNextStep("ralphctl ticket list", "see available tickets");
|
|
1909
1901
|
if (!isInteractive) exitWithCode(EXIT_ERROR);
|
|
1910
1902
|
return;
|
|
1911
1903
|
}
|
|
1912
|
-
throw
|
|
1904
|
+
throw ticketR.error;
|
|
1913
1905
|
}
|
|
1906
|
+
const ticket = ticketR.value;
|
|
1914
1907
|
let newTitle;
|
|
1915
1908
|
let newDescription;
|
|
1916
1909
|
let newLink;
|
|
@@ -1924,10 +1917,15 @@ async function ticketEditCommand(ticketId, options = {}) {
|
|
|
1924
1917
|
default: ticket.title,
|
|
1925
1918
|
validate: (v) => v.trim().length > 0 ? true : "Title is required"
|
|
1926
1919
|
});
|
|
1927
|
-
|
|
1920
|
+
const descR = await editorInput({
|
|
1928
1921
|
message: "Description:",
|
|
1929
1922
|
default: ticket.description
|
|
1930
1923
|
});
|
|
1924
|
+
if (!descR.ok) {
|
|
1925
|
+
showError(`Editor input failed: ${descR.error.message}`);
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
newDescription = descR.value;
|
|
1931
1929
|
newLink = await input3({
|
|
1932
1930
|
message: `${icons.info} Link:`,
|
|
1933
1931
|
default: ticket.link ?? "",
|
|
@@ -1978,28 +1976,29 @@ async function ticketEditCommand(ticketId, options = {}) {
|
|
|
1978
1976
|
console.log(muted("\n No changes made.\n"));
|
|
1979
1977
|
return;
|
|
1980
1978
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
["Title", updated.title],
|
|
1986
|
-
["Project", updated.projectName]
|
|
1987
|
-
]);
|
|
1988
|
-
if (updated.description) {
|
|
1989
|
-
console.log(fieldMultiline("Description", updated.description));
|
|
1990
|
-
}
|
|
1991
|
-
if (updated.link) {
|
|
1992
|
-
console.log(field("Link", updated.link));
|
|
1993
|
-
}
|
|
1994
|
-
console.log("");
|
|
1995
|
-
} catch (err) {
|
|
1996
|
-
if (err instanceof SprintStatusError) {
|
|
1997
|
-
showError(err.message);
|
|
1979
|
+
const updateR = await wrapAsync(() => updateTicket(ticket.id, updates), ensureError);
|
|
1980
|
+
if (!updateR.ok) {
|
|
1981
|
+
if (updateR.error instanceof SprintStatusError) {
|
|
1982
|
+
showError(updateR.error.message);
|
|
1998
1983
|
} else {
|
|
1999
|
-
throw
|
|
1984
|
+
throw updateR.error;
|
|
2000
1985
|
}
|
|
2001
1986
|
if (!isInteractive) exitWithCode(EXIT_ERROR);
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
const updated = updateR.value;
|
|
1990
|
+
showSuccess("Ticket updated!", [
|
|
1991
|
+
["ID", updated.id],
|
|
1992
|
+
["Title", updated.title],
|
|
1993
|
+
["Project", updated.projectName]
|
|
1994
|
+
]);
|
|
1995
|
+
if (updated.description) {
|
|
1996
|
+
console.log(fieldMultiline("Description", updated.description));
|
|
1997
|
+
}
|
|
1998
|
+
if (updated.link) {
|
|
1999
|
+
console.log(field("Link", updated.link));
|
|
2002
2000
|
}
|
|
2001
|
+
console.log("");
|
|
2003
2002
|
}
|
|
2004
2003
|
|
|
2005
2004
|
// src/commands/ticket/list.ts
|
|
@@ -2067,12 +2066,12 @@ async function ticketListCommand(args) {
|
|
|
2067
2066
|
printHeader(`Tickets (${String(filtered.length)})`, icons.ticket);
|
|
2068
2067
|
for (const [projectName, projectTickets] of ticketsByProject) {
|
|
2069
2068
|
log.raw(`${colors.info(icons.project)} ${colors.info(projectName)}`);
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
for (const repo of
|
|
2069
|
+
const projectR = await wrapAsync(() => getProject(projectName), ensureError);
|
|
2070
|
+
if (projectR.ok) {
|
|
2071
|
+
for (const repo of projectR.value.repositories) {
|
|
2073
2072
|
log.raw(` ${muted(repo.name)} ${muted("\u2192")} ${muted(repo.path)}`, 1);
|
|
2074
2073
|
}
|
|
2075
|
-
}
|
|
2074
|
+
} else {
|
|
2076
2075
|
log.raw(` ${muted("(project not found)")}`, 1);
|
|
2077
2076
|
}
|
|
2078
2077
|
log.newline();
|
|
@@ -2104,52 +2103,53 @@ async function ticketShowCommand(args) {
|
|
|
2104
2103
|
if (!selected) return;
|
|
2105
2104
|
ticketId = selected;
|
|
2106
2105
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
const infoLines = [labelValue("ID", ticket.id)];
|
|
2111
|
-
infoLines.push(labelValue("Project", ticket.projectName));
|
|
2112
|
-
infoLines.push(labelValue("Requirements", reqBadge));
|
|
2113
|
-
if (ticket.link) {
|
|
2114
|
-
infoLines.push(labelValue("Link", ticket.link));
|
|
2115
|
-
}
|
|
2116
|
-
try {
|
|
2117
|
-
const project = await getProject(ticket.projectName);
|
|
2118
|
-
infoLines.push("");
|
|
2119
|
-
for (const repo of project.repositories) {
|
|
2120
|
-
infoLines.push(` ${icons.bullet} ${repo.name} ${muted("\u2192")} ${muted(repo.path)}`);
|
|
2121
|
-
}
|
|
2122
|
-
} catch {
|
|
2123
|
-
infoLines.push(labelValue("Repositories", muted("(project not found)")));
|
|
2124
|
-
}
|
|
2125
|
-
log.newline();
|
|
2126
|
-
console.log(renderCard(`${icons.ticket} ${ticket.title}`, infoLines));
|
|
2127
|
-
if (ticket.description) {
|
|
2128
|
-
log.newline();
|
|
2129
|
-
const descLines = [];
|
|
2130
|
-
for (const line2 of ticket.description.split("\n")) {
|
|
2131
|
-
descLines.push(line2);
|
|
2132
|
-
}
|
|
2133
|
-
console.log(renderCard(`${icons.edit} Description`, descLines));
|
|
2134
|
-
}
|
|
2135
|
-
if (ticket.affectedRepositories && ticket.affectedRepositories.length > 0) {
|
|
2136
|
-
log.newline();
|
|
2137
|
-
const affectedLines = [];
|
|
2138
|
-
for (const repoPath of ticket.affectedRepositories) {
|
|
2139
|
-
affectedLines.push(`${icons.bullet} ${repoPath}`);
|
|
2140
|
-
}
|
|
2141
|
-
console.log(renderCard(`${icons.project} Affected Repositories`, affectedLines));
|
|
2142
|
-
}
|
|
2143
|
-
log.newline();
|
|
2144
|
-
} catch (err) {
|
|
2145
|
-
if (err instanceof TicketNotFoundError) {
|
|
2106
|
+
const ticketR = await wrapAsync(() => getTicket(ticketId), ensureError);
|
|
2107
|
+
if (!ticketR.ok) {
|
|
2108
|
+
if (ticketR.error instanceof TicketNotFoundError) {
|
|
2146
2109
|
showError(`Ticket not found: ${ticketId}`);
|
|
2147
2110
|
showNextStep("ralphctl ticket list", "see available tickets");
|
|
2148
2111
|
log.newline();
|
|
2149
2112
|
} else {
|
|
2150
|
-
throw
|
|
2113
|
+
throw ticketR.error;
|
|
2114
|
+
}
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
const ticket = ticketR.value;
|
|
2118
|
+
const reqBadge = ticket.requirementStatus === "approved" ? badge("approved", "success") : badge("pending", "muted");
|
|
2119
|
+
const infoLines = [labelValue("ID", ticket.id)];
|
|
2120
|
+
infoLines.push(labelValue("Project", ticket.projectName));
|
|
2121
|
+
infoLines.push(labelValue("Requirements", reqBadge));
|
|
2122
|
+
if (ticket.link) {
|
|
2123
|
+
infoLines.push(labelValue("Link", ticket.link));
|
|
2124
|
+
}
|
|
2125
|
+
const projectR = await wrapAsync(() => getProject(ticket.projectName), ensureError);
|
|
2126
|
+
if (projectR.ok) {
|
|
2127
|
+
infoLines.push("");
|
|
2128
|
+
for (const repo of projectR.value.repositories) {
|
|
2129
|
+
infoLines.push(` ${icons.bullet} ${repo.name} ${muted("\u2192")} ${muted(repo.path)}`);
|
|
2151
2130
|
}
|
|
2131
|
+
} else {
|
|
2132
|
+
infoLines.push(labelValue("Repositories", muted("(project not found)")));
|
|
2152
2133
|
}
|
|
2134
|
+
log.newline();
|
|
2135
|
+
console.log(renderCard(`${icons.ticket} ${ticket.title}`, infoLines));
|
|
2136
|
+
if (ticket.description) {
|
|
2137
|
+
log.newline();
|
|
2138
|
+
const descLines = [];
|
|
2139
|
+
for (const line2 of ticket.description.split("\n")) {
|
|
2140
|
+
descLines.push(line2);
|
|
2141
|
+
}
|
|
2142
|
+
console.log(renderCard(`${icons.edit} Description`, descLines));
|
|
2143
|
+
}
|
|
2144
|
+
if (ticket.affectedRepositories && ticket.affectedRepositories.length > 0) {
|
|
2145
|
+
log.newline();
|
|
2146
|
+
const affectedLines = [];
|
|
2147
|
+
for (const repoPath of ticket.affectedRepositories) {
|
|
2148
|
+
affectedLines.push(`${icons.bullet} ${repoPath}`);
|
|
2149
|
+
}
|
|
2150
|
+
console.log(renderCard(`${icons.project} Affected Repositories`, affectedLines));
|
|
2151
|
+
}
|
|
2152
|
+
log.newline();
|
|
2153
2153
|
}
|
|
2154
2154
|
|
|
2155
2155
|
// src/commands/ticket/remove.ts
|
|
@@ -2162,7 +2162,7 @@ async function ticketRemoveCommand(args) {
|
|
|
2162
2162
|
if (!selected) return;
|
|
2163
2163
|
ticketId = selected;
|
|
2164
2164
|
}
|
|
2165
|
-
|
|
2165
|
+
const opR = await wrapAsync(async () => {
|
|
2166
2166
|
const ticket = await getTicket(ticketId);
|
|
2167
2167
|
if (!skipConfirm) {
|
|
2168
2168
|
const confirmed = await confirm5({
|
|
@@ -2171,23 +2171,28 @@ async function ticketRemoveCommand(args) {
|
|
|
2171
2171
|
});
|
|
2172
2172
|
if (!confirmed) {
|
|
2173
2173
|
console.log(muted("\nTicket removal cancelled.\n"));
|
|
2174
|
-
return;
|
|
2174
|
+
return null;
|
|
2175
2175
|
}
|
|
2176
2176
|
}
|
|
2177
2177
|
await removeTicket(ticketId);
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
if (
|
|
2178
|
+
return ticket;
|
|
2179
|
+
}, ensureError);
|
|
2180
|
+
if (!opR.ok) {
|
|
2181
|
+
if (opR.error instanceof TicketNotFoundError) {
|
|
2182
2182
|
showError(`Ticket not found: ${ticketId}`);
|
|
2183
2183
|
showNextStep("ralphctl ticket list", "see available tickets");
|
|
2184
2184
|
log.newline();
|
|
2185
|
-
} else if (
|
|
2186
|
-
showError(
|
|
2185
|
+
} else if (opR.error instanceof SprintStatusError) {
|
|
2186
|
+
showError(opR.error.message);
|
|
2187
2187
|
log.newline();
|
|
2188
2188
|
} else {
|
|
2189
|
-
throw
|
|
2189
|
+
throw opR.error;
|
|
2190
2190
|
}
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (opR.value !== null) {
|
|
2194
|
+
showSuccess("Ticket removed", [["ID", ticketId]]);
|
|
2195
|
+
log.newline();
|
|
2191
2196
|
}
|
|
2192
2197
|
}
|
|
2193
2198
|
|
|
@@ -2195,25 +2200,23 @@ async function ticketRemoveCommand(args) {
|
|
|
2195
2200
|
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
2196
2201
|
import { join as join3 } from "path";
|
|
2197
2202
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
2203
|
+
import { Result as Result4 } from "typescript-result";
|
|
2198
2204
|
async function ticketRefineCommand(ticketId, options = {}) {
|
|
2199
2205
|
const isInteractive = options.interactive !== false;
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
sprintId = await resolveSprintId();
|
|
2203
|
-
} catch {
|
|
2206
|
+
const sprintIdR = await wrapAsync(() => resolveSprintId(), ensureError);
|
|
2207
|
+
if (!sprintIdR.ok) {
|
|
2204
2208
|
showWarning("No current sprint set.");
|
|
2205
2209
|
showTip("Create a sprint first or set one with: ralphctl sprint current");
|
|
2206
2210
|
log.newline();
|
|
2207
2211
|
return;
|
|
2208
2212
|
}
|
|
2213
|
+
const sprintId = sprintIdR.value;
|
|
2209
2214
|
const sprint = await getSprint(sprintId);
|
|
2210
2215
|
try {
|
|
2211
2216
|
assertSprintStatus(sprint, ["draft"], "refine ticket");
|
|
2212
2217
|
} catch (err) {
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
log.newline();
|
|
2216
|
-
}
|
|
2218
|
+
showError(err instanceof Error ? err.message : String(err));
|
|
2219
|
+
log.newline();
|
|
2217
2220
|
return;
|
|
2218
2221
|
}
|
|
2219
2222
|
const approvedTickets = sprint.tickets.filter((t) => t.requirementStatus === "approved");
|
|
@@ -2269,21 +2272,18 @@ async function ticketRefineCommand(ticketId, options = {}) {
|
|
|
2269
2272
|
}
|
|
2270
2273
|
let issueContext = "";
|
|
2271
2274
|
if (ticket.link) {
|
|
2275
|
+
const ticketLink = ticket.link;
|
|
2272
2276
|
const fetchSpinner = createSpinner("Fetching issue data...");
|
|
2273
2277
|
fetchSpinner.start();
|
|
2274
|
-
try
|
|
2275
|
-
|
|
2276
|
-
if (issueData) {
|
|
2277
|
-
issueContext = formatIssueContext(issueData);
|
|
2278
|
-
fetchSpinner.succeed(`Issue data fetched (${String(issueData.comments.length)} comment(s))`);
|
|
2279
|
-
} else {
|
|
2280
|
-
fetchSpinner.stop();
|
|
2281
|
-
}
|
|
2282
|
-
} catch (err) {
|
|
2278
|
+
const fetchR = Result4.try(() => fetchIssueFromUrl(ticketLink));
|
|
2279
|
+
if (!fetchR.ok) {
|
|
2283
2280
|
fetchSpinner.fail("Could not fetch issue data");
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2281
|
+
showWarning(`${fetchR.error.message} \u2014 continuing without issue context`);
|
|
2282
|
+
} else if (fetchR.value) {
|
|
2283
|
+
issueContext = formatIssueContext(fetchR.value);
|
|
2284
|
+
fetchSpinner.succeed(`Issue data fetched (${String(fetchR.value.comments.length)} comment(s))`);
|
|
2285
|
+
} else {
|
|
2286
|
+
fetchSpinner.stop();
|
|
2287
2287
|
}
|
|
2288
2288
|
}
|
|
2289
2289
|
const refineDir = getRefinementDir(sprintId, ticket.id);
|
|
@@ -2301,41 +2301,34 @@ async function ticketRefineCommand(ticketId, options = {}) {
|
|
|
2301
2301
|
log.newline();
|
|
2302
2302
|
const spinner = createSpinner(`Starting ${providerName} session...`);
|
|
2303
2303
|
spinner.start();
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
spinner.succeed(`${providerName} session completed`);
|
|
2307
|
-
} catch (err) {
|
|
2304
|
+
const sessionR = await wrapAsync(() => runAiSession(refineDir, prompt, ticket.title), ensureError);
|
|
2305
|
+
if (!sessionR.ok) {
|
|
2308
2306
|
spinner.fail(`${providerName} session failed`);
|
|
2309
|
-
|
|
2310
|
-
showError(err.message);
|
|
2311
|
-
}
|
|
2307
|
+
showError(sessionR.error.message);
|
|
2312
2308
|
log.newline();
|
|
2313
2309
|
return;
|
|
2314
2310
|
}
|
|
2311
|
+
spinner.succeed(`${providerName} session completed`);
|
|
2315
2312
|
log.newline();
|
|
2316
2313
|
if (!await fileExists(outputFile)) {
|
|
2317
2314
|
showWarning("No requirements file found from AI session.");
|
|
2318
2315
|
log.newline();
|
|
2319
2316
|
return;
|
|
2320
2317
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
content = await readFile2(outputFile, "utf-8");
|
|
2324
|
-
} catch {
|
|
2318
|
+
const contentR = await wrapAsync(() => readFile2(outputFile, "utf-8"), ensureError);
|
|
2319
|
+
if (!contentR.ok) {
|
|
2325
2320
|
showError(`Failed to read requirements file: ${outputFile}`);
|
|
2326
2321
|
log.newline();
|
|
2327
2322
|
return;
|
|
2328
2323
|
}
|
|
2329
|
-
|
|
2330
|
-
try
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
if (err instanceof Error) {
|
|
2334
|
-
showError(`Failed to parse requirements file: ${err.message}`);
|
|
2335
|
-
}
|
|
2324
|
+
const content = contentR.value;
|
|
2325
|
+
const parseR = Result4.try(() => parseRequirementsFile(content));
|
|
2326
|
+
if (!parseR.ok) {
|
|
2327
|
+
showError(`Failed to parse requirements file: ${parseR.error.message}`);
|
|
2336
2328
|
log.newline();
|
|
2337
2329
|
return;
|
|
2338
2330
|
}
|
|
2331
|
+
const refinedRequirements = parseR.value;
|
|
2339
2332
|
if (refinedRequirements.length === 0) {
|
|
2340
2333
|
showWarning("No requirements found in output file.");
|
|
2341
2334
|
log.newline();
|
|
@@ -2386,11 +2379,13 @@ import { resolve as resolve2 } from "path";
|
|
|
2386
2379
|
import { confirm as confirm7, input as input4 } from "@inquirer/prompts";
|
|
2387
2380
|
async function taskAddCommand(options = {}) {
|
|
2388
2381
|
const isInteractive = options.interactive !== false;
|
|
2389
|
-
|
|
2382
|
+
const statusCheckR = await wrapAsync(async () => {
|
|
2390
2383
|
const sprintId = await resolveSprintId();
|
|
2391
2384
|
const sprint = await getSprint(sprintId);
|
|
2392
2385
|
assertSprintStatus(sprint, ["draft"], "add tasks");
|
|
2393
|
-
}
|
|
2386
|
+
}, ensureError);
|
|
2387
|
+
if (!statusCheckR.ok) {
|
|
2388
|
+
const err = statusCheckR.error;
|
|
2394
2389
|
if (err instanceof SprintStatusError) {
|
|
2395
2390
|
const mainError = err.message.split("\n")[0] ?? err.message;
|
|
2396
2391
|
showError(mainError);
|
|
@@ -2441,27 +2436,31 @@ async function taskAddCommand(options = {}) {
|
|
|
2441
2436
|
const trimmedTicket = options.ticket?.trim();
|
|
2442
2437
|
ticketId = trimmedTicket === "" ? void 0 : trimmedTicket;
|
|
2443
2438
|
if (ticketId) {
|
|
2444
|
-
|
|
2445
|
-
|
|
2439
|
+
const resolvedTicketId = ticketId;
|
|
2440
|
+
const ticketProjectR = await wrapAsync(async () => {
|
|
2441
|
+
const ticket = await getTicket(resolvedTicketId);
|
|
2446
2442
|
const project = await getProject(ticket.projectName);
|
|
2447
|
-
|
|
2448
|
-
}
|
|
2443
|
+
return project.repositories[0]?.path;
|
|
2444
|
+
}, ensureError);
|
|
2445
|
+
if (ticketProjectR.ok) {
|
|
2446
|
+
projectPath = ticketProjectR.value;
|
|
2447
|
+
} else {
|
|
2449
2448
|
if (!trimmedProject) {
|
|
2450
2449
|
showError(`Ticket not found: ${ticketId}`);
|
|
2451
2450
|
console.log(muted(" Provide --project or a valid --ticket\n"));
|
|
2452
2451
|
exitWithCode(EXIT_ERROR);
|
|
2453
2452
|
}
|
|
2454
2453
|
const validation = await validateProjectPath(trimmedProject);
|
|
2455
|
-
if (validation
|
|
2456
|
-
showError(`Invalid project path: ${validation}`);
|
|
2454
|
+
if (!validation.ok) {
|
|
2455
|
+
showError(`Invalid project path: ${validation.error.message}`);
|
|
2457
2456
|
exitWithCode(EXIT_ERROR);
|
|
2458
2457
|
}
|
|
2459
2458
|
projectPath = resolve2(trimmedProject);
|
|
2460
2459
|
}
|
|
2461
2460
|
} else if (trimmedProject) {
|
|
2462
2461
|
const validation = await validateProjectPath(trimmedProject);
|
|
2463
|
-
if (validation
|
|
2464
|
-
showError(`Invalid project path: ${validation}`);
|
|
2462
|
+
if (!validation.ok) {
|
|
2463
|
+
showError(`Invalid project path: ${validation.error.message}`);
|
|
2465
2464
|
exitWithCode(EXIT_ERROR);
|
|
2466
2465
|
}
|
|
2467
2466
|
projectPath = resolve2(trimmedProject);
|
|
@@ -2475,10 +2474,15 @@ async function taskAddCommand(options = {}) {
|
|
|
2475
2474
|
default: options.name?.trim(),
|
|
2476
2475
|
validate: (v) => v.trim().length > 0 ? true : "Name is required"
|
|
2477
2476
|
});
|
|
2478
|
-
|
|
2477
|
+
const descR = await editorInput({
|
|
2479
2478
|
message: "Description (optional):",
|
|
2480
2479
|
default: options.description?.trim()
|
|
2481
2480
|
});
|
|
2481
|
+
if (!descR.ok) {
|
|
2482
|
+
showError(`Editor input failed: ${descR.error.message}`);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
description = descR.value;
|
|
2482
2486
|
steps = options.steps ? [...options.steps] : [];
|
|
2483
2487
|
const addSteps = await confirm7({
|
|
2484
2488
|
message: `${emoji.donut} ${steps.length > 0 ? `Add more steps? (${String(steps.length)} pre-filled)` : "Add implementation steps?"}`,
|
|
@@ -2518,8 +2522,9 @@ async function taskAddCommand(options = {}) {
|
|
|
2518
2522
|
ticketId = ticketChoice;
|
|
2519
2523
|
const ticket = tickets.find((t) => t.id === ticketChoice);
|
|
2520
2524
|
if (ticket) {
|
|
2521
|
-
|
|
2522
|
-
|
|
2525
|
+
const projR = await wrapAsync(() => getProject(ticket.projectName), ensureError);
|
|
2526
|
+
if (projR.ok) {
|
|
2527
|
+
const project = projR.value;
|
|
2523
2528
|
if (project.repositories.length === 1) {
|
|
2524
2529
|
projectPath = project.repositories[0]?.path;
|
|
2525
2530
|
} else {
|
|
@@ -2532,18 +2537,21 @@ async function taskAddCommand(options = {}) {
|
|
|
2532
2537
|
}))
|
|
2533
2538
|
});
|
|
2534
2539
|
}
|
|
2535
|
-
}
|
|
2540
|
+
} else {
|
|
2536
2541
|
log.warn(`Project '${ticket.projectName}' not found, will prompt for path.`);
|
|
2537
2542
|
}
|
|
2538
2543
|
}
|
|
2539
2544
|
}
|
|
2540
2545
|
} else if (options.ticket) {
|
|
2541
2546
|
ticketId = options.ticket;
|
|
2542
|
-
|
|
2543
|
-
|
|
2547
|
+
const resolvedTicketId = ticketId;
|
|
2548
|
+
const tpR = await wrapAsync(async () => {
|
|
2549
|
+
const ticket = await getTicket(resolvedTicketId);
|
|
2544
2550
|
const project = await getProject(ticket.projectName);
|
|
2545
|
-
|
|
2546
|
-
}
|
|
2551
|
+
return project.repositories[0]?.path;
|
|
2552
|
+
}, ensureError);
|
|
2553
|
+
if (tpR.ok) {
|
|
2554
|
+
projectPath = tpR.value;
|
|
2547
2555
|
}
|
|
2548
2556
|
}
|
|
2549
2557
|
if (projectPath === void 0) {
|
|
@@ -2563,7 +2571,7 @@ async function taskAddCommand(options = {}) {
|
|
|
2563
2571
|
default: options.project?.trim() ?? process.cwd(),
|
|
2564
2572
|
validate: async (v) => {
|
|
2565
2573
|
const result = await validateProjectPath(v.trim());
|
|
2566
|
-
return result;
|
|
2574
|
+
return result.ok ? true : result.error.message;
|
|
2567
2575
|
}
|
|
2568
2576
|
});
|
|
2569
2577
|
projectPath = resolve2(expandTilde(projectPath.trim()));
|
|
@@ -2581,7 +2589,7 @@ async function taskAddCommand(options = {}) {
|
|
|
2581
2589
|
default: options.project?.trim() ?? process.cwd(),
|
|
2582
2590
|
validate: async (v) => {
|
|
2583
2591
|
const result = await validateProjectPath(v.trim());
|
|
2584
|
-
return result;
|
|
2592
|
+
return result.ok ? true : result.error.message;
|
|
2585
2593
|
}
|
|
2586
2594
|
});
|
|
2587
2595
|
projectPath = resolve2(expandTilde(projectPath.trim()));
|
|
@@ -2595,33 +2603,10 @@ async function taskAddCommand(options = {}) {
|
|
|
2595
2603
|
showError("Project path is required");
|
|
2596
2604
|
exitWithCode(EXIT_ERROR);
|
|
2597
2605
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
steps,
|
|
2603
|
-
ticketId,
|
|
2604
|
-
projectPath
|
|
2605
|
-
});
|
|
2606
|
-
showSuccess("Task added!", [
|
|
2607
|
-
["ID", task.id],
|
|
2608
|
-
["Name", task.name],
|
|
2609
|
-
["Project", task.projectPath],
|
|
2610
|
-
["Order", String(task.order)]
|
|
2611
|
-
]);
|
|
2612
|
-
if (task.ticketId) {
|
|
2613
|
-
console.log(field("Ticket", task.ticketId));
|
|
2614
|
-
}
|
|
2615
|
-
if (task.steps.length > 0) {
|
|
2616
|
-
console.log(field("Steps", ""));
|
|
2617
|
-
task.steps.forEach((step, i) => {
|
|
2618
|
-
console.log(muted(` ${String(i + 1)}. ${step}`));
|
|
2619
|
-
});
|
|
2620
|
-
}
|
|
2621
|
-
console.log("");
|
|
2622
|
-
} catch (err) {
|
|
2623
|
-
if (err instanceof SprintStatusError) {
|
|
2624
|
-
const mainError = err.message.split("\n")[0] ?? err.message;
|
|
2606
|
+
const addR = await wrapAsync(() => addTask({ name, description, steps, ticketId, projectPath }), ensureError);
|
|
2607
|
+
if (!addR.ok) {
|
|
2608
|
+
if (addR.error instanceof SprintStatusError) {
|
|
2609
|
+
const mainError = addR.error.message.split("\n")[0] ?? addR.error.message;
|
|
2625
2610
|
showError(mainError);
|
|
2626
2611
|
showNextSteps([
|
|
2627
2612
|
["ralphctl sprint close", "close current sprint"],
|
|
@@ -2631,12 +2616,30 @@ async function taskAddCommand(options = {}) {
|
|
|
2631
2616
|
if (!isInteractive) exitWithCode(EXIT_ERROR);
|
|
2632
2617
|
return;
|
|
2633
2618
|
}
|
|
2634
|
-
throw
|
|
2619
|
+
throw addR.error;
|
|
2620
|
+
}
|
|
2621
|
+
const task = addR.value;
|
|
2622
|
+
showSuccess("Task added!", [
|
|
2623
|
+
["ID", task.id],
|
|
2624
|
+
["Name", task.name],
|
|
2625
|
+
["Project", task.projectPath],
|
|
2626
|
+
["Order", String(task.order)]
|
|
2627
|
+
]);
|
|
2628
|
+
if (task.ticketId) {
|
|
2629
|
+
console.log(field("Ticket", task.ticketId));
|
|
2635
2630
|
}
|
|
2631
|
+
if (task.steps.length > 0) {
|
|
2632
|
+
console.log(field("Steps", ""));
|
|
2633
|
+
task.steps.forEach((step, i) => {
|
|
2634
|
+
console.log(muted(` ${String(i + 1)}. ${step}`));
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
console.log("");
|
|
2636
2638
|
}
|
|
2637
2639
|
|
|
2638
2640
|
// src/commands/task/import.ts
|
|
2639
2641
|
import { readFile as readFile3 } from "fs/promises";
|
|
2642
|
+
import { Result as Result5 } from "typescript-result";
|
|
2640
2643
|
async function taskImportCommand(args) {
|
|
2641
2644
|
const filePath = args[0];
|
|
2642
2645
|
if (!filePath) {
|
|
@@ -2660,22 +2663,19 @@ async function taskImportCommand(args) {
|
|
|
2660
2663
|
log.newline();
|
|
2661
2664
|
return;
|
|
2662
2665
|
}
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
content = await readFile3(filePath, "utf-8");
|
|
2666
|
-
} catch {
|
|
2666
|
+
const contentR = await wrapAsync(() => readFile3(filePath, "utf-8"), ensureError);
|
|
2667
|
+
if (!contentR.ok) {
|
|
2667
2668
|
showError(`Failed to read file: ${filePath}`);
|
|
2668
2669
|
log.newline();
|
|
2669
2670
|
return;
|
|
2670
2671
|
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
data = JSON.parse(content);
|
|
2674
|
-
} catch {
|
|
2672
|
+
const dataR = Result5.try(() => JSON.parse(contentR.value));
|
|
2673
|
+
if (!dataR.ok) {
|
|
2675
2674
|
showError("Invalid JSON format.");
|
|
2676
2675
|
log.newline();
|
|
2677
2676
|
return;
|
|
2678
2677
|
}
|
|
2678
|
+
const data = dataR.value;
|
|
2679
2679
|
const result = ImportTasksSchema.safeParse(data);
|
|
2680
2680
|
if (!result.success) {
|
|
2681
2681
|
showError("Invalid task format");
|
|
@@ -2709,8 +2709,8 @@ async function taskImportCommand(args) {
|
|
|
2709
2709
|
const spinner = createSpinner(`Importing ${String(tasks.length)} task(s)...`).start();
|
|
2710
2710
|
let imported = 0;
|
|
2711
2711
|
for (const taskInput of tasks) {
|
|
2712
|
-
|
|
2713
|
-
|
|
2712
|
+
const addR = await wrapAsync(
|
|
2713
|
+
() => addTask({
|
|
2714
2714
|
name: taskInput.name,
|
|
2715
2715
|
description: taskInput.description,
|
|
2716
2716
|
steps: taskInput.steps ?? [],
|
|
@@ -2718,29 +2718,31 @@ async function taskImportCommand(args) {
|
|
|
2718
2718
|
blockedBy: [],
|
|
2719
2719
|
// Set later
|
|
2720
2720
|
projectPath: taskInput.projectPath
|
|
2721
|
-
})
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
imported++;
|
|
2727
|
-
spinner.text = `Importing tasks... (${String(imported)}/${String(tasks.length)})`;
|
|
2728
|
-
} catch (err) {
|
|
2729
|
-
if (err instanceof SprintStatusError) {
|
|
2721
|
+
}),
|
|
2722
|
+
ensureError
|
|
2723
|
+
);
|
|
2724
|
+
if (!addR.ok) {
|
|
2725
|
+
if (addR.error instanceof SprintStatusError) {
|
|
2730
2726
|
spinner.fail("Import failed");
|
|
2731
|
-
showError(
|
|
2727
|
+
showError(addR.error.message);
|
|
2732
2728
|
log.newline();
|
|
2733
2729
|
return;
|
|
2734
2730
|
}
|
|
2735
2731
|
log.itemError(`Failed to add: ${taskInput.name}`);
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
}
|
|
2732
|
+
console.log(muted(` ${addR.error.message}`));
|
|
2733
|
+
continue;
|
|
2739
2734
|
}
|
|
2735
|
+
const task = addR.value;
|
|
2736
|
+
if (taskInput.id) {
|
|
2737
|
+
localToRealId.set(taskInput.id, task.id);
|
|
2738
|
+
}
|
|
2739
|
+
createdTasks.push({ task: taskInput, realId: task.id });
|
|
2740
|
+
imported++;
|
|
2741
|
+
spinner.text = `Importing tasks... (${String(imported)}/${String(tasks.length)})`;
|
|
2740
2742
|
}
|
|
2741
2743
|
spinner.text = "Resolving task dependencies...";
|
|
2742
2744
|
const tasksFilePath = getTasksFilePath(sprintId);
|
|
2743
|
-
await withFileLock(tasksFilePath, async () => {
|
|
2745
|
+
const lockR = await withFileLock(tasksFilePath, async () => {
|
|
2744
2746
|
const allTasks = await getTasks();
|
|
2745
2747
|
for (const { task: taskInput, realId } of createdTasks) {
|
|
2746
2748
|
const blockedBy = (taskInput.blockedBy ?? []).map((localId) => localToRealId.get(localId) ?? "").filter((id) => id !== "");
|
|
@@ -2753,6 +2755,11 @@ async function taskImportCommand(args) {
|
|
|
2753
2755
|
}
|
|
2754
2756
|
await saveTasks(allTasks);
|
|
2755
2757
|
});
|
|
2758
|
+
if (!lockR.ok) {
|
|
2759
|
+
showError(`Failed to update dependencies: ${lockR.error.message}`);
|
|
2760
|
+
log.newline();
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2756
2763
|
spinner.succeed(`Imported ${String(imported)}/${String(tasks.length)} tasks`);
|
|
2757
2764
|
for (const { task: taskInput, realId } of createdTasks) {
|
|
2758
2765
|
log.itemSuccess(`${realId}: ${taskInput.name}`);
|
|
@@ -2876,76 +2883,75 @@ async function taskShowCommand(args) {
|
|
|
2876
2883
|
if (!selected) return;
|
|
2877
2884
|
taskId = selected;
|
|
2878
2885
|
}
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
labelValue("Order", String(task.order)),
|
|
2885
|
-
labelValue("Project", task.projectPath)
|
|
2886
|
-
];
|
|
2887
|
-
if (task.ticketId) {
|
|
2888
|
-
infoLines.push(labelValue("Ticket", task.ticketId));
|
|
2889
|
-
}
|
|
2890
|
-
if (task.description) {
|
|
2891
|
-
infoLines.push("");
|
|
2892
|
-
infoLines.push(labelValue("Description", ""));
|
|
2893
|
-
for (const line2 of task.description.split("\n")) {
|
|
2894
|
-
infoLines.push(`${" ".repeat(DETAIL_LABEL_WIDTH + 1)}${line2}`);
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
log.newline();
|
|
2898
|
-
console.log(renderCard(`${icons.task} ${task.name}`, infoLines));
|
|
2899
|
-
if (task.steps.length > 0) {
|
|
2886
|
+
const taskR = await wrapAsync(() => getTask(taskId), ensureError);
|
|
2887
|
+
if (!taskR.ok) {
|
|
2888
|
+
if (taskR.error instanceof TaskNotFoundError) {
|
|
2889
|
+
showError(`Task not found: ${taskId}`);
|
|
2890
|
+
showNextStep("ralphctl task list", "see available tasks");
|
|
2900
2891
|
log.newline();
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
const step = task.steps[i] ?? "";
|
|
2904
|
-
const checkbox = task.status === "done" ? colors.success("[x]") : muted("[ ]");
|
|
2905
|
-
stepLines.push(`${checkbox} ${muted(String(i + 1) + ".")} ${step}`);
|
|
2906
|
-
}
|
|
2907
|
-
console.log(renderCard(`${icons.bullet} Steps (${String(task.steps.length)})`, stepLines));
|
|
2892
|
+
} else {
|
|
2893
|
+
throw taskR.error;
|
|
2908
2894
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
const task = taskR.value;
|
|
2898
|
+
const infoLines = [
|
|
2899
|
+
labelValue("ID", task.id),
|
|
2900
|
+
labelValue("Status", formatTaskStatus(task.status)),
|
|
2901
|
+
labelValue("Order", String(task.order)),
|
|
2902
|
+
labelValue("Project", task.projectPath)
|
|
2903
|
+
];
|
|
2904
|
+
if (task.ticketId) {
|
|
2905
|
+
infoLines.push(labelValue("Ticket", task.ticketId));
|
|
2906
|
+
}
|
|
2907
|
+
if (task.description) {
|
|
2908
|
+
infoLines.push("");
|
|
2909
|
+
infoLines.push(labelValue("Description", ""));
|
|
2910
|
+
for (const line2 of task.description.split("\n")) {
|
|
2911
|
+
infoLines.push(`${" ".repeat(DETAIL_LABEL_WIDTH + 1)}${line2}`);
|
|
2916
2912
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
}
|
|
2913
|
+
}
|
|
2914
|
+
log.newline();
|
|
2915
|
+
console.log(renderCard(`${icons.task} ${task.name}`, infoLines));
|
|
2916
|
+
if (task.steps.length > 0) {
|
|
2917
|
+
log.newline();
|
|
2918
|
+
const stepLines = [];
|
|
2919
|
+
for (let i = 0; i < task.steps.length; i++) {
|
|
2920
|
+
const step = task.steps[i] ?? "";
|
|
2921
|
+
const checkbox = task.status === "done" ? colors.success("[x]") : muted("[ ]");
|
|
2922
|
+
stepLines.push(`${checkbox} ${muted(String(i + 1) + ".")} ${step}`);
|
|
2923
|
+
}
|
|
2924
|
+
console.log(renderCard(`${icons.bullet} Steps (${String(task.steps.length)})`, stepLines));
|
|
2925
|
+
}
|
|
2926
|
+
if (task.blockedBy.length > 0) {
|
|
2927
|
+
log.newline();
|
|
2928
|
+
const depLines = [];
|
|
2929
|
+
for (const dep of task.blockedBy) {
|
|
2930
|
+
depLines.push(`${icons.bullet} ${dep}`);
|
|
2927
2931
|
}
|
|
2928
|
-
|
|
2932
|
+
console.log(renderCard(`${icons.warning} Blocked By`, depLines));
|
|
2933
|
+
}
|
|
2934
|
+
if (task.ticketId) {
|
|
2935
|
+
const taskTicketId = task.ticketId;
|
|
2936
|
+
const ticketR = await wrapAsync(() => getTicket(taskTicketId), ensureError);
|
|
2937
|
+
if (ticketR.ok && ticketR.value.requirements) {
|
|
2929
2938
|
log.newline();
|
|
2930
|
-
const
|
|
2931
|
-
|
|
2932
|
-
verifyLines.push(colors.muted(horizontalLine(30, "rounded")));
|
|
2933
|
-
for (const line2 of task.verificationOutput.split("\n").slice(0, 10)) {
|
|
2934
|
-
verifyLines.push(muted(line2));
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
console.log(renderCard(`${icons.success} Verification`, verifyLines));
|
|
2939
|
+
const reqLines = ticketR.value.requirements.split("\n");
|
|
2940
|
+
console.log(renderCard(`${icons.ticket} Requirements`, reqLines));
|
|
2938
2941
|
}
|
|
2942
|
+
}
|
|
2943
|
+
if (task.verified) {
|
|
2939
2944
|
log.newline();
|
|
2940
|
-
|
|
2941
|
-
if (
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
throw err;
|
|
2945
|
+
const verifyLines = [`${colors.success(icons.success)} Verified`];
|
|
2946
|
+
if (task.verificationOutput) {
|
|
2947
|
+
verifyLines.push(colors.muted(horizontalLine(30, "rounded")));
|
|
2948
|
+
for (const line2 of task.verificationOutput.split("\n").slice(0, 10)) {
|
|
2949
|
+
verifyLines.push(muted(line2));
|
|
2950
|
+
}
|
|
2947
2951
|
}
|
|
2952
|
+
console.log(renderCard(`${icons.success} Verification`, verifyLines));
|
|
2948
2953
|
}
|
|
2954
|
+
log.newline();
|
|
2949
2955
|
}
|
|
2950
2956
|
|
|
2951
2957
|
// src/commands/task/status.ts
|
|
@@ -2995,32 +3001,32 @@ async function taskStatusCommand(args, options = {}) {
|
|
|
2995
3001
|
}
|
|
2996
3002
|
return;
|
|
2997
3003
|
}
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
["ID", task.id],
|
|
3002
|
-
["Name", task.name],
|
|
3003
|
-
["Status", formatTaskStatus(task.status)]
|
|
3004
|
-
]);
|
|
3005
|
-
log.newline();
|
|
3006
|
-
} catch (err) {
|
|
3007
|
-
if (err instanceof TaskNotFoundError) {
|
|
3004
|
+
const updateR = await wrapAsync(() => updateTaskStatus(taskId, result.data), ensureError);
|
|
3005
|
+
if (!updateR.ok) {
|
|
3006
|
+
if (updateR.error instanceof TaskNotFoundError) {
|
|
3008
3007
|
showError(`Task not found: ${taskId}`);
|
|
3009
3008
|
showNextStep("ralphctl task list", "see available tasks");
|
|
3010
3009
|
log.newline();
|
|
3011
3010
|
if (options.noInteractive) {
|
|
3012
3011
|
exitWithCode(EXIT_ERROR);
|
|
3013
3012
|
}
|
|
3014
|
-
} else if (
|
|
3015
|
-
showError(
|
|
3013
|
+
} else if (updateR.error instanceof SprintStatusError) {
|
|
3014
|
+
showError(updateR.error.message);
|
|
3016
3015
|
log.newline();
|
|
3017
3016
|
if (options.noInteractive) {
|
|
3018
3017
|
exitWithCode(EXIT_ERROR);
|
|
3019
3018
|
}
|
|
3020
3019
|
} else {
|
|
3021
|
-
throw
|
|
3020
|
+
throw updateR.error;
|
|
3022
3021
|
}
|
|
3022
|
+
return;
|
|
3023
3023
|
}
|
|
3024
|
+
showSuccess("Task status updated!", [
|
|
3025
|
+
["ID", updateR.value.id],
|
|
3026
|
+
["Name", updateR.value.name],
|
|
3027
|
+
["Status", formatTaskStatus(updateR.value.status)]
|
|
3028
|
+
]);
|
|
3029
|
+
log.newline();
|
|
3024
3030
|
}
|
|
3025
3031
|
|
|
3026
3032
|
// src/commands/task/next.ts
|
|
@@ -3075,25 +3081,25 @@ async function taskReorderCommand(args) {
|
|
|
3075
3081
|
if (newOrder === void 0 || isNaN(newOrder) || newOrder < 1) {
|
|
3076
3082
|
newOrder = await inputPositiveInt("New position (1 = highest priority):");
|
|
3077
3083
|
}
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
["ID", task.id],
|
|
3082
|
-
["Name", task.name],
|
|
3083
|
-
["New Order", String(task.order)]
|
|
3084
|
-
]);
|
|
3085
|
-
log.newline();
|
|
3086
|
-
} catch (err) {
|
|
3087
|
-
if (err instanceof TaskNotFoundError) {
|
|
3084
|
+
const reorderR = await wrapAsync(() => reorderTask(taskId, newOrder), ensureError);
|
|
3085
|
+
if (!reorderR.ok) {
|
|
3086
|
+
if (reorderR.error instanceof TaskNotFoundError) {
|
|
3088
3087
|
showError(`Task not found: ${taskId}`);
|
|
3089
3088
|
log.newline();
|
|
3090
|
-
} else if (
|
|
3091
|
-
showError(
|
|
3089
|
+
} else if (reorderR.error instanceof SprintStatusError) {
|
|
3090
|
+
showError(reorderR.error.message);
|
|
3092
3091
|
log.newline();
|
|
3093
3092
|
} else {
|
|
3094
|
-
throw
|
|
3093
|
+
throw reorderR.error;
|
|
3095
3094
|
}
|
|
3095
|
+
return;
|
|
3096
3096
|
}
|
|
3097
|
+
showSuccess("Task reordered!", [
|
|
3098
|
+
["ID", reorderR.value.id],
|
|
3099
|
+
["Name", reorderR.value.name],
|
|
3100
|
+
["New Order", String(reorderR.value.order)]
|
|
3101
|
+
]);
|
|
3102
|
+
log.newline();
|
|
3097
3103
|
}
|
|
3098
3104
|
|
|
3099
3105
|
// src/commands/task/remove.ts
|
|
@@ -3106,7 +3112,7 @@ async function taskRemoveCommand(args) {
|
|
|
3106
3112
|
if (!selected) return;
|
|
3107
3113
|
taskId = selected;
|
|
3108
3114
|
}
|
|
3109
|
-
|
|
3115
|
+
const opR = await wrapAsync(async () => {
|
|
3110
3116
|
const task = await getTask(taskId);
|
|
3111
3117
|
if (!skipConfirm) {
|
|
3112
3118
|
const confirmed = await confirm8({
|
|
@@ -3115,32 +3121,39 @@ async function taskRemoveCommand(args) {
|
|
|
3115
3121
|
});
|
|
3116
3122
|
if (!confirmed) {
|
|
3117
3123
|
console.log(muted("\nTask removal cancelled.\n"));
|
|
3118
|
-
return;
|
|
3124
|
+
return null;
|
|
3119
3125
|
}
|
|
3120
3126
|
}
|
|
3121
3127
|
await removeTask(taskId);
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
if (
|
|
3128
|
+
return task;
|
|
3129
|
+
}, ensureError);
|
|
3130
|
+
if (!opR.ok) {
|
|
3131
|
+
if (opR.error instanceof TaskNotFoundError) {
|
|
3126
3132
|
showError(`Task not found: ${taskId}`);
|
|
3127
3133
|
log.newline();
|
|
3128
|
-
} else if (
|
|
3129
|
-
showError(
|
|
3134
|
+
} else if (opR.error instanceof SprintStatusError) {
|
|
3135
|
+
showError(opR.error.message);
|
|
3130
3136
|
log.newline();
|
|
3131
3137
|
} else {
|
|
3132
|
-
throw
|
|
3138
|
+
throw opR.error;
|
|
3133
3139
|
}
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
if (opR.value !== null) {
|
|
3143
|
+
showSuccess("Task removed", [["ID", taskId]]);
|
|
3144
|
+
log.newline();
|
|
3134
3145
|
}
|
|
3135
3146
|
}
|
|
3136
3147
|
|
|
3137
3148
|
// src/commands/progress/log.ts
|
|
3138
3149
|
async function progressLogCommand(args) {
|
|
3139
|
-
|
|
3150
|
+
const statusCheckR = await wrapAsync(async () => {
|
|
3140
3151
|
const sprintId = await resolveSprintId();
|
|
3141
3152
|
const sprint = await getSprint(sprintId);
|
|
3142
3153
|
assertSprintStatus(sprint, ["active"], "log progress");
|
|
3143
|
-
}
|
|
3154
|
+
}, ensureError);
|
|
3155
|
+
if (!statusCheckR.ok) {
|
|
3156
|
+
const err = statusCheckR.error;
|
|
3144
3157
|
if (err instanceof SprintStatusError) {
|
|
3145
3158
|
const mainError = err.message.split("\n")[0] ?? err.message;
|
|
3146
3159
|
showError(mainError);
|
|
@@ -3158,9 +3171,15 @@ async function progressLogCommand(args) {
|
|
|
3158
3171
|
}
|
|
3159
3172
|
let message = args.join(" ").trim();
|
|
3160
3173
|
if (!message) {
|
|
3161
|
-
|
|
3174
|
+
const editorR = await editorInput({
|
|
3162
3175
|
message: "Progress message:"
|
|
3163
3176
|
});
|
|
3177
|
+
if (!editorR.ok) {
|
|
3178
|
+
showError(`Editor input failed: ${editorR.error.message}`);
|
|
3179
|
+
log.newline();
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
message = editorR.value;
|
|
3164
3183
|
message = message.trim();
|
|
3165
3184
|
}
|
|
3166
3185
|
if (!message) {
|
|
@@ -3168,18 +3187,18 @@ async function progressLogCommand(args) {
|
|
|
3168
3187
|
log.newline();
|
|
3169
3188
|
return;
|
|
3170
3189
|
}
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
} catch (err) {
|
|
3176
|
-
if (err instanceof SprintStatusError) {
|
|
3177
|
-
showError(err.message);
|
|
3190
|
+
const logR = await wrapAsync(() => logProgress(message), ensureError);
|
|
3191
|
+
if (!logR.ok) {
|
|
3192
|
+
if (logR.error instanceof SprintStatusError) {
|
|
3193
|
+
showError(logR.error.message);
|
|
3178
3194
|
log.newline();
|
|
3179
3195
|
} else {
|
|
3180
|
-
throw
|
|
3196
|
+
throw logR.error;
|
|
3181
3197
|
}
|
|
3198
|
+
return;
|
|
3182
3199
|
}
|
|
3200
|
+
showSuccess("Progress logged.");
|
|
3201
|
+
log.newline();
|
|
3183
3202
|
}
|
|
3184
3203
|
|
|
3185
3204
|
// src/commands/progress/show.ts
|
|
@@ -3321,12 +3340,11 @@ function checkGlabInstalled() {
|
|
|
3321
3340
|
}
|
|
3322
3341
|
async function checkDataDirectory() {
|
|
3323
3342
|
const dataDir = getDataDir();
|
|
3324
|
-
|
|
3325
|
-
|
|
3343
|
+
const accessR = await wrapAsync(() => access(dataDir, constants.R_OK | constants.W_OK), ensureError);
|
|
3344
|
+
if (accessR.ok) {
|
|
3326
3345
|
return { name: "Data directory", status: "pass", detail: dataDir };
|
|
3327
|
-
} catch {
|
|
3328
|
-
return { name: "Data directory", status: "fail", detail: `${dataDir} not accessible or writable` };
|
|
3329
3346
|
}
|
|
3347
|
+
return { name: "Data directory", status: "fail", detail: `${dataDir} not accessible or writable` };
|
|
3330
3348
|
}
|
|
3331
3349
|
async function checkProjectPaths() {
|
|
3332
3350
|
const projects = await listProjects();
|
|
@@ -3337,8 +3355,8 @@ async function checkProjectPaths() {
|
|
|
3337
3355
|
for (const project of projects) {
|
|
3338
3356
|
for (const repo of project.repositories) {
|
|
3339
3357
|
const validation = await validateProjectPath(repo.path);
|
|
3340
|
-
if (validation
|
|
3341
|
-
issues.push(`${project.name}/${repo.name}: ${validation}`);
|
|
3358
|
+
if (!validation.ok) {
|
|
3359
|
+
issues.push(`${project.name}/${repo.name}: ${validation.error.message}`);
|
|
3342
3360
|
continue;
|
|
3343
3361
|
}
|
|
3344
3362
|
const gitDir = join4(repo.path, ".git");
|
|
@@ -3367,13 +3385,11 @@ async function checkCurrentSprint() {
|
|
|
3367
3385
|
if (!await fileExists(sprintPath)) {
|
|
3368
3386
|
return { name: "Current sprint", status: "fail", detail: `sprint file missing: ${sprintId}` };
|
|
3369
3387
|
}
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
return { name: "Current sprint", status: "
|
|
3373
|
-
} catch (err) {
|
|
3374
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3375
|
-
return { name: "Current sprint", status: "fail", detail: `invalid sprint data: ${message}` };
|
|
3388
|
+
const result = await readValidatedJson(sprintPath, SprintSchema);
|
|
3389
|
+
if (!result.ok) {
|
|
3390
|
+
return { name: "Current sprint", status: "fail", detail: `invalid sprint data: ${result.error.message}` };
|
|
3376
3391
|
}
|
|
3392
|
+
return { name: "Current sprint", status: "pass", detail: `${result.value.name} (${result.value.status})` };
|
|
3377
3393
|
}
|
|
3378
3394
|
async function doctorCommand() {
|
|
3379
3395
|
printHeader("System Health Check", icons.info);
|
|
@@ -3528,11 +3544,9 @@ function showWelcomeBanner() {
|
|
|
3528
3544
|
showBanner();
|
|
3529
3545
|
}
|
|
3530
3546
|
async function readTasksSafe(sprintId) {
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
return [];
|
|
3535
|
-
}
|
|
3547
|
+
const result = await readValidatedJson(getTasksFilePath(sprintId), TasksSchema);
|
|
3548
|
+
if (!result.ok) return [];
|
|
3549
|
+
return result.value;
|
|
3536
3550
|
}
|
|
3537
3551
|
async function getMenuContext() {
|
|
3538
3552
|
let dashboardData = null;
|
|
@@ -3591,21 +3605,21 @@ async function getMenuContext() {
|
|
|
3591
3605
|
async function interactiveMode() {
|
|
3592
3606
|
let escPressed = false;
|
|
3593
3607
|
while (true) {
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
console.log(line2);
|
|
3602
|
-
}
|
|
3603
|
-
log.newline();
|
|
3608
|
+
const { ctx, dashboardData } = await getMenuContext();
|
|
3609
|
+
clearScreen();
|
|
3610
|
+
showWelcomeBanner();
|
|
3611
|
+
const statusLines = renderStatusHeader(dashboardData);
|
|
3612
|
+
if (statusLines.length > 0) {
|
|
3613
|
+
for (const line2 of statusLines) {
|
|
3614
|
+
console.log(line2);
|
|
3604
3615
|
}
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3616
|
+
log.newline();
|
|
3617
|
+
}
|
|
3618
|
+
const { items: mainMenu, defaultValue } = buildMainMenu(ctx);
|
|
3619
|
+
const effectiveDefault = escPressed ? "exit" : defaultValue;
|
|
3620
|
+
escPressed = false;
|
|
3621
|
+
const commandResult = await wrapAsync(
|
|
3622
|
+
() => escapableSelect(
|
|
3609
3623
|
{
|
|
3610
3624
|
message: `${emoji.donut} What would you like to do?`,
|
|
3611
3625
|
choices: mainMenu,
|
|
@@ -3615,40 +3629,43 @@ async function interactiveMode() {
|
|
|
3615
3629
|
theme: selectTheme
|
|
3616
3630
|
},
|
|
3617
3631
|
{ escLabel: "exit" }
|
|
3618
|
-
)
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
if (command === "exit") {
|
|
3624
|
-
showFarewell();
|
|
3625
|
-
break;
|
|
3626
|
-
}
|
|
3627
|
-
if (command.startsWith("action:")) {
|
|
3628
|
-
const parts = command.split(":");
|
|
3629
|
-
const group = parts[1] ?? "";
|
|
3630
|
-
const subCommand = parts[2] ?? "";
|
|
3631
|
-
log.newline();
|
|
3632
|
-
await executeCommand(group, subCommand);
|
|
3633
|
-
log.newline();
|
|
3634
|
-
await pressEnterToContinue();
|
|
3635
|
-
continue;
|
|
3636
|
-
}
|
|
3637
|
-
if (command === "wizard") {
|
|
3638
|
-
const { runWizard } = await import("./wizard-LRELAN2J.mjs");
|
|
3639
|
-
await runWizard();
|
|
3640
|
-
continue;
|
|
3641
|
-
}
|
|
3642
|
-
const subMenu = buildSubMenu(command, ctx);
|
|
3643
|
-
if (subMenu) {
|
|
3644
|
-
await handleSubMenu(command, subMenu);
|
|
3645
|
-
}
|
|
3646
|
-
} catch (err) {
|
|
3647
|
-
if (err.name === "ExitPromptError") {
|
|
3632
|
+
),
|
|
3633
|
+
ensureError
|
|
3634
|
+
);
|
|
3635
|
+
if (!commandResult.ok) {
|
|
3636
|
+
if (commandResult.error.name === "ExitPromptError") {
|
|
3648
3637
|
showFarewell();
|
|
3649
3638
|
break;
|
|
3650
3639
|
}
|
|
3651
|
-
throw
|
|
3640
|
+
throw commandResult.error;
|
|
3641
|
+
}
|
|
3642
|
+
const command = commandResult.value;
|
|
3643
|
+
if (command === null) {
|
|
3644
|
+
escPressed = true;
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
if (command === "exit") {
|
|
3648
|
+
showFarewell();
|
|
3649
|
+
break;
|
|
3650
|
+
}
|
|
3651
|
+
if (command.startsWith("action:")) {
|
|
3652
|
+
const parts = command.split(":");
|
|
3653
|
+
const group = parts[1] ?? "";
|
|
3654
|
+
const subCommand = parts[2] ?? "";
|
|
3655
|
+
log.newline();
|
|
3656
|
+
await executeCommand(group, subCommand);
|
|
3657
|
+
log.newline();
|
|
3658
|
+
await pressEnterToContinue();
|
|
3659
|
+
continue;
|
|
3660
|
+
}
|
|
3661
|
+
if (command === "wizard") {
|
|
3662
|
+
const { runWizard } = await import("./wizard-MCDDXLGE.mjs");
|
|
3663
|
+
await runWizard();
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
const subMenu = buildSubMenu(command, ctx);
|
|
3667
|
+
if (subMenu) {
|
|
3668
|
+
await handleSubMenu(command, subMenu);
|
|
3652
3669
|
}
|
|
3653
3670
|
}
|
|
3654
3671
|
}
|
|
@@ -3656,35 +3673,38 @@ async function handleSubMenu(commandGroup, initialSubMenu) {
|
|
|
3656
3673
|
let currentTitle = initialSubMenu.title;
|
|
3657
3674
|
let currentItems = initialSubMenu.items;
|
|
3658
3675
|
while (true) {
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3676
|
+
log.newline();
|
|
3677
|
+
const subCommandResult = await wrapAsync(
|
|
3678
|
+
() => escapableSelect({
|
|
3662
3679
|
message: `${emoji.donut} ${currentTitle}`,
|
|
3663
3680
|
choices: currentItems,
|
|
3664
3681
|
pageSize: 30,
|
|
3665
3682
|
loop: true,
|
|
3666
3683
|
theme: selectTheme
|
|
3667
|
-
})
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
await executeCommand(commandGroup, subCommand);
|
|
3673
|
-
log.newline();
|
|
3674
|
-
if (isWorkflowAction(commandGroup, subCommand)) {
|
|
3675
|
-
break;
|
|
3676
|
-
}
|
|
3677
|
-
const { ctx: refreshedCtx } = await getMenuContext();
|
|
3678
|
-
const refreshedMenu = buildSubMenu(commandGroup, refreshedCtx);
|
|
3679
|
-
if (refreshedMenu) {
|
|
3680
|
-
currentTitle = refreshedMenu.title;
|
|
3681
|
-
currentItems = refreshedMenu.items;
|
|
3682
|
-
}
|
|
3683
|
-
} catch (err) {
|
|
3684
|
-
if (err.name === "ExitPromptError") {
|
|
3684
|
+
}),
|
|
3685
|
+
ensureError
|
|
3686
|
+
);
|
|
3687
|
+
if (!subCommandResult.ok) {
|
|
3688
|
+
if (subCommandResult.error.name === "ExitPromptError") {
|
|
3685
3689
|
break;
|
|
3686
3690
|
}
|
|
3687
|
-
throw
|
|
3691
|
+
throw subCommandResult.error;
|
|
3692
|
+
}
|
|
3693
|
+
const subCommand = subCommandResult.value;
|
|
3694
|
+
if (subCommand === null || subCommand === "back") {
|
|
3695
|
+
break;
|
|
3696
|
+
}
|
|
3697
|
+
log.newline();
|
|
3698
|
+
await executeCommand(commandGroup, subCommand);
|
|
3699
|
+
log.newline();
|
|
3700
|
+
if (isWorkflowAction(commandGroup, subCommand)) {
|
|
3701
|
+
break;
|
|
3702
|
+
}
|
|
3703
|
+
const { ctx: refreshedCtx } = await getMenuContext();
|
|
3704
|
+
const refreshedMenu = buildSubMenu(commandGroup, refreshedCtx);
|
|
3705
|
+
if (refreshedMenu) {
|
|
3706
|
+
currentTitle = refreshedMenu.title;
|
|
3707
|
+
currentItems = refreshedMenu.items;
|
|
3688
3708
|
}
|
|
3689
3709
|
}
|
|
3690
3710
|
}
|
|
@@ -3695,12 +3715,9 @@ async function executeCommand(group, subCommand) {
|
|
|
3695
3715
|
log.error(`Unknown command: ${group} ${subCommand}`);
|
|
3696
3716
|
return;
|
|
3697
3717
|
}
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
if (err instanceof Error) {
|
|
3702
|
-
log.error(err.message);
|
|
3703
|
-
}
|
|
3718
|
+
const r = await wrapAsync(() => handler(), ensureError);
|
|
3719
|
+
if (!r.ok) {
|
|
3720
|
+
log.error(r.error.message);
|
|
3704
3721
|
}
|
|
3705
3722
|
}
|
|
3706
3723
|
|
|
@@ -4111,7 +4128,7 @@ Checks performed:
|
|
|
4111
4128
|
// package.json
|
|
4112
4129
|
var package_default = {
|
|
4113
4130
|
name: "ralphctl",
|
|
4114
|
-
version: "0.1.
|
|
4131
|
+
version: "0.1.4",
|
|
4115
4132
|
description: "Sprint and task management CLI for AI-assisted coding",
|
|
4116
4133
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
4117
4134
|
type: "module",
|
|
@@ -4169,6 +4186,7 @@ var package_default = {
|
|
|
4169
4186
|
"gradient-string": "^3.0.0",
|
|
4170
4187
|
ora: "^9.3.0",
|
|
4171
4188
|
tabtab: "^3.0.2",
|
|
4189
|
+
"typescript-result": "^3.5.2",
|
|
4172
4190
|
zod: "^4.3.6"
|
|
4173
4191
|
},
|
|
4174
4192
|
devDependencies: {
|
|
@@ -4229,7 +4247,7 @@ registerCompletionCommands(program);
|
|
|
4229
4247
|
registerDoctorCommands(program);
|
|
4230
4248
|
async function main() {
|
|
4231
4249
|
if (process.env["COMP_CWORD"] && process.env["COMP_POINT"] && process.env["COMP_LINE"]) {
|
|
4232
|
-
const { handleCompletionRequest } = await import("./handle-
|
|
4250
|
+
const { handleCompletionRequest } = await import("./handle-K2AZLTKU.mjs");
|
|
4233
4251
|
if (await handleCompletionRequest(program)) return;
|
|
4234
4252
|
}
|
|
4235
4253
|
if (process.argv.length <= 2 || process.argv[2] === "interactive") {
|
|
@@ -4240,6 +4258,10 @@ async function main() {
|
|
|
4240
4258
|
}
|
|
4241
4259
|
}
|
|
4242
4260
|
main().catch((err) => {
|
|
4243
|
-
|
|
4244
|
-
|
|
4261
|
+
if (err instanceof DomainError) {
|
|
4262
|
+
showError(err.message);
|
|
4263
|
+
process.exit(EXIT_ERROR);
|
|
4264
|
+
}
|
|
4265
|
+
console.error(error("Unexpected error \u2014 please report this bug:"), err instanceof Error ? err.stack : String(err));
|
|
4266
|
+
process.exit(EXIT_ERROR);
|
|
4245
4267
|
});
|