import { mkdirSync, writeFileSync } from "node:fs"; import { join, resolve } from "node:path"; import { Board, protocolMetrics, type TaskMaterial, } from "../../src/materials/index.ts"; import { checkoutVersion, compareVersions } from "../../src/workspace/index.ts"; import { candidateHistory, jsonFile } from "../lib/forward.ts"; import { ofKind, readLedger } from "../lib/ledger.ts"; // 任务书16五项质量评估的候选审阅材料;不判路线、不代替全新上下文逐条阅读。 // node eval/forward/export.ts <研究目录> <全新绝对输出目录> const [study, output, ...extra] = process.argv.slice(2); if (!study || !output || extra.length || output !== resolve(output)) throw new Error( "用法:node eval/forward/export.ts <研究目录> <全新绝对输出目录>", ); mkdirSync(output); // 不覆盖以前的审阅材料。 const records = readLedger(study); const board = new Board(records); const repo = join(study, "repo.git"); const batch = board.decision("batch", 1)!; const baseline = board.code(board.baselineOf(1)); const plans = ofKind(records, "runPlanned").filter( (p) => p.content.round === 1 && p.content.purpose === "forward", ); const index = []; for (const candidate of batch.output.chosen) { const dir = join(output, candidate); mkdirSync(dir); const history = candidateHistory(records, candidate); const candidatePlans = plans.filter((p) => p.content.object === candidate); const started = candidatePlans.filter((p) => ofKind(records, "runStarted").some((s) => s.content.run === p.content.run), ); const protocol = ofKind(records, "protocol") .filter((p) => p.content.candidate === candidate) .at(-1)!; jsonFile(join(dir, "protocol.json"), { record: protocol, resolvedMetrics: protocolMetrics(board, board.protocol(candidate)), }); mkdirSync(join(dir, "baseline")); checkoutVersion(repo, baseline.version, join(dir, "baseline")); for (const attempt of history.attempts) { const path = join(dir, `code-${attempt.code.seq}`); mkdirSync(path); checkoutVersion(repo, attempt.code.content.version, path); jsonFile( join(dir, `changes-${attempt.code.seq}.json`), compareVersions(repo, baseline.version, attempt.code.content.version), ); } // 首轮里每个候选只正式跑一个已通过版本;无启动的候选仍导出历史,但不冒称有最终通过代码。 const firstRun = started[0]; const finalCode = firstRun === undefined ? null : ofKind(records, "code") .filter((c) => c.content.object === candidate && c.seq < firstRun.seq) .at(-1)!; if (finalCode !== null) { mkdirSync(join(dir, "final")); checkoutVersion(repo, finalCode.content.version, join(dir, "final")); } const finalAttempt = finalCode === null ? null : history.attempts.find((a) => a.code.seq === finalCode.seq)!; jsonFile(join(dir, "final-implementation.json"), finalAttempt); jsonFile(join(dir, "attempts.json"), history.attempts); jsonFile(join(dir, "verifications.json"), history.verifications); const calls = ofKind(records, "callStarted").filter((c) => { const material = c.content.materials as TaskMaterial; return ( material.object === candidate && ["implementation", "repair", "verification"].includes(material.kind) ); }); jsonFile( join(dir, "calls.json"), calls.map((c) => ({ record: c, sessions: join(study, "calls", c.content.call, "sessions"), })), ); jsonFile( join(dir, "rejections.json"), ofKind(records, "rejection").filter( (r) => r.content.target === candidate && ["implementation", "repair", "verification"].includes(r.content.kind), ), ); const manifest = { study, candidate, protocolSeq: protocol.seq, baselineVersion: baseline.version, finalCode, ran: started.length > 0, runs: candidatePlans.map((p) => ({ plan: p, result: ofKind(records, "runResult").find( (r) => r.content.run === p.content.run, ) ?? null, artifacts: join(study, "runs", p.content.run), })), }; jsonFile(join(dir, "manifest.json"), manifest); writeFileSync( join(dir, "review.txt"), `任务书16第3.2节:只读本候选,分两步评估,中文直接回复,不写文件。\n` + `第一步只读 protocol.json(冻结协议全文及引用指标原定义)、baseline/、final/、final-implementation.json(正文、位置表、concerns)。\n` + `逐条实现条款、因子、保障检查漏做、协议之外的改进、位置表是否准确,每条附代码文件和行号。先形成独立结论,再打开核验历史。\n` + `第二步读 verifications.json、attempts.json、code-<序号>/、changes-<序号>.json;每次核验对照其中code指向的那一版,不拿最终修好版本倒判历史。判断该拒却通过、不该拒却拒绝;修复是否只改指出处。\n` + `需要时读 calls.json 和 rejections.json;模型材料和原始会话都可追溯。不把提交格式退回当成核验失败。\n` + `答案路径只核对每个可能交出最终答案分支的标签,不预测实际走哪个分支。报告先写实现忠实度,再写逐次核验误判和修复范围。\n` + `按任务书15附录一路线代号归类冻结协议,另标是否完整高上限路线(起点、连续改进全部圆、多起点或扰动、合法收尾),附协议原文位置。\n` + `本候选${manifest.ran ? "已启动正式运行" : "未启动正式运行;没有final/,只评历史,不列成跑过的候选"}。\n`, ); index.push({ candidate, ran: manifest.ran, directory: dir, finalVersion: finalCode?.content.version ?? null, }); } jsonFile(join(output, "index.json"), index); console.log(`候选审阅包:${output}`);