446e26224e
- 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>
186 lines
10 KiB
TypeScript
186 lines
10 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import { join } from "node:path";
|
||
import { test } from "node:test";
|
||
import { Ledger } from "../src/ledger/index.ts";
|
||
import { beliefBook, queryMaterial } from "../src/materials/index.ts";
|
||
import { adopt, selectBestExpected, settleAblation, type BatchInput, type SettleInput } from "../src/rules/index.ts";
|
||
import { tempDir } from "./ledger-helpers.ts";
|
||
import { batch, judged, material, NONE, P_A1, P_A2, protocolOf, r, study, studyUpTo, TABLE_C1, Writer } from "./materials-helpers.ts";
|
||
|
||
// 信念本的留池、离池,按条文手算的数字,第零轮记录在第 1 轮起公开。
|
||
|
||
function fresh(t: import("node:test").TestContext): Writer {
|
||
const l = Ledger.open(join(tempDir(t), "ledger.db"));
|
||
t.after(() => l.close());
|
||
const w = new Writer(l);
|
||
w.l.append("code", { object: "base", version: "v-base", locations: null });
|
||
w.run("run-0", "baseline", 0, null, judged(0.96, "main"));
|
||
return w;
|
||
}
|
||
|
||
const adoptInput = (baseline: number, candidates: Array<{ id: string; score: number }>) => ({
|
||
baseline,
|
||
knownBest: 2.635983,
|
||
epsilon: 0.01,
|
||
direction: "maximize" as const,
|
||
candidates,
|
||
});
|
||
|
||
/** 第 1 轮冻结 r1-c1、r1-c2,选中 r1-c1、跑了没采纳:r1-c2 留到第 2 轮的候选池。 */
|
||
function roundOneNoAdoption(t: import("node:test").TestContext, before: (w: Writer) => void): Writer {
|
||
const w = fresh(t);
|
||
before(w);
|
||
w.protocol("r1-c1", protocolOf(P_A1, 1, "base", ["r1-a1-1"], [], ["c"]));
|
||
w.protocol("r1-c2", protocolOf(P_A2, 1, "base", ["r1-a2-1"], [], ["c"]));
|
||
const b1 = batch(1, 0.96, { "r1-c1": { a1: r(1.0, 1.1, 1.2) }, "r1-c2": { a1: r(0.95, 0.97, 0.99) } });
|
||
assert.deepEqual(b1.output.chosen, ["r1-c1"]);
|
||
w.decision("batch", 1, b1.input, b1.output);
|
||
w.l.append("code", { object: "r1-c1", version: "v-c1", locations: TABLE_C1 });
|
||
w.run("run-1", "forward", 1, "r1-c1", judged(0.965, "main"));
|
||
const a1 = adoptInput(0.96, [{ id: "r1-c1", score: 0.965 }]);
|
||
w.decision("adopt", 1, a1, adopt(a1));
|
||
return w;
|
||
}
|
||
|
||
test("留池:第 1 轮没选中、第 2 轮入选跑了没采纳的候选,第 3 轮不再算留池(复现审阅意见 1)", (t) => {
|
||
const w = roundOneNoAdoption(t, () => undefined);
|
||
w.open("s2-prop", "proposal", 2, ["a1"]);
|
||
// 第 2 轮提案阶段(还没有本轮判重合并):r1-c2 没选中、基线没变,留在候选池。
|
||
assert.ok(beliefBook(w.l, study, 2).includes("r1-c2:没选中,留在本轮候选池;之前的合并范围不附"));
|
||
const b2 = batch(2, 0.96, { "r1-c2": { a1: r(0.95, 0.97, 0.99) } });
|
||
w.decision("batch", 2, b2.input, b2.output);
|
||
w.l.append("code", { object: "r1-c2", version: "v-c2", locations: { factorLocations: [{ factor: "p1", locations: ["x.py"] }], safeguardLocations: [], unassigned: [] } });
|
||
w.run("run-2", "forward", 2, "r1-c2", judged(0.962, "main"));
|
||
const a2 = adoptInput(0.96, [{ id: "r1-c2", score: 0.962 }]);
|
||
w.decision("adopt", 2, a2, adopt(a2));
|
||
w.open("s3-prop", "proposal", 3, ["a1"]);
|
||
const book = beliefBook(w.l, study, 3);
|
||
assert.ok(!book.includes("留在本轮候选池"), "r1-c2 第 2 轮已经入选,不再留池");
|
||
assert.match(book, /- 第 1 轮选批[^\n]*\n(?:[^\n]*\n)*? - r1-c2:合并范围 /);
|
||
assert.ok(book.includes("离开候选池的旧候选**:没有"));
|
||
});
|
||
|
||
test("导出时(round 为 null)不藏任何候选的合并范围(复现复审意见 1)", (t) => {
|
||
const w = roundOneNoAdoption(t, () => undefined);
|
||
w.open("s2-prop", "proposal", 2, ["a1"]);
|
||
const book = beliefBook(w.l, study, null);
|
||
assert.ok(!book.includes("留在本轮候选池"));
|
||
assert.match(book, / - r1-c2:合并范围 /);
|
||
});
|
||
|
||
test("第二次预测:留池候选按 carried 判,不取它上一轮的第一次预测(复现复审意见 2)", (t) => {
|
||
const w = roundOneNoAdoption(t, (w) => {
|
||
w.open("s1-prop", "proposal", 1, ["a1"]);
|
||
w.sub("s1-prop", "a1", "proposal", null, "r1-a1-1", P_A1);
|
||
w.sub("s1-prop", "a1", "proposal", null, "r1-a1-2", P_A2);
|
||
w.close("s1-prop");
|
||
w.decision("dedup", 1, null, { fresh: [{ candidate: "r1-c1", proposals: ["r1-a1-1"] }, { candidate: "r1-c2", proposals: ["r1-a1-2"] }], carried: [] });
|
||
w.open("s1-fp", "firstPrediction", 1, ["a1"]);
|
||
w.sub("s1-fp", "a1", "firstPrediction", "r1-c2", null, { range: r(4.441, 4.442, 4.443), body: "上一轮的第一次" });
|
||
w.close("s1-fp");
|
||
});
|
||
w.open("s2-prop", "proposal", 2, ["a1"]);
|
||
w.close("s2-prop");
|
||
w.decision("dedup", 2, null, { fresh: [], carried: [{ candidate: "r1-c2", proposals: [] }] });
|
||
w.open("s2-sp", "secondPrediction", 2, ["a1"]);
|
||
const m = material(w.l, "secondPrediction", "a1", "s2-sp", "r1-c2", NONE);
|
||
assert.ok(m.firstMessage.includes("(这是留池候选,本轮没有第一次预测)"));
|
||
assert.ok(!m.firstMessage.includes("4.442"));
|
||
});
|
||
|
||
test("离池:基线变了、从没入选的旧候选列在离开候选池里,不再写留池", (t) => {
|
||
const book = beliefBook(studyUpTo(t, "proposal2Open"), study, 2);
|
||
assert.ok(book.includes("离开候选池的旧候选**:r1-c2"));
|
||
assert.ok(!book.includes("留在本轮候选池"));
|
||
});
|
||
|
||
test("信念本的数按条文手算:少多少分、撑几成、世界比特、惊讶、战绩次数、标签", (t) => {
|
||
// 正向的三个候选、各人范围、本批基线 b 和 c3 的实测取自 tests/fixtures/rules/info.json 的 bits_forward_r1_real,
|
||
// 世界比特、惊讶的预期值是那里不看实现算出的独立值。
|
||
const fx = JSON.parse(readFileSync(join(import.meta.dirname, "fixtures", "rules", "info.json"), "utf8")).scenarios[0];
|
||
const b: number = fx.input.b;
|
||
const full: number = fx.input.actual["r1-v1-c3"];
|
||
const w = fresh(t);
|
||
const input: BatchInput = {
|
||
candidates: Object.entries(fx.input.candidates as Record<string, Array<{ researcher: string; range: { q1: number; q5: number; q9: number } }>>).map(
|
||
([id, ranges]) => ({ id, ranges }),
|
||
),
|
||
baseline: b,
|
||
direction: "maximize",
|
||
epsilon: 0.01,
|
||
lambda: 0.1,
|
||
batchCap: 1,
|
||
balance: 9,
|
||
seed: "seed",
|
||
};
|
||
// 这里只要 c3 入选并跑过:选批记录的其余内容照规则算出,chosen 写成 c3。
|
||
w.decision("batch", 1, input, { ...selectBestExpected(input), chosen: ["r1-v1-c3"] });
|
||
w.protocol("r1-v1-c3", protocolOf(P_A1, 1, "base", ["r1-a1-1"], [], ["c"]));
|
||
w.l.append("code", { object: "r1-v1-c3", version: "v-c3", locations: TABLE_C1 });
|
||
w.run("run-1", "forward", 1, "r1-v1-c3", judged(full, "main"));
|
||
const a = adoptInput(b, [{ id: "r1-v1-c3", score: full }]);
|
||
w.decision("adopt", 1, a, adopt(a));
|
||
w.run("run-2", "ablation", 1, "r1-v1-c3-p1", judged(1.3, "main"));
|
||
w.run("run-3", "ablation", 1, "r1-v1-c3-p2", judged(1.45, "main"));
|
||
const settle: SettleInput = {
|
||
baseline: b,
|
||
full,
|
||
direction: "maximize",
|
||
epsilon: 0.01,
|
||
knownBest: 2.635983,
|
||
lambda: 0.1,
|
||
factors: ["p1", "p2"],
|
||
cells: [
|
||
{ factor: "p1", outcome: { kind: "valid", score: 1.3 } },
|
||
{ factor: "p2", outcome: { kind: "valid", score: 1.45 } },
|
||
],
|
||
explanations: [
|
||
{ id: "r1-researcher-1-x1", author: "researcher-1", cells: [{ factor: "p1", range: r(1.25, 1.35, 1.45) }, { factor: "p2", range: r(1.5, 1.55, 1.6) }] },
|
||
{ id: "r1-researcher-2-x1", author: "researcher-2", cells: [{ factor: "p1", range: r(1.1, 1.2, 1.28) }, { factor: "p2", range: r(1.4, 1.44, 1.5) }] },
|
||
],
|
||
};
|
||
w.l.append("settlement", { round: 1, result: { candidate: "r1-v1-c3", input: settle, output: settleAblation(settle) } as never });
|
||
w.open("s2-prop", "proposal", 2, ["researcher-1"]);
|
||
const book = beliefBook(w.l, study, 2);
|
||
// 世界比特 13.018692667257758、惊讶 18.609640474436972(fixture),写 4 位有效数字。
|
||
assert.ok(book.includes("世界比特 13.02,惊讶 18.61 比特"), "正向 c3 的世界比特与惊讶");
|
||
// G = 1.552176048093269 − 0.9597642169962064 = 0.5924118311;关 p1:少 0.252176,撑 0.4257 → 43%;关 p2:少 0.102176,撑 0.1725 → 17%。
|
||
assert.ok(book.includes("关掉后 1.3,少 0.252176 分,撑 43%"));
|
||
assert.ok(book.includes("关掉后 1.45,少 0.102176 分,撑 17%"));
|
||
// Σ撑 = 0.598,相对交互量 −0.402 < −0.2:替代(方法.md 6.6)。
|
||
assert.ok(book.includes("替代:几个因子在做同一件事"));
|
||
// 正向:三人对 c3 的一成都在 2.1 以上,实测 1.552 低于一成:中位数偏高、穿过下沿,没包住。
|
||
for (const who of ["researcher-1", "researcher-2", "researcher-3"]) {
|
||
assert.ok(book.includes(`- ${who},正向 1 次:中位数偏高 1、偏低 0;穿过上沿 0、下沿 1;包住 0`), who);
|
||
}
|
||
// 消融:researcher-1 p1 1.3 落在 1.25–1.35(包住,中位数偏高)、p2 1.45 < 1.5(穿下沿,偏高);
|
||
// researcher-2 p1 1.3 > 1.28(穿上沿,偏低)、p2 1.45 在 1.44–1.5(包住,偏低)。
|
||
assert.ok(book.includes("- researcher-1,消融 2 次:中位数偏高 2、偏低 0;穿过上沿 0、下沿 1;包住 1"));
|
||
assert.ok(book.includes("- researcher-2,消融 2 次:中位数偏高 0、偏低 2;穿过上沿 1、下沿 0;包住 1"));
|
||
});
|
||
|
||
test("第零轮的入口、讨论、志愿、分配在第 1 轮起的研究记录里查得到(方法.md 4.2)", (t) => {
|
||
const m = material(studyUpTo(t, "proposalOpen"), "proposal", "a2", "s1-prop", null, NONE);
|
||
assert.ok(queryMaterial(m, "e-a1-1")!.includes("已被整份替换,作废"));
|
||
assert.ok(queryMaterial(m, "e-a2-1")!.includes("乙一起点"));
|
||
assert.ok(queryMaterial(m, "s0-d1.a1")!.includes("甲的讨论"));
|
||
assert.ok(queryMaterial(m, "s0-claim.a2")!.includes("e-a2-1"));
|
||
assert.ok(queryMaterial(m, "entry-assignment")!.includes("e-a1-3"));
|
||
});
|
||
|
||
test("第 2 轮解释的研究记录里查得到第 1 轮和第零轮的已公开记录", (t) => {
|
||
const m = material(studyUpTo(t, "explOpen2"), "explanation", "a1", "s2-expl", null, { changes: null, artifacts: {} });
|
||
const list = m.sections.find((s) => s.heading === "研究记录")!.text;
|
||
const ids = ["run-1", "run-2", "run-3", "r1-c1", "r1-a1-1", "r1-a2-1", "r1-a1-1.a2.1", "r1-a1-x1", "r1-a2-x1", "s1-wrap.a1", "s0-d1.a1", "s0-claim.a2", "entry-assignment"];
|
||
for (const id of ids) {
|
||
assert.ok(list.includes(`- ${id}:`), `研究记录清单里应有 ${id}`);
|
||
assert.notEqual(queryMaterial(m, id), null, id);
|
||
}
|
||
});
|
||
|
||
test("解释的现在交什么写明每人最多 M 条", (t) => {
|
||
const m = material(studyUpTo(t, "explOpen1"), "explanation", "a1", "s1-expl", null, { changes: null, artifacts: {} });
|
||
assert.ok(m.firstMessage.slice(m.firstMessage.lastIndexOf("# 现在交什么")).includes("最多 2 条"));
|
||
});
|