/review に instruct アクションを追加

worktree レビュー時に追加指示を出せる機能を追加。
- 「Instruct」を選択 → 指示入力 → ワークフロー選択 → 実行 → 自動コミット
- selectWorkflowForInstruction() でワークフロー選択
- instructWorktree() でタスク実行と自動コミットを処理
This commit is contained in:
nrslib 2026-01-29 00:52:01 +09:00
parent 277d490eeb
commit 42ae981b65
2 changed files with 89 additions and 5 deletions

View File

@ -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');
});

View File

@ -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<ReviewAction>(
`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<string | null> {
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<boolean> {
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<void> {
if (action === null) continue;
switch (action) {
case 'instruct':
await instructWorktree(cwd, item);
break;
case 'try':
tryMergeWorktreeBranch(cwd, item);
break;