ralph-review 0.1.6 → 0.1.8
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/README.md +9 -1
- package/package.json +1 -1
- package/src/cli-core.ts +29 -5
- package/src/cli.ts +32 -3
- package/src/commands/config.ts +92 -27
- package/src/commands/init.ts +15 -9
- package/src/commands/run.ts +20 -20
- package/src/commands/stop.ts +1 -1
- package/src/commands/update.ts +133 -0
- package/src/lib/cli-parser.ts +29 -9
- package/src/lib/config.ts +239 -82
- package/src/lib/diagnostics/checks.ts +1 -1
- package/src/lib/diagnostics/remediation.ts +1 -1
- package/src/lib/self-update.ts +573 -0
- package/src/lib/tui/components/StatusBar.tsx +3 -3
- package/src/lib/types/config.ts +1 -1
package/README.md
CHANGED
|
@@ -136,6 +136,9 @@ brew install kenryu42/tap/ralph-review
|
|
|
136
136
|
|
|
137
137
|
# npm (install or update)
|
|
138
138
|
npm install -g ralph-review
|
|
139
|
+
|
|
140
|
+
# Or let ralph-review detect the install method and update itself
|
|
141
|
+
rr update
|
|
139
142
|
```
|
|
140
143
|
|
|
141
144
|
---
|
|
@@ -159,6 +162,7 @@ rrr
|
|
|
159
162
|
|
|
160
163
|
| Command | Description |
|
|
161
164
|
|---------|-------------|
|
|
165
|
+
| `rr` | Interactive Mode |
|
|
162
166
|
| `rr init` | Configure reviewer, fixer, and simplifier agents (auto-detects installed CLIs) |
|
|
163
167
|
| `rr run` | Start review cycle in a tmux session |
|
|
164
168
|
| `rr run --base main` | Review changes against a base branch |
|
|
@@ -169,14 +173,18 @@ rrr
|
|
|
169
173
|
| `rr config show` | Print full configuration |
|
|
170
174
|
| `rr config set KEY VAL` | Update a config value (e.g. `rr config set maxIterations 8`) |
|
|
171
175
|
| `rr list` | List active review sessions |
|
|
172
|
-
| `rr status` | Show current review status |
|
|
173
176
|
| `rr stop` | Stop running review session (`--all` to stop all) |
|
|
174
177
|
| `rr log` | View review logs (`-n 5` for last 5, `--json` for JSON output) |
|
|
175
178
|
| `rr dashboard` | Open review dashboard in browser |
|
|
176
179
|
| `rr doctor` | Run environment and configuration diagnostics (`--fix` to auto-resolve) |
|
|
180
|
+
| `rr update` | Check for and install a newer `ralph-review` version |
|
|
177
181
|
|
|
178
182
|
The `rrr` command is a shorthand alias for `rr run` -- all flags work the same.
|
|
179
183
|
|
|
184
|
+
For update checks without installing, run `rr update --check`. If install-source detection is
|
|
185
|
+
ambiguous, force the package manager with `rr update --manager npm` or
|
|
186
|
+
`rr update --manager brew`.
|
|
187
|
+
|
|
180
188
|
---
|
|
181
189
|
|
|
182
190
|
## Supported Coding Agents
|
package/package.json
CHANGED
package/src/cli-core.ts
CHANGED
|
@@ -81,18 +81,18 @@ export const COMMANDS: CommandDef[] = [
|
|
|
81
81
|
description: "Disable finish sound for this run (override config)",
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
|
-
name: "
|
|
84
|
+
name: "interactive",
|
|
85
85
|
type: "boolean",
|
|
86
|
-
description: "
|
|
86
|
+
description: "Launch Interactive Mode after starting the run (override config)",
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
name: "no-
|
|
89
|
+
name: "no-interactive",
|
|
90
90
|
type: "boolean",
|
|
91
|
-
description: "Start run without
|
|
91
|
+
description: "Start run without launching Interactive Mode (override config)",
|
|
92
92
|
},
|
|
93
93
|
SIMPLIFIER_OPTION,
|
|
94
94
|
],
|
|
95
|
-
examples: ["rr run", "rr run --base main", "rr run --no-
|
|
95
|
+
examples: ["rr run", "rr run --base main", "rr run --no-interactive"],
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
name: "list",
|
|
@@ -104,6 +104,7 @@ export const COMMANDS: CommandDef[] = [
|
|
|
104
104
|
name: "status",
|
|
105
105
|
description: "Show review status",
|
|
106
106
|
examples: ["rr status"],
|
|
107
|
+
hidden: true,
|
|
107
108
|
},
|
|
108
109
|
{
|
|
109
110
|
name: "stop",
|
|
@@ -150,6 +151,29 @@ export const COMMANDS: CommandDef[] = [
|
|
|
150
151
|
],
|
|
151
152
|
examples: ["rr doctor", "rr doctor --fix"],
|
|
152
153
|
},
|
|
154
|
+
{
|
|
155
|
+
name: "update",
|
|
156
|
+
description: "Check for and install a newer ralph-review version",
|
|
157
|
+
options: [
|
|
158
|
+
{
|
|
159
|
+
name: "check",
|
|
160
|
+
type: "boolean",
|
|
161
|
+
description: "Check for an update without installing it",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "manager",
|
|
165
|
+
type: "string",
|
|
166
|
+
placeholder: "npm|brew",
|
|
167
|
+
description: "Override install-source detection",
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
examples: [
|
|
171
|
+
"rr update",
|
|
172
|
+
"rr update --check",
|
|
173
|
+
"rr update --manager npm",
|
|
174
|
+
"rr update --manager brew",
|
|
175
|
+
],
|
|
176
|
+
},
|
|
153
177
|
{
|
|
154
178
|
name: "_run-foreground",
|
|
155
179
|
description: "Internal: run review cycle in tmux foreground",
|
package/src/cli.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { runLog } from "./commands/log";
|
|
|
11
11
|
import { runForeground, startReview } from "./commands/run";
|
|
12
12
|
import { runStatus } from "./commands/status";
|
|
13
13
|
import { runStop } from "./commands/stop";
|
|
14
|
+
import { runUpdate } from "./commands/update";
|
|
14
15
|
import { CliError, type CommandDef, parseCommand } from "./lib/cli-parser";
|
|
15
16
|
|
|
16
17
|
export {
|
|
@@ -41,6 +42,8 @@ export interface CliDeps {
|
|
|
41
42
|
runDashboard: typeof runDashboard;
|
|
42
43
|
runDoctor: typeof runDoctor;
|
|
43
44
|
runList: typeof runList;
|
|
45
|
+
runUpdate: typeof runUpdate;
|
|
46
|
+
isInteractiveTerminal: () => boolean;
|
|
44
47
|
log: (message: string) => void;
|
|
45
48
|
logError: (message: string) => void;
|
|
46
49
|
logMessage: (message: string) => void;
|
|
@@ -51,6 +54,8 @@ const CONSOLE_LOG = console.log.bind(console) as (message: string) => void;
|
|
|
51
54
|
const CLACK_ERROR = p.log.error.bind(p.log) as (message: string) => void;
|
|
52
55
|
const CLACK_MESSAGE = p.log.message.bind(p.log) as (message: string) => void;
|
|
53
56
|
const PROCESS_EXIT = process.exit.bind(process) as (code: number) => void;
|
|
57
|
+
const IS_INTERACTIVE_TERMINAL = (): boolean =>
|
|
58
|
+
process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
54
59
|
|
|
55
60
|
const DEFAULT_CLI_DEPS: CliDeps = {
|
|
56
61
|
parseArgs,
|
|
@@ -69,6 +74,8 @@ const DEFAULT_CLI_DEPS: CliDeps = {
|
|
|
69
74
|
runDashboard,
|
|
70
75
|
runDoctor,
|
|
71
76
|
runList,
|
|
77
|
+
runUpdate,
|
|
78
|
+
isInteractiveTerminal: IS_INTERACTIVE_TERMINAL,
|
|
72
79
|
log: CONSOLE_LOG,
|
|
73
80
|
logError: CLACK_ERROR,
|
|
74
81
|
logMessage: CLACK_MESSAGE,
|
|
@@ -79,6 +86,11 @@ function buildCliDeps(overrides: Partial<CliDeps>): CliDeps {
|
|
|
79
86
|
return { ...DEFAULT_CLI_DEPS, ...overrides };
|
|
80
87
|
}
|
|
81
88
|
|
|
89
|
+
function reportCliError(cliDeps: CliDeps, error: unknown): void {
|
|
90
|
+
cliDeps.logError(`Error: ${error}`);
|
|
91
|
+
cliDeps.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
export async function runCli(
|
|
83
95
|
args: string[] = process.argv.slice(2),
|
|
84
96
|
deps: Partial<CliDeps> = {}
|
|
@@ -91,11 +103,25 @@ export async function runCli(
|
|
|
91
103
|
return;
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
if (!command) {
|
|
106
|
+
if (showHelp && !command) {
|
|
95
107
|
cliDeps.log(cliDeps.printUsage());
|
|
96
108
|
return;
|
|
97
109
|
}
|
|
98
110
|
|
|
111
|
+
if (!command) {
|
|
112
|
+
if (commandArgs.length > 0 || !cliDeps.isInteractiveTerminal()) {
|
|
113
|
+
cliDeps.log(cliDeps.printUsage());
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await cliDeps.runStatus();
|
|
119
|
+
} catch (error) {
|
|
120
|
+
reportCliError(cliDeps, error);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
99
125
|
if (showHelp) {
|
|
100
126
|
const commandHelp = cliDeps.printCommandHelp(command);
|
|
101
127
|
if (commandHelp) {
|
|
@@ -165,6 +191,10 @@ export async function runCli(
|
|
|
165
191
|
await cliDeps.runDoctor(commandArgs);
|
|
166
192
|
break;
|
|
167
193
|
|
|
194
|
+
case "update":
|
|
195
|
+
await cliDeps.runUpdate(commandArgs);
|
|
196
|
+
break;
|
|
197
|
+
|
|
168
198
|
case "list":
|
|
169
199
|
await cliDeps.runList();
|
|
170
200
|
break;
|
|
@@ -176,8 +206,7 @@ export async function runCli(
|
|
|
176
206
|
return;
|
|
177
207
|
}
|
|
178
208
|
} catch (error) {
|
|
179
|
-
cliDeps
|
|
180
|
-
cliDeps.exit(1);
|
|
209
|
+
reportCliError(cliDeps, error);
|
|
181
210
|
}
|
|
182
211
|
}
|
|
183
212
|
|
package/src/commands/config.ts
CHANGED
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
configExists,
|
|
5
5
|
ensureConfigDir,
|
|
6
6
|
loadConfig,
|
|
7
|
+
loadConfigWithDiagnostics,
|
|
7
8
|
parseConfig,
|
|
9
|
+
parseConfigWithDiagnostics,
|
|
8
10
|
saveConfig,
|
|
9
11
|
} from "@/lib/config";
|
|
10
12
|
import {
|
|
@@ -41,7 +43,9 @@ export type ConfigCommandDeps = {
|
|
|
41
43
|
configExists: typeof configExists;
|
|
42
44
|
ensureConfigDir: typeof ensureConfigDir;
|
|
43
45
|
loadConfig: typeof loadConfig;
|
|
46
|
+
loadConfigWithDiagnostics: typeof loadConfigWithDiagnostics;
|
|
44
47
|
parseConfig: typeof parseConfig;
|
|
48
|
+
parseConfigWithDiagnostics: typeof parseConfigWithDiagnostics;
|
|
45
49
|
saveConfig: typeof saveConfig;
|
|
46
50
|
spawn: ConfigCommandSpawner;
|
|
47
51
|
env: Record<string, string | undefined>;
|
|
@@ -68,7 +72,7 @@ const CONFIG_KEYS = [
|
|
|
68
72
|
"defaultReview.type",
|
|
69
73
|
"defaultReview.branch",
|
|
70
74
|
"run.simplifier",
|
|
71
|
-
"run.
|
|
75
|
+
"run.interactive",
|
|
72
76
|
"retry.maxRetries",
|
|
73
77
|
"retry.baseDelayMs",
|
|
74
78
|
"retry.maxDelayMs",
|
|
@@ -251,7 +255,7 @@ export function parseConfigValue(key: ConfigKey, rawValue: string): ConfigValue
|
|
|
251
255
|
return rawValue;
|
|
252
256
|
|
|
253
257
|
case "run.simplifier":
|
|
254
|
-
case "run.
|
|
258
|
+
case "run.interactive":
|
|
255
259
|
if (rawValue !== "true" && rawValue !== "false") {
|
|
256
260
|
throw new Error(`Value for "${key}" must be "true" or "false".`);
|
|
257
261
|
}
|
|
@@ -306,8 +310,8 @@ export function getConfigValue(config: Config, key: ConfigKey): unknown {
|
|
|
306
310
|
return config.defaultReview.type === "base" ? config.defaultReview.branch : undefined;
|
|
307
311
|
case "run.simplifier":
|
|
308
312
|
return config.run?.simplifier;
|
|
309
|
-
case "run.
|
|
310
|
-
return config.run?.
|
|
313
|
+
case "run.interactive":
|
|
314
|
+
return config.run?.interactive;
|
|
311
315
|
case "retry.maxRetries":
|
|
312
316
|
return config.retry?.maxRetries;
|
|
313
317
|
case "retry.baseDelayMs":
|
|
@@ -490,13 +494,13 @@ export function setConfigValue(config: Config, key: ConfigKey, value: ConfigValu
|
|
|
490
494
|
if (typeof value !== "boolean") {
|
|
491
495
|
throw new Error(`Value for "${key}" must be "true" or "false".`);
|
|
492
496
|
}
|
|
493
|
-
next.run = { simplifier: value,
|
|
497
|
+
next.run = { simplifier: value, interactive: next.run?.interactive ?? true };
|
|
494
498
|
return next;
|
|
495
|
-
case "run.
|
|
499
|
+
case "run.interactive":
|
|
496
500
|
if (typeof value !== "boolean") {
|
|
497
501
|
throw new Error(`Value for "${key}" must be "true" or "false".`);
|
|
498
502
|
}
|
|
499
|
-
next.run = { simplifier: next.run?.simplifier ?? false,
|
|
503
|
+
next.run = { simplifier: next.run?.simplifier ?? false, interactive: value };
|
|
500
504
|
return next;
|
|
501
505
|
case "retry.maxRetries":
|
|
502
506
|
next.retry = next.retry ? { ...next.retry } : { ...DEFAULT_RETRY_CONFIG };
|
|
@@ -591,26 +595,55 @@ export function validateConfigInvariants(config: Config): string[] {
|
|
|
591
595
|
if (config.run && typeof config.run.simplifier !== "boolean") {
|
|
592
596
|
errors.push("run.simplifier must be a boolean.");
|
|
593
597
|
}
|
|
594
|
-
if (config.run && typeof config.run.
|
|
595
|
-
errors.push("run.
|
|
598
|
+
if (config.run && typeof config.run.interactive !== "boolean") {
|
|
599
|
+
errors.push("run.interactive must be a boolean.");
|
|
596
600
|
}
|
|
597
601
|
|
|
598
602
|
return errors;
|
|
599
603
|
}
|
|
600
604
|
|
|
605
|
+
function formatConfigValidationMessage(header: string, errors: string[], footer?: string): string {
|
|
606
|
+
const uniqueErrors = [...new Set(errors)];
|
|
607
|
+
const lines = [header];
|
|
608
|
+
for (const error of uniqueErrors) {
|
|
609
|
+
lines.push(`- ${error}`);
|
|
610
|
+
}
|
|
611
|
+
if (footer) {
|
|
612
|
+
lines.push(footer);
|
|
613
|
+
}
|
|
614
|
+
return lines.join("\n");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function collectConfigValidationErrors(config: Config | null, errors: string[]): string[] {
|
|
618
|
+
const combined = [...errors];
|
|
619
|
+
if (config) {
|
|
620
|
+
combined.push(...validateConfigInvariants(config));
|
|
621
|
+
}
|
|
622
|
+
return [...new Set(combined)];
|
|
623
|
+
}
|
|
624
|
+
|
|
601
625
|
async function loadExistingConfig(deps: ConfigCommandDeps): Promise<Config> {
|
|
602
626
|
if (!(await deps.configExists())) {
|
|
603
627
|
throw new Error('Configuration not found. Run "rr init" first.');
|
|
604
628
|
}
|
|
605
629
|
|
|
606
|
-
const
|
|
607
|
-
if (!
|
|
630
|
+
const loaded = await deps.loadConfigWithDiagnostics();
|
|
631
|
+
if (!loaded.exists) {
|
|
632
|
+
throw new Error('Configuration not found. Run "rr init" first.');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const errors = collectConfigValidationErrors(loaded.config, loaded.errors);
|
|
636
|
+
if (!loaded.config || errors.length > 0) {
|
|
608
637
|
throw new Error(
|
|
609
|
-
|
|
638
|
+
formatConfigValidationMessage(
|
|
639
|
+
`Invalid configuration: ${deps.configPath}`,
|
|
640
|
+
errors.length > 0 ? errors : ["Configuration format is invalid."],
|
|
641
|
+
'Run "rr init" to regenerate the file, or fix it manually.'
|
|
642
|
+
)
|
|
610
643
|
);
|
|
611
644
|
}
|
|
612
645
|
|
|
613
|
-
return config;
|
|
646
|
+
return loaded.config;
|
|
614
647
|
}
|
|
615
648
|
|
|
616
649
|
async function runShow(args: string[], deps: ConfigCommandDeps): Promise<void> {
|
|
@@ -650,17 +683,18 @@ async function runSet(args: string[], deps: ConfigCommandDeps): Promise<void> {
|
|
|
650
683
|
const current = await loadExistingConfig(deps);
|
|
651
684
|
const updated = setConfigValue(current, key, parsedValue);
|
|
652
685
|
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
686
|
+
const normalized = deps.parseConfigWithDiagnostics(updated as unknown);
|
|
687
|
+
const validationErrors = collectConfigValidationErrors(normalized.config, normalized.errors);
|
|
688
|
+
if (!normalized.config || validationErrors.length > 0) {
|
|
689
|
+
throw new Error(
|
|
690
|
+
formatConfigValidationMessage(
|
|
691
|
+
"Updated configuration is invalid.",
|
|
692
|
+
validationErrors.length > 0 ? validationErrors : ["Configuration format is invalid."]
|
|
693
|
+
)
|
|
694
|
+
);
|
|
661
695
|
}
|
|
662
696
|
|
|
663
|
-
await deps.saveConfig(normalized);
|
|
697
|
+
await deps.saveConfig(normalized.config);
|
|
664
698
|
deps.log.success(`Updated "${key}" to ${formatValue(parsedValue)}.`);
|
|
665
699
|
}
|
|
666
700
|
|
|
@@ -695,15 +729,20 @@ async function runEdit(args: string[], deps: ConfigCommandDeps): Promise<void> {
|
|
|
695
729
|
return;
|
|
696
730
|
}
|
|
697
731
|
|
|
698
|
-
const
|
|
699
|
-
|
|
732
|
+
const loaded = await deps.loadConfigWithDiagnostics();
|
|
733
|
+
const errors = collectConfigValidationErrors(loaded.config, loaded.errors);
|
|
734
|
+
if (!loaded.config || errors.length > 0) {
|
|
700
735
|
deps.log.warn(
|
|
701
|
-
|
|
736
|
+
formatConfigValidationMessage(
|
|
737
|
+
`Invalid configuration: ${deps.configPath}`,
|
|
738
|
+
errors.length > 0 ? errors : ["Configuration format is invalid."],
|
|
739
|
+
'Run "rr init" to regenerate the file, or fix it manually.'
|
|
740
|
+
)
|
|
702
741
|
);
|
|
703
742
|
return;
|
|
704
743
|
}
|
|
705
744
|
|
|
706
|
-
await deps.saveConfig(config);
|
|
745
|
+
await deps.saveConfig(loaded.config);
|
|
707
746
|
}
|
|
708
747
|
|
|
709
748
|
const DEFAULT_CONFIG_COMMAND_DEPS: ConfigCommandDeps = {
|
|
@@ -711,7 +750,9 @@ const DEFAULT_CONFIG_COMMAND_DEPS: ConfigCommandDeps = {
|
|
|
711
750
|
configExists,
|
|
712
751
|
ensureConfigDir,
|
|
713
752
|
loadConfig,
|
|
753
|
+
loadConfigWithDiagnostics,
|
|
714
754
|
parseConfig,
|
|
755
|
+
parseConfigWithDiagnostics,
|
|
715
756
|
saveConfig,
|
|
716
757
|
spawn: Bun.spawn as unknown as ConfigCommandSpawner,
|
|
717
758
|
env: process.env as Record<string, string | undefined>,
|
|
@@ -731,7 +772,7 @@ function resolveConfigCommandDeps(overrides?: Partial<ConfigCommandDeps>): Confi
|
|
|
731
772
|
return DEFAULT_CONFIG_COMMAND_DEPS;
|
|
732
773
|
}
|
|
733
774
|
|
|
734
|
-
|
|
775
|
+
const deps: ConfigCommandDeps = {
|
|
735
776
|
...DEFAULT_CONFIG_COMMAND_DEPS,
|
|
736
777
|
...overrides,
|
|
737
778
|
log: {
|
|
@@ -739,6 +780,30 @@ function resolveConfigCommandDeps(overrides?: Partial<ConfigCommandDeps>): Confi
|
|
|
739
780
|
...overrides.log,
|
|
740
781
|
},
|
|
741
782
|
};
|
|
783
|
+
|
|
784
|
+
const loadConfigOverride = overrides.loadConfig;
|
|
785
|
+
if (loadConfigOverride && !overrides.loadConfigWithDiagnostics) {
|
|
786
|
+
deps.loadConfigWithDiagnostics = async (path = deps.configPath) => {
|
|
787
|
+
const config = (await loadConfigOverride(path)) ?? null;
|
|
788
|
+
return {
|
|
789
|
+
exists: config !== null,
|
|
790
|
+
config,
|
|
791
|
+
errors: config ? [] : ["Configuration format is invalid."],
|
|
792
|
+
};
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (overrides.parseConfig && !overrides.parseConfigWithDiagnostics) {
|
|
797
|
+
deps.parseConfigWithDiagnostics = (value) => {
|
|
798
|
+
const config = overrides.parseConfig?.(value) ?? null;
|
|
799
|
+
return {
|
|
800
|
+
config,
|
|
801
|
+
errors: config ? [] : ["Configuration format is invalid."],
|
|
802
|
+
};
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return deps;
|
|
742
807
|
}
|
|
743
808
|
|
|
744
809
|
export function createRunConfig(overrides?: Partial<ConfigCommandDeps>) {
|
package/src/commands/init.ts
CHANGED
|
@@ -53,7 +53,7 @@ interface InitInput {
|
|
|
53
53
|
defaultReviewType: "uncommitted" | "base";
|
|
54
54
|
defaultReviewBranch?: string;
|
|
55
55
|
runSimplifierByDefault: boolean;
|
|
56
|
-
|
|
56
|
+
runInteractiveByDefault: boolean;
|
|
57
57
|
soundNotificationsEnabled: boolean;
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -319,7 +319,7 @@ export function buildConfig(input: InitInput): Config {
|
|
|
319
319
|
),
|
|
320
320
|
run: {
|
|
321
321
|
simplifier: input.runSimplifierByDefault,
|
|
322
|
-
|
|
322
|
+
interactive: input.runInteractiveByDefault,
|
|
323
323
|
},
|
|
324
324
|
maxIterations: input.maxIterations,
|
|
325
325
|
iterationTimeout: input.iterationTimeoutMinutes * 60 * 1000,
|
|
@@ -537,7 +537,7 @@ function formatConfigDisplay(config: Config): string {
|
|
|
537
537
|
` Iteration timeout: ${config.iterationTimeout / 1000 / 60} minutes`,
|
|
538
538
|
` Default review: ${defaultReviewDisplay}`,
|
|
539
539
|
` Run simplifier: ${config.run?.simplifier ? "enabled" : "disabled"}`,
|
|
540
|
-
`
|
|
540
|
+
` Interactive Mode: ${(config.run?.interactive ?? true) ? "enabled" : "disabled"}`,
|
|
541
541
|
` Sound notify: ${config.notifications.sound.enabled ? "enabled" : "disabled"}`,
|
|
542
542
|
].join("\n");
|
|
543
543
|
}
|
|
@@ -757,7 +757,7 @@ export async function buildAutoInitInput(
|
|
|
757
757
|
iterationTimeoutMinutes,
|
|
758
758
|
defaultReviewType: "uncommitted",
|
|
759
759
|
runSimplifierByDefault: false,
|
|
760
|
-
|
|
760
|
+
runInteractiveByDefault: true,
|
|
761
761
|
soundNotificationsEnabled: true,
|
|
762
762
|
},
|
|
763
763
|
skippedAgents,
|
|
@@ -919,14 +919,17 @@ async function promptForCustomInitInput(
|
|
|
919
919
|
defaultReviewType: defaultReviewType as "uncommitted" | "base",
|
|
920
920
|
defaultReviewBranch: defaultReviewBranch as string | undefined,
|
|
921
921
|
runSimplifierByDefault: runSimplifierByDefault as boolean,
|
|
922
|
-
|
|
922
|
+
runInteractiveByDefault: DEFAULT_CONFIG.run?.interactive ?? true,
|
|
923
923
|
soundNotificationsEnabled: DEFAULT_CONFIG.notifications?.sound.enabled ?? true,
|
|
924
924
|
} satisfies InitInput;
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
-
async function
|
|
927
|
+
async function promptForRunInteractive(
|
|
928
|
+
runtime: InitRuntime,
|
|
929
|
+
defaultValue: boolean
|
|
930
|
+
): Promise<boolean> {
|
|
928
931
|
const shouldEnable = await runtime.prompt.confirm({
|
|
929
|
-
message: "
|
|
932
|
+
message: "Launch Interactive Mode automatically after 'rr run'?",
|
|
930
933
|
initialValue: defaultValue,
|
|
931
934
|
});
|
|
932
935
|
handleCancel(runtime, shouldEnable);
|
|
@@ -1050,12 +1053,15 @@ export async function runInitWithRuntime(
|
|
|
1050
1053
|
setupMode === "auto"
|
|
1051
1054
|
? {
|
|
1052
1055
|
...resolvedInput,
|
|
1053
|
-
|
|
1056
|
+
runInteractiveByDefault: true,
|
|
1054
1057
|
soundNotificationsEnabled: true,
|
|
1055
1058
|
}
|
|
1056
1059
|
: {
|
|
1057
1060
|
...resolvedInput,
|
|
1058
|
-
|
|
1061
|
+
runInteractiveByDefault: await promptForRunInteractive(
|
|
1062
|
+
runtime,
|
|
1063
|
+
resolvedInput.runInteractiveByDefault
|
|
1064
|
+
),
|
|
1059
1065
|
soundNotificationsEnabled: await promptForSoundNotifications(
|
|
1060
1066
|
runtime,
|
|
1061
1067
|
resolvedInput.soundNotificationsEnabled
|
package/src/commands/run.ts
CHANGED
|
@@ -33,8 +33,8 @@ export interface RunOptions {
|
|
|
33
33
|
commit?: string;
|
|
34
34
|
custom?: string;
|
|
35
35
|
simplifier?: boolean;
|
|
36
|
-
|
|
37
|
-
"no-
|
|
36
|
+
interactive?: boolean;
|
|
37
|
+
"no-interactive"?: boolean;
|
|
38
38
|
sound?: boolean;
|
|
39
39
|
"no-sound"?: boolean;
|
|
40
40
|
}
|
|
@@ -82,20 +82,20 @@ export function resolveRunSimplifierEnabled(options: RunOptions, config: Config
|
|
|
82
82
|
return options.simplifier === true || config?.run?.simplifier === true;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
export function
|
|
86
|
-
if (options.
|
|
87
|
-
throw new Error("Cannot use --
|
|
85
|
+
export function resolveRunInteractiveEnabled(options: RunOptions, config: Config | null): boolean {
|
|
86
|
+
if (options.interactive && options["no-interactive"]) {
|
|
87
|
+
throw new Error("Cannot use --interactive and --no-interactive together");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
if (options.
|
|
90
|
+
if (options.interactive) {
|
|
91
91
|
return true;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
if (options["no-
|
|
94
|
+
if (options["no-interactive"]) {
|
|
95
95
|
return false;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
return config?.run?.
|
|
98
|
+
return config?.run?.interactive ?? true;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export function formatRunAgentsNote(config: Config, reviewOptions: ReviewOptions): string {
|
|
@@ -377,7 +377,7 @@ async function runInBackground(
|
|
|
377
377
|
runtime.prompt.log.success(`Review started in background session: ${sessionName}`);
|
|
378
378
|
const reviewOptions: ReviewOptions = { baseBranch, commitSha, customInstructions, simplifier };
|
|
379
379
|
runtime.prompt.note(formatRunAgentsNote(config, reviewOptions), "Agents");
|
|
380
|
-
runtime.prompt.note("rr
|
|
380
|
+
runtime.prompt.note("rr - Check status\n" + "rr stop - Stop the review", "Commands");
|
|
381
381
|
} catch (error) {
|
|
382
382
|
await runtime.lockfile.removeLockfile(undefined, projectPath, { expectedSessionId: sessionId });
|
|
383
383
|
runtime.prompt.log.error(`Failed to start background session: ${error}`);
|
|
@@ -385,9 +385,9 @@ async function runInBackground(
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
function
|
|
389
|
-
runtime.prompt.log.message("
|
|
390
|
-
runtime.prompt.log.message("
|
|
388
|
+
function logInteractiveReconnectHint(runtime: RunRuntime): void {
|
|
389
|
+
runtime.prompt.log.message("Interactive Mode closed.");
|
|
390
|
+
runtime.prompt.log.message("Launch Interactive Mode: rr");
|
|
391
391
|
runtime.prompt.log.message("Stop session: rr stop");
|
|
392
392
|
}
|
|
393
393
|
|
|
@@ -692,17 +692,17 @@ export async function startReview(
|
|
|
692
692
|
}
|
|
693
693
|
|
|
694
694
|
const runSimplifier = resolveRunSimplifierEnabled(options, config);
|
|
695
|
-
let
|
|
695
|
+
let runInteractive: boolean;
|
|
696
696
|
try {
|
|
697
|
-
|
|
697
|
+
runInteractive = resolveRunInteractiveEnabled(options, config);
|
|
698
698
|
} catch (error) {
|
|
699
699
|
runtime.prompt.log.error(`${error}`);
|
|
700
700
|
runtime.process.exit(1);
|
|
701
701
|
return;
|
|
702
702
|
}
|
|
703
|
-
if (
|
|
704
|
-
runtime.prompt.log.warn("
|
|
705
|
-
|
|
703
|
+
if (runInteractive && !runtime.process.stdoutIsTTY) {
|
|
704
|
+
runtime.prompt.log.warn("Interactive Mode is disabled because stdout is not a TTY.");
|
|
705
|
+
runInteractive = false;
|
|
706
706
|
}
|
|
707
707
|
|
|
708
708
|
// Check if inside tmux - warn about nesting
|
|
@@ -722,7 +722,7 @@ export async function startReview(
|
|
|
722
722
|
soundOverride
|
|
723
723
|
);
|
|
724
724
|
|
|
725
|
-
if (!
|
|
725
|
+
if (!runInteractive) {
|
|
726
726
|
return;
|
|
727
727
|
}
|
|
728
728
|
|
|
@@ -731,8 +731,8 @@ export async function startReview(
|
|
|
731
731
|
const branch = await runtime.getGitBranch(projectPath);
|
|
732
732
|
await runtime.openSessionPanel(projectPath, branch ?? undefined);
|
|
733
733
|
} catch (error) {
|
|
734
|
-
runtime.prompt.log.warn(`Could not
|
|
734
|
+
runtime.prompt.log.warn(`Could not launch Interactive Mode: ${error}`);
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
-
|
|
737
|
+
logInteractiveReconnectHint(runtime);
|
|
738
738
|
}
|
package/src/commands/stop.ts
CHANGED
|
@@ -118,7 +118,7 @@ async function stopCurrentSession(): Promise<void> {
|
|
|
118
118
|
if (allSessions.length > 0) {
|
|
119
119
|
p.log.message(`\nThere are ${allSessions.length} other session(s) running.`);
|
|
120
120
|
p.log.message(
|
|
121
|
-
'Use "rr stop --all" to stop all running review sessions, or "rr
|
|
121
|
+
'Use "rr stop --all" to stop all running review sessions, or "rr" to see details.'
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
124
|
return;
|