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 { 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(); });