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
package/src/commands/fix.ts
CHANGED
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
} from "@/lib/priority-list";
|
|
13
13
|
import { loadFindingsArtifactBySessionId } from "@/lib/review-workflow/findings/artifact";
|
|
14
14
|
import type { FindingId, FindingsArtifact } from "@/lib/review-workflow/findings/types";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
promptForFixSelection,
|
|
17
|
+
runFixSession,
|
|
18
|
+
} from "@/lib/review-workflow/remediation/run-fix-session";
|
|
16
19
|
import {
|
|
17
20
|
createSessionState,
|
|
18
21
|
HEARTBEAT_INTERVAL_MS,
|
|
@@ -67,30 +70,19 @@ export interface FixCommandDeps {
|
|
|
67
70
|
exit: (code: number) => void;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
options: artifact.findings.map((finding) => ({
|
|
75
|
-
value: finding.id,
|
|
76
|
-
label: `${finding.id} [${finding.priority}] ${finding.title}`,
|
|
77
|
-
hint: `${finding.filePath}:${finding.startLine}-${finding.endLine}`,
|
|
78
|
-
})),
|
|
79
|
-
required: false,
|
|
80
|
-
})
|
|
81
|
-
.then((selection) => {
|
|
82
|
-
if (p.isCancel(selection)) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
73
|
+
interface LoadedFixArtifact {
|
|
74
|
+
parsed: ParsedFixCommandOptions;
|
|
75
|
+
artifact: FindingsArtifact;
|
|
76
|
+
}
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
interface PreparedFixCommand extends LoadedFixArtifact {
|
|
79
|
+
commandDeps: FixCommandDeps;
|
|
88
80
|
}
|
|
89
81
|
|
|
90
82
|
const DEFAULT_FIX_COMMAND_DEPS: FixCommandDeps = {
|
|
91
83
|
loadConfig: loadEffectiveConfig,
|
|
92
84
|
loadFindingsArtifactBySessionId,
|
|
93
|
-
promptForSelection:
|
|
85
|
+
promptForSelection: promptForFixSelection,
|
|
94
86
|
runFixSession,
|
|
95
87
|
isTTY: () => process.stdout.isTTY === true,
|
|
96
88
|
isTmuxInstalled,
|
|
@@ -332,31 +324,69 @@ export function parseFixCommandOptions(args: string[]): ParsedFixCommandOptions
|
|
|
332
324
|
};
|
|
333
325
|
}
|
|
334
326
|
|
|
335
|
-
|
|
336
|
-
args: string[]
|
|
337
|
-
|
|
338
|
-
): Promise<
|
|
339
|
-
const commandDeps = { ...DEFAULT_FIX_COMMAND_DEPS, ...deps };
|
|
340
|
-
|
|
327
|
+
async function loadFixCommandArtifact(
|
|
328
|
+
args: string[],
|
|
329
|
+
commandDeps: FixCommandDeps
|
|
330
|
+
): Promise<LoadedFixArtifact | null> {
|
|
341
331
|
let parsed: ParsedFixCommandOptions;
|
|
342
332
|
try {
|
|
343
333
|
parsed = parseFixCommandOptions(args);
|
|
344
334
|
} catch (error) {
|
|
345
335
|
commandDeps.logError(`${error}`);
|
|
346
336
|
commandDeps.exit(1);
|
|
347
|
-
return;
|
|
337
|
+
return null;
|
|
348
338
|
}
|
|
349
339
|
|
|
350
340
|
const artifact = await commandDeps.loadFindingsArtifactBySessionId(CONFIG_DIR, parsed.sessionId);
|
|
351
341
|
if (!artifact) {
|
|
352
342
|
commandDeps.logError(`Findings artifact not found for session ${parsed.sessionId}`);
|
|
353
343
|
commandDeps.exit(1);
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { parsed, artifact };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function prepareFixCommand(
|
|
351
|
+
args: string[],
|
|
352
|
+
deps: Partial<FixCommandDeps>
|
|
353
|
+
): Promise<PreparedFixCommand | null> {
|
|
354
|
+
const commandDeps = { ...DEFAULT_FIX_COMMAND_DEPS, ...deps };
|
|
355
|
+
const loaded = await loadFixCommandArtifact(args, commandDeps);
|
|
356
|
+
return loaded ? { commandDeps, ...loaded } : null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function withPreparedFixCommand(
|
|
360
|
+
args: string[],
|
|
361
|
+
deps: Partial<FixCommandDeps>,
|
|
362
|
+
run: (prepared: PreparedFixCommand) => Promise<void>
|
|
363
|
+
): Promise<void> {
|
|
364
|
+
const prepared = await prepareFixCommand(args, deps);
|
|
365
|
+
if (!prepared) {
|
|
354
366
|
return;
|
|
355
367
|
}
|
|
356
368
|
|
|
357
|
-
|
|
369
|
+
await run(prepared);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function loadRequiredFixConfig(
|
|
373
|
+
commandDeps: FixCommandDeps,
|
|
374
|
+
projectPath: string
|
|
375
|
+
): ReturnType<FixCommandDeps["loadConfig"]> {
|
|
376
|
+
const config = await commandDeps.loadConfig(projectPath);
|
|
377
|
+
if (!config) {
|
|
358
378
|
commandDeps.logError("Failed to load configuration");
|
|
359
379
|
commandDeps.exit(1);
|
|
380
|
+
}
|
|
381
|
+
return config;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function runPreparedFix({
|
|
385
|
+
commandDeps,
|
|
386
|
+
parsed,
|
|
387
|
+
artifact,
|
|
388
|
+
}: PreparedFixCommand): Promise<void> {
|
|
389
|
+
if (!(await loadRequiredFixConfig(commandDeps, artifact.projectPath))) {
|
|
360
390
|
return;
|
|
361
391
|
}
|
|
362
392
|
|
|
@@ -423,33 +453,18 @@ export async function runFix(
|
|
|
423
453
|
}
|
|
424
454
|
}
|
|
425
455
|
|
|
426
|
-
export
|
|
427
|
-
args
|
|
428
|
-
|
|
429
|
-
): Promise<void> {
|
|
430
|
-
const commandDeps = { ...DEFAULT_FIX_COMMAND_DEPS, ...deps };
|
|
431
|
-
|
|
432
|
-
let parsed: ParsedFixCommandOptions;
|
|
433
|
-
try {
|
|
434
|
-
parsed = parseFixCommandOptions(args);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
commandDeps.logError(`${error}`);
|
|
437
|
-
commandDeps.exit(1);
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const artifact = await commandDeps.loadFindingsArtifactBySessionId(CONFIG_DIR, parsed.sessionId);
|
|
442
|
-
if (!artifact) {
|
|
443
|
-
commandDeps.logError(`Findings artifact not found for session ${parsed.sessionId}`);
|
|
444
|
-
commandDeps.exit(1);
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
456
|
+
export function runFix(args: string[] = [], deps: Partial<FixCommandDeps> = {}): Promise<void> {
|
|
457
|
+
return withPreparedFixCommand(args, deps, runPreparedFix);
|
|
458
|
+
}
|
|
447
459
|
|
|
460
|
+
async function runPreparedFixForeground({
|
|
461
|
+
commandDeps,
|
|
462
|
+
parsed,
|
|
463
|
+
artifact,
|
|
464
|
+
}: PreparedFixCommand): Promise<void> {
|
|
448
465
|
const projectPath = commandDeps.env.RR_PROJECT_PATH || artifact.projectPath || commandDeps.cwd();
|
|
449
|
-
const config = await commandDeps
|
|
466
|
+
const config = await loadRequiredFixConfig(commandDeps, projectPath);
|
|
450
467
|
if (!config) {
|
|
451
|
-
commandDeps.logError("Failed to load configuration");
|
|
452
|
-
commandDeps.exit(1);
|
|
453
468
|
return;
|
|
454
469
|
}
|
|
455
470
|
|
|
@@ -593,3 +608,10 @@ export async function runFixForeground(
|
|
|
593
608
|
});
|
|
594
609
|
}
|
|
595
610
|
}
|
|
611
|
+
|
|
612
|
+
export function runFixForeground(
|
|
613
|
+
args: string[] = [],
|
|
614
|
+
deps: Partial<FixCommandDeps> = {}
|
|
615
|
+
): Promise<void> {
|
|
616
|
+
return withPreparedFixCommand(args, deps, runPreparedFixForeground);
|
|
617
|
+
}
|
|
@@ -2,6 +2,10 @@ import * as p from "@clack/prompts";
|
|
|
2
2
|
import type { PendingHandoffArtifact } from "@/lib/handoff";
|
|
3
3
|
|
|
4
4
|
type HandoffAction = "apply" | "discard";
|
|
5
|
+
type HandoffSelect = (input: {
|
|
6
|
+
message: string;
|
|
7
|
+
options: Array<{ value: string; label: string; hint: string }>;
|
|
8
|
+
}) => Promise<unknown>;
|
|
5
9
|
|
|
6
10
|
interface SelectableHandoff {
|
|
7
11
|
handoffId: string;
|
|
@@ -14,10 +18,7 @@ interface ResolvePendingHandoffSelectionOptions {
|
|
|
14
18
|
selector?: string;
|
|
15
19
|
action: HandoffAction;
|
|
16
20
|
isTTY: boolean;
|
|
17
|
-
select?:
|
|
18
|
-
message: string;
|
|
19
|
-
options: Array<{ value: string; label: string; hint: string }>;
|
|
20
|
-
}) => Promise<unknown>;
|
|
21
|
+
select?: HandoffSelect;
|
|
21
22
|
isCancel?: (value: unknown) => boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -105,10 +106,7 @@ async function resolveHandoffSelection<T extends SelectableHandoff>(options: {
|
|
|
105
106
|
action: HandoffAction;
|
|
106
107
|
isTTY: boolean;
|
|
107
108
|
multipleHandoffsMessage: string;
|
|
108
|
-
select?:
|
|
109
|
-
message: string;
|
|
110
|
-
options: Array<{ value: string; label: string; hint: string }>;
|
|
111
|
-
}) => Promise<unknown>;
|
|
109
|
+
select?: HandoffSelect;
|
|
112
110
|
isCancel?: (value: unknown) => boolean;
|
|
113
111
|
}): Promise<HandoffSelectionResult<T>> {
|
|
114
112
|
if (options.selector) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { getCommandDef } from "@/cli";
|
|
3
|
+
|
|
4
|
+
export interface InteractiveCommandDeps {
|
|
5
|
+
getCommandDef: typeof getCommandDef;
|
|
6
|
+
logError: (message: string) => void;
|
|
7
|
+
exit: (code: number) => void;
|
|
8
|
+
isTTY: () => boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PromptSelect = (input: {
|
|
12
|
+
message: string;
|
|
13
|
+
options: Array<{ value: string; label: string; hint: string }>;
|
|
14
|
+
}) => Promise<unknown>;
|
|
15
|
+
|
|
16
|
+
export interface PromptDeps {
|
|
17
|
+
logInfo: (message: string) => void;
|
|
18
|
+
logMessage: (message: string) => void;
|
|
19
|
+
logStep: (message: string) => void;
|
|
20
|
+
logSuccess: (message: string) => void;
|
|
21
|
+
select: PromptSelect;
|
|
22
|
+
isCancel: (value: unknown) => boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createInteractiveCommandDeps(): InteractiveCommandDeps {
|
|
26
|
+
return {
|
|
27
|
+
getCommandDef,
|
|
28
|
+
logError: (message) => p.log.error(message),
|
|
29
|
+
exit: (code) => process.exit(code),
|
|
30
|
+
isTTY: () => process.stdout.isTTY === true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createPromptDeps(): PromptDeps {
|
|
35
|
+
return {
|
|
36
|
+
logInfo: p.log.info,
|
|
37
|
+
logMessage: p.log.message,
|
|
38
|
+
logStep: p.log.step,
|
|
39
|
+
logSuccess: p.log.success,
|
|
40
|
+
select: p.select,
|
|
41
|
+
isCancel: p.isCancel,
|
|
42
|
+
};
|
|
43
|
+
}
|
package/src/commands/list.ts
CHANGED
|
@@ -2,6 +2,20 @@ import * as p from "@clack/prompts";
|
|
|
2
2
|
import { listAllActiveSessions } from "@/lib/session-state";
|
|
3
3
|
import { listRalphSessions } from "@/lib/tmux";
|
|
4
4
|
|
|
5
|
+
interface ListDeps {
|
|
6
|
+
listAllActiveSessions: typeof listAllActiveSessions;
|
|
7
|
+
listRalphSessions: typeof listRalphSessions;
|
|
8
|
+
logInfo: (message: string) => void;
|
|
9
|
+
print: (...args: unknown[]) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DEFAULT_LIST_DEPS: ListDeps = {
|
|
13
|
+
listAllActiveSessions,
|
|
14
|
+
listRalphSessions,
|
|
15
|
+
logInfo: p.log.info,
|
|
16
|
+
print: (...args) => console.log(...args),
|
|
17
|
+
};
|
|
18
|
+
|
|
5
19
|
function formatRelativeStart(startTime: number): string {
|
|
6
20
|
const elapsedSeconds = Math.max(0, Math.floor((Date.now() - startTime) / 1000));
|
|
7
21
|
|
|
@@ -18,10 +32,11 @@ function formatRelativeStart(startTime: number): string {
|
|
|
18
32
|
return `${elapsedHours}h ago`;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
export async function runList(): Promise<void> {
|
|
35
|
+
export async function runList(deps: Partial<ListDeps> = {}): Promise<void> {
|
|
36
|
+
const listDeps = { ...DEFAULT_LIST_DEPS, ...deps };
|
|
22
37
|
const [activeSessions, tmuxSessions] = await Promise.all([
|
|
23
|
-
listAllActiveSessions(),
|
|
24
|
-
listRalphSessions(),
|
|
38
|
+
listDeps.listAllActiveSessions(),
|
|
39
|
+
listDeps.listRalphSessions(),
|
|
25
40
|
]);
|
|
26
41
|
const trackedTmuxSessions = new Set(activeSessions.map((session) => session.sessionName));
|
|
27
42
|
const untrackedTmuxSessions = tmuxSessions.filter(
|
|
@@ -29,17 +44,19 @@ export async function runList(): Promise<void> {
|
|
|
29
44
|
);
|
|
30
45
|
|
|
31
46
|
if (activeSessions.length === 0 && untrackedTmuxSessions.length === 0) {
|
|
32
|
-
|
|
47
|
+
listDeps.logInfo("No active review sessions.");
|
|
33
48
|
} else {
|
|
34
|
-
|
|
49
|
+
listDeps.logInfo("Active review sessions:");
|
|
35
50
|
for (const session of activeSessions) {
|
|
36
51
|
const worktree = session.worktreeBranch ? ` ${session.worktreeBranch}` : "";
|
|
37
|
-
|
|
52
|
+
listDeps.print(
|
|
38
53
|
`${session.sessionId.slice(0, 8)} ${session.sessionName} ${session.projectPath}${worktree} ${formatRelativeStart(session.startTime)}`
|
|
39
54
|
);
|
|
40
55
|
}
|
|
41
56
|
for (const sessionName of untrackedTmuxSessions) {
|
|
42
|
-
|
|
57
|
+
listDeps.print(`${sessionName} (tmux only)`);
|
|
43
58
|
}
|
|
44
59
|
}
|
|
45
60
|
}
|
|
61
|
+
|
|
62
|
+
export type { ListDeps };
|
package/src/commands/log.ts
CHANGED
|
@@ -82,6 +82,16 @@ function isUnknownEmptySession(session: SessionStats): boolean {
|
|
|
82
82
|
return session.status === "unknown" && session.iterations === 0;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function printNoProjectSessions(projectName: string, json: boolean): void {
|
|
86
|
+
if (json) {
|
|
87
|
+
console.log(JSON.stringify({ project: projectName, sessions: [] }, null, 2));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
p.log.info("No review sessions found for current working directory.");
|
|
92
|
+
p.log.message('Start a review with "rr run" first.');
|
|
93
|
+
}
|
|
94
|
+
|
|
85
95
|
function formatDate(timestamp: number): string {
|
|
86
96
|
return new Date(timestamp).toLocaleString();
|
|
87
97
|
}
|
|
@@ -412,12 +422,7 @@ export async function runLog(args: string[]): Promise<void> {
|
|
|
412
422
|
const projectSessions = await listProjectLogSessions(CONFIG_DIR, currentProjectPath);
|
|
413
423
|
|
|
414
424
|
if (projectSessions.length === 0) {
|
|
415
|
-
|
|
416
|
-
console.log(JSON.stringify({ project: projectName, sessions: [] }, null, 2));
|
|
417
|
-
} else {
|
|
418
|
-
p.log.info("No review sessions found for current working directory.");
|
|
419
|
-
p.log.message('Start a review with "rr run" first.');
|
|
420
|
-
}
|
|
425
|
+
printNoProjectSessions(projectName, options.json);
|
|
421
426
|
return;
|
|
422
427
|
}
|
|
423
428
|
|
|
@@ -451,12 +456,7 @@ export async function runLog(args: string[]): Promise<void> {
|
|
|
451
456
|
}
|
|
452
457
|
|
|
453
458
|
if (sessionStats.length === 0) {
|
|
454
|
-
|
|
455
|
-
console.log(JSON.stringify({ project: projectName, sessions: [] }, null, 2));
|
|
456
|
-
} else {
|
|
457
|
-
p.log.info("No review sessions found for current working directory.");
|
|
458
|
-
p.log.message('Start a review with "rr run" first.');
|
|
459
|
-
}
|
|
459
|
+
printNoProjectSessions(projectName, options.json);
|
|
460
460
|
return;
|
|
461
461
|
}
|
|
462
462
|
|
package/src/commands/run.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { parseCommand } from "@/lib/cli-parser";
|
|
|
5
5
|
import { loadEffectiveConfig } from "@/lib/config";
|
|
6
6
|
import { collectIssueItems, runDiagnostics } from "@/lib/diagnostics";
|
|
7
7
|
import { getTmuxInstallHint } from "@/lib/diagnostics/tmux-install";
|
|
8
|
-
import type { DiagnosticsReport } from "@/lib/diagnostics/types";
|
|
8
|
+
import type { DiagnosticItem, DiagnosticsReport } from "@/lib/diagnostics/types";
|
|
9
9
|
import { type CycleResult, runReviewCycle } from "@/lib/engine";
|
|
10
10
|
import { formatReviewType } from "@/lib/format";
|
|
11
11
|
import { formatHandoffNote } from "@/lib/handoff-note";
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "@/lib/priority-list";
|
|
20
20
|
import { runFixSession } from "@/lib/review-workflow/remediation/run-fix-session";
|
|
21
21
|
import type { FixSessionResult } from "@/lib/review-workflow/remediation/types";
|
|
22
|
+
import { mapSessionStatusToFinalStatus } from "@/lib/review-workflow/session-status";
|
|
22
23
|
import {
|
|
23
24
|
createSessionId,
|
|
24
25
|
createSessionState,
|
|
@@ -108,20 +109,6 @@ export function formatRunAgentsNote(config: Config, reviewOptions: ReviewOptions
|
|
|
108
109
|
return lines.join("\n");
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
function mapSessionStatusToFinalStatus(
|
|
112
|
-
status: FixSessionResult["sessionStatus"]
|
|
113
|
-
): CycleResult["finalStatus"] {
|
|
114
|
-
if (status === "failed") {
|
|
115
|
-
return "failed";
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (status === "interrupted") {
|
|
119
|
-
return "interrupted";
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return "completed";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
112
|
function hasAutoFixPriorityMatches(result: CycleResult, priorities: Priority[]): boolean {
|
|
126
113
|
return (
|
|
127
114
|
result.artifact?.findings.some((finding) => priorities.includes(finding.priority)) ?? false
|
|
@@ -257,6 +244,32 @@ export interface RunRuntimeOverrides
|
|
|
257
244
|
timer?: Partial<RunRuntime["timer"]>;
|
|
258
245
|
}
|
|
259
246
|
|
|
247
|
+
function createRunCommandContext(overrides: RunRuntimeOverrides): {
|
|
248
|
+
runtime: RunRuntime;
|
|
249
|
+
projectPath: string;
|
|
250
|
+
} {
|
|
251
|
+
const runtime = createRunRuntime(overrides);
|
|
252
|
+
return {
|
|
253
|
+
runtime,
|
|
254
|
+
projectPath: runtime.process.env.RR_PROJECT_PATH || runtime.process.cwd(),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function logPreflightItems(
|
|
259
|
+
runtime: RunRuntime,
|
|
260
|
+
items: DiagnosticItem[],
|
|
261
|
+
heading: string,
|
|
262
|
+
severity: "error" | "warn"
|
|
263
|
+
): void {
|
|
264
|
+
runtime.prompt.log[severity](heading);
|
|
265
|
+
for (const item of items) {
|
|
266
|
+
runtime.prompt.log.message(` ${item.summary}`);
|
|
267
|
+
item.remediation.forEach((remediation) => {
|
|
268
|
+
runtime.prompt.log.message(` -> ${remediation}`);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
260
273
|
export function createRunRuntime(overrides: RunRuntimeOverrides = {}): RunRuntime {
|
|
261
274
|
const defaults: RunRuntime = {
|
|
262
275
|
prompt: {
|
|
@@ -449,8 +462,7 @@ export async function runForeground(
|
|
|
449
462
|
args: string[] = [],
|
|
450
463
|
overrides: RunRuntimeOverrides = {}
|
|
451
464
|
): Promise<void> {
|
|
452
|
-
const runtime =
|
|
453
|
-
const projectPath = runtime.process.env.RR_PROJECT_PATH || runtime.process.cwd();
|
|
465
|
+
const { runtime, projectPath } = createRunCommandContext(overrides);
|
|
454
466
|
const config = await runtime.loadConfig(projectPath);
|
|
455
467
|
if (!config) {
|
|
456
468
|
runtime.prompt.log.error("Failed to load config");
|
|
@@ -714,8 +726,7 @@ export async function startReview(
|
|
|
714
726
|
args: string[],
|
|
715
727
|
overrides: RunRuntimeOverrides = {}
|
|
716
728
|
): Promise<void> {
|
|
717
|
-
const runtime =
|
|
718
|
-
const projectPath = runtime.process.env.RR_PROJECT_PATH || runtime.process.cwd();
|
|
729
|
+
const { runtime, projectPath } = createRunCommandContext(overrides);
|
|
719
730
|
// Parse options using command definition
|
|
720
731
|
const runDef = runtime.getCommandDef("run");
|
|
721
732
|
if (!runDef) {
|
|
@@ -856,25 +867,13 @@ export async function startReview(
|
|
|
856
867
|
const warnings = issues.filter((item) => item.severity === "warning");
|
|
857
868
|
|
|
858
869
|
if (errors.length > 0) {
|
|
859
|
-
runtime
|
|
860
|
-
for (const item of errors) {
|
|
861
|
-
runtime.prompt.log.message(` ${item.summary}`);
|
|
862
|
-
item.remediation.forEach((remediation) => {
|
|
863
|
-
runtime.prompt.log.message(` -> ${remediation}`);
|
|
864
|
-
});
|
|
865
|
-
}
|
|
870
|
+
logPreflightItems(runtime, errors, "Cannot run review:", "error");
|
|
866
871
|
runtime.process.exit(1);
|
|
867
872
|
return;
|
|
868
873
|
}
|
|
869
874
|
|
|
870
875
|
if (warnings.length > 0) {
|
|
871
|
-
runtime
|
|
872
|
-
for (const item of warnings) {
|
|
873
|
-
runtime.prompt.log.message(` ${item.summary}`);
|
|
874
|
-
item.remediation.forEach((remediation) => {
|
|
875
|
-
runtime.prompt.log.message(` -> ${remediation}`);
|
|
876
|
-
});
|
|
877
|
-
}
|
|
876
|
+
logPreflightItems(runtime, warnings, "Preflight warnings:", "warn");
|
|
878
877
|
}
|
|
879
878
|
|
|
880
879
|
const config = diagnostics.config ?? (await runtime.loadConfig(projectPath));
|
package/src/commands/status.ts
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
import { getGitBranch } from "@/lib/logger";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
interface StatusDeps {
|
|
4
|
+
cwd: () => string;
|
|
5
|
+
getGitBranch: typeof getGitBranch;
|
|
6
|
+
renderDashboard: (payload: { projectPath: string; branch: string | undefined }) => Promise<void>;
|
|
7
|
+
}
|
|
6
8
|
|
|
9
|
+
async function renderDashboardWithDynamicImport(payload: {
|
|
10
|
+
projectPath: string;
|
|
11
|
+
branch: string | undefined;
|
|
12
|
+
}): Promise<void> {
|
|
7
13
|
const { renderDashboard } = await import("@/lib/tui/index");
|
|
14
|
+
await renderDashboard(payload);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_STATUS_DEPS: StatusDeps = {
|
|
18
|
+
cwd: () => process.cwd(),
|
|
19
|
+
getGitBranch,
|
|
20
|
+
renderDashboard: renderDashboardWithDynamicImport,
|
|
21
|
+
};
|
|
8
22
|
|
|
9
|
-
|
|
23
|
+
export async function runStatus(deps: Partial<StatusDeps> = {}): Promise<void> {
|
|
24
|
+
const statusDeps = { ...DEFAULT_STATUS_DEPS, ...deps };
|
|
25
|
+
const projectPath = statusDeps.cwd();
|
|
26
|
+
const branch = await statusDeps.getGitBranch(projectPath);
|
|
27
|
+
|
|
28
|
+
await statusDeps.renderDashboard({
|
|
10
29
|
projectPath,
|
|
11
30
|
branch,
|
|
12
31
|
});
|
|
13
32
|
}
|
|
33
|
+
|
|
34
|
+
export type { StatusDeps };
|