Files
maestro/src/engine/tools/brainstorm.test.ts
T

89 lines
3.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { TOOL_DEFS, executeTool } from './brainstorm.js';
import type { ToolContext } from './core.js';
function makeCtx(): ToolContext {
return { workspacePath: '/tmp/no-such', editAllowed: false };
}
describe('Brainstorm tool', () => {
it('exports the Brainstorm tool definition', () => {
expect(TOOL_DEFS).toHaveProperty('Brainstorm');
const def = TOOL_DEFS['Brainstorm']!;
expect(def.function.name).toBe('Brainstorm');
const params = def.function.parameters as { required?: string[] };
expect(params.required).toEqual(expect.arrayContaining(['task', 'approaches', 'chosen', 'rationale']));
});
it('returns null for other tool names (dispatch isolation)', async () => {
const result = await executeTool('SomethingElse', {}, makeCtx());
expect(result).toBeNull();
});
it('requires task field', async () => {
const result = await executeTool('Brainstorm', { approaches: [{ name: 'a', description: 'x' }, { name: 'b', description: 'y' }], chosen: 'a', rationale: 'r' }, makeCtx());
expect(result?.isError).toBe(true);
expect(result?.output).toContain('task');
});
it('rejects single approach (needs 2+ for comparison)', async () => {
const result = await executeTool('Brainstorm', {
task: 't',
approaches: [{ name: 'only', description: 'one' }],
chosen: 'only',
rationale: 'r',
}, makeCtx());
expect(result?.isError).toBe(true);
expect(result?.output).toContain('2 個以上');
});
it('rejects when chosen does not match any approach name', async () => {
const result = await executeTool('Brainstorm', {
task: 't',
approaches: [{ name: 'a', description: 'x' }, { name: 'b', description: 'y' }],
chosen: 'nonexistent',
rationale: 'r',
}, makeCtx());
expect(result?.isError).toBe(true);
expect(result?.output).toContain('一致しません');
});
it('formats the approaches and marks the chosen one', async () => {
const result = await executeTool('Brainstorm', {
task: 'input/data.xlsx の中身を要約したい',
approaches: [
{ name: 'ReadExcel 直接', description: 'ReadExcel で読む', reliability: 'high', speed: 'fast' },
{ name: 'CSV エクスポート経由', description: 'CSV に変換してから Read', reliability: 'medium', speed: 'medium' },
{ name: 'ファイル拡張子確認後判断', description: 'Bash file で本体形式を確認', reliability: 'high', speed: 'slow' },
],
chosen: 'ReadExcel 直接',
rationale: '通常はこれが最速で確実',
}, makeCtx());
expect(result?.isError).toBe(false);
expect(result?.output).toContain('# Brainstorm: input/data.xlsx');
expect(result?.output).toContain('検討した 3 個のアプローチ');
expect(result?.output).toMatch(/✓\s+\*\*ReadExcel 直接\*\*/);
expect(result?.output).toContain('採用: ReadExcel 直接');
expect(result?.output).toContain('通常はこれが最速で確実');
expect(result?.output).toContain('確実性: high');
});
it('preserves optional context field for stuck-recovery use case', async () => {
const result = await executeTool('Brainstorm', {
task: 'output/foo.xlsx を読みたい',
context: 'ReadExcel が JSZip エラー、ReadPdf も拡張子 mismatch で reject 済み',
approaches: [
{ name: 'Glob で実在確認', description: 'Glob output/* で実際のファイル一覧を取る' },
{ name: 'ユーザーに ASK', description: '正しいパスを確認' },
],
chosen: 'Glob で実在確認',
rationale: '能動的に状況を取りに行ける',
}, makeCtx());
expect(result?.isError).toBe(false);
expect(result?.output).toContain('背景 / これまでの試行');
expect(result?.output).toContain('JSZip エラー');
});
});