ralph-review 0.2.0 → 0.2.2
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/package.json +1 -1
- package/src/commands/init.ts +12 -6
- package/src/lib/agents/models.ts +2 -2
- package/src/lib/tui/dashboard/Dashboard.tsx +0 -1
- package/src/lib/tui/dashboard/ReviewModeOverlay.tsx +91 -139
- package/src/lib/tui/sessions/detail/DetailPane.tsx +0 -3
- package/src/lib/tui/sessions/detail/SessionDetailView.tsx +17 -19
- package/src/lib/tui/sessions/detail/session-detail-parts.tsx +86 -1
- package/src/lib/tui/sessions/history/SessionListDetailPane.tsx +6 -1
- package/src/lib/tui/workspace/Workspace.tsx +0 -3
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -291,18 +291,24 @@ const FIXER_AGENT_PRIORITY: readonly AgentType[] = ["codex", "claude", "droid",
|
|
|
291
291
|
|
|
292
292
|
const MODEL_PRIORITY_MATCHERS: Record<ConfiguredRole, readonly ((model: string) => boolean)[]> = {
|
|
293
293
|
reviewer: [
|
|
294
|
-
(model) => matchesModelId(model, "gpt-5.4"),
|
|
295
|
-
(model) => matchesModelId(model, "gpt-5.3-codex"),
|
|
296
294
|
(model) => matchesModelId(model, "gpt-5.2"),
|
|
297
|
-
(model) => matchesModelId(model, "gpt-5.2-codex"),
|
|
298
295
|
(model) => matchesModelId(model, "claude-opus-4-6"),
|
|
299
|
-
(model) => matchesModelId(model, "
|
|
296
|
+
(model) => matchesModelId(model, "claude-sonnet-4-6"),
|
|
297
|
+
(model) => matchesModelId(model, "claude-opus-4-7"),
|
|
298
|
+
(model) => matchesModelId(model, "glm-5.1"),
|
|
299
|
+
(model) => matchesModelId(model, "gpt-5.3-codex"),
|
|
300
|
+
(model) => matchesModelId(model, "gemini-3.1-pro-preview"),
|
|
301
|
+
(model) => matchesModelId(model, "kimi-k2.6"),
|
|
300
302
|
],
|
|
301
303
|
fixer: [
|
|
304
|
+
(model) => matchesModelId(model, "gpt-5.5"),
|
|
302
305
|
(model) => matchesModelId(model, "gpt-5.4"),
|
|
303
|
-
(model) => matchesModelId(model, "gpt-5.3-codex"),
|
|
304
306
|
(model) => matchesModelId(model, "claude-opus-4-6"),
|
|
305
|
-
(model) => matchesModelId(model, "
|
|
307
|
+
(model) => matchesModelId(model, "gpt-5.3-codex"),
|
|
308
|
+
(model) => matchesModelId(model, "kimi-k2.6"),
|
|
309
|
+
(model) => matchesModelId(model, "gemini-3.1-pro-preview"),
|
|
310
|
+
(model) => matchesModelId(model, "glm-5.1"),
|
|
311
|
+
(model) => matchesModelId(model, "claude-sonnet-4-6"),
|
|
306
312
|
],
|
|
307
313
|
};
|
|
308
314
|
|
package/src/lib/agents/models.ts
CHANGED
|
@@ -13,8 +13,8 @@ export const claudeModelOptions = [
|
|
|
13
13
|
{ value: "claude-opus-4-7", label: "Claude Opus 4.7" },
|
|
14
14
|
{ value: "claude-opus-4-6", label: "Claude Opus 4.6" },
|
|
15
15
|
{ value: "claude-opus-4-5", label: "Claude Opus 4.5" },
|
|
16
|
-
{ value: "sonnet", label: "Claude Sonnet 4.6" },
|
|
17
|
-
{ value: "haiku", label: "Claude Haiku 4.5" },
|
|
16
|
+
{ value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
|
|
17
|
+
{ value: "claude-haiku-4-5", label: "Claude Haiku 4.5" },
|
|
18
18
|
] as const;
|
|
19
19
|
|
|
20
20
|
export const geminiModelOptions = [
|
|
@@ -240,7 +240,6 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000 }: Dashb
|
|
|
240
240
|
findings={state.findings}
|
|
241
241
|
storedFindings={state.storedFindings}
|
|
242
242
|
selectedFindingIds={state.selectedFindingIds}
|
|
243
|
-
selectedFindings={state.selectedFindings}
|
|
244
243
|
fixResults={state.fixResults}
|
|
245
244
|
unresolvedSelectedFindings={state.unresolvedSelectedFindings}
|
|
246
245
|
auditRegressionFindings={state.auditRegressionFindings}
|
|
@@ -13,11 +13,34 @@ type ReviewModeInputMode = Exclude<ReviewModeSelection, "uncommitted">;
|
|
|
13
13
|
type ReviewModeStep = "picker" | "branch-picker" | "commit-picker" | "options";
|
|
14
14
|
type ReviewExecutionMode = "review-only" | "auto-all" | "auto-priority";
|
|
15
15
|
type OptionsFocusTarget =
|
|
16
|
-
| "
|
|
16
|
+
| "iterations"
|
|
17
17
|
| "force-max-iterations"
|
|
18
|
-
| "execution-
|
|
18
|
+
| "execution-review-only"
|
|
19
|
+
| "execution-auto-all"
|
|
20
|
+
| "execution-auto-priority"
|
|
19
21
|
| "custom-instructions";
|
|
20
22
|
|
|
23
|
+
const OPTIONS_FOCUS_ORDER: OptionsFocusTarget[] = [
|
|
24
|
+
"iterations",
|
|
25
|
+
"force-max-iterations",
|
|
26
|
+
"execution-review-only",
|
|
27
|
+
"execution-auto-all",
|
|
28
|
+
"execution-auto-priority",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function executionFocusToMode(focus: OptionsFocusTarget): ReviewExecutionMode | null {
|
|
32
|
+
if (focus === "execution-review-only") return "review-only";
|
|
33
|
+
if (focus === "execution-auto-all") return "auto-all";
|
|
34
|
+
if (focus === "execution-auto-priority") return "auto-priority";
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function executionModeToFocus(mode: ReviewExecutionMode): OptionsFocusTarget {
|
|
39
|
+
if (mode === "review-only") return "execution-review-only";
|
|
40
|
+
if (mode === "auto-all") return "execution-auto-all";
|
|
41
|
+
return "execution-auto-priority";
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
interface ReviewModeOption {
|
|
22
45
|
label: string;
|
|
23
46
|
description: string;
|
|
@@ -347,30 +370,6 @@ function clampMaxIterations(value: number): number {
|
|
|
347
370
|
return Math.min(MAX_MAX_ITERATIONS, Math.max(MIN_MAX_ITERATIONS, Math.trunc(value)));
|
|
348
371
|
}
|
|
349
372
|
|
|
350
|
-
function cycleExecutionMode(current: ReviewExecutionMode, direction: 1 | -1): ReviewExecutionMode {
|
|
351
|
-
const currentIndex = REVIEW_EXECUTION_OPTIONS.findIndex((option) => option.mode === current);
|
|
352
|
-
const nextIndex = Math.min(
|
|
353
|
-
REVIEW_EXECUTION_OPTIONS.length - 1,
|
|
354
|
-
Math.max(0, currentIndex + direction)
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
return REVIEW_EXECUTION_OPTIONS[nextIndex]?.mode ?? current;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function getOptionsFocusOrder(showCustomInstructions: boolean): OptionsFocusTarget[] {
|
|
361
|
-
const focusOrder: OptionsFocusTarget[] = [
|
|
362
|
-
"max-iterations",
|
|
363
|
-
"force-max-iterations",
|
|
364
|
-
"execution-mode",
|
|
365
|
-
];
|
|
366
|
-
|
|
367
|
-
if (showCustomInstructions) {
|
|
368
|
-
focusOrder.push("custom-instructions");
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return focusOrder;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
373
|
export function ReviewModeOverlay({
|
|
375
374
|
defaultReview,
|
|
376
375
|
defaultMaxIterations,
|
|
@@ -396,7 +395,8 @@ export function ReviewModeOverlay({
|
|
|
396
395
|
const [priorityCursorIndex, setPriorityCursorIndex] = useState(0);
|
|
397
396
|
const [customInstructionsDraft, setCustomInstructionsDraft] = useState("");
|
|
398
397
|
const [showCustomInstructions, setShowCustomInstructions] = useState(false);
|
|
399
|
-
const [optionsFocus, setOptionsFocus] = useState<OptionsFocusTarget>("
|
|
398
|
+
const [optionsFocus, setOptionsFocus] = useState<OptionsFocusTarget>("iterations");
|
|
399
|
+
const lastNonCustomFocusRef = useRef<OptionsFocusTarget>("iterations");
|
|
400
400
|
const customInstructionsRef = useRef<TextareaRenderable>(null);
|
|
401
401
|
const maxIterationsInputRef = useRef<InputRenderable>(null);
|
|
402
402
|
|
|
@@ -490,34 +490,13 @@ export function ReviewModeOverlay({
|
|
|
490
490
|
Math.min(68, configurationContentWidth - (showCustomInstructions ? 2 : 0))
|
|
491
491
|
);
|
|
492
492
|
|
|
493
|
-
if (step === "options" && !getOptionsFocusOrder(showCustomInstructions).includes(optionsFocus)) {
|
|
494
|
-
setOptionsFocus("execution-mode");
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function pruneEmptyCustomInstructions() {
|
|
498
|
-
const nextValue = customInstructionsRef.current?.plainText ?? customInstructionsDraft;
|
|
499
|
-
const normalizedValue = nextValue.trim().length === 0 ? "" : nextValue;
|
|
500
|
-
if (normalizedValue !== customInstructionsDraft) {
|
|
501
|
-
setCustomInstructionsDraft(normalizedValue);
|
|
502
|
-
}
|
|
503
|
-
if (normalizedValue.length === 0) {
|
|
504
|
-
setShowCustomInstructions(false);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
493
|
function movePriorityCursor(direction: 1 | -1) {
|
|
509
494
|
setPriorityCursorIndex((current) => clampPriorityCursorIndex(current + direction));
|
|
510
495
|
setError(null);
|
|
511
496
|
}
|
|
512
497
|
|
|
513
|
-
function
|
|
514
|
-
|
|
515
|
-
const base = Number.isFinite(current)
|
|
516
|
-
? current
|
|
517
|
-
: direction > 0
|
|
518
|
-
? initialMaxIterations - 1
|
|
519
|
-
: initialMaxIterations + 1;
|
|
520
|
-
setMaxIterationsDraft(String(clampMaxIterations(base + direction)));
|
|
498
|
+
function advancePriorityCursor() {
|
|
499
|
+
setPriorityCursorIndex((current) => (current + 1) % PRIORITIES.length);
|
|
521
500
|
setError(null);
|
|
522
501
|
}
|
|
523
502
|
|
|
@@ -544,22 +523,6 @@ export function ReviewModeOverlay({
|
|
|
544
523
|
|
|
545
524
|
useKeyboard((key) => {
|
|
546
525
|
if (step === "options") {
|
|
547
|
-
if (key.name === "tab") {
|
|
548
|
-
const focusOrder = getOptionsFocusOrder(showCustomInstructions);
|
|
549
|
-
const currentIndex = focusOrder.indexOf(optionsFocus);
|
|
550
|
-
const direction = key.shift ? -1 : 1;
|
|
551
|
-
const nextIndex = (currentIndex + direction + focusOrder.length) % focusOrder.length;
|
|
552
|
-
const nextFocus = focusOrder[nextIndex];
|
|
553
|
-
if (nextFocus) {
|
|
554
|
-
if (optionsFocus === "custom-instructions" && nextFocus !== "custom-instructions") {
|
|
555
|
-
pruneEmptyCustomInstructions();
|
|
556
|
-
}
|
|
557
|
-
setOptionsFocus(nextFocus);
|
|
558
|
-
setError(null);
|
|
559
|
-
}
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
526
|
if (isCustomInstructionsFocused) {
|
|
564
527
|
if (key.name === "escape") {
|
|
565
528
|
hideCustomInstructions();
|
|
@@ -579,52 +542,50 @@ export function ReviewModeOverlay({
|
|
|
579
542
|
return;
|
|
580
543
|
}
|
|
581
544
|
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
545
|
+
if (isUpNavigationKey(key.name) || isDownNavigationKey(key.name)) {
|
|
546
|
+
const direction = isUpNavigationKey(key.name) ? -1 : 1;
|
|
547
|
+
const currentIndex = OPTIONS_FOCUS_ORDER.indexOf(optionsFocus);
|
|
548
|
+
const nextIndex = Math.min(
|
|
549
|
+
OPTIONS_FOCUS_ORDER.length - 1,
|
|
550
|
+
Math.max(0, currentIndex + direction)
|
|
551
|
+
);
|
|
552
|
+
const nextFocus = OPTIONS_FOCUS_ORDER[nextIndex];
|
|
553
|
+
if (nextFocus && nextFocus !== optionsFocus) {
|
|
554
|
+
setOptionsFocus(nextFocus);
|
|
555
|
+
const mode = executionFocusToMode(nextFocus);
|
|
556
|
+
if (mode) {
|
|
557
|
+
setExecutionMode(mode);
|
|
558
|
+
}
|
|
559
|
+
setError(null);
|
|
591
560
|
}
|
|
561
|
+
return;
|
|
592
562
|
}
|
|
593
563
|
|
|
594
|
-
if (optionsFocus === "force-max-iterations") {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
564
|
+
if (optionsFocus === "force-max-iterations" && key.name === "space") {
|
|
565
|
+
toggleForceMaxIterations();
|
|
566
|
+
return;
|
|
599
567
|
}
|
|
600
568
|
|
|
601
|
-
if (optionsFocus === "execution-
|
|
602
|
-
if (
|
|
603
|
-
setExecutionMode((current) => cycleExecutionMode(current, -1));
|
|
604
|
-
setError(null);
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
if (isDownNavigationKey(key.name)) {
|
|
609
|
-
setExecutionMode((current) => cycleExecutionMode(current, 1));
|
|
610
|
-
setError(null);
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (executionMode === "auto-priority" && isLeftNavigationKey(key.name)) {
|
|
569
|
+
if (optionsFocus === "execution-auto-priority") {
|
|
570
|
+
if (isLeftNavigationKey(key.name)) {
|
|
615
571
|
movePriorityCursor(-1);
|
|
616
572
|
return;
|
|
617
573
|
}
|
|
618
574
|
|
|
619
|
-
if (
|
|
575
|
+
if (isRightNavigationKey(key.name)) {
|
|
620
576
|
movePriorityCursor(1);
|
|
621
577
|
return;
|
|
622
578
|
}
|
|
623
579
|
|
|
624
|
-
if (
|
|
580
|
+
if (key.name === "space") {
|
|
625
581
|
toggleSelectedPriority();
|
|
626
582
|
return;
|
|
627
583
|
}
|
|
584
|
+
|
|
585
|
+
if (key.name === "tab") {
|
|
586
|
+
advancePriorityCursor();
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
628
589
|
}
|
|
629
590
|
|
|
630
591
|
if (key.name === "enter" || key.name === "return") {
|
|
@@ -712,7 +673,8 @@ export function ReviewModeOverlay({
|
|
|
712
673
|
setSelectedPriorities([]);
|
|
713
674
|
setPriorityCursorIndex(0);
|
|
714
675
|
setShowCustomInstructions(false);
|
|
715
|
-
setOptionsFocus("
|
|
676
|
+
setOptionsFocus("iterations");
|
|
677
|
+
lastNonCustomFocusRef.current = "iterations";
|
|
716
678
|
setError(null);
|
|
717
679
|
setStep("options");
|
|
718
680
|
}
|
|
@@ -770,6 +732,9 @@ export function ReviewModeOverlay({
|
|
|
770
732
|
}
|
|
771
733
|
|
|
772
734
|
function openCustomInstructions() {
|
|
735
|
+
if (optionsFocus !== "custom-instructions") {
|
|
736
|
+
lastNonCustomFocusRef.current = optionsFocus;
|
|
737
|
+
}
|
|
773
738
|
setShowCustomInstructions(true);
|
|
774
739
|
setOptionsFocus("custom-instructions");
|
|
775
740
|
setError(null);
|
|
@@ -778,7 +743,7 @@ export function ReviewModeOverlay({
|
|
|
778
743
|
function hideCustomInstructions() {
|
|
779
744
|
syncCustomInstructionsDraft();
|
|
780
745
|
setShowCustomInstructions(false);
|
|
781
|
-
setOptionsFocus(
|
|
746
|
+
setOptionsFocus(lastNonCustomFocusRef.current);
|
|
782
747
|
setError(null);
|
|
783
748
|
}
|
|
784
749
|
|
|
@@ -928,14 +893,13 @@ export function ReviewModeOverlay({
|
|
|
928
893
|
<box flexDirection="column">
|
|
929
894
|
{REVIEW_EXECUTION_OPTIONS.map((option) => {
|
|
930
895
|
const isSelected = option.mode === executionMode;
|
|
931
|
-
const isFocused = optionsFocus ===
|
|
932
|
-
const showFocusMarker = isFocused && option.mode !== "auto-priority";
|
|
896
|
+
const isFocused = optionsFocus === executionModeToFocus(option.mode);
|
|
933
897
|
|
|
934
898
|
return (
|
|
935
899
|
<box key={option.mode} flexDirection="column" paddingX={1} paddingY={0}>
|
|
936
900
|
<box flexDirection="row">
|
|
937
|
-
<text fg={
|
|
938
|
-
{
|
|
901
|
+
<text fg={isFocused ? TUI_COLORS.accent.key : TUI_COLORS.text.dim}>
|
|
902
|
+
{isFocused ? "▶ " : " "}
|
|
939
903
|
</text>
|
|
940
904
|
<text fg={isSelected ? TUI_COLORS.status.success : TUI_COLORS.text.dim}>
|
|
941
905
|
{isSelected ? "◉" : "◎"}
|
|
@@ -989,30 +953,21 @@ export function ReviewModeOverlay({
|
|
|
989
953
|
</text>
|
|
990
954
|
<input
|
|
991
955
|
ref={attachMaxIterationsInput}
|
|
992
|
-
focused={optionsFocus === "
|
|
956
|
+
focused={optionsFocus === "iterations"}
|
|
993
957
|
value={maxIterationsDraft}
|
|
994
958
|
placeholder={String(initialMaxIterations)}
|
|
995
959
|
width={12}
|
|
996
960
|
onChange={handleMaxIterationsInput}
|
|
997
961
|
onInput={handleMaxIterationsInput}
|
|
998
|
-
onKeyDown={(key) => {
|
|
999
|
-
if (isUpNavigationKey(key.name)) {
|
|
1000
|
-
key.preventDefault();
|
|
1001
|
-
key.stopPropagation();
|
|
1002
|
-
adjustMaxIterations(1);
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
if (isDownNavigationKey(key.name)) {
|
|
1007
|
-
key.preventDefault();
|
|
1008
|
-
key.stopPropagation();
|
|
1009
|
-
adjustMaxIterations(-1);
|
|
1010
|
-
}
|
|
1011
|
-
}}
|
|
1012
962
|
/>
|
|
1013
|
-
|
|
963
|
+
</box>
|
|
964
|
+
|
|
965
|
+
<box marginTop={1} paddingX={1} paddingY={0} flexDirection="column" gap={0}>
|
|
966
|
+
<text fg={TUI_COLORS.text.dim}>
|
|
967
|
+
<strong>Force Max Iterations</strong>
|
|
968
|
+
</text>
|
|
969
|
+
<box flexDirection="row">
|
|
1014
970
|
<text fg={isForceFocused ? TUI_COLORS.accent.key : TUI_COLORS.text.dim}>
|
|
1015
|
-
{" "}
|
|
1016
971
|
{isForceFocused ? "▶ " : " "}
|
|
1017
972
|
</text>
|
|
1018
973
|
<text fg={forceMaxIterations ? TUI_COLORS.status.success : TUI_COLORS.text.dim}>
|
|
@@ -1020,7 +975,7 @@ export function ReviewModeOverlay({
|
|
|
1020
975
|
</text>
|
|
1021
976
|
<text fg={forceMaxIterations ? TUI_COLORS.text.primary : TUI_COLORS.text.secondary}>
|
|
1022
977
|
{" "}
|
|
1023
|
-
|
|
978
|
+
{forceMaxIterations ? "Enabled" : "Disabled"}
|
|
1024
979
|
</text>
|
|
1025
980
|
</box>
|
|
1026
981
|
</box>
|
|
@@ -1032,15 +987,12 @@ export function ReviewModeOverlay({
|
|
|
1032
987
|
{renderExecutionModeOptions()}
|
|
1033
988
|
</box>
|
|
1034
989
|
|
|
1035
|
-
{
|
|
990
|
+
{optionsFocus === "execution-auto-priority" && (
|
|
1036
991
|
<box paddingX={1} paddingY={0} flexDirection="column" gap={0}>
|
|
1037
992
|
<box flexDirection="row" paddingLeft={2}>
|
|
1038
993
|
{PRIORITIES.map((priority, index) => {
|
|
1039
994
|
const isSelected = selectedPriorities.includes(priority);
|
|
1040
|
-
const isHighlighted =
|
|
1041
|
-
optionsFocus === "execution-mode" &&
|
|
1042
|
-
executionMode === "auto-priority" &&
|
|
1043
|
-
priorityCursorIndex === index;
|
|
995
|
+
const isHighlighted = priorityCursorIndex === index;
|
|
1044
996
|
|
|
1045
997
|
return (
|
|
1046
998
|
<box key={priority} paddingLeft={1}>
|
|
@@ -1136,8 +1088,7 @@ export function ReviewModeOverlay({
|
|
|
1136
1088
|
}
|
|
1137
1089
|
|
|
1138
1090
|
function renderOptions() {
|
|
1139
|
-
const
|
|
1140
|
-
optionsFocus === "execution-mode" && executionMode === "auto-priority";
|
|
1091
|
+
const isPriorityFocusActive = optionsFocus === "execution-auto-priority";
|
|
1141
1092
|
const isForceControlActive = optionsFocus === "force-max-iterations";
|
|
1142
1093
|
const reviewStartKeyLabel = isCustomInstructionsFocused ? "[Shift+Enter]" : "[Enter]";
|
|
1143
1094
|
|
|
@@ -1150,27 +1101,28 @@ export function ReviewModeOverlay({
|
|
|
1150
1101
|
<text fg={optionsStatusColor}>
|
|
1151
1102
|
{error ?? (
|
|
1152
1103
|
<>
|
|
1153
|
-
<span fg={TUI_COLORS.accent.key}>[
|
|
1154
|
-
<span fg={TUI_COLORS.text.muted}>
|
|
1155
|
-
{
|
|
1104
|
+
<span fg={TUI_COLORS.accent.key}>[↑/↓]</span>
|
|
1105
|
+
<span fg={TUI_COLORS.text.muted}> navigates </span>
|
|
1106
|
+
{isPriorityFocusActive && (
|
|
1156
1107
|
<>
|
|
1108
|
+
<span fg={TUI_COLORS.accent.key}>[←/→]</span>
|
|
1109
|
+
<span fg={TUI_COLORS.text.muted}> priority cursor </span>
|
|
1110
|
+
<span fg={TUI_COLORS.accent.key}>[Tab]</span>
|
|
1111
|
+
<span fg={TUI_COLORS.text.muted}> next priority </span>
|
|
1157
1112
|
<span fg={TUI_COLORS.accent.key}>[Space]</span>
|
|
1158
|
-
<span fg={TUI_COLORS.text.muted}>
|
|
1113
|
+
<span fg={TUI_COLORS.text.muted}> toggles priority </span>
|
|
1159
1114
|
</>
|
|
1160
1115
|
)}
|
|
1161
|
-
{isForceControlActive
|
|
1116
|
+
{isForceControlActive && (
|
|
1162
1117
|
<>
|
|
1163
1118
|
<span fg={TUI_COLORS.accent.key}>[Space]</span>
|
|
1164
1119
|
<span fg={TUI_COLORS.text.muted}> toggles force </span>
|
|
1165
|
-
<span fg={TUI_COLORS.accent.key}>{reviewStartKeyLabel}</span>
|
|
1166
|
-
<span fg={TUI_COLORS.text.muted}> starts review</span>
|
|
1167
|
-
</>
|
|
1168
|
-
) : (
|
|
1169
|
-
<>
|
|
1170
|
-
<span fg={TUI_COLORS.accent.key}>{reviewStartKeyLabel}</span>
|
|
1171
|
-
<span fg={TUI_COLORS.text.muted}> starts review</span>
|
|
1172
1120
|
</>
|
|
1173
1121
|
)}
|
|
1122
|
+
<span fg={TUI_COLORS.accent.key}>[C]</span>
|
|
1123
|
+
<span fg={TUI_COLORS.text.muted}> custom instructions </span>
|
|
1124
|
+
<span fg={TUI_COLORS.accent.key}>{reviewStartKeyLabel}</span>
|
|
1125
|
+
<span fg={TUI_COLORS.text.muted}> starts review</span>
|
|
1174
1126
|
</>
|
|
1175
1127
|
)}
|
|
1176
1128
|
</text>
|
|
@@ -25,7 +25,6 @@ interface DetailPaneProps {
|
|
|
25
25
|
findings: Finding[];
|
|
26
26
|
storedFindings: StoredFinding[];
|
|
27
27
|
selectedFindingIds: FindingId[];
|
|
28
|
-
selectedFindings: StoredFinding[];
|
|
29
28
|
fixResults: FindingFixResult[];
|
|
30
29
|
unresolvedSelectedFindings: StoredFinding[];
|
|
31
30
|
auditRegressionFindings: StoredFinding[];
|
|
@@ -53,7 +52,6 @@ export function DetailPane({
|
|
|
53
52
|
findings,
|
|
54
53
|
storedFindings,
|
|
55
54
|
selectedFindingIds,
|
|
56
|
-
selectedFindings,
|
|
57
55
|
fixResults,
|
|
58
56
|
unresolvedSelectedFindings,
|
|
59
57
|
auditRegressionFindings,
|
|
@@ -112,7 +110,6 @@ export function DetailPane({
|
|
|
112
110
|
findings={findings}
|
|
113
111
|
storedFindings={storedFindings}
|
|
114
112
|
selectedFindingIds={selectedFindingIds}
|
|
115
|
-
selectedFindings={selectedFindings}
|
|
116
113
|
fixResults={fixResults}
|
|
117
114
|
unresolvedSelectedFindings={unresolvedSelectedFindings}
|
|
118
115
|
auditRegressionFindings={auditRegressionFindings}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
FindingsList,
|
|
31
31
|
FixList,
|
|
32
32
|
SectionHeader,
|
|
33
|
+
SelectableStoredFindingsList,
|
|
33
34
|
SkippedList,
|
|
34
35
|
StoredFindingsList,
|
|
35
36
|
toSingleLine,
|
|
@@ -42,7 +43,6 @@ interface SessionDetailViewProps {
|
|
|
42
43
|
findings: Finding[];
|
|
43
44
|
storedFindings: StoredFinding[];
|
|
44
45
|
selectedFindingIds: FindingId[];
|
|
45
|
-
selectedFindings: StoredFinding[];
|
|
46
46
|
fixResults: FindingFixResult[];
|
|
47
47
|
unresolvedSelectedFindings: StoredFinding[];
|
|
48
48
|
auditRegressionFindings: StoredFinding[];
|
|
@@ -157,7 +157,6 @@ export function SessionDetailView({
|
|
|
157
157
|
findings,
|
|
158
158
|
storedFindings,
|
|
159
159
|
selectedFindingIds,
|
|
160
|
-
selectedFindings,
|
|
161
160
|
fixResults,
|
|
162
161
|
unresolvedSelectedFindings,
|
|
163
162
|
auditRegressionFindings,
|
|
@@ -255,12 +254,6 @@ export function SessionDetailView({
|
|
|
255
254
|
session.selectedFindingIds && session.selectedFindingIds.length > 0
|
|
256
255
|
? session.selectedFindingIds
|
|
257
256
|
: selectedFindingIds;
|
|
258
|
-
const workflowSelectedFindings =
|
|
259
|
-
selectedFindings.length > 0
|
|
260
|
-
? selectedFindings
|
|
261
|
-
: workflowSelectedIds
|
|
262
|
-
.map((findingId) => workflowFindingsById.get(findingId))
|
|
263
|
-
.filter((finding): finding is StoredFinding => finding !== undefined);
|
|
264
257
|
const workflowUnresolvedFindings =
|
|
265
258
|
unresolvedSelectedFindings.length > 0
|
|
266
259
|
? unresolvedSelectedFindings
|
|
@@ -334,10 +327,24 @@ export function SessionDetailView({
|
|
|
334
327
|
{batchFirstMode ? (
|
|
335
328
|
<>
|
|
336
329
|
<box flexDirection="column" flexBasis={0} flexGrow={5} minHeight={0}>
|
|
337
|
-
<SectionHeader
|
|
330
|
+
<SectionHeader
|
|
331
|
+
title="Findings"
|
|
332
|
+
count={batchDisplayFindings.length}
|
|
333
|
+
suffix={
|
|
334
|
+
workflowSelectedIds.length > 0 ? (
|
|
335
|
+
<span fg={TUI_COLORS.text.dim}> · {workflowSelectedIds.length} selected</span>
|
|
336
|
+
) : undefined
|
|
337
|
+
}
|
|
338
|
+
/>
|
|
338
339
|
<box flexGrow={1} minHeight={0}>
|
|
339
340
|
{inventoryFindings.length > 0 ? (
|
|
340
|
-
<
|
|
341
|
+
<SelectableStoredFindingsList
|
|
342
|
+
findings={inventoryFindings}
|
|
343
|
+
selectedFindingIds={workflowSelectedIds}
|
|
344
|
+
height="100%"
|
|
345
|
+
focused={focused}
|
|
346
|
+
selectedFirst
|
|
347
|
+
/>
|
|
341
348
|
) : showingCodex ? (
|
|
342
349
|
<CodexReviewDisplay text={displayCodexText ?? ""} height="100%" focused={focused} />
|
|
343
350
|
) : (
|
|
@@ -346,15 +353,6 @@ export function SessionDetailView({
|
|
|
346
353
|
</box>
|
|
347
354
|
</box>
|
|
348
355
|
|
|
349
|
-
{workflowSelectedFindings.length > 0 && (
|
|
350
|
-
<box flexDirection="column" flexBasis={0} flexGrow={2} minHeight={0}>
|
|
351
|
-
<SectionHeader title="Selected findings" count={workflowSelectedFindings.length} />
|
|
352
|
-
<box flexGrow={1} minHeight={0}>
|
|
353
|
-
<StoredFindingsList findings={workflowSelectedFindings} height="100%" />
|
|
354
|
-
</box>
|
|
355
|
-
</box>
|
|
356
|
-
)}
|
|
357
|
-
|
|
358
356
|
{fixResults.length > 0 && (
|
|
359
357
|
<box flexDirection="column" flexBasis={0} flexGrow={2} minHeight={0}>
|
|
360
358
|
<SectionHeader title="Fix results" count={fixResults.length} />
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
FindingFixResult,
|
|
3
|
+
FindingId,
|
|
4
|
+
StoredFinding,
|
|
5
|
+
} from "@/lib/review-workflow/findings/types";
|
|
2
6
|
import { storedFindingToFinding } from "@/lib/review-workflow/presentation";
|
|
3
7
|
import { formatFindingTitleForDisplay } from "@/lib/tui/sessions/finding-title";
|
|
4
8
|
import { PriorityText } from "@/lib/tui/sessions/priority-text";
|
|
@@ -40,12 +44,14 @@ export function FindingsList({
|
|
|
40
44
|
height = 8,
|
|
41
45
|
focused = false,
|
|
42
46
|
scrollable = true,
|
|
47
|
+
showBody = false,
|
|
43
48
|
showConfidence = false,
|
|
44
49
|
}: {
|
|
45
50
|
findings: Finding[];
|
|
46
51
|
height?: BoxHeight;
|
|
47
52
|
focused?: boolean;
|
|
48
53
|
scrollable?: boolean;
|
|
54
|
+
showBody?: boolean;
|
|
49
55
|
showConfidence?: boolean;
|
|
50
56
|
}) {
|
|
51
57
|
if (findings.length === 0) {
|
|
@@ -63,6 +69,7 @@ export function FindingsList({
|
|
|
63
69
|
|
|
64
70
|
return (
|
|
65
71
|
<box key={key} flexDirection="column">
|
|
72
|
+
{showBody && index > 0 && <text> </text>}
|
|
66
73
|
<box flexDirection="row">
|
|
67
74
|
<text>
|
|
68
75
|
<PriorityText priority={finding.priority} />
|
|
@@ -72,6 +79,14 @@ export function FindingsList({
|
|
|
72
79
|
{toSingleLine(formatFindingTitleForDisplay(finding.title))}
|
|
73
80
|
</text>
|
|
74
81
|
</box>
|
|
82
|
+
{showBody && (
|
|
83
|
+
<>
|
|
84
|
+
<text> </text>
|
|
85
|
+
<text fg={TUI_COLORS.text.secondary} paddingLeft={5} wrapMode="word">
|
|
86
|
+
{finding.body.trim()}
|
|
87
|
+
</text>
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
75
90
|
{showConfidence && (
|
|
76
91
|
<text fg={TUI_COLORS.text.dim} paddingLeft={5} wrapMode="none">
|
|
77
92
|
Confidence: {formatConfidenceScore(finding.confidence_score)}
|
|
@@ -100,12 +115,14 @@ export function StoredFindingsList({
|
|
|
100
115
|
height = 8,
|
|
101
116
|
focused = false,
|
|
102
117
|
scrollable = true,
|
|
118
|
+
showBody = false,
|
|
103
119
|
showConfidence = false,
|
|
104
120
|
}: {
|
|
105
121
|
findings: StoredFinding[];
|
|
106
122
|
height?: BoxHeight;
|
|
107
123
|
focused?: boolean;
|
|
108
124
|
scrollable?: boolean;
|
|
125
|
+
showBody?: boolean;
|
|
109
126
|
showConfidence?: boolean;
|
|
110
127
|
}) {
|
|
111
128
|
return (
|
|
@@ -114,11 +131,79 @@ export function StoredFindingsList({
|
|
|
114
131
|
height={height}
|
|
115
132
|
focused={focused}
|
|
116
133
|
scrollable={scrollable}
|
|
134
|
+
showBody={showBody}
|
|
117
135
|
showConfidence={showConfidence}
|
|
118
136
|
/>
|
|
119
137
|
);
|
|
120
138
|
}
|
|
121
139
|
|
|
140
|
+
export function SelectableStoredFindingsList({
|
|
141
|
+
findings,
|
|
142
|
+
selectedFindingIds,
|
|
143
|
+
height = 8,
|
|
144
|
+
focused = false,
|
|
145
|
+
scrollable = true,
|
|
146
|
+
selectedFirst = false,
|
|
147
|
+
}: {
|
|
148
|
+
findings: StoredFinding[];
|
|
149
|
+
selectedFindingIds: FindingId[];
|
|
150
|
+
height?: BoxHeight;
|
|
151
|
+
focused?: boolean;
|
|
152
|
+
scrollable?: boolean;
|
|
153
|
+
selectedFirst?: boolean;
|
|
154
|
+
}) {
|
|
155
|
+
if (findings.length === 0) {
|
|
156
|
+
return (
|
|
157
|
+
<text fg={TUI_COLORS.text.dim} paddingLeft={2}>
|
|
158
|
+
None yet
|
|
159
|
+
</text>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const selectedIdSet = new Set(selectedFindingIds);
|
|
164
|
+
const displayFindings = selectedFirst
|
|
165
|
+
? [
|
|
166
|
+
...findings.filter((finding) => selectedIdSet.has(finding.id)),
|
|
167
|
+
...findings.filter((finding) => !selectedIdSet.has(finding.id)),
|
|
168
|
+
]
|
|
169
|
+
: findings;
|
|
170
|
+
|
|
171
|
+
const content = displayFindings.map((finding) => {
|
|
172
|
+
const isSelected = selectedIdSet.has(finding.id);
|
|
173
|
+
const lineRange = `${finding.startLine}-${finding.endLine}`;
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<box key={finding.id} flexDirection="column">
|
|
177
|
+
<box flexDirection="row" gap={1}>
|
|
178
|
+
<text fg={isSelected ? TUI_COLORS.status.success : TUI_COLORS.text.dim}>
|
|
179
|
+
{isSelected ? "◉" : "◎"}
|
|
180
|
+
</text>
|
|
181
|
+
<text>
|
|
182
|
+
<PriorityText priority={finding.priority} />
|
|
183
|
+
</text>
|
|
184
|
+
<text fg={TUI_COLORS.text.dim}>▸</text>
|
|
185
|
+
<text fg={TUI_COLORS.text.secondary} wrapMode="none">
|
|
186
|
+
{toSingleLine(formatFindingTitleForDisplay(finding.title))}
|
|
187
|
+
</text>
|
|
188
|
+
</box>
|
|
189
|
+
<text fg={TUI_COLORS.text.dim} paddingLeft={7} wrapMode="none">
|
|
190
|
+
{toSingleLine(finding.filePath)}:{lineRange}
|
|
191
|
+
</text>
|
|
192
|
+
</box>
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!scrollable) {
|
|
197
|
+
return <box paddingLeft={2}>{content}</box>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<scrollbox paddingLeft={2} height={height} focused={focused}>
|
|
202
|
+
{content}
|
|
203
|
+
</scrollbox>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
122
207
|
export function FixList({
|
|
123
208
|
fixes,
|
|
124
209
|
showFiles,
|
|
@@ -453,7 +453,12 @@ export function SessionDetailPane({
|
|
|
453
453
|
<text fg={TUI_COLORS.text.secondary} paddingLeft={2}>
|
|
454
454
|
{entry.findings.length} issues found
|
|
455
455
|
</text>
|
|
456
|
-
<StoredFindingsList
|
|
456
|
+
<StoredFindingsList
|
|
457
|
+
findings={entry.findings}
|
|
458
|
+
scrollable={false}
|
|
459
|
+
showBody
|
|
460
|
+
showConfidence
|
|
461
|
+
/>
|
|
457
462
|
</WorkflowSection>
|
|
458
463
|
))}
|
|
459
464
|
|
|
@@ -29,7 +29,6 @@ interface WorkspaceProps {
|
|
|
29
29
|
findings: Finding[];
|
|
30
30
|
storedFindings: StoredFinding[];
|
|
31
31
|
selectedFindingIds: FindingId[];
|
|
32
|
-
selectedFindings: StoredFinding[];
|
|
33
32
|
fixResults: FindingFixResult[];
|
|
34
33
|
unresolvedSelectedFindings: StoredFinding[];
|
|
35
34
|
auditRegressionFindings: StoredFinding[];
|
|
@@ -61,7 +60,6 @@ export function Workspace({
|
|
|
61
60
|
findings,
|
|
62
61
|
storedFindings,
|
|
63
62
|
selectedFindingIds,
|
|
64
|
-
selectedFindings,
|
|
65
63
|
fixResults,
|
|
66
64
|
unresolvedSelectedFindings,
|
|
67
65
|
auditRegressionFindings,
|
|
@@ -103,7 +101,6 @@ export function Workspace({
|
|
|
103
101
|
findings={findings}
|
|
104
102
|
storedFindings={storedFindings}
|
|
105
103
|
selectedFindingIds={selectedFindingIds}
|
|
106
|
-
selectedFindings={selectedFindings}
|
|
107
104
|
fixResults={fixResults}
|
|
108
105
|
unresolvedSelectedFindings={unresolvedSelectedFindings}
|
|
109
106
|
auditRegressionFindings={auditRegressionFindings}
|