Files
InfoRSI/tests/materials.test.ts
T
iomgaa 446e26224e feat: 任务书 09,materials 模块与提示词
- src/materials:每种提交一个材料函数,生成给模型看的材料、第一条消息、对号清单、能读的代码版本;查询只在材料内按编号找;读提示词并算原始字节哈希;信念本按方法.md 6.10 现算。
- 账本里只存成 Json 的冻结协议、位置表、决定、结算的形状和编号规则由 materials 定义并导出。
- vocab 加 Checklist(对号清单)。
- prompts/:参考项目 3 份角色提示词、16 份任务说明,只做必要改动。
- TASK.md 补一句 unversionedNames 不存进代码版本。
- 方法.md 4.3 ① 改为"研究者任务"放信念本;结构.md vocab 上限 1150、补 materials 对外内容。

用户 2026-10-03 开工前的四个决定(选的都是推荐项):"甲:上限改 1150,只放对号清单";"甲:materials 定义并导出";"不放,改方法.md ① 的措辞";"按原提案挑"。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:08:46 -04:00

447 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { test } from "node:test";
import { beliefBook, callId, queryMaterial, readPrompts, type TaskMaterial } from "../src/materials/index.ts";
import { SUBMISSION_KINDS, type SubmissionKind } from "../src/vocab/index.ts";
import { tempDir } from "./ledger-helpers.ts";
import { CHANGES_C1, CRITIQUE_ID, everything, material, NONE, study, studyUpTo } from "./materials-helpers.ts";
// 每种任务至少一个测试:在真实账本里写到某一步,生成材料,查该有的有、不该有的没有、对号清单对。
// 预期值按方法.md 条文手写(研究进程见 materials-helpers.ts 的注释),不从实现推。
const headings = (m: TaskMaterial) => m.sections.map((s) => s.heading);
const has = (m: TaskMaterial, text: string) => assert.ok(everything(m).includes(text), `材料里应有"${text}"`);
const lacks = (m: TaskMaterial, text: string) => assert.ok(!everything(m).includes(text), `材料里不该有"${text}"`);
// ---------------------------------------------------------------------------
// 第零轮(方法.md 4.2)
// ---------------------------------------------------------------------------
test("入口:只有任务与基线;看不到别人已交的入口(4.2 闭门写入口)", (t) => {
const l = studyUpTo(t, "entryA1");
const m = material(l, "entry", "a2", "s0-entry", null, NONE);
assert.deepEqual(m.checklist, { kind: "entry", entries: { min: 2, max: 3 } });
has(m, "本轮基线 base:最终指标 0.96");
assert.deepEqual(m.readable, [{ version: "v-base", label: "本批基线", dir: "/code/v-base" }]);
lacks(m, "入口甲一");
assert.ok(!headings(m).includes("信念本"), "第零轮不放信念本(4.2:信念本不变)");
});
test("讨论:全部入口标明作者,编号按作者从 1 编", (t) => {
const l = studyUpTo(t, "discussionOpen");
const m = material(l, "discussion", "a1", "s0-d1", null, NONE);
assert.deepEqual(m.checklist, { kind: "discussion", entries: { min: 2, max: 3 } });
for (const x of ["e-a1-1:入口甲一", "e-a1-2:入口甲二", "e-a2-1:入口乙一", "e-a2-2:入口乙二"]) has(m, x);
assert.ok(headings(m).includes("入口编号对照"));
});
test("认领:整份替换后新清单接着编号,旧编号作废;至少排 min(N, 入口总数)", (t) => {
const l = studyUpTo(t, "claimOpen");
const m = material(l, "claim", "a2", "s0-claim", null, NONE);
// a1 讨论时整份换成两个新入口,接着 e-a1-2 往后编:e-a1-3、e-a1-4;a2 没换。N = 2,入口 4 个。
assert.deepEqual(m.checklist, { kind: "claim", entries: ["e-a1-3", "e-a1-4", "e-a2-1", "e-a2-2"], minChoices: 2 });
has(m, "e-a1-1(作废):入口甲一");
has(m, "甲的讨论");
has(m, "乙的讨论");
});
// ---------------------------------------------------------------------------
// 第 r 轮(方法.md 4.3)
// ---------------------------------------------------------------------------
test("第 1 轮提案:你认领的入口放在最前;没有上一轮;对号清单", (t) => {
const l = studyUpTo(t, "proposalOpen");
const m = material(l, "proposal", "a1", "s1-prop", null, NONE);
assert.deepEqual(headings(m).slice(0, 2), ["你自己的", "你认领的入口"]);
has(m, "e-a1-3:入口甲三");
has(m, "从这里开始看");
has(m, "这是第 1 轮,没有上一轮");
assert.deepEqual(m.checklist, {
kind: "proposal",
maxProposals: 2,
factorLimit: 3,
continuable: [],
reusableMetrics: [],
takenMetricIds: ["sum-of-radii", "wall-seconds"],
});
const h = headings(m);
assert.ok(h.indexOf("你自己的") < h.indexOf("公共材料") && h.indexOf("公共材料") < h.indexOf("信念本"), "先你自己的、后公共材料");
});
test("第 2 轮提案:上一轮全部收尾、上一轮实验员的提醒、可延续的因子、已登记指标;入口标为第 1 轮的起点", (t) => {
const l = studyUpTo(t, "proposal2Open");
const m = material(l, "proposal", "a2", "s2-prop", null, NONE);
has(m, "甲的收尾");
has(m, "乙的收尾");
has(m, "guard 可能挡掉一些摆法");
has(m, "第 1 轮的起点;之后是否沿用由你定");
assert.deepEqual(m.checklist, {
kind: "proposal",
maxProposals: 2,
factorLimit: 3,
continuable: ["r1-c1-p1", "r1-c1-p2", "r1-c2-p1"],
reusableMetrics: ["restart-count"],
takenMetricIds: ["sum-of-radii", "wall-seconds", "restart-count"],
});
for (const x of ["上一轮的收尾", "上一轮实验员实现时的提醒", '可以写进"延续"的因子', "已登记指标清单", "研究记录", "信念本"]) {
assert.ok(headings(m).includes(x), `应有一节"${x}"`);
}
});
test("第一次预测:给这个候选的原始提案;看不到别人刚交的第一次预测,也看不到作者自评", (t) => {
const l = studyUpTo(t, "firstA1");
const m = material(l, "firstPrediction", "a2", "s1-fp", "r1-c1", NONE);
assert.deepEqual(m.checklist, { kind: "firstPrediction", candidate: "r1-c1" });
has(m, "甲的提案正文");
lacks(m, "5.551");
lacks(m, "甲第一次理由");
lacks(m, "7.772");
for (const x of ["研究记录", "信念本"]) assert.ok(headings(m).includes(x));
});
test("挑刺:按原提案挑,对号清单是那条提案和它的因子", (t) => {
const l = studyUpTo(t, "critiqueOpen");
const m = material(l, "critique", "a2", "s1-crit", "r1-a1-1", NONE);
assert.deepEqual(m.checklist, { kind: "critique", proposal: "r1-a1-1", factors: ["p1", "p2"] });
has(m, "甲的提案正文");
lacks(m, "5.551");
lacks(m, "6.661");
});
test("回应:本人原提案和针对它的全部挑刺", (t) => {
const l = studyUpTo(t, "responseOpen");
const m = material(l, "response", "a1", "s1-resp", "r1-c1", NONE);
has(m, "乙的挑刺:扰动可能混杂(推测)");
assert.deepEqual(m.checklist, {
kind: "response",
candidate: "r1-c1",
critiques: [CRITIQUE_ID],
factorLimit: 3,
continuable: [],
reusableMetrics: [],
takenMetricIds: ["sum-of-radii", "wall-seconds"],
});
});
test("统一:全部被合并的原提案、针对它们的挑刺、要逐条体现的原提案", (t) => {
const l = studyUpTo(t, "responseOpen");
const m = material(l, "unification", "a1", "s1-resp", "r1-c1", NONE);
const c = m.checklist as Extract<TaskMaterial["checklist"], { kind: "unification" }>;
assert.deepEqual([c.sources, c.critiques], [["r1-a1-1"], [CRITIQUE_ID]]);
has(m, "甲的提案正文");
});
test("第二次预测:冻结协议、挑刺与答复配对、本人第一次的三个数;看不到别人的第一次和作者自评(4.3 ⑦)", (t) => {
const l = studyUpTo(t, "secondOpen");
const m = material(l, "secondPrediction", "a2", "s1-sp", "r1-c1", NONE);
assert.deepEqual(m.checklist, { kind: "secondPrediction", candidate: "r1-c1" });
has(m, "6.661 / 6.662 / 6.663");
has(m, "乙的挑刺:扰动可能混杂(推测)");
has(m, "作者的答复:甲答复:保留扰动");
lacks(m, "5.551");
lacks(m, "甲第一次理由");
lacks(m, "7.772");
});
test("解释:本轮运行、提醒、要给三个数的格、产物只进查询;看不到别人的第一次预测", (t) => {
const l = studyUpTo(t, "explOpen1");
const m = material(l, "explanation", "a2", "s1-expl", null, { changes: null, artifacts: { "run-1": { "stdout.log": "日志正文" } } });
assert.deepEqual(m.checklist, { kind: "explanation", cells: ["p1", "p2"], maxExplanations: 2 });
has(m, "分数 1.2");
// 正向运行的答案出自保障:换一句;方法原句只用在消融格(见收尾的测试)。
has(m, "这次答案出自保障 guard,分数可能主要是这条保障的效果。");
lacks(m, "本格答案出自保障");
has(m, "guard 可能挡掉一些摆法");
assert.ok(!m.firstMessage.includes("日志正文"), "产物不进第一条消息");
assert.equal(queryMaterial(m, "run-1/stdout.log"), "运行 run-1 的产物 stdout.log\n\n日志正文");
lacks(m, "5.551");
lacks(m, "8.882");
});
test("收尾:本轮采纳、选格、押的因子", (t) => {
const l = studyUpTo(t, "wrapOpen");
const m = material(l, "wrapUp", "a1", "s1-wrap", null, NONE);
assert.deepEqual(m.checklist, { kind: "wrapUp" });
has(m, "本轮采纳候选 r1-c1");
has(m, "本轮选格:测 p1");
has(m, "程序按方向收益押的因子:r1-c1-p1");
has(m, "r1-c1-p2 停滞扰动(停滞扰动的设定):候选 r1-c1,基线 base;没测:剩余次数不够");
has(m, "本格答案出自保障 guard,量到的可能是这条保障的效果,而不是关掉这个因子的效果;保障动手次数");
lacks(m, "7.772");
});
test("实现:冻结协议、指标定义、要交的位置表、起点代码;没有信念本、成绩和别的候选", (t) => {
const l = studyUpTo(t, "implOpen");
const m = material(l, "implementation", "x1", "s1-impl-c1", "r1-c1", NONE);
assert.deepEqual(m.checklist, { kind: "implementation", candidate: "r1-c1", clauses: ["c1", "c2"], factors: ["p1", "p2"], safeguards: ["guard"] });
assert.deepEqual(m.readable.map((r) => r.version), ["v-base"]);
for (const x of ["任务与本轮", "基线代码", "本任务要处理的东西", "要实现的指标定义", "要交的因子位置表"]) assert.ok(headings(m).includes(x));
assert.ok(!headings(m).includes("信念本"));
has(m, "restart-count");
lacks(m, "乙的提案正文");
lacks(m, "收缩半径修复");
lacks(m, "6.661");
});
test("修复:退回意见、上一次的位置表、上一次的改动", (t) => {
const l = studyUpTo(t, "verifyOpen");
l.append("submission", {
stageId: "s1-ver-c1",
author: "v1",
kind: "verification",
target: "r1-c1",
item: null,
version: 1,
jev: null,
content: {
clauses: [{ clause: "c1", found: true, where: "solve.py:10" }, { clause: "c2", found: true, where: "solve.py:30" }],
factors: [{ factor: "p1", found: true, where: "solve.py:10" }, { factor: "p2", found: false, where: "看了 solve.py 全文" }],
safeguards: [{ safeguard: "guard", found: true, where: "solve.py:40" }],
unassigned: [],
body: "p2 没找到",
},
});
l.append("stageClose", { stageId: "s1-ver-c1" });
l.append("stageOpen", { stageId: "s1-repair-c1", stage: "implementation", round: 1, roster: ["x1"] });
const m = material(l, "repair", "x1", "s1-repair-c1", "r1-c1", { changes: CHANGES_C1, artifacts: null });
assert.deepEqual(m.checklist, { kind: "repair", candidate: "r1-c1", clauses: ["c1", "c2"], factors: ["p1", "p2"], safeguards: ["guard"] });
has(m, "因子 p2 没落实:看了 solve.py 全文");
has(m, "+多起点循环");
assert.deepEqual(m.readable.map((r) => r.version), ["v-base", "v-c1"]);
});
test("核验:没有归属的改动排第一;因子与保障同行只作提醒;对号清单带没有归属的改动", (t) => {
const l = studyUpTo(t, "verifyOpen");
const m = material(l, "verification", "v1", "s1-ver-c1", "r1-c1", { changes: CHANGES_C1, artifacts: null });
assert.deepEqual(headings(m).slice(0, 2), ["分给你的材料", "没归到任何因子或保障的改动"]);
// 改动是候选第 10–22 行,位置表 p1 盖住 10–20:21–22 没有归属。p2 的 30–40 与 guard 的 40–45 在第 40 行重叠。
assert.deepEqual(m.checklist, {
kind: "verification",
unassigned: ["solve.py:21-22"],
candidate: "r1-c1",
clauses: ["c1", "c2"],
factors: ["p1", "p2"],
safeguards: ["guard"],
});
has(m, "solve.py:40:因子 p2、保障 guard");
assert.deepEqual(m.readable.map((r) => r.version), ["v-base", "v-c1"]);
assert.ok(!headings(m).includes("信念本"));
});
test("消融实现:这一格、因子清单、完整代码的位置表;看不到运行成绩", (t) => {
const l = studyUpTo(t, "ablationImplOpen");
const m = material(l, "ablationImplementation", "x1", "s1-ai", "r1-c1-p1", NONE);
assert.deepEqual(m.checklist, { kind: "ablationImplementation", cell: "r1-c1-p1", off: "p1", factors: ["p1", "p2"] });
assert.ok(headings(m).includes("完整代码(消融的起点)"));
assert.deepEqual(m.readable.map((r) => r.version), ["v-c1", "v-base"]);
lacks(m, "1.2");
lacks(m, "甲的解释");
});
test("消融核验:实际要关的因子、改动全文", (t) => {
const l = studyUpTo(t, "ablationImpl");
const m = material(l, "ablationVerification", "v1", "s1-av", "r1-c1-p1", { changes: CHANGES_C1, artifacts: null });
assert.deepEqual(m.checklist, { kind: "ablationVerification", cell: "r1-c1-p1", factors: ["p1", "p2"] });
has(m, "p1(只关指定的因子)");
assert.deepEqual(m.readable.map((r) => r.version), ["v-c1", "v-base", "v-c1-p1"]);
});
// ---------------------------------------------------------------------------
// 七条可见性规则(任务书 09 第二部分),每条一个"不该看到"
// ---------------------------------------------------------------------------
test("可见性 1:同一阶段没关之前,别人看不到已交的提交", (t) => {
const l = studyUpTo(t, "proposalA1");
const m = material(l, "proposal", "a2", "s1-prop", null, NONE);
lacks(m, "甲的提案正文");
lacks(m, "多起点重启");
// 本人自己当前阶段已交的看得到
has(material(l, "proposal", "a1", "s1-prop", null, NONE), "甲的提案正文");
});
test("可见性 2:作者自评到本轮结束解封前,任何任务的材料里都没有", (t) => {
const cases: Array<[string, SubmissionKind, string, string, string | null]> = [
["firstOpen", "firstPrediction", "a1", "s1-fp", "r1-c1"],
["critiqueOpen", "critique", "a2", "s1-crit", "r1-a1-1"],
["responseOpen", "response", "a1", "s1-resp", "r1-c1"],
["secondOpen", "secondPrediction", "a1", "s1-sp", "r1-c1"],
["implOpen", "implementation", "x1", "s1-impl-c1", "r1-c1"],
["wrapOpen", "wrapUp", "a1", "s1-wrap", null],
];
for (const [step, kind, actor, stage, object] of cases) {
const l = studyUpTo(t, step);
const m = material(l, kind, actor, stage, object, NONE);
for (const sealed of ["7.771", "7.772", "8.881", "8.882"]) lacks(m, sealed);
}
// 解封以后(跑过的 r1-c1)进战绩;没跑的 r1-c2 继续封存。
const after = beliefBook(studyUpTo(t, "proposal2Open"), study, 2);
assert.ok(after.includes("作者自评 vs 大家合并:r1-c1(作者 a1)"));
assert.ok(!after.includes("8.882"));
});
test("可见性 3:第一次预测只有本人看得到,之后各阶段也不给别人", (t) => {
const l = studyUpTo(t, "wrapOpen");
for (const m of [material(l, "wrapUp", "a2", "s1-wrap", null, NONE), material(l, "wrapUp", "a1", "s1-wrap", null, NONE)]) {
lacks(m, "5.551");
lacks(m, "6.661");
}
});
test("可见性 4:第二次预测看不到别人第一次的数", (t) => {
const l = studyUpTo(t, "secondOpen");
const m = material(l, "secondPrediction", "a1", "s1-sp", "r1-c1", NONE);
has(m, "5.551 / 5.552 / 5.553");
lacks(m, "6.661");
lacks(m, "乙第一次理由");
});
test("可见性 5:第零轮闭门写入口时看不到别人的入口", (t) => {
const l = studyUpTo(t, "entryA2");
const m = material(l, "entry", "a1", "s0-entry", null, NONE);
lacks(m, "入口乙一");
has(m, "入口甲一");
});
test("可见性 6:别人的退回不放进材料;本人重新发起时放上一次被退回的原因", (t) => {
const l = studyUpTo(t, "secondOpen");
const call = callId("s1-sp", "a1", "secondPrediction", "r1-c1", 1);
l.append("callStarted", { call, actor: "a1", stageId: "s1-sp", model: "glm-5.3", materials: null });
l.append("rejection", { stageId: "s1-sp", author: "a1", kind: "secondPrediction", submitted: { range: { q1: 3, q5: 2, q9: 1 } }, reason: "甲被退回:三个数不递增", jev: null });
l.append("callEnded", { call, wallSeconds: { status: "measured", value: 3 }, status: "notSubmitted", reason: "没交成" });
const retry = material(l, "secondPrediction", "a1", "s1-sp", "r1-c1", NONE);
assert.ok(retry.firstMessage.includes("# 上一次为什么没交成"));
has(retry, "甲被退回:三个数不递增");
lacks(material(l, "secondPrediction", "a1", "s1-sp", "r1-c2", NONE), "甲被退回");
lacks(material(l, "secondPrediction", "a2", "s1-sp", "r1-c1", NONE), "甲被退回");
});
test("可见性 7:信念本只写已公开的记录:正向运行在采纳决定写入后才出现", (t) => {
const before = beliefBook(studyUpTo(t, "run1"), study, 1);
assert.ok(!before.includes("run-1"));
const after = beliefBook(studyUpTo(t, "adopt1"), study, 1);
assert.ok(after.includes("运行 run-1:算数(校验器判合法),分数 1.2"));
});
test("实验员、核验员看不到别的候选", (t) => {
const l = studyUpTo(t, "verifyOpen");
for (const m of [
material(l, "verification", "v1", "s1-ver-c1", "r1-c1", { changes: CHANGES_C1, artifacts: null }),
material(studyUpTo(t, "implOpen"), "implementation", "x1", "s1-impl-c1", "r1-c1", NONE),
]) {
lacks(m, "r1-c2");
lacks(m, "乙的");
assert.equal(queryMaterial(m, "r1-c2"), null);
}
});
test("没有归属的改动:手写两个版本和位置表,算出的清单与手算的一致", (t) => {
const l = studyUpTo(t, "verifyOpen");
// 位置表:p1 a.py:1-5;p2 a.py:20 和整个 b.py;保障 guard a.py:5-6。
l.append("code", {
object: "r1-c1",
version: "v-c1",
locations: {
factorLocations: [{ factor: "p1", locations: ["a.py:1-5"] }, { factor: "p2", locations: ["a.py:20", "b.py"] }],
safeguardLocations: [{ safeguard: "guard", locations: ["a.py:5-6"] }],
unassigned: [{ location: "a.py:7", note: "实验员说是 import" }],
},
});
// 旧版本 → 新版本:a.py 新版本第 1–8 行是改过的;删了旧第 11–12 行(新版本第 10 行之后);删了旧第 21 行(新版本第 19 行之后)。
// b.py 改了第 3–4 行;c.bin 新增二进制;run.sh 只改可执行位;empty.txt 新增空文件。
const changes = {
files: [
{ path: "a.py", change: "modified" as const, modeOnly: false, binary: false as const, hunks: [
{ from: { start: 1, end: 3 }, to: { start: 1, end: 8 } },
{ from: { start: 11, end: 12 }, to: { after: 10 } },
{ from: { start: 21, end: 21 }, to: { after: 19 } },
] },
{ path: "b.py", change: "modified" as const, modeOnly: false, binary: false as const, hunks: [{ from: { start: 3, end: 4 }, to: { start: 3, end: 4 } }] },
{ path: "c.bin", change: "added" as const, modeOnly: false, binary: true as const },
{ path: "empty.txt", change: "added" as const, modeOnly: false, binary: false as const, hunks: [] },
{ path: "run.sh", change: "modified" as const, modeOnly: true, binary: false as const, hunks: [] },
],
diff: "",
};
const m = material(l, "verification", "v1", "s1-ver-c1", "r1-c1", { changes, artifacts: null });
// 手算:a.py 第 6 行归 guard,7–8 行没有归属(实验员自己的说明不算归属);删除点 10 前后(10、11)都没被盖住;
// 删除点 19 后一行 20 归 p2;b.py 整个文件归 p2;其余三个文件按整个文件算,位置表都没写到。
const c = m.checklist as Extract<TaskMaterial["checklist"], { kind: "verification" }>;
assert.deepEqual(c.unassigned, ["a.py:7-8", "a.py:10", "c.bin", "empty.txt", "run.sh"]);
has(m, "- a.py:10(删除 2 行(旧版本第 11–12 行))");
has(m, "- c.bin(新增(二进制文件,按整个文件算))");
has(m, "- run.sh(修改(只改了可执行位,按整个文件算))");
has(m, "- a.py:7:实验员说是 import");
// 因子与保障同一行:p1 的 1–5 与 guard 的 5–6 在第 5 行。
has(m, "- a.py:5:因子 p1、保障 guard");
lacks(m, "因子 p2、保障 guard");
});
test("答案路径不在位置表里、读不到,都写明状态", (t) => {
const l = studyUpTo(t, "run1");
l.append("runPlanned", { run: "run-x", purpose: "forward", round: 1, object: "r1-c2", definition: { command: ["python"], workdir: "/w", image: { digest: `sha256:${"0".repeat(64)}`, runtime: "runc" }, wallSeconds: 120, cores: 1, inheritEnv: false, env: {} } });
l.append("runStarted", { run: "run-x" });
l.append("runResult", {
run: "run-x",
process: { ending: "exited", exitCode: 0, cpuSeconds: { status: "measured", value: 1 }, wallSeconds: { status: "measured", value: 1 }, peakMemoryBytes: { status: "measured", value: 1 } },
judged: {
verdict: "valid", verdictReason: "合法", score: { status: "measured", value: 1.1 }, metrics: {}, extra: null,
answerPath: { status: "read", value: "p9" },
safeguardTriggers: { status: "unreadable", reason: "没写 safeguard-triggers" },
},
});
l.append("code", { object: "r1-c2", version: "v-c2", locations: { factorLocations: [{ factor: "p1", locations: ["x.py"] }], safeguardLocations: [], unassigned: [] } });
const input = { baseline: 0.96, knownBest: 2.635983, epsilon: 0.01, direction: "maximize" as const, candidates: [{ id: "r1-c1", score: 1.2 }] };
l.append("decision", { decision: "adopt", round: 1, rule: "test", ruleVersion: 1, params: null, seed: null, input: input as never, output: { line: 0.01, quarter: 0.4, best: { id: "r1-c1", score: 1.2, improvement: 0.24 }, adopted: "r1-c1" } });
l.append("stageOpen", { stageId: "s1-expl", stage: "explanation", round: 1, roster: ["a1", "a2"] });
const m = material(l, "explanation", "a1", "s1-expl", null, { changes: null, artifacts: {} });
const run = queryMaterial(m, "run-x")!;
assert.ok(run.includes("答案路径写的是 p9,不在位置表里"));
assert.ok(run.includes("保障动手次数读不到:没写 safeguard-triggers"));
});
// ---------------------------------------------------------------------------
// 查询、提示词
// ---------------------------------------------------------------------------
test("查询:材料里的编号查得到;同一账本里材料外的编号查不到", (t) => {
const l = studyUpTo(t, "secondOpen");
const m = material(l, "secondPrediction", "a2", "s1-sp", "r1-c1", NONE);
assert.ok(queryMaterial(m, "r1-a1-1")!.includes("甲的提案正文"));
assert.ok(queryMaterial(m, "task")!.includes("填圆任务说明"));
assert.equal(queryMaterial(m, "s1-sp"), null);
assert.equal(queryMaterial(m, "run-0"), null);
assert.equal(queryMaterial(m, "不存在"), null);
const impl = material(studyUpTo(t, "implOpen"), "implementation", "x1", "s1-impl-c1", "r1-c1", NONE);
assert.equal(queryMaterial(impl, "r1-a1-1"), null);
});
test("提示词:每种提交种类都有任务说明文件,每个角色都有角色提示词", () => {
const p = readPrompts(join(import.meta.dirname, "..", "prompts"));
assert.deepEqual(Object.keys(p.tasks).sort(), Object.keys(SUBMISSION_KINDS).sort());
for (const f of [...Object.values(p.tasks), ...Object.values(p.roles)]) assert.match(f.text, /\S/);
});
test("提示词哈希是文件原始字节的 sha256", (t) => {
const dir = tempDir(t);
mkdirSync(join(dir, "roles"));
mkdirSync(join(dir, "tasks"));
const bytes = Buffer.from("x\r\n中", "utf8");
for (const r of ["researcher", "experimenter", "verifier"]) writeFileSync(join(dir, "roles", `${r}.md`), bytes);
for (const k of Object.keys(SUBMISSION_KINDS)) writeFileSync(join(dir, "tasks", `${k}.md`), bytes);
const p = readPrompts(dir);
// printf 'x\r\n中' | sha256sum
assert.equal(p.roles.researcher.hash, "965da20bc965975b4eae297a49cabf4be539ef62bec455fe932093535de2c593");
assert.equal(p.tasks.wrapUp.hash, "965da20bc965975b4eae297a49cabf4be539ef62bec455fe932093535de2c593");
assert.equal(p.tasks.wrapUp.text, "x\r\n中");
});
test("第一条消息:任务说明文件在最前,现在交什么在最后并列出提交字段", (t) => {
const l = studyUpTo(t, "proposalOpen");
const m = material(l, "proposal", "a1", "s1-prop", null, NONE);
const p = readPrompts(join(import.meta.dirname, "..", "prompts"));
assert.ok(m.firstMessage.startsWith(p.tasks.proposal.text.trimEnd()));
const end = m.firstMessage.slice(m.firstMessage.lastIndexOf("# 现在交什么"));
for (const f of ["`factors`", "`safeguards`", "`metrics`", "`authorEstimate`", "`body`", "最多交 2 条"]) assert.ok(end.includes(f), f);
});