ecea1ae9b6
- 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>
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
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();
|
||
});
|