takt: github-issue-132-moodono-piisu (#144)
This commit is contained in:
parent
3167f038a4
commit
3533946602
@ -53,7 +53,8 @@ vi.mock('../infra/config/loaders/pieceResolver.js', () => ({
|
||||
getPieceDescription: vi.fn(() => ({
|
||||
name: 'default',
|
||||
description: '',
|
||||
pieceStructure: '1. implement\n2. review'
|
||||
pieceStructure: '1. implement\n2. review',
|
||||
movementPreviews: [],
|
||||
})),
|
||||
}));
|
||||
|
||||
|
||||
@ -48,7 +48,8 @@ vi.mock('../features/interactive/index.js', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../infra/config/index.js', () => ({
|
||||
getPieceDescription: vi.fn(() => ({ name: 'default', description: 'test piece', pieceStructure: '' })),
|
||||
getPieceDescription: vi.fn(() => ({ name: 'default', description: 'test piece', pieceStructure: '', movementPreviews: [] })),
|
||||
loadGlobalConfig: vi.fn(() => ({ interactivePreviewMovements: 3 })),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/constants.js', () => ({
|
||||
|
||||
139
src/__tests__/formatMovementPreviews.test.ts
Normal file
139
src/__tests__/formatMovementPreviews.test.ts
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Tests for formatMovementPreviews
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { MovementPreview } from '../infra/config/loaders/pieceResolver.js';
|
||||
import { formatMovementPreviews } from '../features/interactive/interactive.js';
|
||||
|
||||
describe('formatMovementPreviews', () => {
|
||||
const basePreviews: MovementPreview[] = [
|
||||
{
|
||||
name: 'plan',
|
||||
personaDisplayName: 'Planner',
|
||||
personaContent: 'You are a planner.',
|
||||
instructionContent: 'Create a plan for {task}',
|
||||
allowedTools: ['Read', 'Glob', 'Grep'],
|
||||
canEdit: false,
|
||||
},
|
||||
{
|
||||
name: 'implement',
|
||||
personaDisplayName: 'Coder',
|
||||
personaContent: 'You are a coder.',
|
||||
instructionContent: 'Implement the plan.',
|
||||
allowedTools: ['Read', 'Edit', 'Bash'],
|
||||
canEdit: true,
|
||||
},
|
||||
];
|
||||
|
||||
it('should format previews with English labels', () => {
|
||||
const result = formatMovementPreviews(basePreviews, 'en');
|
||||
|
||||
expect(result).toContain('### 1. plan (Planner)');
|
||||
expect(result).toContain('**Persona:**');
|
||||
expect(result).toContain('You are a planner.');
|
||||
expect(result).toContain('**Instruction:**');
|
||||
expect(result).toContain('Create a plan for {task}');
|
||||
expect(result).toContain('**Tools:** Read, Glob, Grep');
|
||||
expect(result).toContain('**Edit:** No');
|
||||
|
||||
expect(result).toContain('### 2. implement (Coder)');
|
||||
expect(result).toContain('**Tools:** Read, Edit, Bash');
|
||||
expect(result).toContain('**Edit:** Yes');
|
||||
});
|
||||
|
||||
it('should format previews with Japanese labels', () => {
|
||||
const result = formatMovementPreviews(basePreviews, 'ja');
|
||||
|
||||
expect(result).toContain('### 1. plan (Planner)');
|
||||
expect(result).toContain('**ペルソナ:**');
|
||||
expect(result).toContain('**インストラクション:**');
|
||||
expect(result).toContain('**ツール:** Read, Glob, Grep');
|
||||
expect(result).toContain('**編集:** 不可');
|
||||
expect(result).toContain('**編集:** 可');
|
||||
});
|
||||
|
||||
it('should show "None" when no tools are allowed (English)', () => {
|
||||
const previews: MovementPreview[] = [
|
||||
{
|
||||
name: 'step',
|
||||
personaDisplayName: 'Agent',
|
||||
personaContent: 'Agent persona',
|
||||
instructionContent: 'Do something',
|
||||
allowedTools: [],
|
||||
canEdit: false,
|
||||
},
|
||||
];
|
||||
|
||||
const result = formatMovementPreviews(previews, 'en');
|
||||
|
||||
expect(result).toContain('**Tools:** None');
|
||||
});
|
||||
|
||||
it('should show "なし" when no tools are allowed (Japanese)', () => {
|
||||
const previews: MovementPreview[] = [
|
||||
{
|
||||
name: 'step',
|
||||
personaDisplayName: 'Agent',
|
||||
personaContent: 'Agent persona',
|
||||
instructionContent: 'Do something',
|
||||
allowedTools: [],
|
||||
canEdit: false,
|
||||
},
|
||||
];
|
||||
|
||||
const result = formatMovementPreviews(previews, 'ja');
|
||||
|
||||
expect(result).toContain('**ツール:** なし');
|
||||
});
|
||||
|
||||
it('should skip empty persona content', () => {
|
||||
const previews: MovementPreview[] = [
|
||||
{
|
||||
name: 'step',
|
||||
personaDisplayName: 'Agent',
|
||||
personaContent: '',
|
||||
instructionContent: 'Do something',
|
||||
allowedTools: [],
|
||||
canEdit: false,
|
||||
},
|
||||
];
|
||||
|
||||
const result = formatMovementPreviews(previews, 'en');
|
||||
|
||||
expect(result).not.toContain('**Persona:**');
|
||||
expect(result).toContain('**Instruction:**');
|
||||
});
|
||||
|
||||
it('should skip empty instruction content', () => {
|
||||
const previews: MovementPreview[] = [
|
||||
{
|
||||
name: 'step',
|
||||
personaDisplayName: 'Agent',
|
||||
personaContent: 'Some persona',
|
||||
instructionContent: '',
|
||||
allowedTools: [],
|
||||
canEdit: false,
|
||||
},
|
||||
];
|
||||
|
||||
const result = formatMovementPreviews(previews, 'en');
|
||||
|
||||
expect(result).toContain('**Persona:**');
|
||||
expect(result).not.toContain('**Instruction:**');
|
||||
});
|
||||
|
||||
it('should return empty string for empty array', () => {
|
||||
const result = formatMovementPreviews([], 'en');
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should separate multiple previews with double newline', () => {
|
||||
const result = formatMovementPreviews(basePreviews, 'en');
|
||||
|
||||
// Two movements should be separated by \n\n
|
||||
const parts = result.split('\n\n### ');
|
||||
expect(parts.length).toBe(2);
|
||||
});
|
||||
});
|
||||
@ -287,6 +287,55 @@ describe('loadGlobalConfig', () => {
|
||||
expect(config.notificationSound).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should load interactive_preview_movements config from config.yaml', () => {
|
||||
const taktDir = join(testHomeDir, '.takt');
|
||||
mkdirSync(taktDir, { recursive: true });
|
||||
writeFileSync(
|
||||
getGlobalConfigPath(),
|
||||
'language: en\ninteractive_preview_movements: 5\n',
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const config = loadGlobalConfig();
|
||||
expect(config.interactivePreviewMovements).toBe(5);
|
||||
});
|
||||
|
||||
it('should save and reload interactive_preview_movements config', () => {
|
||||
const taktDir = join(testHomeDir, '.takt');
|
||||
mkdirSync(taktDir, { recursive: true });
|
||||
writeFileSync(getGlobalConfigPath(), 'language: en\n', 'utf-8');
|
||||
|
||||
const config = loadGlobalConfig();
|
||||
config.interactivePreviewMovements = 7;
|
||||
saveGlobalConfig(config);
|
||||
invalidateGlobalConfigCache();
|
||||
|
||||
const reloaded = loadGlobalConfig();
|
||||
expect(reloaded.interactivePreviewMovements).toBe(7);
|
||||
});
|
||||
|
||||
it('should default interactive_preview_movements to 3', () => {
|
||||
const taktDir = join(testHomeDir, '.takt');
|
||||
mkdirSync(taktDir, { recursive: true });
|
||||
writeFileSync(getGlobalConfigPath(), 'language: en\n', 'utf-8');
|
||||
|
||||
const config = loadGlobalConfig();
|
||||
expect(config.interactivePreviewMovements).toBe(3);
|
||||
});
|
||||
|
||||
it('should accept interactive_preview_movements: 0 to disable', () => {
|
||||
const taktDir = join(testHomeDir, '.takt');
|
||||
mkdirSync(taktDir, { recursive: true });
|
||||
writeFileSync(
|
||||
getGlobalConfigPath(),
|
||||
'language: en\ninteractive_preview_movements: 0\n',
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const config = loadGlobalConfig();
|
||||
expect(config.interactivePreviewMovements).toBe(0);
|
||||
});
|
||||
|
||||
describe('provider/model compatibility validation', () => {
|
||||
it('should throw when provider is codex but model is a Claude alias (opus)', () => {
|
||||
const taktDir = join(testHomeDir, '.takt');
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Tests for getPieceDescription and buildWorkflowString
|
||||
* Tests for getPieceDescription, buildWorkflowString, and buildMovementPreviews
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
||||
import { mkdtempSync, writeFileSync, mkdirSync, rmSync, chmodSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { getPieceDescription } from '../infra/config/loaders/pieceResolver.js';
|
||||
@ -49,6 +49,7 @@ movements:
|
||||
expect(result.pieceStructure).toBe(
|
||||
'1. plan (タスク計画)\n2. implement (実装)\n3. review'
|
||||
);
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return workflow structure with parallel movements', () => {
|
||||
@ -91,6 +92,7 @@ movements:
|
||||
' - arch_review\n' +
|
||||
'3. fix (修正)'
|
||||
);
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle movements without descriptions', () => {
|
||||
@ -115,6 +117,7 @@ movements:
|
||||
expect(result.name).toBe('minimal');
|
||||
expect(result.description).toBe('');
|
||||
expect(result.pieceStructure).toBe('1. step1\n2. step2');
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty strings when piece is not found', () => {
|
||||
@ -123,6 +126,7 @@ movements:
|
||||
expect(result.name).toBe('nonexistent');
|
||||
expect(result.description).toBe('');
|
||||
expect(result.pieceStructure).toBe('');
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle parallel movements without descriptions', () => {
|
||||
@ -151,5 +155,411 @@ movements:
|
||||
' - child1\n' +
|
||||
' - child2'
|
||||
);
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPieceDescription with movementPreviews', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = mkdtempSync(join(tmpdir(), 'takt-test-previews-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('should return movement previews when previewCount is specified', () => {
|
||||
const pieceYaml = `name: preview-test
|
||||
description: Test piece
|
||||
initial_movement: plan
|
||||
max_iterations: 5
|
||||
|
||||
movements:
|
||||
- name: plan
|
||||
description: Planning
|
||||
persona: Plan the task
|
||||
instruction: "Create a plan for {task}"
|
||||
allowed_tools:
|
||||
- Read
|
||||
- Glob
|
||||
rules:
|
||||
- condition: plan complete
|
||||
next: implement
|
||||
- name: implement
|
||||
description: Implementation
|
||||
persona: Implement the code
|
||||
instruction: "Implement according to plan"
|
||||
edit: true
|
||||
allowed_tools:
|
||||
- Read
|
||||
- Edit
|
||||
- Bash
|
||||
rules:
|
||||
- condition: done
|
||||
next: review
|
||||
- name: review
|
||||
persona: Review the code
|
||||
instruction: "Review changes"
|
||||
rules:
|
||||
- condition: approved
|
||||
next: COMPLETE
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'preview-test.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 3);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(3);
|
||||
|
||||
// First movement: plan
|
||||
expect(result.movementPreviews[0].name).toBe('plan');
|
||||
expect(result.movementPreviews[0].personaContent).toBe('Plan the task');
|
||||
expect(result.movementPreviews[0].instructionContent).toBe('Create a plan for {task}');
|
||||
expect(result.movementPreviews[0].allowedTools).toEqual(['Read', 'Glob']);
|
||||
expect(result.movementPreviews[0].canEdit).toBe(false);
|
||||
|
||||
// Second movement: implement
|
||||
expect(result.movementPreviews[1].name).toBe('implement');
|
||||
expect(result.movementPreviews[1].personaContent).toBe('Implement the code');
|
||||
expect(result.movementPreviews[1].instructionContent).toBe('Implement according to plan');
|
||||
expect(result.movementPreviews[1].allowedTools).toEqual(['Read', 'Edit', 'Bash']);
|
||||
expect(result.movementPreviews[1].canEdit).toBe(true);
|
||||
|
||||
// Third movement: review
|
||||
expect(result.movementPreviews[2].name).toBe('review');
|
||||
expect(result.movementPreviews[2].personaContent).toBe('Review the code');
|
||||
expect(result.movementPreviews[2].canEdit).toBe(false);
|
||||
});
|
||||
|
||||
it('should return empty previews when previewCount is 0', () => {
|
||||
const pieceYaml = `name: test
|
||||
initial_movement: step1
|
||||
max_iterations: 1
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent
|
||||
instruction: "Do step1"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 0);
|
||||
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty previews when previewCount is not specified', () => {
|
||||
const pieceYaml = `name: test
|
||||
initial_movement: step1
|
||||
max_iterations: 1
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent
|
||||
instruction: "Do step1"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir);
|
||||
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should stop at COMPLETE movement', () => {
|
||||
const pieceYaml = `name: test-complete
|
||||
initial_movement: step1
|
||||
max_iterations: 3
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent1
|
||||
instruction: "Step 1"
|
||||
rules:
|
||||
- condition: done
|
||||
next: COMPLETE
|
||||
- name: step2
|
||||
persona: agent2
|
||||
instruction: "Step 2"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-complete.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 5);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].name).toBe('step1');
|
||||
});
|
||||
|
||||
it('should stop at ABORT movement', () => {
|
||||
const pieceYaml = `name: test-abort
|
||||
initial_movement: step1
|
||||
max_iterations: 3
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent1
|
||||
instruction: "Step 1"
|
||||
rules:
|
||||
- condition: abort
|
||||
next: ABORT
|
||||
- name: step2
|
||||
persona: agent2
|
||||
instruction: "Step 2"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-abort.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 5);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].name).toBe('step1');
|
||||
});
|
||||
|
||||
it('should read persona content from file when personaPath is set', () => {
|
||||
const personaContent = '# Planner Persona\nYou are a planning expert.';
|
||||
const personaPath = join(tempDir, 'planner.md');
|
||||
writeFileSync(personaPath, personaContent);
|
||||
|
||||
const pieceYaml = `name: test-persona-file
|
||||
initial_movement: plan
|
||||
max_iterations: 1
|
||||
|
||||
personas:
|
||||
planner: ./planner.md
|
||||
|
||||
movements:
|
||||
- name: plan
|
||||
persona: planner
|
||||
instruction: "Plan the task"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-persona-file.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 1);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].name).toBe('plan');
|
||||
expect(result.movementPreviews[0].personaContent).toBe(personaContent);
|
||||
});
|
||||
|
||||
it('should limit previews to maxCount', () => {
|
||||
const pieceYaml = `name: test-limit
|
||||
initial_movement: step1
|
||||
max_iterations: 5
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent1
|
||||
instruction: "Step 1"
|
||||
rules:
|
||||
- condition: done
|
||||
next: step2
|
||||
- name: step2
|
||||
persona: agent2
|
||||
instruction: "Step 2"
|
||||
rules:
|
||||
- condition: done
|
||||
next: step3
|
||||
- name: step3
|
||||
persona: agent3
|
||||
instruction: "Step 3"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-limit.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 2);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(2);
|
||||
expect(result.movementPreviews[0].name).toBe('step1');
|
||||
expect(result.movementPreviews[1].name).toBe('step2');
|
||||
});
|
||||
|
||||
it('should handle movements without rules (stop after first)', () => {
|
||||
const pieceYaml = `name: test-no-rules
|
||||
initial_movement: step1
|
||||
max_iterations: 3
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent1
|
||||
instruction: "Step 1"
|
||||
- name: step2
|
||||
persona: agent2
|
||||
instruction: "Step 2"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-no-rules.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 3);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].name).toBe('step1');
|
||||
});
|
||||
|
||||
it('should return empty previews when initial movement not found in list', () => {
|
||||
const pieceYaml = `name: test-missing-initial
|
||||
initial_movement: nonexistent
|
||||
max_iterations: 1
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent
|
||||
instruction: "Do something"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-missing-initial.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 3);
|
||||
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle self-referencing rule (prevent infinite loop)', () => {
|
||||
const pieceYaml = `name: test-self-ref
|
||||
initial_movement: step1
|
||||
max_iterations: 5
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent1
|
||||
instruction: "Step 1"
|
||||
rules:
|
||||
- condition: loop
|
||||
next: step1
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-self-ref.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 5);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].name).toBe('step1');
|
||||
});
|
||||
|
||||
it('should handle multi-node cycle A→B→A (prevent duplicate previews)', () => {
|
||||
const pieceYaml = `name: test-cycle
|
||||
initial_movement: stepA
|
||||
max_iterations: 10
|
||||
|
||||
movements:
|
||||
- name: stepA
|
||||
persona: agentA
|
||||
instruction: "Step A"
|
||||
rules:
|
||||
- condition: next
|
||||
next: stepB
|
||||
- name: stepB
|
||||
persona: agentB
|
||||
instruction: "Step B"
|
||||
rules:
|
||||
- condition: back
|
||||
next: stepA
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-cycle.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 10);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(2);
|
||||
expect(result.movementPreviews[0].name).toBe('stepA');
|
||||
expect(result.movementPreviews[1].name).toBe('stepB');
|
||||
});
|
||||
|
||||
it('should return empty movementPreviews when piece is not found', () => {
|
||||
const result = getPieceDescription('nonexistent', tempDir, 3);
|
||||
|
||||
expect(result.movementPreviews).toEqual([]);
|
||||
});
|
||||
|
||||
it('should use inline persona content when no personaPath', () => {
|
||||
const pieceYaml = `name: test-inline
|
||||
initial_movement: step1
|
||||
max_iterations: 1
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: You are an inline persona
|
||||
instruction: "Do something"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-inline.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 1);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].personaContent).toBe('You are an inline persona');
|
||||
});
|
||||
|
||||
it('should fallback to empty personaContent when personaPath file becomes unreadable', () => {
|
||||
// Create the persona file so it passes existsSync during parsing
|
||||
const personaPath = join(tempDir, 'unreadable-persona.md');
|
||||
writeFileSync(personaPath, '# Persona content');
|
||||
// Make the file unreadable so readFileSync fails in buildMovementPreviews
|
||||
chmodSync(personaPath, 0o000);
|
||||
|
||||
const pieceYaml = `name: test-unreadable-persona
|
||||
initial_movement: plan
|
||||
max_iterations: 1
|
||||
|
||||
personas:
|
||||
planner: ./unreadable-persona.md
|
||||
|
||||
movements:
|
||||
- name: plan
|
||||
persona: planner
|
||||
instruction: "Plan the task"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-unreadable-persona.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
try {
|
||||
const result = getPieceDescription(piecePath, tempDir, 1);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].name).toBe('plan');
|
||||
expect(result.movementPreviews[0].personaContent).toBe('');
|
||||
expect(result.movementPreviews[0].instructionContent).toBe('Plan the task');
|
||||
} finally {
|
||||
// Restore permissions so cleanup can remove the file
|
||||
chmodSync(personaPath, 0o644);
|
||||
}
|
||||
});
|
||||
|
||||
it('should include personaDisplayName in previews', () => {
|
||||
const pieceYaml = `name: test-display
|
||||
initial_movement: step1
|
||||
max_iterations: 1
|
||||
|
||||
movements:
|
||||
- name: step1
|
||||
persona: agent
|
||||
persona_name: Custom Agent Name
|
||||
instruction: "Do something"
|
||||
`;
|
||||
|
||||
const piecePath = join(tempDir, 'test-display.yaml');
|
||||
writeFileSync(piecePath, pieceYaml);
|
||||
|
||||
const result = getPieceDescription(piecePath, tempDir, 1);
|
||||
|
||||
expect(result.movementPreviews).toHaveLength(1);
|
||||
expect(result.movementPreviews[0].personaDisplayName).toBe('Custom Agent Name');
|
||||
});
|
||||
});
|
||||
|
||||
@ -11,7 +11,7 @@ import { fetchIssue, formatIssueAsTask, checkGhCli, parseIssueNumbers, type GitH
|
||||
import { selectAndExecuteTask, determinePiece, saveTaskFromInteractive, createIssueFromTask, type SelectAndExecuteOptions } from '../../features/tasks/index.js';
|
||||
import { executePipeline } from '../../features/pipeline/index.js';
|
||||
import { interactiveMode } from '../../features/interactive/index.js';
|
||||
import { getPieceDescription } from '../../infra/config/index.js';
|
||||
import { getPieceDescription, loadGlobalConfig } from '../../infra/config/index.js';
|
||||
import { DEFAULT_PIECE_NAME } from '../../shared/constants.js';
|
||||
import { program, resolvedCwd, pipelineMode } from './program.js';
|
||||
import { resolveAgentOverrides, parseCreateWorktreeOption, isDirectTask } from './helpers.js';
|
||||
@ -124,7 +124,9 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const pieceContext = getPieceDescription(pieceId, resolvedCwd);
|
||||
const globalConfig = loadGlobalConfig();
|
||||
const previewCount = globalConfig.interactivePreviewMovements;
|
||||
const pieceContext = getPieceDescription(pieceId, resolvedCwd, previewCount);
|
||||
const result = await interactiveMode(resolvedCwd, initialInput, pieceContext);
|
||||
|
||||
switch (result.action) {
|
||||
|
||||
@ -67,6 +67,8 @@ export interface GlobalConfig {
|
||||
preventSleep?: boolean;
|
||||
/** Enable notification sounds (default: true when undefined) */
|
||||
notificationSound?: boolean;
|
||||
/** Number of movement previews to inject into interactive mode (0 to disable, max 10) */
|
||||
interactivePreviewMovements?: number;
|
||||
/** Number of tasks to run concurrently in takt run (default: 1 = sequential) */
|
||||
concurrency: number;
|
||||
}
|
||||
|
||||
@ -318,6 +318,8 @@ export const GlobalConfigSchema = z.object({
|
||||
prevent_sleep: z.boolean().optional(),
|
||||
/** Enable notification sounds (default: true when undefined) */
|
||||
notification_sound: z.boolean().optional(),
|
||||
/** Number of movement previews to inject into interactive mode (0 to disable, max 10) */
|
||||
interactive_preview_movements: z.number().int().min(0).max(10).optional().default(3),
|
||||
/** Number of tasks to run concurrently in takt run (default: 1 = sequential, max: 10) */
|
||||
concurrency: z.number().int().min(1).max(10).optional().default(1),
|
||||
});
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
loadSessionState,
|
||||
clearSessionState,
|
||||
type SessionState,
|
||||
type MovementPreview,
|
||||
} from '../../infra/config/index.js';
|
||||
import { isQuietMode } from '../../shared/context.js';
|
||||
import { getProvider, type ProviderType } from '../../infra/providers/index.js';
|
||||
@ -90,8 +91,44 @@ function resolveLanguage(lang?: Language): 'en' | 'ja' {
|
||||
return lang === 'ja' ? 'ja' : 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format MovementPreview[] into a Markdown string for template injection.
|
||||
* Each movement is rendered with its persona and instruction content.
|
||||
*/
|
||||
export function formatMovementPreviews(previews: MovementPreview[], lang: 'en' | 'ja'): string {
|
||||
return previews.map((p, i) => {
|
||||
const toolsStr = p.allowedTools.length > 0
|
||||
? p.allowedTools.join(', ')
|
||||
: (lang === 'ja' ? 'なし' : 'None');
|
||||
const editStr = p.canEdit
|
||||
? (lang === 'ja' ? '可' : 'Yes')
|
||||
: (lang === 'ja' ? '不可' : 'No');
|
||||
const personaLabel = lang === 'ja' ? 'ペルソナ' : 'Persona';
|
||||
const instructionLabel = lang === 'ja' ? 'インストラクション' : 'Instruction';
|
||||
const toolsLabel = lang === 'ja' ? 'ツール' : 'Tools';
|
||||
const editLabel = lang === 'ja' ? '編集' : 'Edit';
|
||||
|
||||
const lines = [
|
||||
`### ${i + 1}. ${p.name} (${p.personaDisplayName})`,
|
||||
];
|
||||
if (p.personaContent) {
|
||||
lines.push(`**${personaLabel}:**`, p.personaContent);
|
||||
}
|
||||
if (p.instructionContent) {
|
||||
lines.push(`**${instructionLabel}:**`, p.instructionContent);
|
||||
}
|
||||
lines.push(`**${toolsLabel}:** ${toolsStr}`, `**${editLabel}:** ${editStr}`);
|
||||
return lines.join('\n');
|
||||
}).join('\n\n');
|
||||
}
|
||||
|
||||
function getInteractivePrompts(lang: 'en' | 'ja', pieceContext?: PieceContext) {
|
||||
const systemPrompt = loadTemplate('score_interactive_system_prompt', lang, {});
|
||||
const hasPreview = !!pieceContext?.movementPreviews?.length;
|
||||
const systemPrompt = loadTemplate('score_interactive_system_prompt', lang, {
|
||||
hasPiecePreview: hasPreview,
|
||||
pieceStructure: pieceContext?.pieceStructure ?? '',
|
||||
movementDetails: hasPreview ? formatMovementPreviews(pieceContext!.movementPreviews!, lang) : '',
|
||||
});
|
||||
const policyContent = loadTemplate('score_interactive_policy', lang, {});
|
||||
|
||||
return {
|
||||
@ -149,10 +186,15 @@ function buildSummaryPrompt(
|
||||
}
|
||||
|
||||
const hasPiece = !!pieceContext;
|
||||
const hasPreview = !!pieceContext?.movementPreviews?.length;
|
||||
const summaryMovementDetails = hasPreview
|
||||
? `\n### ${lang === 'ja' ? '処理するエージェント' : 'Processing Agents'}\n${formatMovementPreviews(pieceContext!.movementPreviews!, lang)}`
|
||||
: '';
|
||||
return loadTemplate('score_summary_system_prompt', lang, {
|
||||
pieceInfo: hasPiece,
|
||||
pieceName: pieceContext?.name ?? '',
|
||||
pieceDescription: pieceContext?.description ?? '',
|
||||
movementDetails: summaryMovementDetails,
|
||||
conversation,
|
||||
});
|
||||
}
|
||||
@ -220,6 +262,8 @@ export interface PieceContext {
|
||||
description: string;
|
||||
/** Piece structure (numbered list of movements) */
|
||||
pieceStructure: string;
|
||||
/** Movement previews (persona + instruction content for first N movements) */
|
||||
movementPreviews?: MovementPreview[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -11,7 +11,7 @@ import { stringify as stringifyYaml } from 'yaml';
|
||||
import { promptInput, confirm } from '../../../shared/prompt/index.js';
|
||||
import { success, info, error } from '../../../shared/ui/index.js';
|
||||
import { summarizeTaskName, type TaskFileData } from '../../../infra/task/index.js';
|
||||
import { getPieceDescription } from '../../../infra/config/index.js';
|
||||
import { getPieceDescription, loadGlobalConfig } from '../../../infra/config/index.js';
|
||||
import { determinePiece } from '../execute/selectAndExecute.js';
|
||||
import { createLogger, getErrorMessage } from '../../../shared/utils/index.js';
|
||||
import { isIssueReference, resolveIssueTask, parseIssueNumbers, createIssue } from '../../../infra/github/index.js';
|
||||
@ -151,7 +151,9 @@ export async function addTask(cwd: string, task?: string): Promise<void> {
|
||||
}
|
||||
piece = pieceId;
|
||||
|
||||
const pieceContext = getPieceDescription(pieceId, cwd);
|
||||
const globalConfig = loadGlobalConfig();
|
||||
const previewCount = globalConfig.interactivePreviewMovements;
|
||||
const pieceContext = getPieceDescription(pieceId, cwd, previewCount);
|
||||
|
||||
// Interactive mode: AI conversation to refine task
|
||||
const result = await interactiveMode(cwd, undefined, pieceContext);
|
||||
|
||||
@ -35,6 +35,7 @@ function createDefaultGlobalConfig(): GlobalConfig {
|
||||
logLevel: 'info',
|
||||
provider: 'claude',
|
||||
enableBuiltinPieces: true,
|
||||
interactivePreviewMovements: 3,
|
||||
concurrency: 1,
|
||||
};
|
||||
}
|
||||
@ -107,6 +108,7 @@ export class GlobalConfigManager {
|
||||
branchNameStrategy: parsed.branch_name_strategy,
|
||||
preventSleep: parsed.prevent_sleep,
|
||||
notificationSound: parsed.notification_sound,
|
||||
interactivePreviewMovements: parsed.interactive_preview_movements,
|
||||
concurrency: parsed.concurrency,
|
||||
};
|
||||
validateProviderModelCompatibility(config.provider, config.model);
|
||||
@ -177,6 +179,9 @@ export class GlobalConfigManager {
|
||||
if (config.notificationSound !== undefined) {
|
||||
raw.notification_sound = config.notificationSound;
|
||||
}
|
||||
if (config.interactivePreviewMovements !== undefined) {
|
||||
raw.interactive_preview_movements = config.interactivePreviewMovements;
|
||||
}
|
||||
if (config.concurrency !== undefined && config.concurrency > 1) {
|
||||
raw.concurrency = config.concurrency;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ export {
|
||||
loadAllPiecesWithSources,
|
||||
listPieces,
|
||||
listPieceEntries,
|
||||
type MovementPreview,
|
||||
type PieceDirEntry,
|
||||
type PieceSource,
|
||||
type PieceWithSource,
|
||||
|
||||
@ -20,6 +20,7 @@ export {
|
||||
loadAllPiecesWithSources,
|
||||
listPieces,
|
||||
listPieceEntries,
|
||||
type MovementPreview,
|
||||
type PieceDirEntry,
|
||||
type PieceSource,
|
||||
type PieceWithSource,
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* using the priority chain: project-local → user → builtin.
|
||||
*/
|
||||
|
||||
import { existsSync, readdirSync, statSync } from 'node:fs';
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
||||
import { join, resolve, isAbsolute } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import type { PieceConfig, PieceMovement } from '../../../core/models/index.js';
|
||||
@ -176,22 +176,100 @@ function buildWorkflowString(movements: PieceMovement[]): string {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export interface MovementPreview {
|
||||
/** Movement name (e.g., "plan") */
|
||||
name: string;
|
||||
/** Persona display name (e.g., "Planner") */
|
||||
personaDisplayName: string;
|
||||
/** Persona prompt content (read from personaPath file) */
|
||||
personaContent: string;
|
||||
/** Instruction template content (already resolved at parse time) */
|
||||
instructionContent: string;
|
||||
/** Allowed tools for this movement */
|
||||
allowedTools: string[];
|
||||
/** Whether this movement can edit files */
|
||||
canEdit: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build movement previews for the first N movements of a piece.
|
||||
* Follows the execution order: initialMovement → rules[0].next → ...
|
||||
*
|
||||
* @param piece - Loaded PieceConfig
|
||||
* @param maxCount - Maximum number of previews to build
|
||||
* @returns Array of MovementPreview (may be shorter than maxCount)
|
||||
*/
|
||||
function buildMovementPreviews(piece: PieceConfig, maxCount: number): MovementPreview[] {
|
||||
if (maxCount <= 0 || piece.movements.length === 0) return [];
|
||||
|
||||
const movementMap = new Map<string, PieceMovement>();
|
||||
for (const m of piece.movements) {
|
||||
movementMap.set(m.name, m);
|
||||
}
|
||||
|
||||
const previews: MovementPreview[] = [];
|
||||
const visited = new Set<string>();
|
||||
let currentName: string | undefined = piece.initialMovement;
|
||||
|
||||
while (currentName && previews.length < maxCount) {
|
||||
if (currentName === 'COMPLETE' || currentName === 'ABORT') break;
|
||||
if (visited.has(currentName)) break;
|
||||
visited.add(currentName);
|
||||
|
||||
const movement = movementMap.get(currentName);
|
||||
if (!movement) break;
|
||||
|
||||
let personaContent = '';
|
||||
if (movement.personaPath) {
|
||||
try {
|
||||
personaContent = readFileSync(movement.personaPath, 'utf-8');
|
||||
} catch (err) {
|
||||
log.debug('Failed to read persona file for preview', {
|
||||
path: movement.personaPath,
|
||||
error: getErrorMessage(err),
|
||||
});
|
||||
}
|
||||
} else if (movement.persona) {
|
||||
personaContent = movement.persona;
|
||||
}
|
||||
|
||||
previews.push({
|
||||
name: movement.name,
|
||||
personaDisplayName: movement.personaDisplayName,
|
||||
personaContent,
|
||||
instructionContent: movement.instructionTemplate,
|
||||
allowedTools: movement.allowedTools ?? [],
|
||||
canEdit: movement.edit === true,
|
||||
});
|
||||
|
||||
const nextName = movement.rules?.[0]?.next;
|
||||
if (!nextName) break;
|
||||
currentName = nextName;
|
||||
}
|
||||
|
||||
return previews;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get piece description by identifier.
|
||||
* Returns the piece name, description, and workflow structure.
|
||||
* Returns the piece name, description, workflow structure, and optional movement previews.
|
||||
*/
|
||||
export function getPieceDescription(
|
||||
identifier: string,
|
||||
projectCwd: string,
|
||||
): { name: string; description: string; pieceStructure: string } {
|
||||
previewCount?: number,
|
||||
): { name: string; description: string; pieceStructure: string; movementPreviews: MovementPreview[] } {
|
||||
const piece = loadPieceByIdentifier(identifier, projectCwd);
|
||||
if (!piece) {
|
||||
return { name: identifier, description: '', pieceStructure: '' };
|
||||
return { name: identifier, description: '', pieceStructure: '', movementPreviews: [] };
|
||||
}
|
||||
return {
|
||||
name: piece.name,
|
||||
description: piece.description ?? '',
|
||||
pieceStructure: buildWorkflowString(piece.movements),
|
||||
movementPreviews: previewCount && previewCount > 0
|
||||
? buildMovementPreviews(piece, previewCount)
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -138,7 +138,8 @@ export function resolveRefToContent(
|
||||
}
|
||||
|
||||
if (facetType && context) {
|
||||
return resolveFacetByName(ref, facetType, context);
|
||||
const facetContent = resolveFacetByName(ref, facetType, context);
|
||||
if (facetContent !== undefined) return facetContent;
|
||||
}
|
||||
|
||||
return resolveResourceContent(ref, pieceDir);
|
||||
|
||||
@ -13,7 +13,7 @@ Focus on creating task instructions for the piece. Do not execute tasks or inves
|
||||
| Principle | Standard |
|
||||
|-----------|----------|
|
||||
| Focus on instruction creation | Task execution is always the piece's job |
|
||||
| Restrain investigation | Do not investigate unless explicitly requested |
|
||||
| Smart delegation | Delegate what agents can investigate on their own |
|
||||
| Concise responses | Key points only. Avoid verbose explanations |
|
||||
|
||||
## Understanding User Intent
|
||||
@ -28,19 +28,19 @@ The user is NOT asking YOU to do the work, but asking you to create task instruc
|
||||
|
||||
## Investigation Guidelines
|
||||
|
||||
### When Investigation IS Appropriate (Rare)
|
||||
### When Investigation IS Appropriate
|
||||
|
||||
Only when the user explicitly asks YOU to investigate:
|
||||
- "Read the README to understand the project structure"
|
||||
- "Read file X to see what it does"
|
||||
- "What does this project do?"
|
||||
When it improves instruction quality:
|
||||
- Verifying file or module existence (narrowing targets)
|
||||
- Understanding project structure (improving instruction accuracy)
|
||||
- When the user explicitly asks you to investigate
|
||||
|
||||
### When Investigation is NOT Appropriate (Most Cases)
|
||||
### When Investigation is NOT Appropriate
|
||||
|
||||
When the user is describing a task for the piece:
|
||||
- "Review the changes" → Create instructions without investigating
|
||||
- "Fix the code" → Create instructions without investigating
|
||||
- "Implement X" → Create instructions without investigating
|
||||
When agents can investigate on their own:
|
||||
- Implementation details (code internals, dependency analysis)
|
||||
- Determining how to make changes
|
||||
- Running tests or builds
|
||||
|
||||
## Strict Requirements
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
template: score_interactive_system_prompt
|
||||
role: system prompt for interactive planning mode
|
||||
vars: (none)
|
||||
vars: hasPiecePreview, pieceStructure, movementDetails
|
||||
caller: features/interactive
|
||||
-->
|
||||
# Interactive Mode Assistant
|
||||
@ -24,3 +24,22 @@ Handles TAKT's interactive mode, conversing with users to create task instructio
|
||||
- Investigate codebase, understand prerequisites, identify target files (piece's job)
|
||||
- Execute tasks (piece's job)
|
||||
- Mention slash commands
|
||||
{{#if hasPiecePreview}}
|
||||
|
||||
## Piece Structure
|
||||
|
||||
This task will be processed through the following workflow:
|
||||
{{pieceStructure}}
|
||||
|
||||
### Agent Details
|
||||
|
||||
The following agents will process the task sequentially. Understand each agent's capabilities and instructions to improve the quality of your task instructions.
|
||||
|
||||
{{movementDetails}}
|
||||
|
||||
### Delegation Guidance
|
||||
|
||||
- Do not include excessive detail in instructions for things the agents above can investigate and determine on their own
|
||||
- Clearly include information that agents cannot resolve on their own (user intent, priorities, constraints, etc.)
|
||||
- Delegate codebase investigation, implementation details, and dependency analysis to the agents
|
||||
{{/if}}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
template: score_summary_system_prompt
|
||||
role: system prompt for conversation-to-task summarization
|
||||
vars: pieceInfo, pieceName, pieceDescription, conversation
|
||||
vars: pieceInfo, pieceName, pieceDescription, movementDetails, conversation
|
||||
caller: features/interactive
|
||||
-->
|
||||
You are a task summarizer. Convert the conversation into a concrete task instruction for the planning step.
|
||||
@ -18,6 +18,7 @@ Requirements:
|
||||
## Destination of Your Task Instruction
|
||||
This task instruction will be passed to the "{{pieceName}}" piece.
|
||||
Piece description: {{pieceDescription}}
|
||||
{{movementDetails}}
|
||||
|
||||
Create the instruction in the format expected by this piece.
|
||||
{{/if}}
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
| 原則 | 基準 |
|
||||
|------|------|
|
||||
| 指示書作成に専念 | タスク実行は常にピースの仕事 |
|
||||
| 調査の抑制 | 明示的な依頼がない限り調査しない |
|
||||
| スマートな委譲 | エージェントが調査できる内容は委ねる |
|
||||
| 簡潔な返答 | 要点のみ。冗長な説明を避ける |
|
||||
|
||||
## ユーザーの意図の理解
|
||||
@ -28,19 +28,19 @@
|
||||
|
||||
## 調査の判断基準
|
||||
|
||||
### 調査してよい場合(稀)
|
||||
### 調査してよい場合
|
||||
|
||||
ユーザーが明示的に「あなた」に調査を依頼した場合のみ:
|
||||
- 「READMEを読んでプロジェクト構造を理解して」
|
||||
- 「ファイルXを読んで何をしているか見て」
|
||||
- 「このプロジェクトは何をするもの?」
|
||||
指示書の質を上げるために有益な場合:
|
||||
- ファイルやモジュールの存在確認(対象の絞り込み)
|
||||
- プロジェクト構造の把握(指示書の精度向上)
|
||||
- ユーザーが明示的に調査を依頼した場合
|
||||
|
||||
### 調査してはいけない場合(ほとんど)
|
||||
### 調査しない場合
|
||||
|
||||
ユーザーがピース向けのタスクを説明している場合:
|
||||
- 「変更をレビューして」→ 調査せずに指示書を作成
|
||||
- 「コードを修正して」→ 調査せずに指示書を作成
|
||||
- 「Xを実装して」→ 調査せずに指示書を作成
|
||||
エージェントが自分で調査できる内容:
|
||||
- 実装の詳細(コードの中身、依存関係の解析)
|
||||
- 変更方法の特定(どう修正するか)
|
||||
- テスト・ビルドの実行
|
||||
|
||||
## 厳守事項
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
template: score_interactive_system_prompt
|
||||
role: system prompt for interactive planning mode
|
||||
vars: (none)
|
||||
vars: hasPiecePreview, pieceStructure, movementDetails
|
||||
caller: features/interactive
|
||||
-->
|
||||
# 対話モードアシスタント
|
||||
@ -24,3 +24,22 @@ TAKTの対話モードを担当し、ユーザーと会話してピース実行
|
||||
- コードベース調査、前提把握、対象ファイル特定(ピースの仕事)
|
||||
- タスクの実行(ピースの仕事)
|
||||
- スラッシュコマンドへの言及
|
||||
{{#if hasPiecePreview}}
|
||||
|
||||
## ピース構成
|
||||
|
||||
このタスクは以下のワークフローで処理されます:
|
||||
{{pieceStructure}}
|
||||
|
||||
### エージェント詳細
|
||||
|
||||
以下のエージェントが順次タスクを処理します。各エージェントの能力と指示内容を理解し、指示書の質を高めてください。
|
||||
|
||||
{{movementDetails}}
|
||||
|
||||
### 委譲ガイダンス
|
||||
|
||||
- 上記エージェントが自ら調査・判断できる内容は、指示書に過度な詳細を含める必要はありません
|
||||
- エージェントが自力で解決できない情報(ユーザーの意図、優先度、制約条件など)を指示書に明確に含めてください
|
||||
- コードベースの調査、実装詳細の特定、依存関係の解析はエージェントに委ねてください
|
||||
{{/if}}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
template: score_summary_system_prompt
|
||||
role: system prompt for conversation-to-task summarization
|
||||
vars: pieceInfo, pieceName, pieceDescription, conversation
|
||||
vars: pieceInfo, pieceName, pieceDescription, movementDetails, conversation
|
||||
caller: features/interactive
|
||||
-->
|
||||
あなたはTAKTの対話モードを担当しています。これまでの会話内容を、ピース実行用の具体的なタスク指示書に変換してください。
|
||||
@ -25,6 +25,7 @@
|
||||
## あなたが作成する指示書の行き先
|
||||
このタスク指示書は「{{pieceName}}」ピースに渡されます。
|
||||
ピースの内容: {{pieceDescription}}
|
||||
{{movementDetails}}
|
||||
|
||||
指示書は、このピースが期待する形式で作成してください。
|
||||
{{/if}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user