feat: flow 第 1 部分(骨架、研究开始、第零轮)与第零轮检验
按任务书 12 实现 src/flow:从账本现算下一步的纯函数、主循环、恢复入口、派模型任务和跑正式运行两个共用动作、冻结配置、基线、第零轮三个研究者阶段和入口分配,走到第 1 轮时停下。materials 导出 finalEntries、加 researcherId 和 StudyRecord。 审阅采纳两条:研究编号本机已用过时冻结前报错;docker run 没启动时写完结果就停下,不在本进程里无限起新编号。 eval/ 新增第零轮检验:起研究命令行、回放假模型、检验用 Jev 客户端、六问质量评估、16-real-1 回放、杀进程与两份同时跑。tsc 与 depcruise 也检查 eval/,新增 eval 只经 index.ts 用正式代码的规则。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { copyFileSync, mkdtempSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { entryId } from "../../src/materials/index.ts";
|
||||
import type { ReplayItem, ReplayScript } from "../lib/replay.ts";
|
||||
|
||||
// 从参考项目 16-real-1 的账本取第零轮的提交,按任务书 12 第 5.3 节转成新项目的格式,写成回放脚本。
|
||||
// 原账本只读:先复制到临时目录,再以只读方式打开复制件,原目录里不写任何东西。
|
||||
// node eval/round-zero/extract-16.ts
|
||||
|
||||
const SOURCE = "/home/iomgaa/rb-acceptance/16-real-1/board.sqlite";
|
||||
export const REPLAY_FILE = "eval/round-zero/replay-16-real-1.json";
|
||||
const PHASE_STAGE: Record<string, string> = { "entry-draft": "entryDraft", "entry-discuss": "discussion", "entry-claim": "claim" };
|
||||
|
||||
const copy = join(mkdtempSync(join(tmpdir(), "inforsi-rb16-")), "board.sqlite");
|
||||
copyFileSync(SOURCE, copy);
|
||||
const db = new DatabaseSync(copy, { readOnly: true });
|
||||
|
||||
type OldEntry = { name: string; startFrom: string; whyWorth: string };
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT st.phase, st.id AS stage, s.actor, s.version, s.created_seq AS seq, s.payload
|
||||
FROM submissions s JOIN stages st ON st.id = s.stage_id
|
||||
WHERE st.phase IN ('entry-draft', 'entry-discuss', 'entry-claim') ORDER BY s.created_seq`,
|
||||
)
|
||||
.all() as Array<{ phase: string; stage: string; actor: string; version: number; seq: number; payload: string }>;
|
||||
const fact = db.prepare("SELECT payload FROM facts WHERE kind = 'entry-assignment'").all() as Array<{ payload: string }>;
|
||||
db.close();
|
||||
if (fact.length !== 1) throw new Error(`entry-assignment 应有 1 条,实有 ${fact.length} 条`);
|
||||
|
||||
/** 当年入口 {name, startFrom, whyWorth} → 新项目 {name, body}。 */
|
||||
const entry = (e: OldEntry) => ({ name: e.name, body: `从哪里开始看:${e.startFrom}\n\n为什么值得走:${e.whyWorth}` });
|
||||
/** 当年入口编号 researcher-1-e4 → 新项目 e-researcher-1-4。 */
|
||||
function newId(old: string): string {
|
||||
const m = /^(researcher-\d+)-e(\d+)$/.exec(old);
|
||||
if (m === null) throw new Error(`入口编号 ${old} 不是 researcher-<i>-e<n> 的写法`);
|
||||
return entryId(m[1]!, Number(m[2]));
|
||||
}
|
||||
|
||||
const models: Record<string, ReplayItem[]> = {};
|
||||
for (const r of rows) {
|
||||
const p = JSON.parse(r.payload) as { entries?: OldEntry[]; body?: string; choices?: string[] };
|
||||
const payload =
|
||||
r.phase === "entry-draft"
|
||||
? { entries: p.entries!.map(entry) }
|
||||
: r.phase === "entry-discuss"
|
||||
? { body: p.body!, ...(p.entries === undefined ? {} : { entries: p.entries.map(entry) }) }
|
||||
: { choices: p.choices!.map(newId) };
|
||||
(models[r.actor] ??= []).push({ from: `${r.stage} ${PHASE_STAGE[r.phase]} seq ${r.seq} v${r.version}`, arguments: { payload } });
|
||||
}
|
||||
|
||||
const old = JSON.parse(fact[0]!.payload) as { assignments: Array<{ researcher: string; entryId: string; rank: number | null }>; entries: Array<{ entryId: string; author: string }> };
|
||||
const expected = old.assignments.map((a) => ({ researcher: a.researcher, entry: newId(a.entryId), rank: a.rank }));
|
||||
const script: ReplayScript & { expected: typeof expected; expectedEntries: string[] } = {
|
||||
source: `${SOURCE}(第零轮 submissions,created_seq ${rows[0]!.seq}–${rows.at(-1)!.seq})`,
|
||||
models,
|
||||
expected,
|
||||
expectedEntries: old.entries.map((e) => newId(e.entryId)),
|
||||
};
|
||||
writeFileSync(REPLAY_FILE, `${JSON.stringify(script, null, 2)}\n`);
|
||||
|
||||
// 核对:三阶段各几份、作者、版本,以及当年的分配。
|
||||
console.log(`共 ${rows.length} 份提交,序号 ${rows[0]!.seq}–${rows.at(-1)!.seq}`);
|
||||
for (const phase of Object.keys(PHASE_STAGE)) {
|
||||
const these = rows.filter((r) => r.phase === phase);
|
||||
console.log(`${phase}:${these.length} 份;${these.map((r) => `${r.actor}@${r.seq}(v${r.version}${JSON.parse(r.payload).entries ? `,${JSON.parse(r.payload).entries.length} 个入口` : ""})`).join("、")}`);
|
||||
}
|
||||
console.log(`当年分配:${expected.map((a) => `${a.researcher} → ${a.entry}(第 ${a.rank} 志愿)`).join(";")}`);
|
||||
console.log(`当年最终入口:${script.expectedEntries.join("、")}`);
|
||||
console.log(`已写 ${REPLAY_FILE}`);
|
||||
Reference in New Issue
Block a user