maestro/src/engine/reflection/reflection-prompt.test.ts
oss-sync 5502478636
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (5b6df2f)
2026-06-10 08:40:41 +00:00

161 lines
5.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { buildSystemPrompt, buildUserPrompt } from './reflection-prompt.js';
import type { ReflectionInput } from './types.js';
function makeInput(overrides: Partial<ReflectionInput> = {}): ReflectionInput {
return {
originalJobId: 'job-1',
userId: 'u-1',
pieceName: 'chat',
pieceSource: 'builtin',
outcome: 'succeeded',
taskTitle: 'Summarize the report',
taskBody: 'Please summarize quarterly-report.pdf',
activityLogSummary: 'ReadPdf -> Write summary.md (2 iterations)',
postCompletionComments: [],
feedback: { rating: null, comment: null, tags: [] },
resultText: 'Wrote output/summary.md',
observedRevisions: {},
memoryIndex: '- [fact one](fact_one.md)',
memoryEntries: [],
pieceYaml: 'movements:\n - name: execute\n',
...overrides,
};
}
describe('buildSystemPrompt', () => {
it('forces submit_reflection tool-call-only output', () => {
const sys = buildSystemPrompt();
expect(sys).toContain('submit_reflection');
});
it('states the memory rules: non-trivial lessons, type tagging, 3-change cap, abstain path', () => {
const sys = buildSystemPrompt();
expect(sys).toContain('非自明');
expect(sys).toContain('user | feedback | project | reference');
expect(sys).toContain('最大 3 件');
expect(sys).toContain('abstain_reason');
expect(sys).toContain('should_edit=false');
});
it('requires Why/How-to-apply lines for feedback/project entries', () => {
const sys = buildSystemPrompt();
expect(sys).toContain('**Why:**');
expect(sys).toContain('**How to apply:**');
});
it('hardens piece edits: full-YAML replacement and no engine sentinels in rules[].next', () => {
const sys = buildSystemPrompt();
expect(sys).toContain('完全置換');
expect(sys).toContain('COMPLETE / ABORT / ASK');
expect(sys).toContain('rules[].next');
});
it('is deterministic (no timestamps or randomness)', () => {
expect(buildSystemPrompt()).toBe(buildSystemPrompt());
});
});
describe('buildUserPrompt', () => {
it('includes task title, body, activity log summary, result and outcome', () => {
const prompt = buildUserPrompt(makeInput());
expect(prompt).toContain('title: Summarize the report');
expect(prompt).toContain('body: Please summarize quarterly-report.pdf');
expect(prompt).toContain('ReadPdf -> Write summary.md (2 iterations)');
expect(prompt).toContain('status: succeeded');
expect(prompt).toContain('result: Wrote output/summary.md');
});
it('contains all section headers', () => {
const prompt = buildUserPrompt(makeInput());
for (const header of [
'## 元タスク',
'## 活動ログ (圧縮済み)',
'## ジョブ後のユーザーコメント',
'## 明示フィードバック',
'## 結果',
'## 現在の memory スナップショット',
'## 現在の piece YAML',
]) {
expect(prompt).toContain(header);
}
});
it('renders post-completion comments with timestamp and author', () => {
const prompt = buildUserPrompt(makeInput({
postCompletionComments: [
{ author: 'alice', body: 'wrong file was summarized', createdAt: '2026-06-10T00:00:00Z' },
{ author: 'bob', body: 'please retry', createdAt: '2026-06-10T01:00:00Z' },
],
}));
expect(prompt).toContain('- [2026-06-10T00:00:00Z] alice: wrong file was summarized');
expect(prompt).toContain('- [2026-06-10T01:00:00Z] bob: please retry');
expect(prompt).not.toContain('(なし)');
});
it('shows (なし) when there are no post-completion comments', () => {
const prompt = buildUserPrompt(makeInput({ postCompletionComments: [] }));
expect(prompt).toContain('(なし)');
});
it('shows "rating: none" when no explicit feedback exists', () => {
const prompt = buildUserPrompt(makeInput());
expect(prompt).toContain('rating: none');
expect(prompt).not.toContain('rating: good');
expect(prompt).not.toContain('rating: bad');
});
it('renders rating, comment and tags when feedback exists', () => {
const prompt = buildUserPrompt(makeInput({
feedback: { rating: 'good', comment: 'nice work', tags: ['speed', 'quality'] },
}));
expect(prompt).toContain('rating: good');
expect(prompt).toContain('comment: nice work');
expect(prompt).toContain('tags: speed, quality');
});
it('omits comment/tags lines when feedback has neither', () => {
const prompt = buildUserPrompt(makeInput({
feedback: { rating: 'good', comment: null, tags: [] },
}));
expect(prompt).toContain('rating: good');
expect(prompt).not.toContain('comment:');
expect(prompt).not.toContain('tags:');
});
it('appends the bad-rating investigation directive only for rating=bad', () => {
const bad = buildUserPrompt(makeInput({
feedback: { rating: 'bad', comment: null, tags: [] },
}));
expect(bad).toContain('**低評価**');
const good = buildUserPrompt(makeInput({
feedback: { rating: 'good', comment: null, tags: [] },
}));
expect(good).not.toContain('**低評価**');
const none = buildUserPrompt(makeInput());
expect(none).not.toContain('**低評価**');
});
it('embeds the memory index, or (空) when the user has no memory', () => {
const withMemory = buildUserPrompt(makeInput());
expect(withMemory).toContain('- [fact one](fact_one.md)');
expect(withMemory).not.toContain('(空)');
const empty = buildUserPrompt(makeInput({ memoryIndex: '' }));
expect(empty).toContain('(空)');
});
it('wraps the current piece YAML in a yaml code fence', () => {
const prompt = buildUserPrompt(makeInput());
expect(prompt).toContain('```yaml\nmovements:\n - name: execute\n\n```');
});
it('does not truncate the activity log summary (truncation happens upstream in load-inputs)', () => {
const long = 'x'.repeat(20000);
const prompt = buildUserPrompt(makeInput({ activityLogSummary: long }));
expect(prompt).toContain(long);
});
});