From 1e8909d512bda1c1a6bf30b4a20820d9213f5ca3 Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:25:50 +0900 Subject: [PATCH] resolved #52, resolved #59 --- README.md | 4 + docs/README.ja.md | 4 + src/__tests__/pipelineExecution.test.ts | 53 ++++++++++++++ src/cli.ts | 4 +- src/commands/pipelineExecution.ts | 97 ++++++++++++++----------- 5 files changed, 118 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index de6385a..c57304e 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,9 @@ takt --task "fix the auth bug" -w magi -b feat/fix-auth # Specify repository (for PR creation) takt --task "fix the auth bug" --auto-pr --repo owner/repo + +# Run workflow only — skip branch creation, commit, and push +takt --task "fix the auth bug" --skip-git ``` In pipeline mode, PRs are **not** created unless `--auto-pr` is explicitly specified. @@ -154,6 +157,7 @@ In pipeline mode, PRs are **not** created unless `--auto-pr` is explicitly speci | `-w, --workflow ` | Workflow to use | | `-b, --branch ` | Branch name (auto-generated if omitted) | | `--auto-pr` | Create PR after execution (interactive: skip confirmation, pipeline: enable PR) | +| `--skip-git` | Skip branch creation, commit, and push (pipeline mode, workflow-only) | | `--repo ` | Repository for PR creation | ## Workflows diff --git a/docs/README.ja.md b/docs/README.ja.md index 8d39f06..e9b0a18 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -125,6 +125,9 @@ takt --task "バグを修正" -w magi -b feat/fix-bug # リポジトリ指定(PR作成時) takt --task "バグを修正" --auto-pr --repo owner/repo + +# ワークフロー実行のみ(ブランチ作成・commit・pushをスキップ) +takt --task "バグを修正" --skip-git ``` パイプラインモードでは `--auto-pr` を指定しない限りPRは作成されません。 @@ -152,6 +155,7 @@ takt --task "バグを修正" --auto-pr --repo owner/repo | `-w, --workflow ` | ワークフロー指定 | | `-b, --branch ` | ブランチ名指定(省略時は自動生成) | | `--auto-pr` | PR作成(対話: 確認スキップ、パイプライン: PR有効化) | +| `--skip-git` | ブランチ作成・commit・pushをスキップ(パイプラインモード、ワークフロー実行のみ) | | `--repo ` | リポジトリ指定(PR作成時) | ## ワークフロー diff --git a/src/__tests__/pipelineExecution.test.ts b/src/__tests__/pipelineExecution.test.ts index eaf0f3b..94d7e4a 100644 --- a/src/__tests__/pipelineExecution.test.ts +++ b/src/__tests__/pipelineExecution.test.ts @@ -345,4 +345,57 @@ describe('executePipeline', () => { ); }); }); + + describe('--skip-git', () => { + it('should skip branch creation, commit, push when skipGit is true', async () => { + mockExecuteTask.mockResolvedValueOnce(true); + + const exitCode = await executePipeline({ + task: 'Fix the bug', + workflow: 'default', + autoPr: false, + skipGit: true, + cwd: '/tmp/test', + }); + + expect(exitCode).toBe(0); + expect(mockExecuteTask).toHaveBeenCalledWith('Fix the bug', '/tmp/test', 'default'); + + // No git operations should have been called + const gitCalls = mockExecFileSync.mock.calls.filter( + (call: unknown[]) => call[0] === 'git', + ); + expect(gitCalls).toHaveLength(0); + expect(mockPushBranch).not.toHaveBeenCalled(); + }); + + it('should ignore --auto-pr when skipGit is true', async () => { + mockExecuteTask.mockResolvedValueOnce(true); + + const exitCode = await executePipeline({ + task: 'Fix the bug', + workflow: 'default', + autoPr: true, + skipGit: true, + cwd: '/tmp/test', + }); + + expect(exitCode).toBe(0); + expect(mockCreatePullRequest).not.toHaveBeenCalled(); + }); + + it('should still return workflow failure exit code when skipGit is true', async () => { + mockExecuteTask.mockResolvedValueOnce(false); + + const exitCode = await executePipeline({ + task: 'Fix the bug', + workflow: 'default', + autoPr: false, + skipGit: true, + cwd: '/tmp/test', + }); + + expect(exitCode).toBe(3); + }); + }); }); diff --git a/src/cli.ts b/src/cli.ts index 86c2dee..3434be6 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -198,7 +198,8 @@ program .option('-b, --branch ', 'Branch name (auto-generated if omitted)') .option('--auto-pr', 'Create PR after successful execution') .option('--repo ', 'Repository (defaults to current)') - .option('-t, --task ', 'Task content (triggers pipeline/non-interactive mode)'); + .option('-t, --task ', 'Task content (triggers pipeline/non-interactive mode)') + .option('--skip-git', 'Skip branch creation, commit, and push (pipeline mode)'); // Common initialization for all commands program.hook('preAction', async () => { @@ -327,6 +328,7 @@ program branch: opts.branch as string | undefined, autoPr: opts.autoPr === true, repo: opts.repo as string | undefined, + skipGit: opts.skipGit === true, cwd: resolvedCwd, }); diff --git a/src/commands/pipelineExecution.ts b/src/commands/pipelineExecution.ts index bcff55c..964ae72 100644 --- a/src/commands/pipelineExecution.ts +++ b/src/commands/pipelineExecution.ts @@ -39,6 +39,8 @@ export interface PipelineExecutionOptions { autoPr: boolean; /** Repository in owner/repo format */ repo?: string; + /** Skip branch creation, commit, and push (workflow-only execution) */ + skipGit?: boolean; /** Working directory */ cwd: string; } @@ -135,7 +137,7 @@ function buildPipelinePrBody( * Returns a process exit code (0 on success, 2-5 on specific failures). */ export async function executePipeline(options: PipelineExecutionOptions): Promise { - const { cwd, workflow, autoPr } = options; + const { cwd, workflow, autoPr, skipGit } = options; const globalConfig = loadGlobalConfig(); const pipelineConfig = globalConfig.pipeline; let issue: GitHubIssue | undefined; @@ -164,20 +166,23 @@ export async function executePipeline(options: PipelineExecutionOptions): Promis return EXIT_ISSUE_FETCH_FAILED; } - // --- Step 2: Create branch --- - const branch = options.branch ?? generatePipelineBranchName(pipelineConfig, options.issueNumber); - info(`Creating branch: ${branch}`); - try { - createBranch(cwd, branch); - success(`Branch created: ${branch}`); - } catch (err) { - error(`Failed to create branch: ${err instanceof Error ? err.message : String(err)}`); - return EXIT_GIT_OPERATION_FAILED; + // --- Step 2: Create branch (skip if --skip-git) --- + let branch: string | undefined; + if (!skipGit) { + branch = options.branch ?? generatePipelineBranchName(pipelineConfig, options.issueNumber); + info(`Creating branch: ${branch}`); + try { + createBranch(cwd, branch); + success(`Branch created: ${branch}`); + } catch (err) { + error(`Failed to create branch: ${err instanceof Error ? err.message : String(err)}`); + return EXIT_GIT_OPERATION_FAILED; + } } // --- Step 3: Run workflow --- info(`Running workflow: ${workflow}`); - log.info('Pipeline workflow execution starting', { workflow, branch, issueNumber: options.issueNumber }); + log.info('Pipeline workflow execution starting', { workflow, branch, skipGit, issueNumber: options.issueNumber }); const taskSuccess = await executeTask(task, cwd, workflow); @@ -187,52 +192,58 @@ export async function executePipeline(options: PipelineExecutionOptions): Promis } success(`Workflow '${workflow}' completed`); - // --- Step 4: Commit & push --- - const commitMessage = buildCommitMessage(pipelineConfig, issue, options.task); + // --- Step 4: Commit & push (skip if --skip-git) --- + if (!skipGit && branch) { + const commitMessage = buildCommitMessage(pipelineConfig, issue, options.task); - info('Committing changes...'); - try { - const commitHash = commitChanges(cwd, commitMessage); - if (commitHash) { - success(`Changes committed: ${commitHash}`); - } else { - info('No changes to commit'); + info('Committing changes...'); + try { + const commitHash = commitChanges(cwd, commitMessage); + if (commitHash) { + success(`Changes committed: ${commitHash}`); + } else { + info('No changes to commit'); + } + + info(`Pushing to origin/${branch}...`); + pushBranch(cwd, branch); + success(`Pushed to origin/${branch}`); + } catch (err) { + error(`Git operation failed: ${err instanceof Error ? err.message : String(err)}`); + return EXIT_GIT_OPERATION_FAILED; } - - info(`Pushing to origin/${branch}...`); - pushBranch(cwd, branch); - success(`Pushed to origin/${branch}`); - } catch (err) { - error(`Git operation failed: ${err instanceof Error ? err.message : String(err)}`); - return EXIT_GIT_OPERATION_FAILED; } // --- Step 5: Create PR (if --auto-pr) --- if (autoPr) { - info('Creating pull request...'); - const prTitle = issue ? issue.title : (options.task ?? 'Pipeline task'); - const report = `Workflow \`${workflow}\` completed successfully.`; - const prBody = buildPipelinePrBody(pipelineConfig, issue, report); + if (skipGit) { + info('--auto-pr is ignored when --skip-git is specified (no push was performed)'); + } else if (branch) { + info('Creating pull request...'); + const prTitle = issue ? issue.title : (options.task ?? 'Pipeline task'); + const report = `Workflow \`${workflow}\` completed successfully.`; + const prBody = buildPipelinePrBody(pipelineConfig, issue, report); - const prResult = createPullRequest(cwd, { - branch, - title: prTitle, - body: prBody, - repo: options.repo, - }); + const prResult = createPullRequest(cwd, { + branch, + title: prTitle, + body: prBody, + repo: options.repo, + }); - if (prResult.success) { - success(`PR created: ${prResult.url}`); - } else { - error(`PR creation failed: ${prResult.error}`); - return EXIT_PR_CREATION_FAILED; + if (prResult.success) { + success(`PR created: ${prResult.url}`); + } else { + error(`PR creation failed: ${prResult.error}`); + return EXIT_PR_CREATION_FAILED; + } } } // --- Summary --- console.log(); status('Issue', issue ? `#${issue.number} "${issue.title}"` : 'N/A'); - status('Branch', branch); + status('Branch', branch ?? '(current)'); status('Workflow', workflow); status('Result', 'Success', 'green');