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" // Given: issue reference "#99"
const issueText = 'Issue #99: Fix login timeout\n\nThe login page times out after 30 seconds.'; const issueText = 'Issue #99: Fix login timeout\n\nThe login page times out after 30 seconds.';
mockResolveIssueTask.mockReturnValue(issueText); mockResolveIssueTask.mockReturnValue(issueText);
mockDeterminePiece.mockResolvedValue('default');
mockSummarizeTaskName.mockResolvedValue('fix-login-timeout'); mockSummarizeTaskName.mockResolvedValue('fix-login-timeout');
mockConfirm.mockResolvedValue(false); mockConfirm.mockResolvedValue(false);
@ -288,6 +289,9 @@ describe('addTask', () => {
// Then: resolveIssueTask was called // Then: resolveIssueTask was called
expect(mockResolveIssueTask).toHaveBeenCalledWith('#99'); 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) // Then: task file created with issue text directly (no AI summarization)
const taskFile = path.join(testDir, '.takt', 'tasks', 'fix-login-timeout.yaml'); const taskFile = path.join(testDir, '.takt', 'tasks', 'fix-login-timeout.yaml');
expect(fs.existsSync(taskFile)).toBe(true); expect(fs.existsSync(taskFile)).toBe(true);
@ -298,6 +302,7 @@ describe('addTask', () => {
it('should proceed to worktree settings after issue fetch', async () => { it('should proceed to worktree settings after issue fetch', async () => {
// Given: issue with worktree enabled // Given: issue with worktree enabled
mockResolveIssueTask.mockReturnValue('Issue text'); mockResolveIssueTask.mockReturnValue('Issue text');
mockDeterminePiece.mockResolvedValue('default');
mockSummarizeTaskName.mockResolvedValue('issue-task'); mockSummarizeTaskName.mockResolvedValue('issue-task');
mockConfirm.mockResolvedValue(true); mockConfirm.mockResolvedValue(true);
mockPromptInput mockPromptInput
@ -331,6 +336,7 @@ describe('addTask', () => {
// Given: issue reference "#99" // Given: issue reference "#99"
const issueText = 'Issue #99: Fix login timeout'; const issueText = 'Issue #99: Fix login timeout';
mockResolveIssueTask.mockReturnValue(issueText); mockResolveIssueTask.mockReturnValue(issueText);
mockDeterminePiece.mockResolvedValue('default');
mockSummarizeTaskName.mockResolvedValue('fix-login-timeout'); mockSummarizeTaskName.mockResolvedValue('fix-login-timeout');
mockConfirm.mockResolvedValue(false); mockConfirm.mockResolvedValue(false);
@ -344,6 +350,42 @@ describe('addTask', () => {
expect(content).toContain('issue: 99'); 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', () => { describe('create_issue action', () => {
it('should call createIssue when create_issue action is selected', async () => { it('should call createIssue when create_issue action is selected', async () => {
// Given: interactive mode returns create_issue action // Given: interactive mode returns create_issue action

View File

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