Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .beads/.local_version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.49.0
1 change: 1 addition & 0 deletions src/commands/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ describe('conflict resolution helpers', () => {
});

describe('keyboard handler behavior', () => {
// Contract tests: mirror expected key handling semantics without instantiating the TUI.
test('escape key triggers abort callback', () => {
let abortCalled = false;
let panelHidden = false;
Expand Down
262 changes: 192 additions & 70 deletions src/commands/run.tsx

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ function mergeConfigs(global: StoredConfig, project: StoredConfig): StoredConfig
if (project.notifications !== undefined) {
merged.notifications = { ...merged.notifications, ...project.notifications };
}
if (project.review !== undefined) {
merged.review = { ...merged.review, ...project.review };
}

return merged;
}
Expand Down Expand Up @@ -432,6 +435,32 @@ export function getDefaultAgentConfig(
return undefined;
}

/**
* Resolve a specific agent configuration by name or plugin id.
* Unlike getDefaultAgentConfig, this does not apply shorthand agentOptions.
*/
export function getAgentConfigByName(
storedConfig: StoredConfig,
agentName: string
): AgentPluginConfig | undefined {
const registry = getAgentRegistry();

const found = storedConfig.agents?.find(
(a) => a.name === agentName || a.plugin === agentName
);
if (found) return found;

if (registry.hasPlugin(agentName)) {
return {
name: `review-${agentName}`,
plugin: agentName,
options: {},
};
}

return undefined;
}

/**
* Get default tracker configuration based on available plugins
*/
Expand Down Expand Up @@ -584,6 +613,18 @@ export async function buildConfig(
...(options.sandbox ?? {}),
};

const reviewEnabled =
options.review ?? storedConfig.review?.enabled ?? false;
const reviewAgentName = options.reviewAgent ?? storedConfig.review?.agent;
const reviewModel = options.reviewModel ?? storedConfig.review?.model;
const reviewAgentConfig = reviewEnabled
? (reviewAgentName
? (reviewAgentName === agentConfig.name || reviewAgentName === agentConfig.plugin
? agentConfig
: getAgentConfigByName(storedConfig, reviewAgentName))
: agentConfig)
: undefined;

return {
agent: agentConfig,
tracker: trackerConfig,
Expand All @@ -606,7 +647,14 @@ export async function buildConfig(
sandbox,
// CLI --prompt takes precedence over config file prompt_template
promptTemplate: options.promptPath ?? storedConfig.prompt_template,
// CLI --review-prompt (no config file option - uses project/global template dirs)
reviewPromptPath: options.reviewPromptPath,
autoCommit: storedConfig.autoCommit ?? false,
review: {
enabled: reviewEnabled,
agent: reviewAgentConfig,
model: reviewModel,
},
};
}

Expand Down Expand Up @@ -641,6 +689,15 @@ export async function validateConfig(
errors.push(`Tracker plugin '${config.tracker.plugin}' not found`);
}

// Validate review agent if enabled
if (config.review?.enabled) {
if (!config.review.agent) {
errors.push('Review enabled but reviewer agent not configured or found');
} else if (!agentRegistry.hasPlugin(config.review.agent.plugin)) {
errors.push(`Review agent plugin '${config.review.agent.plugin}' not found`);
}
}

// Validate tracker-specific requirements
if (
config.tracker.plugin === 'beads' ||
Expand Down
4 changes: 4 additions & 0 deletions src/config/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,10 @@ describe('StoredConfigSchema', () => {
expect(() => StoredConfigSchema.parse({ parallel: { maxWorkers: 33 } })).toThrow();
});

test('validates parallel.maxWorkers is integer', () => {
expect(() => StoredConfigSchema.parse({ parallel: { maxWorkers: 2.5 } })).toThrow();
});

test('validates parallel.mode values', () => {
expect(() => StoredConfigSchema.parse({ parallel: { mode: 'invalid' } })).toThrow();
});
Expand Down
13 changes: 13 additions & 0 deletions src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ export const ConflictResolutionConfigSchema = z.object({
maxFiles: z.number().int().min(1).max(100).optional(),
});

/**
* Review configuration schema
*/
export const ReviewConfigSchema = z.object({
enabled: z.boolean().optional(),
agent: z.string().optional(),
model: z.string().optional(),
prompt_template: z.string().optional(),
});

/**
* Agent plugin configuration schema
*/
Expand Down Expand Up @@ -218,6 +228,9 @@ export const StoredConfigSchema = z

// Conflict resolution configuration for parallel execution
conflictResolution: ConflictResolutionConfigSchema.optional(),

// Review configuration
review: ReviewConfigSchema.optional(),
})
.strict();

Expand Down
45 changes: 45 additions & 0 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,30 @@ export interface NotificationsConfig {
sound?: NotificationSoundMode;
}

/**
* Review configuration for optional reviewer agent stage.
*/
export interface ReviewConfig {
/** Whether review is enabled */
enabled?: boolean;
/** Agent name or plugin id to use for review */
agent?: string;
/** Model override for the reviewer agent */
model?: string;
}

/**
* Resolved review configuration used at runtime.
*/
export interface ReviewRuntimeConfig {
/** Whether review is enabled */
enabled: boolean;
/** Resolved reviewer agent config (if enabled) */
agent?: AgentPluginConfig;
/** Model override for the reviewer agent */
model?: string;
}

export type SandboxMode = 'auto' | 'bwrap' | 'sandbox-exec' | 'off';

export interface SandboxConfig {
Expand Down Expand Up @@ -184,6 +208,18 @@ export interface RuntimeOptions {

/** Enable parallel execution, optionally with worker count (--parallel [N]) */
parallel?: number | boolean;

/** Enable or disable review stage */
review?: boolean;

/** Override reviewer agent plugin/name */
reviewAgent?: string;

/** Custom review prompt template path */
reviewPromptPath?: string;

/** Override model for reviewer agent */
reviewModel?: string;
}

/**
Expand Down Expand Up @@ -295,6 +331,9 @@ export interface StoredConfig {

/** Conflict resolution configuration for parallel execution */
conflictResolution?: ConflictResolutionConfig;

/** Review configuration */
review?: ReviewConfig;
}

/**
Expand Down Expand Up @@ -342,6 +381,9 @@ export interface RalphConfig {
/** Custom prompt template path (resolved) */
promptTemplate?: string;

/** Custom review prompt template path (from CLI --review-prompt flag only) */
reviewPromptPath?: string;

/** Session ID for log file naming and tracking */
sessionId?: string;

Expand All @@ -357,6 +399,9 @@ export interface RalphConfig {

/** Conflict resolution configuration for parallel execution */
conflictResolution?: ConflictResolutionConfig;

/** Optional reviewer stage configuration */
review?: ReviewRuntimeConfig;
}

/**
Expand Down
Loading