From d9786c11fb5748a513438f9e7f2f789265ee9108 Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Sun, 1 Feb 2026 00:20:41 +0900 Subject: [PATCH] =?UTF-8?q?--worktree=E3=81=A7=E5=AF=BE=E8=A9=B1=E3=82=B9?= =?UTF-8?q?=E3=82=AD=E3=83=83=E3=83=97=20--create-worktree=E3=81=A7?= =?UTF-8?q?=E5=AF=BE=E8=A9=B1=E3=82=B9=E3=82=AD=E3=83=83=E3=83=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/cli-worktree.test.ts | 22 +++++++- src/cli.ts | 84 +++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 19 deletions(-) diff --git a/src/__tests__/cli-worktree.test.ts b/src/__tests__/cli-worktree.test.ts index 614b03c..d6ae9c2 100644 --- a/src/__tests__/cli-worktree.test.ts +++ b/src/__tests__/cli-worktree.test.ts @@ -200,5 +200,25 @@ describe('confirmAndCreateWorktree', () => { // Then expect(mockInfo).toHaveBeenCalledWith('Generating branch name...'); }); -}); + it('should skip prompt when override is false', async () => { + const result = await confirmAndCreateWorktree('/project', 'task', false); + + expect(result.execCwd).toBe('/project'); + expect(result.isWorktree).toBe(false); + expect(mockConfirm).not.toHaveBeenCalled(); + }); + + it('should skip prompt when override is true and still create clone', async () => { + mockSummarizeTaskName.mockResolvedValue('task'); + mockCreateSharedClone.mockReturnValue({ + path: '/project/../20260128T0504-task', + branch: 'takt/20260128T0504-task', + }); + + const result = await confirmAndCreateWorktree('/project', 'task', true); + + expect(mockConfirm).not.toHaveBeenCalled(); + expect(result.isWorktree).toBe(true); + }); +}); diff --git a/src/cli.ts b/src/cli.ts index ef5bdfb..885b05b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -107,20 +107,31 @@ async function selectWorkflow(cwd: string): Promise { * Execute a task with workflow selection, optional worktree, and auto-commit. * Shared by direct task execution and interactive mode. */ +export interface SelectAndExecuteOptions { + autoPr?: boolean; + repo?: string; + workflow?: string; + createWorktree?: boolean | undefined; +} + async function selectAndExecuteTask( cwd: string, task: string, - options?: { autoPr?: boolean; repo?: string }, + options?: SelectAndExecuteOptions, agentOverrides?: TaskExecutionOptions, ): Promise { - const selectedWorkflow = await selectWorkflow(cwd); + const selectedWorkflow = await determineWorkflow(cwd, options?.workflow); if (selectedWorkflow === null) { info('Cancelled'); return; } - const { execCwd, isWorktree, branch } = await confirmAndCreateWorktree(cwd, task); + const { execCwd, isWorktree, branch } = await confirmAndCreateWorktree( + cwd, + task, + options?.createWorktree, + ); log.info('Starting task execution', { workflow: selectedWorkflow, worktree: isWorktree }); const taskSuccess = await executeTask(task, execCwd, selectedWorkflow, cwd, agentOverrides); @@ -164,11 +175,28 @@ async function selectAndExecuteTask( * Returns the execution directory and whether a clone was created. * Task name is summarized to English by AI for use in branch/clone names. */ +async function determineWorkflow(cwd: string, override?: string): Promise { + if (override) { + const availableWorkflows = listWorkflows(); + const knownWorkflows = availableWorkflows.length === 0 ? [DEFAULT_WORKFLOW_NAME] : availableWorkflows; + if (!knownWorkflows.includes(override)) { + error(`Workflow not found: ${override}`); + return null; + } + return override; + } + return selectWorkflow(cwd); +} + export async function confirmAndCreateWorktree( cwd: string, task: string, + createWorktreeOverride?: boolean | undefined, ): Promise { - const useWorktree = await confirm('Create worktree?', true); + const useWorktree = + typeof createWorktreeOverride === 'boolean' + ? createWorktreeOverride + : await confirm('Create worktree?', true); if (!useWorktree) { return { execCwd: cwd, isWorktree: false }; @@ -201,6 +229,23 @@ function resolveAgentOverrides(): TaskExecutionOptions | undefined { return { provider, model }; } +function parseCreateWorktreeOption(value?: string): boolean | undefined { + if (!value) { + return undefined; + } + + const normalized = value.toLowerCase(); + if (normalized === 'yes' || normalized === 'true') { + return true; + } + if (normalized === 'no' || normalized === 'false') { + return false; + } + + error('Invalid value for --create-worktree. Use yes or no.'); + process.exit(1); +} + program .name('takt') .description('TAKT: Task Agent Koordination Tool') @@ -216,7 +261,8 @@ program .option('--provider ', 'Override agent provider (claude|codex|mock)') .option('--model ', 'Override agent model') .option('-t, --task ', 'Task content (triggers pipeline/non-interactive mode)') - .option('--skip-git', 'Skip branch creation, commit, and push (pipeline mode)'); + .option('--skip-git', 'Skip branch creation, commit, and push (pipeline mode)') + .option('--create-worktree ', 'Skip the worktree prompt by explicitly specifying yes or no'); // Common initialization for all commands program.hook('preAction', async () => { @@ -336,6 +382,13 @@ program .action(async (task?: string) => { const opts = program.opts(); const agentOverrides = resolveAgentOverrides(); + const createWorktreeOverride = parseCreateWorktreeOption(opts.createWorktree as string | undefined); + const selectOptions: SelectAndExecuteOptions = { + autoPr: opts.autoPr === true, + repo: opts.repo as string | undefined, + workflow: opts.workflow as string | undefined, + createWorktree: createWorktreeOverride, + }; // --- Pipeline mode (non-interactive): triggered by --task --- if (pipelineMode) { @@ -346,11 +399,11 @@ program branch: opts.branch as string | undefined, autoPr: opts.autoPr === true, repo: opts.repo as string | undefined, - skipGit: opts.skipGit === true, - cwd: resolvedCwd, - provider: agentOverrides?.provider, - model: agentOverrides?.model, - }); + skipGit: opts.skipGit === true, + cwd: resolvedCwd, + provider: agentOverrides?.provider, + model: agentOverrides?.model, + }); if (exitCode !== 0) { process.exit(exitCode); @@ -360,17 +413,12 @@ program // --- Normal (interactive) mode --- - const prOptions = { - autoPr: opts.autoPr === true, - repo: opts.repo as string | undefined, - }; - // Resolve --issue N to task text (same as #N) const issueFromOption = opts.issue as number | undefined; if (issueFromOption) { try { const resolvedTask = resolveIssueTask(`#${issueFromOption}`); - await selectAndExecuteTask(resolvedCwd, resolvedTask, prOptions, agentOverrides); + await selectAndExecuteTask(resolvedCwd, resolvedTask, selectOptions, agentOverrides); } catch (e) { error(e instanceof Error ? e.message : String(e)); process.exit(1); @@ -391,7 +439,7 @@ program } } - await selectAndExecuteTask(resolvedCwd, resolvedTask, prOptions, agentOverrides); + await selectAndExecuteTask(resolvedCwd, resolvedTask, selectOptions, agentOverrides); return; } @@ -402,7 +450,7 @@ program return; } - await selectAndExecuteTask(resolvedCwd, result.task, prOptions, agentOverrides); + await selectAndExecuteTask(resolvedCwd, result.task, selectOptions, agentOverrides); }); program.parse();