Files
maestro/test/memory-l1.test.ts
T
wangjia ecea1ae9b6 feat(agent): L1 记忆注入——重试任务带上次失败原因喂回 prompt
- store.lastRunErrorOf(taskId):取最近一次失败 run 的 error(任意 kind)
- orchestrator.claimOne:写 job.json 时填充 task.lastRunError 下发 worker
- runner 三处 prompt(executor/planner/conflict)注入「## 上次失败原因」段(截断 2000 字符)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 22:09:06 +08:00

60 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buildPrompt, buildPlannerPrompt, buildConflictPrompt } from '../src/executor/runner.js';
import { Store } from '../src/store/index.js';
import type { Task } from '../src/model/types.js';
function fakeTask(over: Partial<Task> = {}): Task {
return {
id: 'tsk_x', title: '改个东西', complexity: 'medium', depth: 1,
operations: '把 A 改成 B', spec: null, plan: null, deps: [],
...over,
} as Task;
}
const SECTION = '上次失败原因';
test('L1: 无 lastRunError 时三处 prompt 都不含「上次失败原因」段', () => {
const t = fakeTask();
assert.ok(!buildPrompt(t).includes(SECTION));
assert.ok(!buildPlannerPrompt(fakeTask({ complexity: 'medium' }), 'spec').includes(SECTION));
assert.ok(!buildPlannerPrompt(fakeTask({ complexity: 'hard' }), 'decompose').includes(SECTION));
assert.ok(!buildConflictPrompt(t, ['a.ts']).includes(SECTION));
});
test('L1: 有 lastRunError 时三处 prompt 注入失败原因', () => {
const err = 'verify 失败:npm test 退出码 1\nFAIL src/foo.test.ts';
const t = fakeTask({ lastRunError: err });
for (const p of [
buildPrompt(t),
buildPlannerPrompt(fakeTask({ complexity: 'medium', lastRunError: err }), 'spec'),
buildPlannerPrompt(fakeTask({ complexity: 'hard', lastRunError: err }), 'decompose'),
buildConflictPrompt(t, ['a.ts']),
]) {
assert.ok(p.includes(SECTION), '应含失败段标题');
assert.ok(p.includes('npm test 退出码 1'), '应含具体错误');
}
});
test('L1: 超长错误被截断到 2000 字符', () => {
const err = 'X'.repeat(5000);
const p = buildPrompt(fakeTask({ lastRunError: err }));
assert.ok(p.includes('已截断'));
assert.ok(!p.includes('X'.repeat(2100)));
});
test('lastRunErrorOf 取最近一次失败 run 的错误(任意 kind', () => {
const s = new Store(':memory:');
const p = s.createProject({ name: 'l1', repoPath: '/tmp/l1-' + Math.random() });
const t = s.createTask({ projectId: p.id, title: 'feat', complexity: 'easy' });
assert.equal(s.lastRunErrorOf(t.id), null, '无失败 run → null');
s.setOperations(t.id, 'do it');
s.transition(t.id, 'queued');
s.transition(t.id, 'executing');
const run = s.startRun(t.id, 'executor', { worktree: '/tmp/w', branch: 'b' });
s.failTaskAttempt(t.id, run.id, '第一次失败:编译错误');
assert.equal(s.lastRunErrorOf(t.id), '第一次失败:编译错误');
s.close();
});