ralphctl 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-JGUOR4Z5.mjs → add-CIM72NE3.mjs} +2 -2
- package/dist/{chunk-JXMHLW42.mjs → chunk-7JLZQICD.mjs} +4 -3
- package/dist/{chunk-HL4ZMHCQ.mjs → chunk-JOQO4HMM.mjs} +8 -0
- package/dist/{chunk-CDOPLXFK.mjs → chunk-JYCGQA2D.mjs} +156 -61
- package/dist/{chunk-4GHVNKLV.mjs → chunk-MRN3Z2XC.mjs} +227 -60
- package/dist/cli.mjs +105 -10
- package/dist/{mount-XZPBDRPZ.mjs → mount-XMN3S4W6.mjs} +315 -176
- package/dist/prompts/plan-common.md +23 -4
- package/dist/prompts/task-evaluation-resume.md +6 -1
- package/dist/prompts/validation-checklist.md +1 -1
- package/dist/{start-MMWC7QLI.mjs → start-D35SOXMM.mjs} +2 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-D2YGPLIV.mjs";
|
|
7
7
|
import {
|
|
8
8
|
editorInput
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7JLZQICD.mjs";
|
|
10
10
|
import {
|
|
11
11
|
addProjectRepo,
|
|
12
12
|
getProject,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
updateTask,
|
|
53
53
|
updateTaskStatus,
|
|
54
54
|
validateImportTasks
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-JYCGQA2D.mjs";
|
|
56
56
|
import {
|
|
57
57
|
fetchIssueFromUrl,
|
|
58
58
|
formatIssueContext,
|
|
@@ -61,8 +61,9 @@ import {
|
|
|
61
61
|
getTicket,
|
|
62
62
|
listTickets,
|
|
63
63
|
removeTicket,
|
|
64
|
+
truncate,
|
|
64
65
|
updateTicket
|
|
65
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-JOQO4HMM.mjs";
|
|
66
67
|
import {
|
|
67
68
|
EXIT_ERROR,
|
|
68
69
|
exitWithCode
|
|
@@ -176,7 +177,7 @@ import {
|
|
|
176
177
|
// package.json
|
|
177
178
|
var package_default = {
|
|
178
179
|
name: "ralphctl",
|
|
179
|
-
version: "0.
|
|
180
|
+
version: "0.4.0",
|
|
180
181
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
181
182
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
182
183
|
type: "module",
|
|
@@ -421,8 +422,8 @@ function useCurrentPrompt() {
|
|
|
421
422
|
}
|
|
422
423
|
|
|
423
424
|
// src/integration/ui/prompts/confirm-prompt.tsx
|
|
424
|
-
import "react";
|
|
425
|
-
import { Box, Text } from "ink";
|
|
425
|
+
import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
426
|
+
import { Box, Text, useInput, useStdout } from "ink";
|
|
426
427
|
import { ConfirmInput } from "@inkjs/ui";
|
|
427
428
|
|
|
428
429
|
// src/integration/ui/theme/tokens.ts
|
|
@@ -491,43 +492,112 @@ var spacing = {
|
|
|
491
492
|
var FIELD_LABEL_WIDTH = 12;
|
|
492
493
|
|
|
493
494
|
// src/integration/ui/prompts/confirm-prompt.tsx
|
|
494
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
495
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
496
|
+
var RESERVED_ROWS = 10;
|
|
497
|
+
var MIN_VIEWPORT = 6;
|
|
498
|
+
var MAX_VIEWPORT = 40;
|
|
499
|
+
function useTerminalRows() {
|
|
500
|
+
const { stdout } = useStdout();
|
|
501
|
+
const [rows, setRows] = useState2(stdout.rows);
|
|
502
|
+
useEffect2(() => {
|
|
503
|
+
const onResize = () => {
|
|
504
|
+
setRows(stdout.rows);
|
|
505
|
+
};
|
|
506
|
+
stdout.on("resize", onResize);
|
|
507
|
+
return () => {
|
|
508
|
+
stdout.off("resize", onResize);
|
|
509
|
+
};
|
|
510
|
+
}, [stdout]);
|
|
511
|
+
return rows;
|
|
512
|
+
}
|
|
495
513
|
function ConfirmPrompt({ options, onSubmit }) {
|
|
496
514
|
const hint = options.default === false ? "(y/N)" : "(Y/n)";
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
515
|
+
const details = options.details?.trim();
|
|
516
|
+
const lines = useMemo(() => details ? details.split("\n") : [], [details]);
|
|
517
|
+
const terminalRows = useTerminalRows();
|
|
518
|
+
const viewport = Math.max(MIN_VIEWPORT, Math.min(MAX_VIEWPORT, terminalRows - RESERVED_ROWS));
|
|
519
|
+
const total = lines.length;
|
|
520
|
+
const maxOffset = Math.max(0, total - viewport);
|
|
521
|
+
const scrollable = total > viewport;
|
|
522
|
+
const [offset, setOffset] = useState2(0);
|
|
523
|
+
useInput((_input, key) => {
|
|
524
|
+
if (!scrollable) return;
|
|
525
|
+
if (key.upArrow) setOffset((o) => Math.max(0, o - 1));
|
|
526
|
+
else if (key.downArrow) setOffset((o) => Math.min(maxOffset, o + 1));
|
|
527
|
+
else if (key.pageUp) setOffset((o) => Math.max(0, o - viewport));
|
|
528
|
+
else if (key.pageDown) setOffset((o) => Math.min(maxOffset, o + viewport));
|
|
529
|
+
});
|
|
530
|
+
const visibleLines = scrollable ? lines.slice(offset, offset + viewport) : lines;
|
|
531
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
532
|
+
details ? /* @__PURE__ */ jsxs(
|
|
533
|
+
Box,
|
|
510
534
|
{
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
535
|
+
flexDirection: "column",
|
|
536
|
+
borderStyle: "round",
|
|
537
|
+
borderColor: inkColors.muted,
|
|
538
|
+
paddingX: spacing.gutter,
|
|
539
|
+
marginBottom: spacing.section,
|
|
540
|
+
children: [
|
|
541
|
+
visibleLines.map((line, idx) => /* @__PURE__ */ jsx(Text, { children: line.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
542
|
+
/* @__PURE__ */ jsxs(Text, { color: inkColors.muted, children: [
|
|
543
|
+
glyphs.quoteRail,
|
|
544
|
+
" "
|
|
545
|
+
] }),
|
|
546
|
+
line
|
|
547
|
+
] }) : " " }, idx)),
|
|
548
|
+
scrollable ? /* @__PURE__ */ jsxs(Text, { color: inkColors.muted, children: [
|
|
549
|
+
glyphs.inlineDot,
|
|
550
|
+
" lines ",
|
|
551
|
+
String(offset + 1),
|
|
552
|
+
"\u2013",
|
|
553
|
+
String(Math.min(offset + viewport, total)),
|
|
554
|
+
" of",
|
|
555
|
+
" ",
|
|
556
|
+
String(total),
|
|
557
|
+
" ",
|
|
558
|
+
glyphs.inlineDot,
|
|
559
|
+
" \u2191/\u2193 scroll ",
|
|
560
|
+
glyphs.inlineDot,
|
|
561
|
+
" PgUp/PgDn page"
|
|
562
|
+
] }) : null
|
|
563
|
+
]
|
|
518
564
|
}
|
|
519
|
-
)
|
|
565
|
+
) : null,
|
|
566
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
567
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
568
|
+
emoji.donut,
|
|
569
|
+
" ",
|
|
570
|
+
options.message,
|
|
571
|
+
" "
|
|
572
|
+
] }),
|
|
573
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
574
|
+
hint,
|
|
575
|
+
" "
|
|
576
|
+
] }),
|
|
577
|
+
/* @__PURE__ */ jsx(
|
|
578
|
+
ConfirmInput,
|
|
579
|
+
{
|
|
580
|
+
defaultChoice: options.default === false ? "cancel" : "confirm",
|
|
581
|
+
onConfirm: () => {
|
|
582
|
+
onSubmit(true);
|
|
583
|
+
},
|
|
584
|
+
onCancel: () => {
|
|
585
|
+
onSubmit(false);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
)
|
|
589
|
+
] })
|
|
520
590
|
] });
|
|
521
591
|
}
|
|
522
592
|
|
|
523
593
|
// src/integration/ui/prompts/input-prompt.tsx
|
|
524
|
-
import { useState as
|
|
525
|
-
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
594
|
+
import { useState as useState3 } from "react";
|
|
595
|
+
import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
|
|
526
596
|
import { TextInput } from "@inkjs/ui";
|
|
527
597
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
528
598
|
function InputPrompt({ options, onSubmit, onCancel }) {
|
|
529
|
-
const [error2, setError] =
|
|
530
|
-
|
|
599
|
+
const [error2, setError] = useState3(null);
|
|
600
|
+
useInput2((_input, key) => {
|
|
531
601
|
if (key.escape) onCancel();
|
|
532
602
|
});
|
|
533
603
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
@@ -565,13 +635,13 @@ function InputPrompt({ options, onSubmit, onCancel }) {
|
|
|
565
635
|
}
|
|
566
636
|
|
|
567
637
|
// src/integration/ui/prompts/select-prompt.tsx
|
|
568
|
-
import { useState as
|
|
569
|
-
import { Box as Box3, Text as Text3, useInput as
|
|
638
|
+
import { useState as useState4 } from "react";
|
|
639
|
+
import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
|
|
570
640
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
571
641
|
function SelectPrompt({ options, onSubmit, onCancel }) {
|
|
572
642
|
const initialIdx = findInitialIdx(options);
|
|
573
|
-
const [focusedIdx, setFocusedIdx] =
|
|
574
|
-
|
|
643
|
+
const [focusedIdx, setFocusedIdx] = useState4(initialIdx);
|
|
644
|
+
useInput3((_input, key) => {
|
|
575
645
|
if (key.escape) {
|
|
576
646
|
onCancel();
|
|
577
647
|
return;
|
|
@@ -634,14 +704,14 @@ function stepFocus(choices, from, delta) {
|
|
|
634
704
|
}
|
|
635
705
|
|
|
636
706
|
// src/integration/ui/prompts/checkbox-prompt.tsx
|
|
637
|
-
import { useState as
|
|
638
|
-
import { Box as Box4, Text as Text4, useInput as
|
|
707
|
+
import { useState as useState5 } from "react";
|
|
708
|
+
import { Box as Box4, Text as Text4, useInput as useInput4 } from "ink";
|
|
639
709
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
640
710
|
function CheckboxPrompt({ options, onSubmit, onCancel }) {
|
|
641
711
|
const initialFocus = options.choices.findIndex((c) => !isDisabled2(c));
|
|
642
|
-
const [focusedIdx, setFocusedIdx] =
|
|
643
|
-
const [checked, setChecked] =
|
|
644
|
-
|
|
712
|
+
const [focusedIdx, setFocusedIdx] = useState5(initialFocus >= 0 ? initialFocus : 0);
|
|
713
|
+
const [checked, setChecked] = useState5(() => seedCheckedSet(options));
|
|
714
|
+
useInput4((input, key) => {
|
|
645
715
|
if (key.escape) {
|
|
646
716
|
onCancel();
|
|
647
717
|
return;
|
|
@@ -717,8 +787,8 @@ function stepFocus2(choices, from, delta) {
|
|
|
717
787
|
}
|
|
718
788
|
|
|
719
789
|
// src/integration/ui/prompts/editor-prompt.tsx
|
|
720
|
-
import { useState as
|
|
721
|
-
import { Box as Box5, Text as Text5, useInput as
|
|
790
|
+
import { useState as useState6, useMemo as useMemo2 } from "react";
|
|
791
|
+
import { Box as Box5, Text as Text5, useInput as useInput5 } from "ink";
|
|
722
792
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
723
793
|
var MIN_EDIT_ROWS = 8;
|
|
724
794
|
function splitLines(text) {
|
|
@@ -734,13 +804,13 @@ function clampCursor(lines, cursor) {
|
|
|
734
804
|
return { row, col };
|
|
735
805
|
}
|
|
736
806
|
function EditorPrompt({ options, onSubmit, onCancel }) {
|
|
737
|
-
const [lines, setLines] =
|
|
738
|
-
const [cursor, setCursor] =
|
|
807
|
+
const [lines, setLines] = useState6(() => splitLines(options.default ?? ""));
|
|
808
|
+
const [cursor, setCursor] = useState6(() => {
|
|
739
809
|
const init = splitLines(options.default ?? "");
|
|
740
810
|
const lastRow = init.length - 1;
|
|
741
811
|
return { row: lastRow, col: (init[lastRow] ?? "").length };
|
|
742
812
|
});
|
|
743
|
-
|
|
813
|
+
useInput5((input, key) => {
|
|
744
814
|
if (key.escape || key.ctrl && input === "c") {
|
|
745
815
|
onCancel();
|
|
746
816
|
return;
|
|
@@ -836,7 +906,7 @@ function EditorPrompt({ options, onSubmit, onCancel }) {
|
|
|
836
906
|
});
|
|
837
907
|
}
|
|
838
908
|
});
|
|
839
|
-
const renderedLines =
|
|
909
|
+
const renderedLines = useMemo2(() => {
|
|
840
910
|
const padCount = Math.max(0, MIN_EDIT_ROWS - lines.length);
|
|
841
911
|
const padded = lines.map((line, i) => {
|
|
842
912
|
if (i !== cursor.row) return line.length > 0 ? line : " ";
|
|
@@ -883,11 +953,11 @@ function EditorPrompt({ options, onSubmit, onCancel }) {
|
|
|
883
953
|
}
|
|
884
954
|
|
|
885
955
|
// src/integration/ui/prompts/file-browser-prompt.tsx
|
|
886
|
-
import { useEffect as
|
|
956
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useState as useState7 } from "react";
|
|
887
957
|
import { readdirSync, statSync } from "fs";
|
|
888
958
|
import { homedir } from "os";
|
|
889
959
|
import { dirname, join, resolve } from "path";
|
|
890
|
-
import { Box as Box6, Text as Text6, useInput as
|
|
960
|
+
import { Box as Box6, Text as Text6, useInput as useInput6 } from "ink";
|
|
891
961
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
892
962
|
function listDirectories(dirPath) {
|
|
893
963
|
try {
|
|
@@ -905,20 +975,20 @@ function isGitRepo(dirPath) {
|
|
|
905
975
|
}
|
|
906
976
|
var PAGE_SIZE = 12;
|
|
907
977
|
function FileBrowserPrompt({ options, onSubmit, onCancel }) {
|
|
908
|
-
const [currentPath, setCurrentPath] =
|
|
978
|
+
const [currentPath, setCurrentPath] = useState7(
|
|
909
979
|
() => options.startPath ? resolve(options.startPath) : homedir()
|
|
910
980
|
);
|
|
911
|
-
const [dirs, setDirs] =
|
|
912
|
-
const [cursor, setCursor] =
|
|
913
|
-
const [offset, setOffset] =
|
|
914
|
-
|
|
981
|
+
const [dirs, setDirs] = useState7([]);
|
|
982
|
+
const [cursor, setCursor] = useState7(0);
|
|
983
|
+
const [offset, setOffset] = useState7(0);
|
|
984
|
+
useEffect3(() => {
|
|
915
985
|
setDirs(listDirectories(currentPath));
|
|
916
986
|
setCursor(0);
|
|
917
987
|
setOffset(0);
|
|
918
988
|
}, [currentPath]);
|
|
919
989
|
const message = options.message ?? "Browse to directory:";
|
|
920
990
|
const parent = dirname(currentPath);
|
|
921
|
-
|
|
991
|
+
useInput6((input, key) => {
|
|
922
992
|
if (key.escape) {
|
|
923
993
|
onCancel();
|
|
924
994
|
return;
|
|
@@ -952,11 +1022,11 @@ function FileBrowserPrompt({ options, onSubmit, onCancel }) {
|
|
|
952
1022
|
return;
|
|
953
1023
|
}
|
|
954
1024
|
});
|
|
955
|
-
|
|
1025
|
+
useEffect3(() => {
|
|
956
1026
|
if (cursor < offset) setOffset(cursor);
|
|
957
1027
|
else if (cursor >= offset + PAGE_SIZE) setOffset(cursor - PAGE_SIZE + 1);
|
|
958
1028
|
}, [cursor, offset]);
|
|
959
|
-
const visible =
|
|
1029
|
+
const visible = useMemo3(() => dirs.slice(offset, offset + PAGE_SIZE), [dirs, offset]);
|
|
960
1030
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
|
|
961
1031
|
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
962
1032
|
emoji.donut,
|
|
@@ -1897,7 +1967,7 @@ async function selectTicket(message = "Select ticket:", filter) {
|
|
|
1897
1967
|
default: true
|
|
1898
1968
|
});
|
|
1899
1969
|
if (create) {
|
|
1900
|
-
const { ticketAddCommand } = await import("./add-
|
|
1970
|
+
const { ticketAddCommand } = await import("./add-CIM72NE3.mjs");
|
|
1901
1971
|
await ticketAddCommand({ interactive: true });
|
|
1902
1972
|
const updated = await listTickets();
|
|
1903
1973
|
const refiltered = filter ? updated.filter(filter) : updated;
|
|
@@ -2688,6 +2758,8 @@ function parseArgs2(args) {
|
|
|
2688
2758
|
if (arg === "--project") {
|
|
2689
2759
|
options.project = nextArg;
|
|
2690
2760
|
i++;
|
|
2761
|
+
} else if (arg === "--auto") {
|
|
2762
|
+
options.auto = true;
|
|
2691
2763
|
} else if (!arg?.startsWith("-")) {
|
|
2692
2764
|
sprintId = arg;
|
|
2693
2765
|
}
|
|
@@ -2710,7 +2782,7 @@ async function sprintRefineCommand(args) {
|
|
|
2710
2782
|
console.log(field("ID", sprint.id));
|
|
2711
2783
|
log.newline();
|
|
2712
2784
|
const shared = getSharedDeps();
|
|
2713
|
-
const pipeline = createRefinePipeline(shared, { project: options.project });
|
|
2785
|
+
const pipeline = createRefinePipeline(shared, { project: options.project, auto: options.auto });
|
|
2714
2786
|
const result = await executePipeline(pipeline, { sprintId: id });
|
|
2715
2787
|
if (!result.ok) {
|
|
2716
2788
|
showError(result.error.message);
|
|
@@ -3893,6 +3965,100 @@ async function taskImportCommand(args) {
|
|
|
3893
3965
|
}
|
|
3894
3966
|
}
|
|
3895
3967
|
|
|
3968
|
+
// src/integration/cli/commands/task/why.ts
|
|
3969
|
+
function collectBlockers(root, byId) {
|
|
3970
|
+
const out = [];
|
|
3971
|
+
const visited = /* @__PURE__ */ new Set([root.id]);
|
|
3972
|
+
function walk(task, depth) {
|
|
3973
|
+
for (const blockerId of task.blockedBy) {
|
|
3974
|
+
const blocker = byId.get(blockerId);
|
|
3975
|
+
if (!blocker) {
|
|
3976
|
+
out.push({
|
|
3977
|
+
task: { id: blockerId, name: `(missing ${blockerId})`, status: "todo" },
|
|
3978
|
+
depth,
|
|
3979
|
+
missing: true
|
|
3980
|
+
});
|
|
3981
|
+
continue;
|
|
3982
|
+
}
|
|
3983
|
+
if (visited.has(blocker.id)) continue;
|
|
3984
|
+
visited.add(blocker.id);
|
|
3985
|
+
out.push({ task: blocker, depth, missing: false });
|
|
3986
|
+
if (blocker.status !== "done") walk(blocker, depth + 1);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
walk(root, 0);
|
|
3990
|
+
return out;
|
|
3991
|
+
}
|
|
3992
|
+
function renderBlockerLine(node) {
|
|
3993
|
+
const indent = " " + " ".repeat(node.depth);
|
|
3994
|
+
const connector = colors.muted(node.depth === 0 ? "\u251C\u2500" : "\u21B3");
|
|
3995
|
+
const idPart = colors.muted(node.task.id);
|
|
3996
|
+
if (node.missing) {
|
|
3997
|
+
return `${indent}${connector} ${colors.error(icons.error)} ${idPart} ${colors.error("(referenced but missing)")}`;
|
|
3998
|
+
}
|
|
3999
|
+
const status = formatTaskStatus(node.task.status);
|
|
4000
|
+
const marker = node.task.status === "done" ? colors.success(icons.success) : colors.warning(icons.warning);
|
|
4001
|
+
return `${indent}${connector} ${marker} ${idPart} ${node.task.name} ${status}`;
|
|
4002
|
+
}
|
|
4003
|
+
async function taskWhyCommand(taskId) {
|
|
4004
|
+
let id = taskId;
|
|
4005
|
+
if (!id) {
|
|
4006
|
+
const selected = await selectTask("Which task is blocked?");
|
|
4007
|
+
if (!selected) return;
|
|
4008
|
+
id = selected;
|
|
4009
|
+
}
|
|
4010
|
+
const r = await wrapAsync(() => getTasks(), ensureError);
|
|
4011
|
+
if (!r.ok) {
|
|
4012
|
+
showError(r.error.message);
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
const tasks = r.value;
|
|
4016
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
4017
|
+
const root = byId.get(id);
|
|
4018
|
+
if (!root) {
|
|
4019
|
+
showError(new TaskNotFoundError(id).message);
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
printHeader("Why blocked?");
|
|
4023
|
+
log.newline();
|
|
4024
|
+
console.log(` ${icons.task} ${colors.highlight(root.name)} ${formatTaskStatus(root.status)}`);
|
|
4025
|
+
console.log(` ${colors.muted("id:")} ${colors.muted(root.id)}`);
|
|
4026
|
+
log.newline();
|
|
4027
|
+
if (root.status === "done") {
|
|
4028
|
+
console.log(` ${colors.success(icons.success)} ${colors.success("Task is done \u2014 nothing blocking it.")}`);
|
|
4029
|
+
log.newline();
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
if (root.blockedBy.length === 0) {
|
|
4033
|
+
console.log(` ${colors.success(icons.success)} ${colors.success("No blockers \u2014 ready to execute.")}`);
|
|
4034
|
+
log.newline();
|
|
4035
|
+
showNextStep(`ralphctl task status ${root.id} in_progress`, "Start working on this task");
|
|
4036
|
+
log.newline();
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
4039
|
+
const nodes = collectBlockers(root, byId);
|
|
4040
|
+
const unmet = nodes.filter((n) => !n.missing && n.task.status !== "done");
|
|
4041
|
+
const leafUnmet = unmet.filter((n) => n.task.blockedBy.every((bid) => byId.get(bid)?.status === "done"));
|
|
4042
|
+
console.log(` ${colors.muted("Dependency chain:")}`);
|
|
4043
|
+
for (const node of nodes) console.log(renderBlockerLine(node));
|
|
4044
|
+
log.newline();
|
|
4045
|
+
if (unmet.length === 0) {
|
|
4046
|
+
console.log(` ${colors.success(icons.success)} ${colors.success("All blockers are done \u2014 ready to execute.")}`);
|
|
4047
|
+
log.newline();
|
|
4048
|
+
showNextStep(`ralphctl task status ${root.id} in_progress`, "Start working on this task");
|
|
4049
|
+
log.newline();
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
const actionable = leafUnmet.length > 0 ? leafUnmet : unmet;
|
|
4053
|
+
console.log(
|
|
4054
|
+
` ${colors.warning(icons.warning)} ${colors.warning(`Unblock by completing ${String(actionable.length)} task${actionable.length !== 1 ? "s" : ""} first:`)}`
|
|
4055
|
+
);
|
|
4056
|
+
for (const node of actionable) {
|
|
4057
|
+
console.log(` ${colors.muted("\u2192")} ${colors.highlight(node.task.id)} ${node.task.name}`);
|
|
4058
|
+
}
|
|
4059
|
+
log.newline();
|
|
4060
|
+
}
|
|
4061
|
+
|
|
3896
4062
|
// src/integration/cli/commands/ticket/edit.ts
|
|
3897
4063
|
function validateUrl(url) {
|
|
3898
4064
|
try {
|
|
@@ -4092,7 +4258,7 @@ async function ticketListCommand(args) {
|
|
|
4092
4258
|
log.raw(` ${icons.bullet} ${formatTicketDisplay(ticket)} ${reqBadge}`);
|
|
4093
4259
|
if (ticket.description) {
|
|
4094
4260
|
const preview = ticket.description.split("\n")[0] ?? "";
|
|
4095
|
-
const truncated = preview
|
|
4261
|
+
const truncated = truncate(preview, 60);
|
|
4096
4262
|
log.raw(` ${muted(truncated)}`, 1);
|
|
4097
4263
|
}
|
|
4098
4264
|
}
|
|
@@ -5048,6 +5214,7 @@ export {
|
|
|
5048
5214
|
taskNextCommand,
|
|
5049
5215
|
taskReorderCommand,
|
|
5050
5216
|
taskImportCommand,
|
|
5217
|
+
taskWhyCommand,
|
|
5051
5218
|
ticketEditCommand,
|
|
5052
5219
|
ticketListCommand,
|
|
5053
5220
|
ticketShowCommand,
|
package/dist/cli.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
configShowCommand,
|
|
6
6
|
createSharedDeps,
|
|
7
7
|
doctorCommand,
|
|
8
|
+
getNextAction,
|
|
9
|
+
loadDashboardData,
|
|
8
10
|
progressLogCommand,
|
|
9
11
|
progressShowCommand,
|
|
10
12
|
projectListCommand,
|
|
@@ -33,12 +35,13 @@ import {
|
|
|
33
35
|
taskReorderCommand,
|
|
34
36
|
taskShowCommand,
|
|
35
37
|
taskStatusCommand,
|
|
38
|
+
taskWhyCommand,
|
|
36
39
|
ticketEditCommand,
|
|
37
40
|
ticketListCommand,
|
|
38
41
|
ticketRefineCommand,
|
|
39
42
|
ticketRemoveCommand,
|
|
40
43
|
ticketShowCommand
|
|
41
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-MRN3Z2XC.mjs";
|
|
42
45
|
import {
|
|
43
46
|
projectAddCommand
|
|
44
47
|
} from "./chunk-D2YGPLIV.mjs";
|
|
@@ -47,13 +50,15 @@ import {
|
|
|
47
50
|
} from "./chunk-3QBEBKMZ.mjs";
|
|
48
51
|
import {
|
|
49
52
|
ticketAddCommand
|
|
50
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-7JLZQICD.mjs";
|
|
51
54
|
import "./chunk-NUYQK5MN.mjs";
|
|
52
55
|
import {
|
|
53
56
|
getTasks,
|
|
54
57
|
sprintStartCommand
|
|
55
|
-
} from "./chunk-
|
|
56
|
-
import
|
|
58
|
+
} from "./chunk-JYCGQA2D.mjs";
|
|
59
|
+
import {
|
|
60
|
+
truncate
|
|
61
|
+
} from "./chunk-JOQO4HMM.mjs";
|
|
57
62
|
import {
|
|
58
63
|
EXIT_ERROR
|
|
59
64
|
} from "./chunk-CFUVE2BP.mjs";
|
|
@@ -68,6 +73,7 @@ import {
|
|
|
68
73
|
import {
|
|
69
74
|
colors,
|
|
70
75
|
error,
|
|
76
|
+
formatSprintStatus,
|
|
71
77
|
icons,
|
|
72
78
|
log,
|
|
73
79
|
printBanner,
|
|
@@ -195,7 +201,7 @@ async function sprintInsightsCommand(args) {
|
|
|
195
201
|
console.log(` ${colors.accent("Evaluation output:")}`);
|
|
196
202
|
for (const task of withOutput) {
|
|
197
203
|
const output = task.evaluationOutput ?? "";
|
|
198
|
-
const truncated = output
|
|
204
|
+
const truncated = truncate(output, 200);
|
|
199
205
|
console.log(` ${icons.bullet} ${colors.accent(task.name)}: ${colors.muted(truncated)}`);
|
|
200
206
|
}
|
|
201
207
|
log.newline();
|
|
@@ -281,10 +287,11 @@ Examples:
|
|
|
281
287
|
sprint.command("switch").description("Quick sprint switcher (opens selector)").action(async () => {
|
|
282
288
|
await sprintSwitchCommand();
|
|
283
289
|
});
|
|
284
|
-
sprint.command("refine [id]").description("Refine ticket specifications").option("--project <name>", "Only refine tickets for specific project").action(async (id, opts) => {
|
|
290
|
+
sprint.command("refine [id]").description("Refine ticket specifications").option("--project <name>", "Only refine tickets for specific project").option("--auto", "Run without approval prompts (AI drafts requirements autonomously)").action(async (id, opts) => {
|
|
285
291
|
const args = [];
|
|
286
292
|
if (id) args.push(id);
|
|
287
293
|
if (opts?.project) args.push("--project", opts.project);
|
|
294
|
+
if (opts?.auto) args.push("--auto");
|
|
288
295
|
await sprintRefineCommand(args);
|
|
289
296
|
});
|
|
290
297
|
sprint.command("ideate [id]").description("Quick idea to tasks (refine + plan in one session)").option("--auto", "Run without user interaction (AI decides autonomously)").option("--all-paths", "Explore all project repositories instead of prompting for selection").option("--project <name>", "Pre-select project (skip interactive selection)").action(async (id, opts) => {
|
|
@@ -424,6 +431,9 @@ Examples:
|
|
|
424
431
|
});
|
|
425
432
|
});
|
|
426
433
|
task.command("next").description("Get next task").action(taskNextCommand);
|
|
434
|
+
task.command("why [id]").description("Explain why a task is blocked (walks the dependency chain)").action(async (id) => {
|
|
435
|
+
await taskWhyCommand(id);
|
|
436
|
+
});
|
|
427
437
|
task.command("reorder [id] [position]").description("Change task priority").action(async (id, position) => {
|
|
428
438
|
const args = [];
|
|
429
439
|
if (id) args.push(id);
|
|
@@ -576,6 +586,85 @@ Checks performed:
|
|
|
576
586
|
});
|
|
577
587
|
}
|
|
578
588
|
|
|
589
|
+
// src/integration/cli/commands/next/next.ts
|
|
590
|
+
function toCommand(action) {
|
|
591
|
+
return `ralphctl ${action.group} ${action.subCommand}`;
|
|
592
|
+
}
|
|
593
|
+
function computePayload(data) {
|
|
594
|
+
if (!data) {
|
|
595
|
+
return { sprint: null, action: null, reason: "no-sprint" };
|
|
596
|
+
}
|
|
597
|
+
const sprint = { id: data.sprint.id, name: data.sprint.name, status: data.sprint.status };
|
|
598
|
+
if (data.sprint.status === "closed") {
|
|
599
|
+
return { sprint, action: null, reason: "sprint-closed" };
|
|
600
|
+
}
|
|
601
|
+
const next = getNextAction(data);
|
|
602
|
+
if (!next) {
|
|
603
|
+
return { sprint, action: null, reason: "all-done" };
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
sprint,
|
|
607
|
+
action: { command: toCommand(next), label: next.label, description: next.description },
|
|
608
|
+
reason: "action-ready"
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function renderPorcelain(payload) {
|
|
612
|
+
if (payload.action) {
|
|
613
|
+
console.log(payload.action.command);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
console.log("");
|
|
617
|
+
}
|
|
618
|
+
function renderHuman(payload) {
|
|
619
|
+
log.newline();
|
|
620
|
+
if (payload.reason === "no-sprint") {
|
|
621
|
+
console.log(` ${colors.muted(icons.inactive)} ${colors.muted("No current sprint.")}`);
|
|
622
|
+
console.log(` ${colors.muted(icons.tip)} ${colors.muted("Create one to get started:")}`);
|
|
623
|
+
console.log(` ${colors.highlight("ralphctl sprint create")}`);
|
|
624
|
+
log.newline();
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (!payload.sprint) return;
|
|
628
|
+
const sprintLine = `${icons.sprint} ${colors.highlight(payload.sprint.name)} ${formatSprintStatus(payload.sprint.status)}`;
|
|
629
|
+
console.log(` ${sprintLine}`);
|
|
630
|
+
if (payload.reason === "sprint-closed") {
|
|
631
|
+
console.log(` ${colors.muted(icons.info)} ${colors.muted("Sprint is closed. Start a new one:")}`);
|
|
632
|
+
console.log(` ${colors.highlight("ralphctl sprint create")}`);
|
|
633
|
+
log.newline();
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (payload.reason === "all-done") {
|
|
637
|
+
console.log(` ${colors.success(icons.success)} ${colors.success("Nothing left to do.")}`);
|
|
638
|
+
log.newline();
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const action = payload.action;
|
|
642
|
+
if (!action) return;
|
|
643
|
+
console.log(` ${colors.muted(icons.tip)} ${colors.muted(action.label + ":")} ${colors.muted(action.description)}`);
|
|
644
|
+
console.log(` ${colors.highlight(action.command)}`);
|
|
645
|
+
log.newline();
|
|
646
|
+
}
|
|
647
|
+
async function nextCommand(options = {}) {
|
|
648
|
+
const data = await loadDashboardData();
|
|
649
|
+
const payload = computePayload(data);
|
|
650
|
+
if (options.json) {
|
|
651
|
+
console.log(JSON.stringify(payload));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (options.porcelain) {
|
|
655
|
+
renderPorcelain(payload);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
renderHuman(payload);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/integration/cli/commands/next/register.ts
|
|
662
|
+
function registerNextCommands(program2) {
|
|
663
|
+
program2.command("next").description("Suggest the next workflow action for the current sprint").option("--porcelain", "Print only the suggested command (for shell/tmux integration)").option("--json", "Emit a machine-readable JSON payload").action(async (options) => {
|
|
664
|
+
await nextCommand(options);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
579
668
|
// src/application/entrypoint.ts
|
|
580
669
|
setSharedDeps(createSharedDeps());
|
|
581
670
|
var program = new Command();
|
|
@@ -601,6 +690,12 @@ registerDashboardCommands(program);
|
|
|
601
690
|
registerConfigCommands(program);
|
|
602
691
|
registerCompletionCommands(program);
|
|
603
692
|
registerDoctorCommands(program);
|
|
693
|
+
registerNextCommands(program);
|
|
694
|
+
function isQuietCommand(argv) {
|
|
695
|
+
const cmd = argv[2];
|
|
696
|
+
if (cmd === "next" && (argv.includes("--porcelain") || argv.includes("--json"))) return true;
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
604
699
|
async function main() {
|
|
605
700
|
if (process.env["COMP_CWORD"] && process.env["COMP_POINT"] && process.env["COMP_LINE"]) {
|
|
606
701
|
const { handleCompletionRequest } = await import("./handle-BBAZJ44Y.mjs");
|
|
@@ -610,7 +705,7 @@ async function main() {
|
|
|
610
705
|
const isBare = argv.length <= 2;
|
|
611
706
|
const isInteractive = argv[2] === "interactive";
|
|
612
707
|
if (isBare || isInteractive) {
|
|
613
|
-
const { mountInkApp } = await import("./mount-
|
|
708
|
+
const { mountInkApp } = await import("./mount-XMN3S4W6.mjs");
|
|
614
709
|
const { fallback } = await mountInkApp({ initialView: "repl" });
|
|
615
710
|
if (!fallback) return;
|
|
616
711
|
printBanner();
|
|
@@ -621,10 +716,10 @@ async function main() {
|
|
|
621
716
|
return;
|
|
622
717
|
}
|
|
623
718
|
if (argv[2] === "sprint" && argv[3] === "start") {
|
|
624
|
-
const { parseSprintStartArgs } = await import("./start-
|
|
719
|
+
const { parseSprintStartArgs } = await import("./start-D35SOXMM.mjs");
|
|
625
720
|
const parsed = parseSprintStartArgs(argv.slice(4));
|
|
626
721
|
if (parsed.ok) {
|
|
627
|
-
const { mountInkApp } = await import("./mount-
|
|
722
|
+
const { mountInkApp } = await import("./mount-XMN3S4W6.mjs");
|
|
628
723
|
const { getSharedDeps } = await import("./bootstrap-FMHG6DRY.mjs");
|
|
629
724
|
let sprintId;
|
|
630
725
|
try {
|
|
@@ -642,7 +737,7 @@ async function main() {
|
|
|
642
737
|
}
|
|
643
738
|
}
|
|
644
739
|
}
|
|
645
|
-
printBanner();
|
|
740
|
+
if (!isQuietCommand(argv)) printBanner();
|
|
646
741
|
await program.parseAsync(argv);
|
|
647
742
|
}
|
|
648
743
|
main().catch((err) => {
|