issue参照時にもピース選択を実施 #97

This commit is contained in:
nrslib 2026-02-06 18:48:09 +09:00
parent 919215fad3
commit 973c7df85d
2 changed files with 55 additions and 9 deletions

View File

@ -275,6 +275,7 @@ describe('addTask', () => {
// Given: issue reference "#99"
const issueText = 'Issue #99: Fix login timeout\n\nThe login page times out after 30 seconds.';
mockResolveIssueTask.mockReturnValue(issueText);
mockDeterminePiece.mockResolvedValue('default');
mockSummarizeTaskName.mockResolvedValue('fix-login-timeout');
mockConfirm.mockResolvedValue(false);
@ -288,6 +289,9 @@ describe('addTask', () => {
// Then: resolveIssueTask was called
expect(mockResolveIssueTask).toHaveBeenCalledWith('#99');
// Then: determinePiece was called for piece selection
expect(mockDeterminePiece).toHaveBeenCalledWith(testDir);
// Then: task file created with issue text directly (no AI summarization)
const taskFile = path.join(testDir, '.takt', 'tasks', 'fix-login-timeout.yaml');
expect(fs.existsSync(taskFile)).toBe(true);
@ -298,6 +302,7 @@ describe('addTask', () => {
it('should proceed to worktree settings after issue fetch', async () => {
// Given: issue with worktree enabled
mockResolveIssueTask.mockReturnValue('Issue text');
mockDeterminePiece.mockResolvedValue('default');
mockSummarizeTaskName.mockResolvedValue('issue-task');
mockConfirm.mockResolvedValue(true);
mockPromptInput
@ -331,6 +336,7 @@ describe('addTask', () => {
// Given: issue reference "#99"
const issueText = 'Issue #99: Fix login timeout';
mockResolveIssueTask.mockReturnValue(issueText);
mockDeterminePiece.mockResolvedValue('default');
mockSummarizeTaskName.mockResolvedValue('fix-login-timeout');
mockConfirm.mockResolvedValue(false);
@ -344,6 +350,42 @@ describe('addTask', () => {
expect(content).toContain('issue: 99');
});
it('should include piece selection in task file when issue reference is used', async () => {
// Given: issue reference "#99" with non-default piece selection
const issueText = 'Issue #99: Fix login timeout';
mockResolveIssueTask.mockReturnValue(issueText);
mockDeterminePiece.mockResolvedValue('review');
mockSummarizeTaskName.mockResolvedValue('fix-login-timeout');
mockConfirm.mockResolvedValue(false);
// When
await addTask(testDir, '#99');
// Then: task file contains piece field
const taskFile = path.join(testDir, '.takt', 'tasks', 'fix-login-timeout.yaml');
expect(fs.existsSync(taskFile)).toBe(true);
const content = fs.readFileSync(taskFile, 'utf-8');
expect(content).toContain('piece: review');
});
it('should cancel when piece selection returns null for issue reference', async () => {
// Given: issue fetched successfully but user cancels piece selection
const issueText = 'Issue #99: Fix login timeout';
mockResolveIssueTask.mockReturnValue(issueText);
mockDeterminePiece.mockResolvedValue(null);
// When
await addTask(testDir, '#99');
// Then: no task file created (cancelled at piece selection)
const tasksDir = path.join(testDir, '.takt', 'tasks');
const files = fs.readdirSync(tasksDir);
expect(files.length).toBe(0);
// Then: issue was fetched before cancellation
expect(mockResolveIssueTask).toHaveBeenCalledWith('#99');
});
describe('create_issue action', () => {
it('should call createIssue when create_issue action is selected', async () => {
// Given: interactive mode returns create_issue action

View File

@ -106,18 +106,14 @@ export async function saveTaskFromInteractive(
* add command handler
*
* Flow:
* 1.
* 2. AI対話モードでタスクを詰める
* 3. AIがタスク要約を生成
* 4. AIで生成
* 5. /
* 6. YAMLファイル作成
* A) Issue参照の場合: issue取得 YAML作成
* B) それ以外: ピース選択 AI対話モード YAML作成
*/
export async function addTask(cwd: string, task?: string): Promise<void> {
const tasksDir = path.join(cwd, '.takt', 'tasks');
fs.mkdirSync(tasksDir, { recursive: true });
// 1. ピース選択Issue参照以外の場合、対話モードの前に実施
// ピース選択とタスク内容の決定
let taskContent: string;
let issueNumber: number | undefined;
let piece: string | undefined;
@ -137,6 +133,14 @@ export async function addTask(cwd: string, task?: string): Promise<void> {
info(`Failed to fetch issue ${task}: ${msg}`);
return;
}
// ピース選択issue取得成功後
const pieceId = await determinePiece(cwd);
if (pieceId === null) {
info('Cancelled.');
return;
}
piece = pieceId;
} else {
// ピース選択を先に行い、結果を対話モードに渡す
const pieceId = await determinePiece(cwd);
@ -165,7 +169,7 @@ export async function addTask(cwd: string, task?: string): Promise<void> {
taskContent = result.task;
}
// 3. ワークツリー/ブランチ設定
// ワークツリー/ブランチ設定
let worktree: boolean | string | undefined;
let branch: string | undefined;
@ -180,7 +184,7 @@ export async function addTask(cwd: string, task?: string): Promise<void> {
}
}
// 4. YAMLファイル作成
// YAMLファイル作成
const filePath = await saveTaskFile(cwd, taskContent, {
piece,
issue: issueNumber,