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>
This commit is contained in:
2026-10-03 04:08:46 -04:00
parent cba1678eaf
commit 446e26224e
41 changed files with 3665 additions and 10 deletions
+270
View File
@@ -0,0 +1,270 @@
import { compareIds } from "../rules/index.ts";
import type { ChecklistOf } from "../vocab/index.ts";
import {
beliefSection,
codeSection,
Draft,
entryVersions,
factorLines,
metricsSection,
ownSection,
publicRecords,
registeredMetrics,
researcherTaskSection,
runLines,
validEntries,
} from "./blocks.ts";
import { json, num, rangeText, score } from "./format.ts";
import { factorId, factorKey } from "./shapes.ts";
// 研究者的 11 种任务,每种一个函数,明写从账本取哪几样(方法.md 第三部分、4.2、4.3)。
// 顺序:你自己的 → 公共材料(任务与本轮、基线、研究记录、信念本、各任务另加的几节、本任务要处理的东西、已登记指标清单)。
// 别人的提交一律经 Board.published(已关阶段的最后一版)读;作者自评、别人的第一次预测、别人的退回都不读。
type Own = ReadonlyArray<{ heading: string; lines: string[] }>;
/**
* 研究者材料的骨架。middle 放在信念本之后、指标清单之前。
* 第零轮(4.2)只有任务与本轮、基线:不跑实验,信念本不变,也还没有研究记录和指标可列。
*/
function frame(d: Draft, own: Own, middle: () => void): void {
ownSection(d, own);
d.group("公共材料", "下面是大家看到的同一份材料,不隐藏任何正式证据;第一部分已经给过的内容,这里只写一句指引。");
researcherTaskSection(d);
codeSection(d);
if (d.round >= 1) {
publicRecords(d);
beliefSection(d);
}
middle();
if (d.round >= 1) metricsSection(d);
}
/** 提案、回应、统一共用的因子规则(方法.md 第五部分"延续""指标"、4.3 ②)。 */
function factorRules(d: Draft) {
const registered = registeredMetrics(d.board).map((m) => m.id);
return {
factorLimit: d.req.study.factorLimit,
continuable: d.board.protocols().flatMap((p) => p.protocol.factors.map((_, i) => factorId(p.candidate, factorKey(i)))),
reusableMetrics: registered,
takenMetricIds: [...d.req.study.metrics.map((m) => m.id), ...registered],
};
}
const PRESUMPTION =
'挑刺里标"(推测)"的判断,是挑刺人没对照代码或已测运行核实过的猜想;作者在回应里同意它,也不会让它变成已核实。当作别人的猜想看,不当证据。';
// ---------------------------------------------------------------------------
// 第零轮(方法.md 4.2)
// ---------------------------------------------------------------------------
/** 0.1 闭门写入口:任务说明、基线代码、基线分数;看不到别人的入口(任何入口都不读)。 */
export function entry(d: Draft): ChecklistOf<"entry"> {
frame(d, [], () => undefined);
return { kind: "entry", entries: { min: 2, max: 3 } };
}
/** 入口和之前各次讨论:只读已关阶段的(写入口阶段关了才有讨论)。 */
function entriesAndDiscussions(d: Draft): void {
const lines: string[] = [];
for (const e of validEntries(d.board)) {
d.record(e.id, `入口 ${e.id}:${e.name}(作者 ${e.author})`, e.body);
lines.push(`### 入口 ${e.id}:${e.name}(作者 ${e.author})`, "", e.body, "");
}
for (const s of d.board.published("discussion")) lines.push(`### ${s.author} 的讨论(阶段 ${s.stageId})`, "", s.content.body, "");
d.section("本任务要处理的东西", lines);
const table: string[] = ['程序给的号;整份替换后的新清单接着往后编,旧编号作废,不会指到新入口。'];
for (const [author, versions] of entryVersions(d.board)) {
versions.forEach((v, i) => {
const last = i === versions.length - 1;
table.push(`- ${author},${i === 0 ? "写入口时交的" : `第 ${i} 次替换`}${last ? "(最后一版,有效)" : "(已被整份替换,编号作废)"}:`);
v.entries.forEach((e, j) => table.push(` - ${v.ids[j]}${last ? "" : "(作废)"}:${e.name}`));
});
}
d.section("入口编号对照", table);
}
/** 0.2 讨论:全部入口(标明作者)、之前各次的讨论。 */
export function discussion(d: Draft): ChecklistOf<"discussion"> {
frame(d, [], () => entriesAndDiscussions(d));
return { kind: "discussion", entries: { min: 2, max: 3 } };
}
/** 0.3 认领:最终的入口与全部讨论;至少排 min(N, 入口总数) 个。 */
export function claim(d: Draft): ChecklistOf<"claim"> {
frame(d, [], () => entriesAndDiscussions(d));
const entries = validEntries(d.board).map((e) => e.id);
const n = d.board.stage(d.req.stageId).roster.length;
return { kind: "claim", entries, minChoices: Math.min(n, entries.length) };
}
// ---------------------------------------------------------------------------
// 第 r 轮(方法.md 4.3)
// ---------------------------------------------------------------------------
/** 某一轮实验员的提醒:每个候选最新一次实现或修复的 concerns,加消融实现的(只读已关阶段)。 */
function concernLines(d: Draft, round: number): string[] {
const b = d.board;
const candidates = [...new Set([...b.published("implementation"), ...b.published("repair")].filter((s) => s.round === round).map((s) => s.target!))];
const lines = [
...candidates.map((c) => `- 候选 ${c}:${b.implementation(c)!.content.concerns}`),
...b.published("ablationImplementation").filter((s) => s.round === round).map((s) => `- 消融格 ${s.target}:${s.content.concerns}`),
];
return lines.length === 0 ? ["(没有)"] : lines;
}
/** ② 提案:公共材料,加上一轮全部收尾、上一轮实验员的提醒、可以写进"延续"的因子。 */
export function proposal(d: Draft): ChecklistOf<"proposal"> {
frame(d, [], () => {
const prev = d.round - 1;
const wraps = d.board.published("wrapUp").filter((w) => w.round === prev);
d.section(
"上一轮的收尾",
prev < 1 ? ["(这是第 1 轮,没有上一轮)"] : wraps.flatMap((w) => [`### ${w.author} 的收尾`, "", w.content.body, ""]),
);
d.section("上一轮实验员实现时的提醒", prev < 1 ? ["(这是第 1 轮,没有上一轮)"] : concernLines(d, prev));
const lines = d.board.protocols().flatMap((p) =>
p.protocol.factors.map((f, i) => `- ${factorId(p.candidate, factorKey(i))} ${f.name}(关键设定:${f.keySettings};候选 ${p.candidate},基线 ${p.protocol.baseline}${f.continues === null ? "" : `;它自己延续 ${f.continues}`})`),
);
d.section('可以写进"延续"的因子', lines.length === 0 ? ["(还没有任何冻结的因子)"] : lines);
});
return { kind: "proposal", maxProposals: d.req.study.proposalLimit, ...factorRules(d) };
}
/** 一条原提案全文,带作者。 */
function proposalLines(d: Draft, id: string): string[] {
const p = d.board.proposal(id);
return [`### 原提案 ${id}(作者 ${p.author})`, "", json(p.content)];
}
/** 一个候选由哪几条原提案合并而成(判重合并的决定,方法.md 4.3 ③)。 */
function mergeLine(d: Draft, candidate: string): string {
const ids = d.board.proposalsOf(candidate);
return ids.length === 1 ? `候选 ${candidate} 就是原提案 ${ids[0]}。` : `候选 ${candidate} 由判重合并的 ${ids.join("、")} 合成,作者都保留作者关系。`;
}
/** 合并说明,后面跟这几条原提案的全文。 */
function candidateProposals(d: Draft, candidate: string): string[] {
return [mergeLine(d, candidate), "", ...d.board.proposalsOf(candidate).flatMap((id) => proposalLines(d, id))];
}
/** ④ 第一次预测:这个候选的全部原始提案。看不到作者自评(账本里已拆出封存)、看不到别人的预测。 */
export function firstPrediction(d: Draft): ChecklistOf<"firstPrediction"> {
const candidate = d.req.object!;
frame(d, [], () => d.section("本任务要处理的东西", candidateProposals(d, candidate)));
return { kind: "firstPrediction", candidate };
}
/** ⑤ 挑刺:被挑的那条原提案全文(用户 2026-10-03 决定 4:按原提案挑)。 */
export function critique(d: Draft): ChecklistOf<"critique"> {
const id = d.req.object!;
frame(d, [], () => d.section("本任务要处理的东西", proposalLines(d, id)));
return { kind: "critique", proposal: id, factors: d.board.proposal(id).content.factors.map((_, i) => factorKey(i)) };
}
/**
* ⑥ 回应与统一:这个候选的全部原提案、针对它们的全部挑刺(已关的挑刺阶段)。
* 两者材料相同;统一另要逐条体现每份原提案(sources)。
*/
function answer(d: Draft): { candidate: string; critiques: string[] } & ReturnType<typeof factorRules> {
const candidate = d.req.object!;
const ids = d.board.proposalsOf(candidate);
const list = d.board.published("critique").filter((c) => ids.includes(c.target!)).sort((a, b) => compareIds(a.item!, b.item!));
frame(d, [], () => {
d.section("本任务要处理的东西", candidateProposals(d, candidate));
d.section(
"本任务要答复的挑刺",
list.length === 0
? ["(没有针对这个候选的挑刺,replies 交空数组)"]
: [PRESUMPTION, "", ...list.flatMap((c) => [`### 挑刺 ${c.item}(${c.author} 挑提案 ${c.target} 的 ${c.content.target})`, "", c.content.body, ""])],
);
});
return { candidate, critiques: list.map((c) => c.item!), ...factorRules(d) };
}
export const response = (d: Draft): ChecklistOf<"response"> => ({ kind: "response", ...answer(d) });
export function unification(d: Draft): ChecklistOf<"unification"> {
const a = answer(d);
return { kind: "unification", ...a, sources: d.board.proposalsOf(a.candidate) };
}
/**
* ⑦ 第二次预测:冻结协议、挑刺与回应按挑刺编号配对、本人第一次给这个候选的三个数(4.3 ⑦)。
* 第一次预测只取本人本轮的;别人的第一次和作者自评不读。留池候选本轮不做第一次预测(4.3 ④)。
*/
export function secondPrediction(d: Draft): ChecklistOf<"secondPrediction"> {
const candidate = d.req.object!;
const first = () =>
d.board.published("firstPrediction").filter((s) => s.author === d.req.actor && s.target === candidate && s.round === d.round).at(-1)!;
const own = [
{
heading: "你第一次给这个候选的三个数",
lines: d.board.carried(d.round).includes(candidate)
? ["(这是留池候选,本轮没有第一次预测)"]
: [`一成 / 一半 / 九成:${rangeText(first().content.range)}(当时只读了原始提案)`, "", "读了冻结协议和挑刺与回应再给一次:该改就改,不该改就照旧,两份都会存档。"],
},
];
frame(d, own, () => {
const p = d.board.protocol(candidate);
const critiques = d.board.published("critique");
const pairs = p.replies.flatMap((r) => {
const c = critiques.find((x) => x.item === r.critique)!;
return [`### 挑刺 ${r.critique}(${c.author} 挑的)`, "", c.content.body, "", `作者的答复:${r.reply}`, ""];
});
d.section("本任务要处理的东西", [mergeLine(d, candidate), "", `### 候选 ${candidate} 的冻结协议`, "", json(p)]);
d.section("挑刺与回应", pairs.length === 0 ? ["(没有针对这个候选的挑刺)"] : [PRESUMPTION, "", ...pairs]);
});
return { kind: "secondPrediction", candidate };
}
/**
* ⑭ 解释:本轮各候选的运行与分数、实验员的提醒、核验结论、被采纳改法的因子(要给三个数的格)、
* 本轮运行的产物(只放进查询)。本轮正向运行在采纳决定写入后公开。
*/
export function explanation(d: Draft): ChecklistOf<"explanation"> {
const adopted = d.board.decision("adopt", d.round)!.output.adopted!;
const p = d.board.protocol(adopted);
frame(d, [], () => {
const runs = d.board.runs().filter((r) => r.plan.purpose === "forward" && r.plan.round === d.round && d.board.runPublic(r));
const verifications = d.board.published("verification").filter((v) => v.round === d.round);
d.section("本任务要处理的东西", [
"### 本轮正向运行",
"",
...runs.flatMap((r) => [...runLines(d, r), `(判分全文用 research_query 查 ${r.plan.run})`, ""]),
"### 核验结论",
"",
...(verifications.length === 0 ? ["(没有)"] : verifications.map((v) => `- 候选 ${v.target}:${v.content.body}`)),
]);
d.section("本轮实验员实现时的提醒", concernLines(d, d.round));
d.section("本轮要给三个数的格", [
`被采纳的改法:候选 ${adopted},完整代码 ${score(d.board.baselineScore(adopted))};本批基线 ${p.baseline} ${score(d.board.baselineScore(p.baseline))}。`,
"每条解释对下面每一格各给三个数:只关这一个因子、其余因子和保障照完整代码保留时,那次运行的最终指标(按分数原值)。",
"",
...factorLines(p),
]);
const artifacts = d.req.artifacts!;
const listed = Object.keys(artifacts)
.sort(compareIds)
.flatMap((run) => Object.entries(artifacts[run]!).map(([file, text]) => `- ${d.record(`${run}/${file}`, `运行 ${run} 的产物 ${file}`, text)}`));
d.section("本轮运行的产物", ["用 research_query 按编号查全文:", ...(listed.length === 0 ? ["(没有)"] : listed)]);
});
return { kind: "explanation", cells: p.factors.map((_, i) => factorKey(i)), maxExplanations: d.req.study.proposalLimit };
}
/** ⑱ 收尾:本轮的采纳、选格(结算与押的因子在信念本里),实验员的提醒。 */
export function wrapUp(d: Draft): ChecklistOf<"wrapUp"> {
frame(d, [], () => {
const adopt = d.board.decision("adopt", d.round)!.output;
const cells = d.board.decision("cells", d.round);
d.section("本任务要处理的东西", [
adopt.adopted === null
? `本轮没有采纳:最好的有效候选 ${adopt.best === null ? "没有" : `${adopt.best.id},改进 ${score(adopt.best.improvement)}`},采纳线 ${score(adopt.line)}。`
: `本轮采纳候选 ${adopt.adopted},改进 ${score(adopt.best!.improvement)},采纳线 ${score(adopt.line)}。`,
cells === undefined ? "本轮没有选格。" : `本轮选格:测 ${cells.output.chosen.length === 0 ? "(一格都不测)" : cells.output.chosen.join("、")},净值 ${num(cells.output.chosenNet)}。`,
'消融结算、各格结论和押的因子见"信念本"。',
]);
d.section("本轮实验员实现时的提醒", concernLines(d, d.round));
});
return { kind: "wrapUp" };
}