ralph-review 0.2.2 → 0.2.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/README.md +123 -16
- package/package.json +7 -5
- package/src/cli-core.ts +51 -88
- package/src/cli-rrr.ts +1 -4
- package/src/cli.ts +1 -2
- package/src/commands/apply.ts +35 -20
- package/src/commands/config-handlers.ts +68 -69
- package/src/commands/config-model.ts +147 -125
- package/src/commands/doctor.ts +2 -4
- package/src/commands/fix.ts +73 -51
- package/src/commands/handoff-selection.ts +6 -8
- package/src/commands/interactive-deps.ts +43 -0
- package/src/commands/list.ts +24 -7
- package/src/commands/log.ts +12 -12
- package/src/commands/run.ts +32 -33
- package/src/commands/status.ts +25 -4
- package/src/commands/stop.ts +99 -62
- package/src/commands/update.ts +2 -4
- package/src/lib/agents/claude.ts +4 -16
- package/src/lib/agents/core.ts +16 -0
- package/src/lib/agents/droid.ts +4 -15
- package/src/lib/agents/models.ts +9 -0
- package/src/lib/cli-parser.ts +19 -14
- package/src/lib/handoff.ts +16 -7
- package/src/lib/logging/session-log.ts +2 -1
- package/src/lib/prompts/defaults/review.md +1 -1
- package/src/lib/prompts/protocol.ts +2 -1
- package/src/lib/review-workflow/findings/artifact.ts +3 -1
- package/src/lib/review-workflow/findings/types.ts +1 -1
- package/src/lib/review-workflow/remediation/prompt.ts +7 -7
- package/src/lib/review-workflow/remediation/run-batch-fix-phase.ts +30 -20
- package/src/lib/review-workflow/remediation/run-fix-session.ts +70 -68
- package/src/lib/review-workflow/results/finalize-result.ts +20 -3
- package/src/lib/review-workflow/run-review-cycle.ts +1 -12
- package/src/lib/review-workflow/session-status.ts +13 -0
- package/src/lib/review-workflow/shared/framed-json.ts +2 -47
- package/src/lib/session/state.ts +50 -38
- package/src/lib/structured-output.ts +24 -9
- package/src/lib/tui/dashboard/HelpOverlay.tsx +13 -57
- package/src/lib/tui/dashboard/ReviewModeOverlay.tsx +2 -2
- package/src/lib/tui/dashboard/StatusBar.tsx +12 -50
- package/src/lib/tui/dashboard/StopSessionPickerOverlay.tsx +4 -22
- package/src/lib/tui/sessions/detail/DetailPane.tsx +6 -64
- package/src/lib/tui/sessions/detail/IdleStateView.tsx +10 -12
- package/src/lib/tui/sessions/detail/SessionDetailView.tsx +1 -1
- package/src/lib/tui/sessions/detail/session-detail-parts.tsx +66 -87
- package/src/lib/tui/sessions/history/SessionListOverlay.tsx +17 -75
- package/src/lib/tui/sessions/review-summary-parser.ts +2 -68
- package/src/lib/tui/shared/CenteredModal.tsx +44 -0
- package/src/lib/tui/shared/KeyboardShortcutsModal.tsx +14 -0
- package/src/lib/tui/shared/ShortcutHint.tsx +33 -0
- package/src/lib/tui/workspace/Workspace.tsx +6 -91
- package/src/lib/tui/workspace/use-workspace-state.ts +113 -61
- package/src/lib/types/fix.ts +15 -48
- package/src/lib/types/guards.ts +47 -0
- package/src/lib/types/review.ts +5 -39
|
@@ -39,6 +39,11 @@ interface ParsedShowArgs extends ParsedScopedArgs {
|
|
|
39
39
|
verbose: boolean;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
type LoadedEffectiveConfig = Awaited<
|
|
43
|
+
ReturnType<ConfigCommandDeps["loadEffectiveConfigWithDiagnostics"]>
|
|
44
|
+
>;
|
|
45
|
+
type ValidEffectiveConfig = LoadedEffectiveConfig & { config: Config };
|
|
46
|
+
|
|
42
47
|
const SHOW_USAGE = "Usage: rr config show [--local|--global] [--json] [--verbose]";
|
|
43
48
|
|
|
44
49
|
function shellQuote(value: string): string {
|
|
@@ -61,61 +66,72 @@ function printValue(value: unknown, print: (value: string) => void): void {
|
|
|
61
66
|
print(String(value));
|
|
62
67
|
}
|
|
63
68
|
|
|
69
|
+
async function warnIfEffectiveConfigInvalid(
|
|
70
|
+
deps: ConfigCommandDeps,
|
|
71
|
+
remediation: string
|
|
72
|
+
): Promise<ValidEffectiveConfig | null> {
|
|
73
|
+
const effective = await deps.loadEffectiveConfigWithDiagnostics(deps.cwd());
|
|
74
|
+
const errors = collectEffectiveConfigValidationErrors(effective);
|
|
75
|
+
if (!effective.config || errors.length > 0) {
|
|
76
|
+
deps.log.warn(
|
|
77
|
+
formatConfigValidationMessage(
|
|
78
|
+
getEffectiveConfigErrorHeader(effective),
|
|
79
|
+
errors.length > 0 ? errors : ["Configuration format is invalid."],
|
|
80
|
+
remediation
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { ...effective, config: effective.config };
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
function parseScopedArgs(args: string[], defaultScope: ResolvedReadScope): ParsedScopedArgs {
|
|
65
90
|
const positional: string[] = [];
|
|
66
|
-
|
|
67
|
-
let sawLocal = false;
|
|
68
|
-
let sawGlobal = false;
|
|
91
|
+
const state = { scope: defaultScope, sawLocal: false, sawGlobal: false };
|
|
69
92
|
|
|
70
93
|
for (const arg of args) {
|
|
71
|
-
if (arg
|
|
72
|
-
|
|
73
|
-
throw new Error("Cannot use --local and --global together.");
|
|
74
|
-
}
|
|
75
|
-
sawLocal = true;
|
|
76
|
-
scope = "local";
|
|
77
|
-
continue;
|
|
94
|
+
if (!parseScopeFlag(arg, state)) {
|
|
95
|
+
positional.push(arg);
|
|
78
96
|
}
|
|
97
|
+
}
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
return { scope: state.scope, positional };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseScopeFlag(
|
|
103
|
+
arg: string,
|
|
104
|
+
state: { scope: ResolvedReadScope; sawLocal: boolean; sawGlobal: boolean }
|
|
105
|
+
): boolean {
|
|
106
|
+
if (arg === "--local") {
|
|
107
|
+
if (state.sawGlobal) {
|
|
108
|
+
throw new Error("Cannot use --local and --global together.");
|
|
87
109
|
}
|
|
110
|
+
state.sawLocal = true;
|
|
111
|
+
state.scope = "local";
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
88
114
|
|
|
89
|
-
|
|
115
|
+
if (arg === "--global") {
|
|
116
|
+
if (state.sawLocal) {
|
|
117
|
+
throw new Error("Cannot use --local and --global together.");
|
|
118
|
+
}
|
|
119
|
+
state.sawGlobal = true;
|
|
120
|
+
state.scope = "global";
|
|
121
|
+
return true;
|
|
90
122
|
}
|
|
91
123
|
|
|
92
|
-
return
|
|
124
|
+
return false;
|
|
93
125
|
}
|
|
94
126
|
|
|
95
127
|
function parseShowArgs(args: string[]): ParsedShowArgs {
|
|
96
128
|
const positional: string[] = [];
|
|
97
|
-
|
|
98
|
-
let sawLocal = false;
|
|
99
|
-
let sawGlobal = false;
|
|
129
|
+
const scopeState = { scope: "effective" as ResolvedReadScope, sawLocal: false, sawGlobal: false };
|
|
100
130
|
let json = false;
|
|
101
131
|
let verbose = false;
|
|
102
132
|
|
|
103
133
|
for (const arg of args) {
|
|
104
|
-
if (arg
|
|
105
|
-
if (sawGlobal) {
|
|
106
|
-
throw new Error("Cannot use --local and --global together.");
|
|
107
|
-
}
|
|
108
|
-
sawLocal = true;
|
|
109
|
-
scope = "local";
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (arg === "--global") {
|
|
114
|
-
if (sawLocal) {
|
|
115
|
-
throw new Error("Cannot use --local and --global together.");
|
|
116
|
-
}
|
|
117
|
-
sawGlobal = true;
|
|
118
|
-
scope = "global";
|
|
134
|
+
if (parseScopeFlag(arg, scopeState)) {
|
|
119
135
|
continue;
|
|
120
136
|
}
|
|
121
137
|
|
|
@@ -132,7 +148,7 @@ function parseShowArgs(args: string[]): ParsedShowArgs {
|
|
|
132
148
|
positional.push(arg);
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
return { scope, positional, json, verbose };
|
|
151
|
+
return { scope: scopeState.scope, positional, json, verbose };
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
export async function runShow(args: string[], deps: ConfigCommandDeps): Promise<void> {
|
|
@@ -296,17 +312,10 @@ export async function runSet(args: string[], deps: ConfigCommandDeps): Promise<v
|
|
|
296
312
|
await deps.saveConfig(normalized.config);
|
|
297
313
|
deps.log.success(`Updated "${key}" to ${formatValue(parsedValue)}.`);
|
|
298
314
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
formatConfigValidationMessage(
|
|
304
|
-
getEffectiveConfigErrorHeader(effective),
|
|
305
|
-
effectiveErrors.length > 0 ? effectiveErrors : ["Configuration format is invalid."],
|
|
306
|
-
"Fix the repo-local override or restore compatible global values, then try again."
|
|
307
|
-
)
|
|
308
|
-
);
|
|
309
|
-
}
|
|
315
|
+
await warnIfEffectiveConfigInvalid(
|
|
316
|
+
deps,
|
|
317
|
+
"Fix the repo-local override or restore compatible global values, then try again."
|
|
318
|
+
);
|
|
310
319
|
}
|
|
311
320
|
|
|
312
321
|
export async function runEdit(args: string[], deps: ConfigCommandDeps): Promise<void> {
|
|
@@ -347,16 +356,11 @@ export async function runEdit(args: string[], deps: ConfigCommandDeps): Promise<
|
|
|
347
356
|
}
|
|
348
357
|
|
|
349
358
|
if (parsed.scope === "local") {
|
|
350
|
-
const effective = await
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
getEffectiveConfigErrorHeader(effective),
|
|
356
|
-
errors.length > 0 ? errors : ["Configuration format is invalid."],
|
|
357
|
-
'Run "rr init" and choose Repo-local config to regenerate the file, or fix it manually.'
|
|
358
|
-
)
|
|
359
|
-
);
|
|
359
|
+
const effective = await warnIfEffectiveConfigInvalid(
|
|
360
|
+
deps,
|
|
361
|
+
'Run "rr init" and choose Repo-local config to regenerate the file, or fix it manually.'
|
|
362
|
+
);
|
|
363
|
+
if (!effective) {
|
|
360
364
|
return;
|
|
361
365
|
}
|
|
362
366
|
|
|
@@ -378,16 +382,11 @@ export async function runEdit(args: string[], deps: ConfigCommandDeps): Promise<
|
|
|
378
382
|
return;
|
|
379
383
|
}
|
|
380
384
|
|
|
381
|
-
const effective = await
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
getEffectiveConfigErrorHeader(effective),
|
|
387
|
-
effectiveErrors.length > 0 ? effectiveErrors : ["Configuration format is invalid."],
|
|
388
|
-
"Fix the repo-local override or restore compatible global values, then try again."
|
|
389
|
-
)
|
|
390
|
-
);
|
|
385
|
+
const effective = await warnIfEffectiveConfigInvalid(
|
|
386
|
+
deps,
|
|
387
|
+
"Fix the repo-local override or restore compatible global values, then try again."
|
|
388
|
+
);
|
|
389
|
+
if (!effective) {
|
|
391
390
|
return;
|
|
392
391
|
}
|
|
393
392
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isAgentType,
|
|
9
9
|
isReasoningLevel,
|
|
10
10
|
type ReasoningLevel,
|
|
11
|
+
type RetryOverrideConfig,
|
|
11
12
|
} from "@/lib/types";
|
|
12
13
|
|
|
13
14
|
type ConfigRole = "reviewer" | "fixer";
|
|
@@ -116,6 +117,40 @@ function parseInteger(value: string, key: ConfigKey): number {
|
|
|
116
117
|
return parsed;
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
function parseBoundedIntegerUpdate(
|
|
121
|
+
key: ParsedScalarConfigUpdate["key"],
|
|
122
|
+
rawValue: string,
|
|
123
|
+
minimum: number
|
|
124
|
+
): ParsedScalarConfigUpdate {
|
|
125
|
+
const parsed = parseInteger(requireNonNullRawValue(key, rawValue), key);
|
|
126
|
+
if (parsed < minimum) {
|
|
127
|
+
throw new Error(`Value for "${key}" must be greater than or equal to ${minimum}.`);
|
|
128
|
+
}
|
|
129
|
+
return { key, value: parsed } as ParsedScalarConfigUpdate;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function requireNumberConfigValue(key: ConfigKey, value: ConfigValue, requirement: string): number {
|
|
133
|
+
if (typeof value === "number") {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw new Error(`Value for "${key}" must be an integer ${requirement}.`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function requirePiRoleSettings<T extends AgentSettings | AgentOverrideSettings>(
|
|
141
|
+
current: T | undefined,
|
|
142
|
+
role: ConfigRole,
|
|
143
|
+
command: string
|
|
144
|
+
): T {
|
|
145
|
+
if (!current || current.agent !== "pi") {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Cannot set "${role}.agent" to "pi" in a single-key update. Run "${command}" for pi setup.`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return current;
|
|
152
|
+
}
|
|
153
|
+
|
|
119
154
|
function requireNonNullRawValue(key: ConfigKey, rawValue: string): string {
|
|
120
155
|
if (rawValue === "null") {
|
|
121
156
|
throw new Error(`Value "null" is not allowed for "${key}".`);
|
|
@@ -205,36 +240,20 @@ export function parseConfigUpdate(key: ConfigKey, rawValue: string): ParsedConfi
|
|
|
205
240
|
return createRoleReasoningUpdate(key, rawValue);
|
|
206
241
|
|
|
207
242
|
case "maxIterations": {
|
|
208
|
-
|
|
209
|
-
if (parsed <= 0) {
|
|
210
|
-
throw new Error(`Value for "${key}" must be greater than 0.`);
|
|
211
|
-
}
|
|
212
|
-
return { key, value: parsed };
|
|
243
|
+
return parseBoundedIntegerUpdate(key, rawValue, 1);
|
|
213
244
|
}
|
|
214
245
|
|
|
215
246
|
case "iterationTimeout": {
|
|
216
|
-
|
|
217
|
-
if (parsed <= 0) {
|
|
218
|
-
throw new Error(`Value for "${key}" must be greater than 0.`);
|
|
219
|
-
}
|
|
220
|
-
return { key, value: parsed };
|
|
247
|
+
return parseBoundedIntegerUpdate(key, rawValue, 1);
|
|
221
248
|
}
|
|
222
249
|
|
|
223
250
|
case "retry.maxRetries": {
|
|
224
|
-
|
|
225
|
-
if (parsed < 0) {
|
|
226
|
-
throw new Error(`Value for "${key}" must be greater than or equal to 0.`);
|
|
227
|
-
}
|
|
228
|
-
return { key, value: parsed };
|
|
251
|
+
return parseBoundedIntegerUpdate(key, rawValue, 0);
|
|
229
252
|
}
|
|
230
253
|
|
|
231
254
|
case "retry.baseDelayMs":
|
|
232
255
|
case "retry.maxDelayMs": {
|
|
233
|
-
|
|
234
|
-
if (parsed <= 0) {
|
|
235
|
-
throw new Error(`Value for "${key}" must be greater than 0.`);
|
|
236
|
-
}
|
|
237
|
-
return { key, value: parsed };
|
|
256
|
+
return parseBoundedIntegerUpdate(key, rawValue, 1);
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
case "defaultReview.type":
|
|
@@ -329,18 +348,9 @@ function ensureRoleForMutation(
|
|
|
329
348
|
function applyRoleAgentUpdate(config: Config, role: ConfigRole, nextAgent: AgentType): Config {
|
|
330
349
|
const current = readRoleSettings(role, config);
|
|
331
350
|
if (nextAgent === "pi") {
|
|
332
|
-
|
|
333
|
-
throw new Error(
|
|
334
|
-
`Cannot set "${role}.agent" to "pi" in a single-key update. Run "rr init" for pi setup.`
|
|
335
|
-
);
|
|
336
|
-
}
|
|
351
|
+
const piSettings = requirePiRoleSettings(current, role, "rr init");
|
|
337
352
|
|
|
338
|
-
writeRoleSettings(role, config,
|
|
339
|
-
agent: "pi",
|
|
340
|
-
provider: current.provider,
|
|
341
|
-
model: current.model,
|
|
342
|
-
reasoning: current.reasoning,
|
|
343
|
-
});
|
|
353
|
+
writeRoleSettings(role, config, piSettings);
|
|
344
354
|
return config;
|
|
345
355
|
}
|
|
346
356
|
|
|
@@ -362,6 +372,86 @@ function applyRoleAgentUpdate(config: Config, role: ConfigRole, nextAgent: Agent
|
|
|
362
372
|
return config;
|
|
363
373
|
}
|
|
364
374
|
|
|
375
|
+
function setRequiredRetryValue(
|
|
376
|
+
config: Config,
|
|
377
|
+
field: keyof typeof DEFAULT_RETRY_CONFIG,
|
|
378
|
+
value: number
|
|
379
|
+
): void {
|
|
380
|
+
config.retry = config.retry ? { ...config.retry } : { ...DEFAULT_RETRY_CONFIG };
|
|
381
|
+
config.retry[field] = value;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function setOverrideRetryValue(
|
|
385
|
+
config: ConfigOverride,
|
|
386
|
+
field: keyof RetryOverrideConfig,
|
|
387
|
+
value: number
|
|
388
|
+
): void {
|
|
389
|
+
config.retry = {
|
|
390
|
+
...(config.retry && config.retry !== null ? config.retry : {}),
|
|
391
|
+
[field]: value,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function applyRoleProviderUpdate(
|
|
396
|
+
settings: AgentSettings | AgentOverrideSettings,
|
|
397
|
+
role: ConfigRole,
|
|
398
|
+
key: RoleConfigKey,
|
|
399
|
+
value: ConfigValue,
|
|
400
|
+
clearNonPiProvider: boolean
|
|
401
|
+
): void {
|
|
402
|
+
if (value === null) {
|
|
403
|
+
if (settings.agent !== "pi") {
|
|
404
|
+
if (clearNonPiProvider) {
|
|
405
|
+
delete settings.provider;
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
throw new Error(`Cannot unset "${role}.provider" while "${role}.agent" is "pi".`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (typeof value !== "string") {
|
|
413
|
+
throw new Error(`Value for "${key}" must be a string or null.`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (settings.agent !== "pi") {
|
|
417
|
+
throw new Error(`"${role}.provider" is only valid when "${role}.agent" is "pi".`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
settings.provider = value;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function applyRoleModelUpdate(
|
|
424
|
+
settings: AgentSettings | AgentOverrideSettings,
|
|
425
|
+
role: ConfigRole,
|
|
426
|
+
key: RoleConfigKey,
|
|
427
|
+
value: ConfigValue,
|
|
428
|
+
assignNullForNonPi: boolean
|
|
429
|
+
): void {
|
|
430
|
+
if (settings.agent === "pi") {
|
|
431
|
+
if (value === null || typeof value !== "string") {
|
|
432
|
+
throw new Error(`Cannot unset "${role}.model" while "${role}.agent" is "pi".`);
|
|
433
|
+
}
|
|
434
|
+
settings.model = value;
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (value === null) {
|
|
439
|
+
if (assignNullForNonPi) {
|
|
440
|
+
settings.model = value;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
delete settings.model;
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (typeof value === "string") {
|
|
448
|
+
settings.model = value;
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
throw new Error(`Value for "${key}" must be a string or null.`);
|
|
453
|
+
}
|
|
454
|
+
|
|
365
455
|
export function setConfigValue(config: Config, key: ConfigKey, value: ConfigValue): Config {
|
|
366
456
|
const next = structuredClone(config) as Config;
|
|
367
457
|
|
|
@@ -385,49 +475,12 @@ export function setConfigValue(config: Config, key: ConfigKey, value: ConfigValu
|
|
|
385
475
|
const settings = ensureRoleForMutation(next, role, field);
|
|
386
476
|
|
|
387
477
|
if (field === "provider") {
|
|
388
|
-
|
|
389
|
-
if (settings.agent !== "pi") {
|
|
390
|
-
return next;
|
|
391
|
-
}
|
|
392
|
-
throw new Error(`Cannot unset "${role}.provider" while "${role}.agent" is "pi".`);
|
|
393
|
-
}
|
|
394
|
-
if (typeof value !== "string") {
|
|
395
|
-
throw new Error(`Value for "${key}" must be a string or null.`);
|
|
396
|
-
}
|
|
397
|
-
if (settings.agent !== "pi") {
|
|
398
|
-
throw new Error(`"${role}.provider" is only valid when "${role}.agent" is "pi".`);
|
|
399
|
-
}
|
|
400
|
-
settings.provider = value;
|
|
478
|
+
applyRoleProviderUpdate(settings, role, key as RoleConfigKey, value, false);
|
|
401
479
|
return next;
|
|
402
480
|
}
|
|
403
481
|
|
|
404
482
|
if (field === "model") {
|
|
405
|
-
|
|
406
|
-
if (value === null || typeof value !== "string") {
|
|
407
|
-
throw new Error(`Cannot unset "${role}.model" while "${role}.agent" is "pi".`);
|
|
408
|
-
}
|
|
409
|
-
settings.model = value;
|
|
410
|
-
return next;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (value === null) {
|
|
414
|
-
delete settings.model;
|
|
415
|
-
} else if (typeof value === "string") {
|
|
416
|
-
settings.model = value;
|
|
417
|
-
} else {
|
|
418
|
-
throw new Error(`Value for "${key}" must be a string or null.`);
|
|
419
|
-
}
|
|
420
|
-
return next;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (settings.agent === "pi") {
|
|
424
|
-
if (value === null) {
|
|
425
|
-
delete settings.reasoning;
|
|
426
|
-
} else if (typeof value === "string" && isReasoningLevel(value)) {
|
|
427
|
-
settings.reasoning = value;
|
|
428
|
-
} else {
|
|
429
|
-
throw new Error(`Value for "${key}" must be one of: low, medium, high, xhigh, max.`);
|
|
430
|
-
}
|
|
483
|
+
applyRoleModelUpdate(settings, role, key as RoleConfigKey, value, false);
|
|
431
484
|
return next;
|
|
432
485
|
}
|
|
433
486
|
|
|
@@ -479,25 +532,25 @@ export function setConfigValue(config: Config, key: ConfigKey, value: ConfigValu
|
|
|
479
532
|
next.defaultReview = { type: "base", branch: value };
|
|
480
533
|
return next;
|
|
481
534
|
case "retry.maxRetries":
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
535
|
+
setRequiredRetryValue(
|
|
536
|
+
next,
|
|
537
|
+
"maxRetries",
|
|
538
|
+
requireNumberConfigValue(key, value, "greater than or equal to 0")
|
|
539
|
+
);
|
|
487
540
|
return next;
|
|
488
541
|
case "retry.baseDelayMs":
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
542
|
+
setRequiredRetryValue(
|
|
543
|
+
next,
|
|
544
|
+
"baseDelayMs",
|
|
545
|
+
requireNumberConfigValue(key, value, "greater than 0")
|
|
546
|
+
);
|
|
494
547
|
return next;
|
|
495
548
|
case "retry.maxDelayMs":
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
549
|
+
setRequiredRetryValue(
|
|
550
|
+
next,
|
|
551
|
+
"maxDelayMs",
|
|
552
|
+
requireNumberConfigValue(key, value, "greater than 0")
|
|
553
|
+
);
|
|
501
554
|
return next;
|
|
502
555
|
case "notifications.sound.enabled":
|
|
503
556
|
if (typeof value !== "boolean") {
|
|
@@ -552,17 +605,13 @@ function applyOverrideRoleAgentUpdate(
|
|
|
552
605
|
): ConfigOverride {
|
|
553
606
|
const current = readOverrideRoleSettings(role, config);
|
|
554
607
|
if (nextAgent === "pi") {
|
|
555
|
-
|
|
556
|
-
throw new Error(
|
|
557
|
-
`Cannot set "${role}.agent" to "pi" in a single-key update. Run "rr init --local" for pi setup.`
|
|
558
|
-
);
|
|
559
|
-
}
|
|
608
|
+
const piSettings = requirePiRoleSettings(current, role, "rr init --local");
|
|
560
609
|
|
|
561
610
|
writeOverrideRoleSettings(role, config, {
|
|
562
611
|
agent: "pi",
|
|
563
|
-
provider:
|
|
564
|
-
model:
|
|
565
|
-
reasoning:
|
|
612
|
+
provider: piSettings.provider,
|
|
613
|
+
model: piSettings.model,
|
|
614
|
+
reasoning: piSettings.reasoning,
|
|
566
615
|
});
|
|
567
616
|
return config;
|
|
568
617
|
}
|
|
@@ -600,30 +649,12 @@ export function setConfigOverrideValue(
|
|
|
600
649
|
const settings = ensureOverrideRoleForMutation(next, role);
|
|
601
650
|
|
|
602
651
|
if (field === "provider") {
|
|
603
|
-
|
|
604
|
-
if (settings.agent !== "pi") {
|
|
605
|
-
delete settings.provider;
|
|
606
|
-
return next;
|
|
607
|
-
}
|
|
608
|
-
throw new Error(`Cannot unset "${role}.provider" while "${role}.agent" is "pi".`);
|
|
609
|
-
}
|
|
610
|
-
if (settings.agent !== "pi") {
|
|
611
|
-
throw new Error(`"${role}.provider" is only valid when "${role}.agent" is "pi".`);
|
|
612
|
-
}
|
|
613
|
-
settings.provider = value;
|
|
652
|
+
applyRoleProviderUpdate(settings, role, update.key, value, true);
|
|
614
653
|
return next;
|
|
615
654
|
}
|
|
616
655
|
|
|
617
656
|
if (field === "model") {
|
|
618
|
-
|
|
619
|
-
if (value === null) {
|
|
620
|
-
throw new Error(`Cannot unset "${role}.model" while "${role}.agent" is "pi".`);
|
|
621
|
-
}
|
|
622
|
-
settings.model = value;
|
|
623
|
-
return next;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
settings.model = value;
|
|
657
|
+
applyRoleModelUpdate(settings, role, update.key, value, true);
|
|
627
658
|
return next;
|
|
628
659
|
}
|
|
629
660
|
|
|
@@ -657,22 +688,13 @@ export function setConfigOverrideValue(
|
|
|
657
688
|
next.defaultReview = { type: "base", branch: update.value };
|
|
658
689
|
return next;
|
|
659
690
|
case "retry.maxRetries":
|
|
660
|
-
next
|
|
661
|
-
...(next.retry && next.retry !== null ? next.retry : {}),
|
|
662
|
-
maxRetries: update.value,
|
|
663
|
-
};
|
|
691
|
+
setOverrideRetryValue(next, "maxRetries", update.value);
|
|
664
692
|
return next;
|
|
665
693
|
case "retry.baseDelayMs":
|
|
666
|
-
next
|
|
667
|
-
...(next.retry && next.retry !== null ? next.retry : {}),
|
|
668
|
-
baseDelayMs: update.value,
|
|
669
|
-
};
|
|
694
|
+
setOverrideRetryValue(next, "baseDelayMs", update.value);
|
|
670
695
|
return next;
|
|
671
696
|
case "retry.maxDelayMs":
|
|
672
|
-
next
|
|
673
|
-
...(next.retry && next.retry !== null ? next.retry : {}),
|
|
674
|
-
maxDelayMs: update.value,
|
|
675
|
-
};
|
|
697
|
+
setOverrideRetryValue(next, "maxDelayMs", update.value);
|
|
676
698
|
return next;
|
|
677
699
|
case "notifications.sound.enabled":
|
|
678
700
|
next.notifications = {
|
package/src/commands/doctor.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
|
+
import type { SpinnerFactory } from "@/cli-io";
|
|
2
3
|
import { runDiagnostics } from "@/lib/diagnostics";
|
|
3
4
|
import type { FixResult, RemediationDependencies } from "@/lib/diagnostics/remediation";
|
|
4
5
|
import { applyFixes as defaultApplyFixes, isFixable } from "@/lib/diagnostics/remediation";
|
|
@@ -17,10 +18,7 @@ interface DoctorRuntime {
|
|
|
17
18
|
) => Promise<FixResult[]>;
|
|
18
19
|
intro: (message: string) => void;
|
|
19
20
|
note: (message: string, title: string) => void;
|
|
20
|
-
spinner:
|
|
21
|
-
start: (message: string) => void;
|
|
22
|
-
stop: (message: string) => void;
|
|
23
|
-
};
|
|
21
|
+
spinner: SpinnerFactory;
|
|
24
22
|
log: {
|
|
25
23
|
error: (message: string) => void;
|
|
26
24
|
warn: (message: string) => void;
|