feat(api): 任务改动 diff 端点——GET /api/tasks/:id/diff 按文件切分

结果评审需要看每个文件的具体改动。新增端点跑 git diff defaultBranch...branch,
按 diff --git 切分成 [{path, diff}],供前端点击文件展开查看。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-29 12:11:03 +08:00
parent 8765e61498
commit dc7c3030a5
+19 -1
View File
@@ -9,7 +9,7 @@ import { syncProject, hasTodoJson } from '../sync/todo-sync.js';
import { resolvedExecutorModels } from '../executor/models.js';
import { classifyComplexity, type ClassifierFn } from '../executor/classify.js';
import { acceptAndMerge } from '../executor/exec-merge.js';
import { createWorktree } from '../executor/worktree.js';
import { createWorktree, git } from '../executor/worktree.js';
import { resolveLogo, LOGO_MIME } from './logo.js';
import { readTranscript, TranscriptError } from '../executor/transcript.js';
import { createReadStream, createWriteStream, mkdirSync, readFileSync, statSync } from 'node:fs';
@@ -342,6 +342,24 @@ export function buildServer(opts: ApiOptions): FastifyInstance {
return store.listRuns(id);
});
// 任务改动 diff(结果评审用):git diff defaultBranch...branch,按文件切分返回,供点击文件看具体改动
app.get('/api/tasks/:id/diff', async (req) => {
const { id } = req.params as { id: string };
const task = store.getTask(id);
if (!task) throw new StoreError(`任务不存在: ${id}`);
const project = store.getProject(task.projectId);
const branch = task.result?.branch;
if (!project || !branch) return { files: [] };
let raw = '';
try { raw = await git(project.repoPath, ['diff', `${project.defaultBranch}...${branch}`]); } catch { return { files: [] }; }
const files = raw.split(/^diff --git /m).filter(Boolean).map((p) => {
const m = p.match(/^a\/(\S+)\s+b\/(\S+)/);
const path = m ? m[2] : (p.split('\n')[0] || '').trim();
return { path, diff: 'diff --git ' + p };
});
return { files };
});
// 实时流(SSE):跟随任务「最近一条 run」的 transcript 文件,把新写入的 agent 消息逐行推送。
// 活跃 runstatus=started= 真实时;已结束 run = 一次性回放后 end。客户端 EventSource 消费。
app.get('/api/tasks/:id/stream', (req, reply) => {