feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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 エラー');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user