diff --git a/src/__tests__/reviewTasks.test.ts b/src/__tests__/reviewTasks.test.ts index 81745c0..573a315 100644 --- a/src/__tests__/reviewTasks.test.ts +++ b/src/__tests__/reviewTasks.test.ts @@ -170,9 +170,10 @@ describe('buildReviewItems', () => { }); describe('ReviewAction type', () => { - it('should include try, merge, delete (no skip)', () => { - const actions: ReviewAction[] = ['try', 'merge', 'delete']; - expect(actions).toHaveLength(3); + it('should include instruct, try, merge, delete (no skip)', () => { + const actions: ReviewAction[] = ['instruct', 'try', 'merge', 'delete']; + expect(actions).toHaveLength(4); + expect(actions).toContain('instruct'); expect(actions).toContain('try'); expect(actions).not.toContain('skip'); }); diff --git a/src/commands/reviewTasks.ts b/src/commands/reviewTasks.ts index 9a80625..c3c37b9 100644 --- a/src/commands/reviewTasks.ts +++ b/src/commands/reviewTasks.ts @@ -14,14 +14,19 @@ import { buildReviewItems, type WorktreeReviewItem, } from '../task/worktree.js'; -import { selectOption, confirm } from '../prompt/index.js'; +import { selectOption, confirm, promptInput } from '../prompt/index.js'; import { info, success, error as logError, warn } from '../utils/ui.js'; import { createLogger } from '../utils/debug.js'; +import { executeTask } from './taskExecution.js'; +import { autoCommitWorktree } from '../task/autoCommit.js'; +import { listWorkflows } from '../config/workflowLoader.js'; +import { getCurrentWorkflow } from '../config/paths.js'; +import { DEFAULT_WORKFLOW_NAME } from '../constants.js'; const log = createLogger('review-tasks'); /** Actions available for a reviewed worktree */ -export type ReviewAction = 'try' | 'merge' | 'delete'; +export type ReviewAction = 'try' | 'merge' | 'delete' | 'instruct'; /** * Check if a branch has already been merged into HEAD. @@ -66,6 +71,7 @@ async function showDiffAndPromptAction( const action = await selectOption( `Action for ${item.info.branch}:`, [ + { label: 'Instruct', value: 'instruct', description: 'Give additional instructions to modify this worktree' }, { label: 'Try merge', value: 'try', description: 'Merge without cleanup (keep worktree & branch)' }, { label: 'Merge & cleanup', value: 'merge', description: 'Merge (if needed) and remove worktree & branch' }, { label: 'Delete', value: 'delete', description: 'Discard changes, remove worktree and branch' }, @@ -176,6 +182,80 @@ export function deleteWorktreeBranch(projectDir: string, item: WorktreeReviewIte } } +/** + * Get the workflow to use for instruction. + * If multiple workflows available, prompt user to select. + */ +async function selectWorkflowForInstruction(projectDir: string): Promise { + const availableWorkflows = listWorkflows(); + const currentWorkflow = getCurrentWorkflow(projectDir); + + if (availableWorkflows.length === 0) { + return DEFAULT_WORKFLOW_NAME; + } + + if (availableWorkflows.length === 1 && availableWorkflows[0]) { + return availableWorkflows[0]; + } + + // Multiple workflows: let user select + const options = availableWorkflows.map((name) => ({ + label: name === currentWorkflow ? `${name} (current)` : name, + value: name, + })); + + return await selectOption('Select workflow:', options); +} + +/** + * Instruct worktree: give additional instructions to modify the worktree. + * Executes a task on the worktree and auto-commits if successful. + */ +export async function instructWorktree( + projectDir: string, + item: WorktreeReviewItem, +): Promise { + const { branch } = item.info; + const worktreePath = item.info.path; + + // 1. Prompt for instruction + const instruction = await promptInput('Enter instruction'); + if (!instruction) { + info('Cancelled'); + return false; + } + + // 2. Select workflow + const selectedWorkflow = await selectWorkflowForInstruction(projectDir); + if (!selectedWorkflow) { + info('Cancelled'); + return false; + } + + log.info('Instructing worktree', { branch, worktreePath, workflow: selectedWorkflow }); + info(`Running instruction on ${branch}...`); + + // 3. Execute task on worktree + const taskSuccess = await executeTask(instruction, worktreePath, selectedWorkflow, projectDir); + + // 4. Auto-commit if successful + if (taskSuccess) { + const commitResult = autoCommitWorktree(worktreePath, item.taskSlug); + if (commitResult.success && commitResult.commitHash) { + info(`Auto-committed: ${commitResult.commitHash}`); + } else if (!commitResult.success) { + warn(`Auto-commit skipped: ${commitResult.message}`); + } + success(`Instruction completed on ${branch}`); + log.info('Instruction completed', { branch }); + } else { + logError(`Instruction failed on ${branch}`); + log.error('Instruction failed', { branch }); + } + + return taskSuccess; +} + /** * Main entry point: review worktree tasks interactively. */ @@ -219,6 +299,9 @@ export async function reviewTasks(cwd: string): Promise { if (action === null) continue; switch (action) { + case 'instruct': + await instructWorktree(cwd, item); + break; case 'try': tryMergeWorktreeBranch(cwd, item); break;