会話内容からタスク要件をまとめる必要がなくなったので削除
This commit is contained in:
parent
fabf4bcd27
commit
184a1d756a
@ -69,45 +69,33 @@ vi.mock('../infra/github/issue.js', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
import { interactiveMode } from '../features/interactive/index.js';
|
import { interactiveMode } from '../features/interactive/index.js';
|
||||||
import { getProvider } from '../infra/providers/index.js';
|
|
||||||
import { promptInput, confirm } from '../shared/prompt/index.js';
|
import { promptInput, confirm } from '../shared/prompt/index.js';
|
||||||
import { summarizeTaskName } from '../infra/task/summarize.js';
|
import { summarizeTaskName } from '../infra/task/summarize.js';
|
||||||
import { determineWorkflow } from '../features/tasks/execute/selectAndExecute.js';
|
import { determineWorkflow } from '../features/tasks/execute/selectAndExecute.js';
|
||||||
import { getWorkflowDescription } from '../infra/config/loaders/workflowResolver.js';
|
import { getWorkflowDescription } from '../infra/config/loaders/workflowResolver.js';
|
||||||
import { resolveIssueTask } from '../infra/github/issue.js';
|
import { resolveIssueTask } from '../infra/github/issue.js';
|
||||||
import { addTask, summarizeConversation } from '../features/tasks/index.js';
|
import { addTask } from '../features/tasks/index.js';
|
||||||
|
|
||||||
const mockResolveIssueTask = vi.mocked(resolveIssueTask);
|
const mockResolveIssueTask = vi.mocked(resolveIssueTask);
|
||||||
|
|
||||||
const mockInteractiveMode = vi.mocked(interactiveMode);
|
const mockInteractiveMode = vi.mocked(interactiveMode);
|
||||||
const mockGetProvider = vi.mocked(getProvider);
|
|
||||||
const mockPromptInput = vi.mocked(promptInput);
|
const mockPromptInput = vi.mocked(promptInput);
|
||||||
const mockConfirm = vi.mocked(confirm);
|
const mockConfirm = vi.mocked(confirm);
|
||||||
const mockSummarizeTaskName = vi.mocked(summarizeTaskName);
|
const mockSummarizeTaskName = vi.mocked(summarizeTaskName);
|
||||||
const mockDetermineWorkflow = vi.mocked(determineWorkflow);
|
const mockDetermineWorkflow = vi.mocked(determineWorkflow);
|
||||||
const mockGetWorkflowDescription = vi.mocked(getWorkflowDescription);
|
const mockGetWorkflowDescription = vi.mocked(getWorkflowDescription);
|
||||||
|
|
||||||
/** Helper: set up mocks for the full happy path */
|
|
||||||
function setupFullFlowMocks(overrides?: {
|
function setupFullFlowMocks(overrides?: {
|
||||||
conversationTask?: string;
|
task?: string;
|
||||||
summaryContent?: string;
|
|
||||||
slug?: string;
|
slug?: string;
|
||||||
}) {
|
}) {
|
||||||
const task = overrides?.conversationTask ?? 'User: 認証機能を追加したい\n\nAssistant: 了解です。';
|
const task = overrides?.task ?? '# 認証機能追加\nJWT認証を実装する';
|
||||||
const summary = overrides?.summaryContent ?? '# 認証機能追加\nJWT認証を実装する';
|
|
||||||
const slug = overrides?.slug ?? 'add-auth';
|
const slug = overrides?.slug ?? 'add-auth';
|
||||||
|
|
||||||
mockDetermineWorkflow.mockResolvedValue('default');
|
mockDetermineWorkflow.mockResolvedValue('default');
|
||||||
mockGetWorkflowDescription.mockReturnValue({ name: 'default', description: '' });
|
mockGetWorkflowDescription.mockReturnValue({ name: 'default', description: '' });
|
||||||
mockInteractiveMode.mockResolvedValue({ confirmed: true, task });
|
mockInteractiveMode.mockResolvedValue({ confirmed: true, task });
|
||||||
|
|
||||||
const mockProviderCall = vi.fn().mockResolvedValue({ content: summary });
|
|
||||||
mockGetProvider.mockReturnValue({ call: mockProviderCall } as any);
|
|
||||||
|
|
||||||
mockSummarizeTaskName.mockResolvedValue(slug);
|
mockSummarizeTaskName.mockResolvedValue(slug);
|
||||||
mockConfirm.mockResolvedValue(false);
|
mockConfirm.mockResolvedValue(false);
|
||||||
|
|
||||||
return { mockProviderCall };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let testDir: string;
|
let testDir: string;
|
||||||
@ -135,11 +123,9 @@ describe('addTask', () => {
|
|||||||
// When
|
// When
|
||||||
await addTask(testDir);
|
await addTask(testDir);
|
||||||
|
|
||||||
// Then: no task file created
|
|
||||||
const tasksDir = path.join(testDir, '.takt', 'tasks');
|
const tasksDir = path.join(testDir, '.takt', 'tasks');
|
||||||
const files = fs.existsSync(tasksDir) ? fs.readdirSync(tasksDir) : [];
|
const files = fs.existsSync(tasksDir) ? fs.readdirSync(tasksDir) : [];
|
||||||
expect(files.length).toBe(0);
|
expect(files.length).toBe(0);
|
||||||
expect(mockGetProvider).not.toHaveBeenCalled();
|
|
||||||
expect(mockSummarizeTaskName).not.toHaveBeenCalled();
|
expect(mockSummarizeTaskName).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,38 +146,14 @@ describe('addTask', () => {
|
|||||||
expect(content).toContain('JWT認証を実装する');
|
expect(content).toContain('JWT認証を実装する');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should summarize conversation via provider.call', async () => {
|
it('should use first line of task for filename generation', async () => {
|
||||||
// Given
|
|
||||||
const { mockProviderCall } = setupFullFlowMocks({
|
|
||||||
conversationTask: 'User: バグ修正して\n\nAssistant: どのバグですか?',
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
await addTask(testDir);
|
|
||||||
|
|
||||||
// Then: provider.call was called with conversation text
|
|
||||||
expect(mockProviderCall).toHaveBeenCalledWith(
|
|
||||||
'task-summarizer',
|
|
||||||
'User: バグ修正して\n\nAssistant: どのバグですか?',
|
|
||||||
expect.objectContaining({
|
|
||||||
cwd: testDir,
|
|
||||||
maxTurns: 1,
|
|
||||||
allowedTools: [],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use first line of summary for filename generation', async () => {
|
|
||||||
// Given: summary with multiple lines
|
|
||||||
setupFullFlowMocks({
|
setupFullFlowMocks({
|
||||||
summaryContent: 'First line summary\nSecond line details',
|
task: 'First line summary\nSecond line details',
|
||||||
slug: 'first-line',
|
slug: 'first-line',
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
|
||||||
await addTask(testDir);
|
await addTask(testDir);
|
||||||
|
|
||||||
// Then: summarizeTaskName receives only the first line
|
|
||||||
expect(mockSummarizeTaskName).toHaveBeenCalledWith('First line summary', { cwd: testDir });
|
expect(mockSummarizeTaskName).toHaveBeenCalledWith('First line summary', { cwd: testDir });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -354,11 +316,9 @@ describe('addTask', () => {
|
|||||||
// When
|
// When
|
||||||
await addTask(testDir, '#99');
|
await addTask(testDir, '#99');
|
||||||
|
|
||||||
// Then: no task file created, no crash
|
|
||||||
const tasksDir = path.join(testDir, '.takt', 'tasks');
|
const tasksDir = path.join(testDir, '.takt', 'tasks');
|
||||||
const files = fs.readdirSync(tasksDir);
|
const files = fs.readdirSync(tasksDir);
|
||||||
expect(files.length).toBe(0);
|
expect(files.length).toBe(0);
|
||||||
expect(mockGetProvider).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include issue number in task file when issue reference is used', async () => {
|
it('should include issue number in task file when issue reference is used', async () => {
|
||||||
@ -378,27 +338,3 @@ describe('addTask', () => {
|
|||||||
expect(content).toContain('issue: 99');
|
expect(content).toContain('issue: 99');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('summarizeConversation', () => {
|
|
||||||
it('should call provider with summarize system prompt', async () => {
|
|
||||||
// Given
|
|
||||||
const mockCall = vi.fn().mockResolvedValue({ content: 'Summary text' });
|
|
||||||
mockGetProvider.mockReturnValue({ call: mockCall } as any);
|
|
||||||
|
|
||||||
// When
|
|
||||||
const result = await summarizeConversation('/project', 'conversation text');
|
|
||||||
|
|
||||||
// Then
|
|
||||||
expect(result).toBe('Summary text');
|
|
||||||
expect(mockCall).toHaveBeenCalledWith(
|
|
||||||
'task-summarizer',
|
|
||||||
'conversation text',
|
|
||||||
expect.objectContaining({
|
|
||||||
cwd: '/project',
|
|
||||||
maxTurns: 1,
|
|
||||||
allowedTools: [],
|
|
||||||
systemPrompt: expect.stringContaining('会話履歴からタスクの要件をまとめてください'),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@ -105,7 +105,6 @@ describe('YAML content integrity', () => {
|
|||||||
expect(() => getPrompt('interactive.noTranscript', 'en')).not.toThrow();
|
expect(() => getPrompt('interactive.noTranscript', 'en')).not.toThrow();
|
||||||
expect(() => getPromptObject('interactive.ui', 'en')).not.toThrow();
|
expect(() => getPromptObject('interactive.ui', 'en')).not.toThrow();
|
||||||
expect(() => getPrompt('summarize.slugGenerator')).not.toThrow();
|
expect(() => getPrompt('summarize.slugGenerator')).not.toThrow();
|
||||||
expect(() => getPrompt('summarize.conversationSummarizer')).not.toThrow();
|
|
||||||
expect(() => getPrompt('claude.agentDefault')).not.toThrow();
|
expect(() => getPrompt('claude.agentDefault')).not.toThrow();
|
||||||
expect(() => getPrompt('claude.judgePrompt')).not.toThrow();
|
expect(() => getPrompt('claude.judgePrompt')).not.toThrow();
|
||||||
expect(() => getPromptObject('instruction.metadata', 'en')).not.toThrow();
|
expect(() => getPromptObject('instruction.metadata', 'en')).not.toThrow();
|
||||||
@ -126,7 +125,6 @@ describe('YAML content integrity', () => {
|
|||||||
expect(() => getPrompt('interactive.noTranscript', 'ja')).not.toThrow();
|
expect(() => getPrompt('interactive.noTranscript', 'ja')).not.toThrow();
|
||||||
expect(() => getPromptObject('interactive.ui', 'ja')).not.toThrow();
|
expect(() => getPromptObject('interactive.ui', 'ja')).not.toThrow();
|
||||||
expect(() => getPrompt('summarize.slugGenerator', 'ja')).not.toThrow();
|
expect(() => getPrompt('summarize.slugGenerator', 'ja')).not.toThrow();
|
||||||
expect(() => getPrompt('summarize.conversationSummarizer', 'ja')).not.toThrow();
|
|
||||||
expect(() => getPrompt('claude.agentDefault', 'ja')).not.toThrow();
|
expect(() => getPrompt('claude.agentDefault', 'ja')).not.toThrow();
|
||||||
expect(() => getPrompt('claude.judgePrompt', 'ja')).not.toThrow();
|
expect(() => getPrompt('claude.judgePrompt', 'ja')).not.toThrow();
|
||||||
expect(() => getPromptObject('instruction.metadata', 'ja')).not.toThrow();
|
expect(() => getPromptObject('instruction.metadata', 'ja')).not.toThrow();
|
||||||
|
|||||||
@ -13,37 +13,12 @@ import { success, info } from '../../../shared/ui/index.js';
|
|||||||
import { summarizeTaskName, type TaskFileData } from '../../../infra/task/index.js';
|
import { summarizeTaskName, type TaskFileData } from '../../../infra/task/index.js';
|
||||||
import { loadGlobalConfig, getWorkflowDescription } from '../../../infra/config/index.js';
|
import { loadGlobalConfig, getWorkflowDescription } from '../../../infra/config/index.js';
|
||||||
import { determineWorkflow } from '../execute/selectAndExecute.js';
|
import { determineWorkflow } from '../execute/selectAndExecute.js';
|
||||||
import { getProvider, type ProviderType } from '../../../infra/providers/index.js';
|
|
||||||
import { createLogger, getErrorMessage } from '../../../shared/utils/index.js';
|
import { createLogger, getErrorMessage } from '../../../shared/utils/index.js';
|
||||||
import { getPrompt } from '../../../shared/prompts/index.js';
|
|
||||||
import { isIssueReference, resolveIssueTask, parseIssueNumbers } from '../../../infra/github/index.js';
|
import { isIssueReference, resolveIssueTask, parseIssueNumbers } from '../../../infra/github/index.js';
|
||||||
import { interactiveMode } from '../../interactive/index.js';
|
import { interactiveMode } from '../../interactive/index.js';
|
||||||
|
|
||||||
const log = createLogger('add-task');
|
const log = createLogger('add-task');
|
||||||
|
|
||||||
/**
|
|
||||||
* Summarize conversation history into a task description using AI.
|
|
||||||
*/
|
|
||||||
export async function summarizeConversation(cwd: string, conversationText: string): Promise<string> {
|
|
||||||
const globalConfig = loadGlobalConfig();
|
|
||||||
const providerType = (globalConfig.provider as ProviderType) ?? 'claude';
|
|
||||||
const provider = getProvider(providerType);
|
|
||||||
|
|
||||||
info('Summarizing task from conversation...');
|
|
||||||
|
|
||||||
const response = await provider.call('task-summarizer', conversationText, {
|
|
||||||
cwd,
|
|
||||||
maxTurns: 1,
|
|
||||||
allowedTools: [],
|
|
||||||
systemPrompt: getPrompt('summarize.conversationSummarizer'),
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a unique task filename with AI-summarized slug
|
|
||||||
*/
|
|
||||||
async function generateFilename(tasksDir: string, taskContent: string, cwd: string): Promise<string> {
|
async function generateFilename(tasksDir: string, taskContent: string, cwd: string): Promise<string> {
|
||||||
info('Generating task filename...');
|
info('Generating task filename...');
|
||||||
const slug = await summarizeTaskName(taskContent, { cwd });
|
const slug = await summarizeTaskName(taskContent, { cwd });
|
||||||
@ -112,8 +87,8 @@ export async function addTask(cwd: string, task?: string): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 会話履歴からタスク要約を生成
|
// interactiveMode already returns a summarized task from conversation
|
||||||
taskContent = await summarizeConversation(cwd, result.task);
|
taskContent = result.task;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 要約からファイル名生成
|
// 3. 要約からファイル名生成
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export {
|
|||||||
type SelectAndExecuteOptions,
|
type SelectAndExecuteOptions,
|
||||||
type WorktreeConfirmationResult,
|
type WorktreeConfirmationResult,
|
||||||
} from './execute/selectAndExecute.js';
|
} from './execute/selectAndExecute.js';
|
||||||
export { addTask, summarizeConversation } from './add/index.js';
|
export { addTask } from './add/index.js';
|
||||||
export { watchTasks } from './watch/index.js';
|
export { watchTasks } from './watch/index.js';
|
||||||
export {
|
export {
|
||||||
listTasks,
|
listTasks,
|
||||||
|
|||||||
@ -96,12 +96,6 @@ summarize:
|
|||||||
worktreeを作るときブランチ名をAIで生成 → ai-branch-naming
|
worktreeを作るときブランチ名をAIで生成 → ai-branch-naming
|
||||||
レビュー画面に元の指示を表示する → show-original-instruction
|
レビュー画面に元の指示を表示する → show-original-instruction
|
||||||
|
|
||||||
conversationSummarizer: |
|
|
||||||
会話履歴からタスクの要件をまとめてください。
|
|
||||||
タスク実行エージェントへの指示として使われます。
|
|
||||||
具体的・簡潔に、必要な情報をすべて含めてください。
|
|
||||||
マークダウン形式で出力してください。
|
|
||||||
|
|
||||||
# ===== Claude Client =====
|
# ===== Claude Client =====
|
||||||
claude:
|
claude:
|
||||||
agentDefault: "You are the {agentName} agent. Follow the standard {agentName} workflow."
|
agentDefault: "You are the {agentName} agent. Follow the standard {agentName} workflow."
|
||||||
|
|||||||
@ -109,12 +109,6 @@ summarize:
|
|||||||
worktreeを作るときブランチ名をAIで生成 → ai-branch-naming
|
worktreeを作るときブランチ名をAIで生成 → ai-branch-naming
|
||||||
レビュー画面に元の指示を表示する → show-original-instruction
|
レビュー画面に元の指示を表示する → show-original-instruction
|
||||||
|
|
||||||
conversationSummarizer: |
|
|
||||||
会話履歴からタスクの要件をまとめてください。
|
|
||||||
タスク実行エージェントへの指示として使われます。
|
|
||||||
具体的・簡潔に、必要な情報をすべて含めてください。
|
|
||||||
マークダウン形式で出力してください。
|
|
||||||
|
|
||||||
# ===== Claude Client =====
|
# ===== Claude Client =====
|
||||||
claude:
|
claude:
|
||||||
agentDefault: "You are the {agentName} agent. Follow the standard {agentName} workflow."
|
agentDefault: "You are the {agentName} agent. Follow the standard {agentName} workflow."
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user