eval: 第 1 轮检验脚本(跑、杀进程、导出去来历、盲评编号、Jev 三问、数字与过程)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 13:59:54 -04:00
parent f98d664a16
commit a2a1392f01
12 changed files with 1259 additions and 1 deletions
+52
View File
@@ -0,0 +1,52 @@
import { appendFileSync, existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import type { ChildProcess } from "node:child_process";
import { sleep } from "./study.ts";
// 账本里没有时间戳,墙钟时间由检验自己记:研究进程跑着的时候每隔一会儿看一眼账本,
// 新出现的每条记录记下"第一次看到它的时刻",写成 timeline.jsonl(每行 {seq, kind, at},at 是毫秒)。
// 只读打开账本、只查序号和种类,不经 Ledger.records()(那会每次重读、重查全部内容)。
// 时刻比真实写入晚最多一个间隔,用来算阶段级的墙钟足够。
const POLL_MS = 1000;
export interface TimelineRow {
seq: number;
kind: string;
at: number;
}
/** 已经记下的最大序号;文件还没有就是 0。接着跑时往同一个文件后面追加。 */
function lastSeq(file: string): number {
if (!existsSync(file)) return 0;
return readTimeline(file).reduce((m, r) => Math.max(m, r.seq), 0);
}
/** 研究进程退出前一直记;进程退出后再看最后一眼。调用前要先等 waitForLedger(表建好了才能读)。 */
export async function watchTimeline(studyDir: string, child: ChildProcess, file: string): Promise<void> {
let seen = lastSeq(file);
const look = () => {
const db = new DatabaseSync(join(studyDir, "ledger.sqlite"), { readOnly: true });
try {
const rows = db.prepare("SELECT seq, kind FROM records WHERE seq > ? ORDER BY seq").all(seen) as Array<{ seq: number; kind: string }>;
const at = Date.now();
for (const r of rows) appendFileSync(file, `${JSON.stringify({ seq: r.seq, kind: r.kind, at })}\n`);
if (rows.length > 0) seen = rows.at(-1)!.seq;
} finally {
db.close();
}
};
while (child.exitCode === null && child.signalCode === null) {
look();
await sleep(POLL_MS);
}
look();
}
export function readTimeline(file: string): TimelineRow[] {
return readFileSync(file, "utf8")
.split("\n")
.filter((l) => l !== "")
.map((l) => JSON.parse(l) as TimelineRow);
}