ralph-review 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/package.json
CHANGED
package/src/commands/run.ts
CHANGED
|
@@ -582,7 +582,26 @@ export async function startReview(
|
|
|
582
582
|
|
|
583
583
|
const loadedConfig = await runtime.loadConfig();
|
|
584
584
|
|
|
585
|
-
|
|
585
|
+
if (options.custom !== undefined && options.custom.trim().length === 0) {
|
|
586
|
+
runtime.prompt.log.error("--custom cannot be empty");
|
|
587
|
+
runtime.process.exit(1);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (options.commit !== undefined) {
|
|
592
|
+
options.commit = options.commit.trim();
|
|
593
|
+
if (options.commit.length === 0) {
|
|
594
|
+
runtime.prompt.log.error("--commit cannot be empty");
|
|
595
|
+
runtime.process.exit(1);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const hasExplicitMode =
|
|
601
|
+
options.base !== undefined ||
|
|
602
|
+
options.uncommitted === true ||
|
|
603
|
+
options.commit !== undefined ||
|
|
604
|
+
options.custom !== undefined;
|
|
586
605
|
if (!hasExplicitMode) {
|
|
587
606
|
if (loadedConfig?.defaultReview?.type === "base") {
|
|
588
607
|
options.base = loadedConfig.defaultReview.branch;
|
|
@@ -590,15 +609,35 @@ export async function startReview(
|
|
|
590
609
|
// else: defaults to uncommitted behavior (no base flag)
|
|
591
610
|
}
|
|
592
611
|
|
|
593
|
-
|
|
594
|
-
options.base
|
|
595
|
-
options.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
612
|
+
if (options.base !== undefined) {
|
|
613
|
+
options.base = options.base.trim();
|
|
614
|
+
if (options.base.length === 0) {
|
|
615
|
+
runtime.prompt.log.error("--base cannot be empty");
|
|
616
|
+
runtime.process.exit(1);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (options.base !== undefined && options.commit !== undefined) {
|
|
622
|
+
runtime.prompt.log.error("Cannot use --base and --commit together");
|
|
623
|
+
runtime.process.exit(1);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (options.uncommitted && options.base !== undefined) {
|
|
628
|
+
runtime.prompt.log.error("Cannot use --uncommitted and --base together");
|
|
629
|
+
runtime.process.exit(1);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (options.uncommitted && options.commit !== undefined) {
|
|
634
|
+
runtime.prompt.log.error("Cannot use --uncommitted and --commit together");
|
|
635
|
+
runtime.process.exit(1);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
599
638
|
|
|
600
|
-
if (
|
|
601
|
-
runtime.prompt.log.error(
|
|
639
|
+
if (options.uncommitted && options.custom !== undefined) {
|
|
640
|
+
runtime.prompt.log.error("Cannot use --uncommitted and --custom together");
|
|
602
641
|
runtime.process.exit(1);
|
|
603
642
|
return;
|
|
604
643
|
}
|
|
@@ -611,6 +650,7 @@ export async function startReview(
|
|
|
611
650
|
projectPath: runtime.process.cwd(),
|
|
612
651
|
baseBranch: options.base,
|
|
613
652
|
commitSha: options.commit,
|
|
653
|
+
customInstructions: options.custom,
|
|
614
654
|
capabilityDiscoveryOptions: {
|
|
615
655
|
probeAgents: getDynamicProbeAgents(loadedConfig),
|
|
616
656
|
},
|
package/src/lib/agents/codex.ts
CHANGED
|
@@ -52,6 +52,12 @@ export const codexConfig: AgentConfig = {
|
|
|
52
52
|
|
|
53
53
|
const baseReviewArgs = withReasoningEffort(["exec", "--json"], reasoning);
|
|
54
54
|
|
|
55
|
+
if (reviewOptions?.customInstructions) {
|
|
56
|
+
const fullPrompt = prompt ? `review ${prompt}` : "review";
|
|
57
|
+
const customArgs = withReasoningEffort(["exec", "--full-auto", "--json"], reasoning);
|
|
58
|
+
return withModel([...customArgs, fullPrompt], model);
|
|
59
|
+
}
|
|
60
|
+
|
|
55
61
|
if (reviewOptions?.commitSha) {
|
|
56
62
|
return withModel([...baseReviewArgs, "review", "--commit", reviewOptions.commitSha], model);
|
|
57
63
|
}
|
|
@@ -60,12 +66,6 @@ export const codexConfig: AgentConfig = {
|
|
|
60
66
|
return withModel([...baseReviewArgs, "review", "--base", reviewOptions.baseBranch], model);
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
if (reviewOptions?.customInstructions) {
|
|
64
|
-
const fullPrompt = prompt ? `review ${prompt}` : "review";
|
|
65
|
-
const customArgs = withReasoningEffort(["exec", "--full-auto", "--json"], reasoning);
|
|
66
|
-
return withModel([...customArgs, fullPrompt], model);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
69
|
return withModel([...baseReviewArgs, "review", "--uncommitted"], model);
|
|
70
70
|
},
|
|
71
71
|
buildEnv: defaultBuildEnv,
|
|
@@ -20,6 +20,7 @@ interface RunDiagnosticsDependencies {
|
|
|
20
20
|
) => Promise<AgentCapabilitiesMap>;
|
|
21
21
|
isGitRepository?: (path: string) => Promise<boolean>;
|
|
22
22
|
hasUncommittedChanges?: (path: string) => Promise<boolean>;
|
|
23
|
+
gitRefExists?: (path: string, ref: string) => Promise<boolean>;
|
|
23
24
|
cleanupStaleLockfile?: typeof cleanupStaleLockfile;
|
|
24
25
|
hasActiveLockfile?: typeof hasActiveLockfile;
|
|
25
26
|
isTmuxInstalled?: () => boolean;
|
|
@@ -34,6 +35,7 @@ export interface RunDiagnosticsOptions {
|
|
|
34
35
|
projectPath?: string;
|
|
35
36
|
baseBranch?: string;
|
|
36
37
|
commitSha?: string;
|
|
38
|
+
customInstructions?: string;
|
|
37
39
|
capabilitiesByAgent?: AgentCapabilitiesMap;
|
|
38
40
|
capabilityDiscoveryOptions?: CapabilityDiscoveryOptions;
|
|
39
41
|
dependencies?: RunDiagnosticsDependencies;
|
|
@@ -136,6 +138,15 @@ async function hasGitUncommittedChanges(path: string): Promise<boolean> {
|
|
|
136
138
|
return result.stdout.trim().length > 0;
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
async function hasGitRef(path: string, ref: string): Promise<boolean> {
|
|
142
|
+
if (ref.trim().length === 0) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result = await runGitInPath(path, ["rev-parse", "--verify", ref]);
|
|
147
|
+
return result.exitCode === 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
139
150
|
function buildReport(
|
|
140
151
|
context: DiagnosticContext,
|
|
141
152
|
items: DiagnosticItem[],
|
|
@@ -165,6 +176,7 @@ export async function runDiagnostics(
|
|
|
165
176
|
const resolveCapabilityDiscovery = deps.discoverAgentCapabilities ?? discoverAgentCapabilities;
|
|
166
177
|
const resolveIsGitRepo = deps.isGitRepository ?? isGitRepository;
|
|
167
178
|
const resolveHasChanges = deps.hasUncommittedChanges ?? hasGitUncommittedChanges;
|
|
179
|
+
const resolveGitRefExists = deps.gitRefExists ?? hasGitRef;
|
|
168
180
|
const resolveCleanupStaleLockfile = deps.cleanupStaleLockfile ?? cleanupStaleLockfile;
|
|
169
181
|
const resolveHasActiveLockfile = deps.hasActiveLockfile ?? hasActiveLockfile;
|
|
170
182
|
const resolveIsTmuxInstalled = deps.isTmuxInstalled ?? isTmuxInstalled;
|
|
@@ -423,6 +435,79 @@ export async function runDiagnostics(
|
|
|
423
435
|
}
|
|
424
436
|
|
|
425
437
|
if (context === "run") {
|
|
438
|
+
if (insideGitRepo && !gitRepoError && options.baseBranch) {
|
|
439
|
+
let baseRefExists = false;
|
|
440
|
+
let baseRefError: string | null = null;
|
|
441
|
+
try {
|
|
442
|
+
baseRefExists = await resolveGitRefExists(projectPath, options.baseBranch);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
baseRefError = `${error}`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (baseRefError) {
|
|
448
|
+
items.push({
|
|
449
|
+
id: "git-base-ref",
|
|
450
|
+
category: "git",
|
|
451
|
+
title: "Base ref",
|
|
452
|
+
severity: "error",
|
|
453
|
+
summary: `Unable to validate base ref '${options.baseBranch}'.`,
|
|
454
|
+
details: baseRefError,
|
|
455
|
+
remediation: [runStep("git branch --all"), thenStep("rr run --base <existing-ref>")],
|
|
456
|
+
});
|
|
457
|
+
} else {
|
|
458
|
+
items.push({
|
|
459
|
+
id: "git-base-ref",
|
|
460
|
+
category: "git",
|
|
461
|
+
title: "Base ref",
|
|
462
|
+
severity: baseRefExists ? "ok" : "error",
|
|
463
|
+
summary: baseRefExists
|
|
464
|
+
? `Base ref '${options.baseBranch}' exists.`
|
|
465
|
+
: `Base ref '${options.baseBranch}' was not found.`,
|
|
466
|
+
remediation: baseRefExists
|
|
467
|
+
? []
|
|
468
|
+
: [runStep("git branch --all"), thenStep("rr run --base <existing-ref>")],
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (insideGitRepo && !gitRepoError && options.commitSha) {
|
|
474
|
+
let commitRefExists = false;
|
|
475
|
+
let commitRefError: string | null = null;
|
|
476
|
+
try {
|
|
477
|
+
commitRefExists = await resolveGitRefExists(projectPath, options.commitSha);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
commitRefError = `${error}`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (commitRefError) {
|
|
483
|
+
items.push({
|
|
484
|
+
id: "git-commit-ref",
|
|
485
|
+
category: "git",
|
|
486
|
+
title: "Commit ref",
|
|
487
|
+
severity: "error",
|
|
488
|
+
summary: `Unable to validate commit ref '${options.commitSha}'.`,
|
|
489
|
+
details: commitRefError,
|
|
490
|
+
remediation: [
|
|
491
|
+
runStep("git rev-parse --verify <commit>"),
|
|
492
|
+
thenStep("rr run --commit <sha>"),
|
|
493
|
+
],
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
items.push({
|
|
497
|
+
id: "git-commit-ref",
|
|
498
|
+
category: "git",
|
|
499
|
+
title: "Commit ref",
|
|
500
|
+
severity: commitRefExists ? "ok" : "error",
|
|
501
|
+
summary: commitRefExists
|
|
502
|
+
? `Commit ref '${options.commitSha}' exists.`
|
|
503
|
+
: `Commit ref '${options.commitSha}' was not found.`,
|
|
504
|
+
remediation: commitRefExists
|
|
505
|
+
? []
|
|
506
|
+
: [runStep("git log --oneline -n 20"), thenStep("rr run --commit <existing-sha>")],
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
426
511
|
if (!options.baseBranch && !options.commitSha && insideGitRepo && !gitRepoError) {
|
|
427
512
|
let hasChanges = false;
|
|
428
513
|
let hasChangesError: string | null = null;
|
package/src/lib/format.ts
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import type { ReviewOptions } from "@/lib/types";
|
|
2
2
|
|
|
3
|
+
function formatCustomReviewType(customInstructions: string): string {
|
|
4
|
+
const instruction = customInstructions.slice(0, 40);
|
|
5
|
+
return customInstructions.length > 40 ? `custom (${instruction}...)` : `custom (${instruction})`;
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
export function formatReviewType(reviewOptions: ReviewOptions | undefined): string {
|
|
4
9
|
if (!reviewOptions) return "uncommitted changes";
|
|
5
10
|
|
|
6
|
-
if (reviewOptions.customInstructions) {
|
|
7
|
-
const instruction = reviewOptions.customInstructions.slice(0, 40);
|
|
8
|
-
return reviewOptions.customInstructions.length > 40
|
|
9
|
-
? `custom (${instruction}...)`
|
|
10
|
-
: `custom (${instruction})`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
11
|
if (reviewOptions.commitSha) {
|
|
14
12
|
const shortSha = reviewOptions.commitSha.slice(0, 7);
|
|
13
|
+
if (reviewOptions.customInstructions) {
|
|
14
|
+
return `commit (${shortSha}) + ${formatCustomReviewType(reviewOptions.customInstructions)}`;
|
|
15
|
+
}
|
|
15
16
|
return `commit (${shortSha})`;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
if (reviewOptions.baseBranch) {
|
|
20
|
+
if (reviewOptions.customInstructions) {
|
|
21
|
+
return `base (${reviewOptions.baseBranch}) + ${formatCustomReviewType(reviewOptions.customInstructions)}`;
|
|
22
|
+
}
|
|
19
23
|
return `base (${reviewOptions.baseBranch})`;
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
if (reviewOptions.customInstructions) {
|
|
27
|
+
return formatCustomReviewType(reviewOptions.customInstructions);
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
return "uncommitted changes";
|
|
23
31
|
}
|
|
@@ -19,6 +19,16 @@ const BASE_BRANCH_PROMPT_BACKUP = (branch: string) =>
|
|
|
19
19
|
const COMMIT_PROMPT = (commitHash: string) =>
|
|
20
20
|
`Review the code changes for the commit ${commitHash}. Provide prioritized, actionable findings.`;
|
|
21
21
|
|
|
22
|
+
const CUSTOM_FOCUS_PROMPT = (customInstructions: string) =>
|
|
23
|
+
`Additional review focus from user instructions:\n${customInstructions}`;
|
|
24
|
+
|
|
25
|
+
function withCustomFocus(instruction: string, customInstructions?: string): string {
|
|
26
|
+
if (!customInstructions) {
|
|
27
|
+
return instruction;
|
|
28
|
+
}
|
|
29
|
+
return `${instruction}\n\n${CUSTOM_FOCUS_PROMPT(customInstructions)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
export interface ReviewerPromptOptions {
|
|
23
33
|
repoPath: string;
|
|
24
34
|
baseBranch?: string;
|
|
@@ -26,19 +36,20 @@ export interface ReviewerPromptOptions {
|
|
|
26
36
|
customInstructions?: string;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
/**
|
|
39
|
+
/** Target priority: commitSha > baseBranch > uncommitted (default), with custom focus overlay. */
|
|
30
40
|
export function createReviewerPrompt(options: ReviewerPromptOptions): string {
|
|
31
41
|
const { repoPath, baseBranch, commitSha, customInstructions } = options;
|
|
32
42
|
|
|
33
43
|
let instruction: string;
|
|
34
44
|
|
|
35
45
|
if (commitSha) {
|
|
36
|
-
instruction = COMMIT_PROMPT(commitSha);
|
|
46
|
+
instruction = withCustomFocus(COMMIT_PROMPT(commitSha), customInstructions);
|
|
37
47
|
} else if (baseBranch) {
|
|
38
48
|
const mergeBaseSha = mergeBaseWithHead(repoPath, baseBranch);
|
|
39
|
-
|
|
49
|
+
const baseInstruction = mergeBaseSha
|
|
40
50
|
? BASE_BRANCH_PROMPT(baseBranch, mergeBaseSha)
|
|
41
51
|
: BASE_BRANCH_PROMPT_BACKUP(baseBranch);
|
|
52
|
+
instruction = withCustomFocus(baseInstruction, customInstructions);
|
|
42
53
|
} else if (customInstructions) {
|
|
43
54
|
instruction = customInstructions;
|
|
44
55
|
} else {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useTerminalDimensions } from "@opentui/react";
|
|
2
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import { formatReviewType } from "@/lib/format";
|
|
3
4
|
import type { LockData } from "@/lib/lockfile";
|
|
4
5
|
import { TUI_COLORS } from "@/lib/tui/colors";
|
|
5
6
|
import type {
|
|
@@ -81,24 +82,6 @@ function toSingleLine(value: string): string {
|
|
|
81
82
|
return value.replace(/\s+/g, " ").trim();
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function formatReviewType(reviewOptions: ReviewOptions | undefined): string {
|
|
85
|
-
if (!reviewOptions) return "uncommitted changes";
|
|
86
|
-
|
|
87
|
-
if (reviewOptions.customInstructions) {
|
|
88
|
-
return `custom (${toSingleLine(reviewOptions.customInstructions)})`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (reviewOptions.commitSha) {
|
|
92
|
-
return `commit (${toSingleLine(reviewOptions.commitSha)})`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (reviewOptions.baseBranch) {
|
|
96
|
-
return `base (${toSingleLine(reviewOptions.baseBranch)})`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return "uncommitted changes";
|
|
100
|
-
}
|
|
101
|
-
|
|
102
85
|
interface FixListProps {
|
|
103
86
|
fixes: FixEntry[];
|
|
104
87
|
showFiles: boolean;
|
|
@@ -566,7 +549,7 @@ export function SessionPanel({
|
|
|
566
549
|
<box flexDirection="row" gap={1}>
|
|
567
550
|
<text fg={TUI_COLORS.text.muted}>Review Type:</text>
|
|
568
551
|
<text fg={TUI_COLORS.text.primary} wrapMode="none">
|
|
569
|
-
{formatReviewType(reviewOptions)}
|
|
552
|
+
{toSingleLine(formatReviewType(reviewOptions))}
|
|
570
553
|
</text>
|
|
571
554
|
</box>
|
|
572
555
|
|