takt: Execute アクションで tasks.yaml への追加をスキップする skipTaskList オプション (#334)
- SelectAndExecuteOptions に skipTaskList フラグを追加 - routing.ts の Execute アクションで skipTaskList: true を設定 - taskRecord の null チェックで条件分岐を統一 - テストを現在の taskResultHandler API に合わせて修正
This commit is contained in:
parent
f307ed80f0
commit
e5902b87ad
165
src/__tests__/selectAndExecute-skipTaskList.test.ts
Normal file
165
src/__tests__/selectAndExecute-skipTaskList.test.ts
Normal file
@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Tests for skipTaskList option in selectAndExecuteTask
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
const {
|
||||
mockAddTask,
|
||||
mockExecuteTask,
|
||||
mockPersistTaskResult,
|
||||
mockPersistTaskError,
|
||||
mockBuildBooleanTaskResult,
|
||||
} = vi.hoisted(() => ({
|
||||
mockAddTask: vi.fn(() => ({
|
||||
name: 'test-task',
|
||||
content: 'test task',
|
||||
filePath: '/project/.takt/tasks.yaml',
|
||||
createdAt: '2026-02-14T00:00:00.000Z',
|
||||
status: 'pending',
|
||||
data: { task: 'test task' },
|
||||
})),
|
||||
mockExecuteTask: vi.fn(),
|
||||
mockPersistTaskResult: vi.fn(),
|
||||
mockPersistTaskError: vi.fn(),
|
||||
mockBuildBooleanTaskResult: vi.fn(() => ({ task: 'mock-result' })),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/prompt/index.js', () => ({
|
||||
confirm: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../infra/config/index.js', () => ({
|
||||
resolvePieceConfigValue: vi.fn(),
|
||||
loadPieceByIdentifier: vi.fn(() => ({ name: 'default' })),
|
||||
listPieces: vi.fn(() => ['default']),
|
||||
listPieceEntries: vi.fn(() => []),
|
||||
isPiecePath: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock('../infra/task/index.js', () => ({
|
||||
createSharedClone: vi.fn(),
|
||||
autoCommitAndPush: vi.fn(),
|
||||
summarizeTaskName: vi.fn(),
|
||||
resolveBaseBranch: vi.fn(() => ({ branch: 'main' })),
|
||||
TaskRunner: vi.fn(() => ({
|
||||
addTask: (...args: unknown[]) => mockAddTask(...args),
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/ui/index.js', () => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
withProgress: async <T>(
|
||||
_startMessage: string,
|
||||
_completionMessage: string | ((result: T) => string),
|
||||
operation: () => Promise<T>,
|
||||
): Promise<T> => operation(),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/utils/index.js', async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
createLogger: () => ({
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../infra/github/index.js', () => ({
|
||||
createPullRequest: vi.fn(),
|
||||
buildPrBody: vi.fn(),
|
||||
pushBranch: vi.fn(),
|
||||
findExistingPr: vi.fn(),
|
||||
commentOnPr: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../features/tasks/execute/taskExecution.js', () => ({
|
||||
executeTask: (...args: unknown[]) => mockExecuteTask(...args),
|
||||
}));
|
||||
|
||||
vi.mock('../features/tasks/execute/taskResultHandler.js', () => ({
|
||||
buildBooleanTaskResult: (...args: unknown[]) => mockBuildBooleanTaskResult(...args),
|
||||
persistTaskResult: (...args: unknown[]) => mockPersistTaskResult(...args),
|
||||
persistTaskError: (...args: unknown[]) => mockPersistTaskError(...args),
|
||||
}));
|
||||
|
||||
vi.mock('../features/pieceSelection/index.js', () => ({
|
||||
selectPiece: vi.fn(),
|
||||
}));
|
||||
|
||||
import { confirm } from '../shared/prompt/index.js';
|
||||
import { selectAndExecuteTask } from '../features/tasks/execute/selectAndExecute.js';
|
||||
|
||||
const mockConfirm = vi.mocked(confirm);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockExecuteTask.mockResolvedValue(true);
|
||||
// worktree を使わない(confirm で false)
|
||||
mockConfirm.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
describe('skipTaskList option in selectAndExecuteTask', () => {
|
||||
it('skipTaskList: true の場合はタスクリストに追加しない', async () => {
|
||||
await selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
skipTaskList: true,
|
||||
});
|
||||
|
||||
expect(mockAddTask).not.toHaveBeenCalled();
|
||||
expect(mockPersistTaskResult).not.toHaveBeenCalled();
|
||||
expect(mockExecuteTask).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skipTaskList: false の場合はタスクリストに追加する', async () => {
|
||||
await selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
skipTaskList: false,
|
||||
});
|
||||
|
||||
expect(mockAddTask).toHaveBeenCalled();
|
||||
expect(mockBuildBooleanTaskResult).toHaveBeenCalled();
|
||||
expect(mockPersistTaskResult).toHaveBeenCalled();
|
||||
expect(mockExecuteTask).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skipTaskList 未指定の場合はタスクリストに追加する', async () => {
|
||||
await selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
});
|
||||
|
||||
expect(mockAddTask).toHaveBeenCalled();
|
||||
expect(mockPersistTaskResult).toHaveBeenCalled();
|
||||
expect(mockExecuteTask).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skipTaskList: true でエラー時は persistTaskError を呼ばない', async () => {
|
||||
mockExecuteTask.mockRejectedValue(new Error('Task execution failed'));
|
||||
|
||||
await expect(
|
||||
selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
skipTaskList: true,
|
||||
}),
|
||||
).rejects.toThrow('Task execution failed');
|
||||
|
||||
expect(mockAddTask).not.toHaveBeenCalled();
|
||||
expect(mockPersistTaskError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skipTaskList: false でエラー時は persistTaskError を呼ぶ', async () => {
|
||||
mockExecuteTask.mockRejectedValue(new Error('Task execution failed'));
|
||||
|
||||
await expect(
|
||||
selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
skipTaskList: false,
|
||||
}),
|
||||
).rejects.toThrow('Task execution failed');
|
||||
|
||||
expect(mockAddTask).toHaveBeenCalled();
|
||||
expect(mockPersistTaskError).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -214,6 +214,7 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
selectOptions.interactiveUserInput = true;
|
||||
selectOptions.piece = pieceId;
|
||||
selectOptions.interactiveMetadata = { confirmed: true, task: confirmedTask };
|
||||
selectOptions.skipTaskList = true;
|
||||
await selectAndExecuteTask(resolvedCwd, confirmedTask, selectOptions, agentOverrides);
|
||||
},
|
||||
create_issue: async ({ task: confirmedTask }) => {
|
||||
|
||||
@ -108,7 +108,9 @@ export async function selectAndExecuteTask(
|
||||
|
||||
log.info('Starting task execution', { piece: pieceIdentifier, worktree: isWorktree, autoPr: shouldCreatePr, draftPr: shouldDraftPr });
|
||||
const taskRunner = new TaskRunner(cwd);
|
||||
const taskRecord = taskRunner.addTask(task, {
|
||||
let taskRecord: Awaited<ReturnType<TaskRunner['addTask']>> | null = null;
|
||||
if (options?.skipTaskList !== true) {
|
||||
taskRecord = taskRunner.addTask(task, {
|
||||
piece: pieceIdentifier,
|
||||
...(isWorktree ? { worktree: true } : {}),
|
||||
...(branch ? { branch } : {}),
|
||||
@ -117,6 +119,7 @@ export async function selectAndExecuteTask(
|
||||
draft_pr: shouldDraftPr,
|
||||
...(taskSlug ? { slug: taskSlug } : {}),
|
||||
});
|
||||
}
|
||||
const startedAt = new Date().toISOString();
|
||||
|
||||
let taskSuccess: boolean;
|
||||
@ -132,9 +135,11 @@ export async function selectAndExecuteTask(
|
||||
});
|
||||
} catch (err) {
|
||||
const completedAt = new Date().toISOString();
|
||||
if (taskRecord) {
|
||||
persistTaskError(taskRunner, taskRecord, startedAt, completedAt, err, {
|
||||
responsePrefix: 'Task failed: ',
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
@ -160,6 +165,7 @@ export async function selectAndExecuteTask(
|
||||
}
|
||||
|
||||
const effectiveSuccess = taskSuccess && !prFailed;
|
||||
if (taskRecord) {
|
||||
const taskResult = buildBooleanTaskResult({
|
||||
task: taskRecord,
|
||||
taskSuccess: effectiveSuccess,
|
||||
@ -171,6 +177,7 @@ export async function selectAndExecuteTask(
|
||||
...(isWorktree ? { worktreePath: execCwd } : {}),
|
||||
});
|
||||
persistTaskResult(taskRunner, taskResult);
|
||||
}
|
||||
|
||||
if (!effectiveSuccess) {
|
||||
process.exit(1);
|
||||
|
||||
@ -145,4 +145,6 @@ export interface SelectAndExecuteOptions {
|
||||
interactiveMetadata?: InteractiveMetadata;
|
||||
/** GitHub Issues to associate with the PR (adds "Closes #N" for each issue) */
|
||||
issues?: GitHubIssue[];
|
||||
/** Skip adding task to tasks.yaml */
|
||||
skipTaskList?: boolean;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user