takt/src/__tests__/team-leader-schema-loader.test.ts
nrslib 798e89605d feat: TeamLeader に refill threshold と動的パート追加を導入
TeamLeaderRunner を4モジュールに分割(execution, aggregation, common, streaming)し、
パート完了時にキュー残数が refill_threshold 以下になると追加タスクを動的に生成する
worker pool 型の実行モデルを実装。ParallelLogger に LineTimeSliceBuffer を追加し
ストリーミング出力を改善。deep-research ピースに team_leader 設定を追加。
2026-02-26 22:33:22 +09:00

121 lines
3.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { join } from 'node:path';
import { PieceMovementRawSchema } from '../core/models/schemas.js';
import { normalizePieceConfig } from '../infra/config/loaders/pieceParser.js';
describe('team_leader schema', () => {
it('max_parts <= 3 の設定を受け付ける', () => {
const raw = {
name: 'implement',
team_leader: {
persona: 'team-leader',
max_parts: 3,
timeout_ms: 120000,
},
instruction_template: 'decompose',
};
const result = PieceMovementRawSchema.safeParse(raw);
expect(result.success).toBe(true);
});
it('max_parts > 3 は拒否する', () => {
const raw = {
name: 'implement',
team_leader: {
max_parts: 4,
},
instruction_template: 'decompose',
};
const result = PieceMovementRawSchema.safeParse(raw);
expect(result.success).toBe(false);
});
it('refill_threshold > max_parts は拒否する', () => {
const raw = {
name: 'implement',
team_leader: {
max_parts: 2,
refill_threshold: 3,
},
instruction_template: 'decompose',
};
const result = PieceMovementRawSchema.safeParse(raw);
expect(result.success).toBe(false);
});
it('parallel と team_leader の同時指定は拒否する', () => {
const raw = {
name: 'implement',
parallel: [{ name: 'sub', instruction_template: 'x' }],
team_leader: {
max_parts: 2,
},
instruction_template: 'decompose',
};
const result = PieceMovementRawSchema.safeParse(raw);
expect(result.success).toBe(false);
});
it('arpeggio と team_leader の同時指定は拒否する', () => {
const raw = {
name: 'implement',
arpeggio: {
source: 'csv',
source_path: './data.csv',
template: './prompt.md',
},
team_leader: {
max_parts: 2,
},
instruction_template: 'decompose',
};
const result = PieceMovementRawSchema.safeParse(raw);
expect(result.success).toBe(false);
});
});
describe('normalizePieceConfig team_leader', () => {
it('team_leader を内部形式へ正規化する', () => {
const pieceDir = join(process.cwd(), 'src', '__tests__');
const raw = {
name: 'piece',
movements: [
{
name: 'implement',
team_leader: {
persona: 'team-leader',
max_parts: 2,
timeout_ms: 90000,
part_persona: 'coder',
part_allowed_tools: ['Read', 'Edit'],
part_edit: true,
part_permission_mode: 'edit',
},
instruction_template: 'decompose',
},
],
};
const config = normalizePieceConfig(raw, pieceDir);
const movement = config.movements[0];
expect(movement).toBeDefined();
expect(movement!.teamLeader).toEqual({
persona: 'team-leader',
personaPath: undefined,
maxParts: 2,
refillThreshold: 0,
timeoutMs: 90000,
partPersona: 'coder',
partPersonaPath: undefined,
partAllowedTools: ['Read', 'Edit'],
partEdit: true,
partPermissionMode: 'edit',
});
});
});