refactor
This commit is contained in:
parent
b648a8ea6b
commit
482fa51266
@ -71,9 +71,13 @@ vi.mock('../config/workflowLoader.js', () => ({
|
|||||||
listWorkflows: vi.fn(() => []),
|
listWorkflows: vi.fn(() => []),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../constants.js', () => ({
|
vi.mock('../constants.js', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('../constants.js')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
DEFAULT_WORKFLOW_NAME: 'default',
|
DEFAULT_WORKFLOW_NAME: 'default',
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('../github/issue.js', () => ({
|
vi.mock('../github/issue.js', () => ({
|
||||||
isIssueReference: vi.fn((s: string) => /^#\d+$/.test(s)),
|
isIssueReference: vi.fn((s: string) => /^#\d+$/.test(s)),
|
||||||
@ -88,7 +92,7 @@ import { confirm } from '../prompt/index.js';
|
|||||||
import { createSharedClone } from '../task/clone.js';
|
import { createSharedClone } from '../task/clone.js';
|
||||||
import { summarizeTaskName } from '../task/summarize.js';
|
import { summarizeTaskName } from '../task/summarize.js';
|
||||||
import { info } from '../utils/ui.js';
|
import { info } from '../utils/ui.js';
|
||||||
import { confirmAndCreateWorktree } from '../cli.js';
|
import { confirmAndCreateWorktree } from '../commands/selectAndExecute.js';
|
||||||
|
|
||||||
const mockConfirm = vi.mocked(confirm);
|
const mockConfirm = vi.mocked(confirm);
|
||||||
const mockCreateSharedClone = vi.mocked(createSharedClone);
|
const mockCreateSharedClone = vi.mocked(createSharedClone);
|
||||||
|
|||||||
@ -20,7 +20,7 @@ vi.mock('../utils/debug.js', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../cli.js', () => ({
|
vi.mock('../context.js', () => ({
|
||||||
isQuietMode: vi.fn(() => false),
|
isQuietMode: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -122,7 +122,7 @@ vi.mock('../config/projectConfig.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../cli.js', () => ({
|
vi.mock('../context.js', () => ({
|
||||||
isQuietMode: vi.fn().mockReturnValue(true),
|
isQuietMode: vi.fn().mockReturnValue(true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -104,7 +104,7 @@ vi.mock('../config/projectConfig.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../cli.js', () => ({
|
vi.mock('../context.js', () => ({
|
||||||
isQuietMode: vi.fn().mockReturnValue(true),
|
isQuietMode: vi.fn().mockReturnValue(true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,7 @@ vi.mock('./workflowExecution.js', () => ({
|
|||||||
executeWorkflow: vi.fn(),
|
executeWorkflow: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../cli.js', () => ({
|
vi.mock('../context.js', () => ({
|
||||||
isQuietMode: vi.fn(() => false),
|
isQuietMode: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
180
src/cli.ts
180
src/cli.ts
@ -27,10 +27,10 @@ import {
|
|||||||
getEffectiveDebugConfig,
|
getEffectiveDebugConfig,
|
||||||
} from './config/index.js';
|
} from './config/index.js';
|
||||||
import { clearAgentSessions, getCurrentWorkflow, isVerboseMode } from './config/paths.js';
|
import { clearAgentSessions, getCurrentWorkflow, isVerboseMode } from './config/paths.js';
|
||||||
|
import { setQuietMode } from './context.js';
|
||||||
import { info, error, success, setLogLevel } from './utils/ui.js';
|
import { info, error, success, setLogLevel } from './utils/ui.js';
|
||||||
import { initDebugLogger, createLogger, setVerboseConsole } from './utils/debug.js';
|
import { initDebugLogger, createLogger, setVerboseConsole } from './utils/debug.js';
|
||||||
import {
|
import {
|
||||||
executeTask,
|
|
||||||
runAllTasks,
|
runAllTasks,
|
||||||
switchWorkflow,
|
switchWorkflow,
|
||||||
switchConfig,
|
switchConfig,
|
||||||
@ -41,16 +41,14 @@ import {
|
|||||||
interactiveMode,
|
interactiveMode,
|
||||||
executePipeline,
|
executePipeline,
|
||||||
} from './commands/index.js';
|
} from './commands/index.js';
|
||||||
import { listWorkflows, isWorkflowPath } from './config/workflowLoader.js';
|
|
||||||
import { selectOptionWithDefault, confirm } from './prompt/index.js';
|
|
||||||
import { createSharedClone } from './task/clone.js';
|
|
||||||
import { autoCommitAndPush } from './task/autoCommit.js';
|
|
||||||
import { summarizeTaskName } from './task/summarize.js';
|
|
||||||
import { DEFAULT_WORKFLOW_NAME } from './constants.js';
|
import { DEFAULT_WORKFLOW_NAME } from './constants.js';
|
||||||
import { checkForUpdates } from './utils/updateNotifier.js';
|
import { checkForUpdates } from './utils/updateNotifier.js';
|
||||||
import { getErrorMessage } from './utils/error.js';
|
import { getErrorMessage } from './utils/error.js';
|
||||||
import { resolveIssueTask, isIssueReference } from './github/issue.js';
|
import { resolveIssueTask, isIssueReference } from './github/issue.js';
|
||||||
import { createPullRequest, buildPrBody } from './github/pr.js';
|
import {
|
||||||
|
selectAndExecuteTask,
|
||||||
|
type SelectAndExecuteOptions,
|
||||||
|
} from './commands/selectAndExecute.js';
|
||||||
import type { TaskExecutionOptions } from './commands/taskExecution.js';
|
import type { TaskExecutionOptions } from './commands/taskExecution.js';
|
||||||
import type { ProviderType } from './providers/index.js';
|
import type { ProviderType } from './providers/index.js';
|
||||||
|
|
||||||
@ -70,168 +68,6 @@ let pipelineMode = false;
|
|||||||
/** Whether quiet mode is active (--quiet flag or config, set in preAction) */
|
/** Whether quiet mode is active (--quiet flag or config, set in preAction) */
|
||||||
let quietMode = false;
|
let quietMode = false;
|
||||||
|
|
||||||
export interface WorktreeConfirmationResult {
|
|
||||||
execCwd: string;
|
|
||||||
isWorktree: boolean;
|
|
||||||
branch?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a workflow interactively.
|
|
||||||
* Returns the selected workflow name, or null if cancelled.
|
|
||||||
*/
|
|
||||||
async function selectWorkflow(cwd: string): Promise<string | null> {
|
|
||||||
const availableWorkflows = listWorkflows(cwd);
|
|
||||||
const currentWorkflow = getCurrentWorkflow(cwd);
|
|
||||||
|
|
||||||
if (availableWorkflows.length === 0) {
|
|
||||||
info(`No workflows found. Using default: ${DEFAULT_WORKFLOW_NAME}`);
|
|
||||||
return DEFAULT_WORKFLOW_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableWorkflows.length === 1 && availableWorkflows[0]) {
|
|
||||||
return availableWorkflows[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = availableWorkflows.map((name) => ({
|
|
||||||
label: name === currentWorkflow ? `${name} (current)` : name,
|
|
||||||
value: name,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const defaultWorkflow = availableWorkflows.includes(currentWorkflow)
|
|
||||||
? currentWorkflow
|
|
||||||
: (availableWorkflows.includes(DEFAULT_WORKFLOW_NAME)
|
|
||||||
? DEFAULT_WORKFLOW_NAME
|
|
||||||
: availableWorkflows[0] || DEFAULT_WORKFLOW_NAME);
|
|
||||||
|
|
||||||
return selectOptionWithDefault('Select workflow:', options, defaultWorkflow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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?: SelectAndExecuteOptions,
|
|
||||||
agentOverrides?: TaskExecutionOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
const workflowIdentifier = await determineWorkflow(cwd, options?.workflow);
|
|
||||||
|
|
||||||
if (workflowIdentifier === null) {
|
|
||||||
info('Cancelled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { execCwd, isWorktree, branch } = await confirmAndCreateWorktree(
|
|
||||||
cwd,
|
|
||||||
task,
|
|
||||||
options?.createWorktree,
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info('Starting task execution', { workflow: workflowIdentifier, worktree: isWorktree });
|
|
||||||
const taskSuccess = await executeTask({
|
|
||||||
task,
|
|
||||||
cwd: execCwd,
|
|
||||||
workflowIdentifier,
|
|
||||||
projectCwd: cwd,
|
|
||||||
agentOverrides,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (taskSuccess && isWorktree) {
|
|
||||||
const commitResult = autoCommitAndPush(execCwd, task, cwd);
|
|
||||||
if (commitResult.success && commitResult.commitHash) {
|
|
||||||
success(`Auto-committed & pushed: ${commitResult.commitHash}`);
|
|
||||||
} else if (!commitResult.success) {
|
|
||||||
error(`Auto-commit failed: ${commitResult.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// PR creation: --auto-pr → create automatically, otherwise ask
|
|
||||||
if (commitResult.success && commitResult.commitHash && branch) {
|
|
||||||
const shouldCreatePr = options?.autoPr === true || await confirm('Create pull request?', false);
|
|
||||||
if (shouldCreatePr) {
|
|
||||||
info('Creating pull request...');
|
|
||||||
const prBody = buildPrBody(undefined, `Workflow \`${workflowIdentifier}\` completed successfully.`);
|
|
||||||
const prResult = createPullRequest(execCwd, {
|
|
||||||
branch,
|
|
||||||
title: task.length > 100 ? `${task.slice(0, 97)}...` : task,
|
|
||||||
body: prBody,
|
|
||||||
repo: options?.repo,
|
|
||||||
});
|
|
||||||
if (prResult.success) {
|
|
||||||
success(`PR created: ${prResult.url}`);
|
|
||||||
} else {
|
|
||||||
error(`PR creation failed: ${prResult.error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!taskSuccess) {
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine workflow to use.
|
|
||||||
*
|
|
||||||
* - If override looks like a path (isWorkflowPath), return it directly (validation is done at load time).
|
|
||||||
* - If override is a name, validate it exists in available workflows.
|
|
||||||
* - If no override, prompt user to select interactively.
|
|
||||||
*/
|
|
||||||
async function determineWorkflow(cwd: string, override?: string): Promise<string | null> {
|
|
||||||
if (override) {
|
|
||||||
// Path-based: skip name validation (loader handles existence check)
|
|
||||||
if (isWorkflowPath(override)) {
|
|
||||||
return override;
|
|
||||||
}
|
|
||||||
// Name-based: validate workflow name exists
|
|
||||||
const availableWorkflows = listWorkflows(cwd);
|
|
||||||
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<WorktreeConfirmationResult> {
|
|
||||||
const useWorktree =
|
|
||||||
typeof createWorktreeOverride === 'boolean'
|
|
||||||
? createWorktreeOverride
|
|
||||||
: await confirm('Create worktree?', true);
|
|
||||||
|
|
||||||
if (!useWorktree) {
|
|
||||||
return { execCwd: cwd, isWorktree: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summarize task name to English slug using AI
|
|
||||||
info('Generating branch name...');
|
|
||||||
const taskSlug = await summarizeTaskName(task, { cwd });
|
|
||||||
|
|
||||||
const result = createSharedClone(cwd, {
|
|
||||||
worktree: true,
|
|
||||||
taskSlug,
|
|
||||||
});
|
|
||||||
info(`Clone created: ${result.path} (branch: ${result.branch})`);
|
|
||||||
|
|
||||||
return { execCwd: result.path, isWorktree: true, branch: result.branch };
|
|
||||||
}
|
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
function resolveAgentOverrides(): TaskExecutionOptions | undefined {
|
function resolveAgentOverrides(): TaskExecutionOptions | undefined {
|
||||||
@ -315,14 +151,12 @@ program.hook('preAction', async () => {
|
|||||||
|
|
||||||
// Quiet mode: CLI flag takes precedence over config
|
// Quiet mode: CLI flag takes precedence over config
|
||||||
quietMode = rootOpts.quiet === true || config.minimalOutput === true;
|
quietMode = rootOpts.quiet === true || config.minimalOutput === true;
|
||||||
|
setQuietMode(quietMode);
|
||||||
|
|
||||||
log.info('TAKT CLI starting', { version: cliVersion, cwd: resolvedCwd, verbose, pipelineMode, quietMode });
|
log.info('TAKT CLI starting', { version: cliVersion, cwd: resolvedCwd, verbose, pipelineMode, quietMode });
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Get whether quiet mode is active (CLI flag or config, resolved in preAction) */
|
// isQuietMode is now exported from context.ts to avoid circular dependencies
|
||||||
export function isQuietMode(): boolean {
|
|
||||||
return quietMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Subcommands ---
|
// --- Subcommands ---
|
||||||
|
|
||||||
|
|||||||
@ -13,3 +13,9 @@ export { switchConfig, getCurrentPermissionMode, setPermissionMode, type Permiss
|
|||||||
export { listTasks } from './listTasks.js';
|
export { listTasks } from './listTasks.js';
|
||||||
export { interactiveMode } from './interactive.js';
|
export { interactiveMode } from './interactive.js';
|
||||||
export { executePipeline, type PipelineExecutionOptions } from './pipelineExecution.js';
|
export { executePipeline, type PipelineExecutionOptions } from './pipelineExecution.js';
|
||||||
|
export {
|
||||||
|
selectAndExecuteTask,
|
||||||
|
confirmAndCreateWorktree,
|
||||||
|
type SelectAndExecuteOptions,
|
||||||
|
type WorktreeConfirmationResult,
|
||||||
|
} from './selectAndExecute.js';
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
import * as readline from 'node:readline';
|
import * as readline from 'node:readline';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { loadGlobalConfig } from '../config/globalConfig.js';
|
import { loadGlobalConfig } from '../config/globalConfig.js';
|
||||||
import { isQuietMode } from '../cli.js';
|
import { isQuietMode } from '../context.js';
|
||||||
import { loadAgentSessions, updateAgentSession } from '../config/paths.js';
|
import { loadAgentSessions, updateAgentSession } from '../config/paths.js';
|
||||||
import { getProvider, type ProviderType } from '../providers/index.js';
|
import { getProvider, type ProviderType } from '../providers/index.js';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
|
|||||||
184
src/commands/selectAndExecute.ts
Normal file
184
src/commands/selectAndExecute.ts
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* Task execution orchestration.
|
||||||
|
*
|
||||||
|
* Coordinates workflow selection, worktree creation, task execution,
|
||||||
|
* auto-commit, and PR creation. Extracted from cli.ts to avoid
|
||||||
|
* mixing CLI parsing with business logic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getCurrentWorkflow } from '../config/paths.js';
|
||||||
|
import { listWorkflows, isWorkflowPath } from '../config/workflowLoader.js';
|
||||||
|
import { selectOptionWithDefault, confirm } from '../prompt/index.js';
|
||||||
|
import { createSharedClone } from '../task/clone.js';
|
||||||
|
import { autoCommitAndPush } from '../task/autoCommit.js';
|
||||||
|
import { summarizeTaskName } from '../task/summarize.js';
|
||||||
|
import { DEFAULT_WORKFLOW_NAME } from '../constants.js';
|
||||||
|
import { info, error, success } from '../utils/ui.js';
|
||||||
|
import { createLogger } from '../utils/debug.js';
|
||||||
|
import { createPullRequest, buildPrBody } from '../github/pr.js';
|
||||||
|
import { executeTask } from './taskExecution.js';
|
||||||
|
import type { TaskExecutionOptions } from './taskExecution.js';
|
||||||
|
|
||||||
|
const log = createLogger('selectAndExecute');
|
||||||
|
|
||||||
|
export interface WorktreeConfirmationResult {
|
||||||
|
execCwd: string;
|
||||||
|
isWorktree: boolean;
|
||||||
|
branch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectAndExecuteOptions {
|
||||||
|
autoPr?: boolean;
|
||||||
|
repo?: string;
|
||||||
|
workflow?: string;
|
||||||
|
createWorktree?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a workflow interactively.
|
||||||
|
* Returns the selected workflow name, or null if cancelled.
|
||||||
|
*/
|
||||||
|
async function selectWorkflow(cwd: string): Promise<string | null> {
|
||||||
|
const availableWorkflows = listWorkflows(cwd);
|
||||||
|
const currentWorkflow = getCurrentWorkflow(cwd);
|
||||||
|
|
||||||
|
if (availableWorkflows.length === 0) {
|
||||||
|
info(`No workflows found. Using default: ${DEFAULT_WORKFLOW_NAME}`);
|
||||||
|
return DEFAULT_WORKFLOW_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableWorkflows.length === 1 && availableWorkflows[0]) {
|
||||||
|
return availableWorkflows[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = availableWorkflows.map((name) => ({
|
||||||
|
label: name === currentWorkflow ? `${name} (current)` : name,
|
||||||
|
value: name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const defaultWorkflow = availableWorkflows.includes(currentWorkflow)
|
||||||
|
? currentWorkflow
|
||||||
|
: (availableWorkflows.includes(DEFAULT_WORKFLOW_NAME)
|
||||||
|
? DEFAULT_WORKFLOW_NAME
|
||||||
|
: availableWorkflows[0] || DEFAULT_WORKFLOW_NAME);
|
||||||
|
|
||||||
|
return selectOptionWithDefault('Select workflow:', options, defaultWorkflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine workflow to use.
|
||||||
|
*
|
||||||
|
* - If override looks like a path (isWorkflowPath), return it directly (validation is done at load time).
|
||||||
|
* - If override is a name, validate it exists in available workflows.
|
||||||
|
* - If no override, prompt user to select interactively.
|
||||||
|
*/
|
||||||
|
async function determineWorkflow(cwd: string, override?: string): Promise<string | null> {
|
||||||
|
if (override) {
|
||||||
|
// Path-based: skip name validation (loader handles existence check)
|
||||||
|
if (isWorkflowPath(override)) {
|
||||||
|
return override;
|
||||||
|
}
|
||||||
|
// Name-based: validate workflow name exists
|
||||||
|
const availableWorkflows = listWorkflows(cwd);
|
||||||
|
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<WorktreeConfirmationResult> {
|
||||||
|
const useWorktree =
|
||||||
|
typeof createWorktreeOverride === 'boolean'
|
||||||
|
? createWorktreeOverride
|
||||||
|
: await confirm('Create worktree?', true);
|
||||||
|
|
||||||
|
if (!useWorktree) {
|
||||||
|
return { execCwd: cwd, isWorktree: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summarize task name to English slug using AI
|
||||||
|
info('Generating branch name...');
|
||||||
|
const taskSlug = await summarizeTaskName(task, { cwd });
|
||||||
|
|
||||||
|
const result = createSharedClone(cwd, {
|
||||||
|
worktree: true,
|
||||||
|
taskSlug,
|
||||||
|
});
|
||||||
|
info(`Clone created: ${result.path} (branch: ${result.branch})`);
|
||||||
|
|
||||||
|
return { execCwd: result.path, isWorktree: true, branch: result.branch };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a task with workflow selection, optional worktree, and auto-commit.
|
||||||
|
* Shared by direct task execution and interactive mode.
|
||||||
|
*/
|
||||||
|
export async function selectAndExecuteTask(
|
||||||
|
cwd: string,
|
||||||
|
task: string,
|
||||||
|
options?: SelectAndExecuteOptions,
|
||||||
|
agentOverrides?: TaskExecutionOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
const workflowIdentifier = await determineWorkflow(cwd, options?.workflow);
|
||||||
|
|
||||||
|
if (workflowIdentifier === null) {
|
||||||
|
info('Cancelled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { execCwd, isWorktree, branch } = await confirmAndCreateWorktree(
|
||||||
|
cwd,
|
||||||
|
task,
|
||||||
|
options?.createWorktree,
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info('Starting task execution', { workflow: workflowIdentifier, worktree: isWorktree });
|
||||||
|
const taskSuccess = await executeTask({
|
||||||
|
task,
|
||||||
|
cwd: execCwd,
|
||||||
|
workflowIdentifier,
|
||||||
|
projectCwd: cwd,
|
||||||
|
agentOverrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (taskSuccess && isWorktree) {
|
||||||
|
const commitResult = autoCommitAndPush(execCwd, task, cwd);
|
||||||
|
if (commitResult.success && commitResult.commitHash) {
|
||||||
|
success(`Auto-committed & pushed: ${commitResult.commitHash}`);
|
||||||
|
} else if (!commitResult.success) {
|
||||||
|
error(`Auto-commit failed: ${commitResult.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PR creation: --auto-pr → create automatically, otherwise ask
|
||||||
|
if (commitResult.success && commitResult.commitHash && branch) {
|
||||||
|
const shouldCreatePr = options?.autoPr === true || await confirm('Create pull request?', false);
|
||||||
|
if (shouldCreatePr) {
|
||||||
|
info('Creating pull request...');
|
||||||
|
const prBody = buildPrBody(undefined, `Workflow \`${workflowIdentifier}\` completed successfully.`);
|
||||||
|
const prResult = createPullRequest(execCwd, {
|
||||||
|
branch,
|
||||||
|
title: task.length > 100 ? `${task.slice(0, 97)}...` : task,
|
||||||
|
body: prBody,
|
||||||
|
repo: options?.repo,
|
||||||
|
});
|
||||||
|
if (prResult.success) {
|
||||||
|
success(`PR created: ${prResult.url}`);
|
||||||
|
} else {
|
||||||
|
error(`PR creation failed: ${prResult.error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!taskSuccess) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,7 +14,7 @@ import {
|
|||||||
updateWorktreeSession,
|
updateWorktreeSession,
|
||||||
} from '../config/paths.js';
|
} from '../config/paths.js';
|
||||||
import { loadGlobalConfig } from '../config/globalConfig.js';
|
import { loadGlobalConfig } from '../config/globalConfig.js';
|
||||||
import { isQuietMode } from '../cli.js';
|
import { isQuietMode } from '../context.js';
|
||||||
import {
|
import {
|
||||||
header,
|
header,
|
||||||
info,
|
info,
|
||||||
|
|||||||
19
src/context.ts
Normal file
19
src/context.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Runtime context shared across modules.
|
||||||
|
*
|
||||||
|
* Holds process-wide state (quiet mode, etc.) that would otherwise
|
||||||
|
* create circular dependencies if exported from cli.ts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Whether quiet mode is active (set during CLI initialization) */
|
||||||
|
let quietMode = false;
|
||||||
|
|
||||||
|
/** Get whether quiet mode is active (CLI flag or config, resolved in preAction) */
|
||||||
|
export function isQuietMode(): boolean {
|
||||||
|
return quietMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set quiet mode state. Called from CLI preAction hook. */
|
||||||
|
export function setQuietMode(value: boolean): void {
|
||||||
|
quietMode = value;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user