d2bfb971ab
- src/config:research.json 的字段定义(TypeBox,全部必填、不认识的字段报错,每个字段带中文说明和类别)、一次列出全部错误的校验、步骤清单纯函数。 - 流程写成完整流程 / 朴素循环两选一,第零轮、消融写成开 / 关两选一,冲突组合写不出来,不另写检查。 - 规则名取自 rules 登记表,config 改为依赖 rules(结构.md 第二部分、.dependency-cruiser.cjs)。 - ledger 顺手两处(结构.md 第四部分):消融结算按轮次唯一、去掉消融编号;私人笔记去掉版本号;表结构版本号升到 2。 - 结构.md 第八部分补 config 对外内容、更新 ledger 一节;WORK_LOG 更新。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
6.5 KiB
TypeScript
137 lines
6.5 KiB
TypeScript
import { BATCH_RULES, CELL_RULES, type BatchRuleName, type CellRuleName } from "../rules/index.ts";
|
|
import type { ResearchConfig } from "./schema.ts";
|
|
|
|
// 步骤清单(docs/结构.md 第三部分,方法.md 第十一部分):把配置里的流程、开关、规则翻译成这份研究要走的步骤。
|
|
// flow、导出、恢复都只读这份清单,不再读配置里的开关。被关掉的步骤也列出来,标"按配置跳过"并写明原因,
|
|
// flow 据此在账本里记一条"按配置跳过"。步骤里的先后、"若采纳的改法有 2 个以上因子"这类运行时条件归 flow。
|
|
|
|
/** 整份研究的步骤,按先后排。 */
|
|
export const STUDY_STEPS = [
|
|
"freezeConfig", // 1. 冻结配置
|
|
"baselineRun", // 2. 基线运行
|
|
"entryDraft", // 3. 第零轮:闭门写入口
|
|
"discussion1", // 3. 第零轮:第 1 次讨论
|
|
"discussion2", // 3. 第零轮:第 2 次讨论
|
|
"claim", // 3. 第零轮:认领
|
|
"assignEntries", // 3. 第零轮:分配(程序)
|
|
"rounds", // 4. 一轮接一轮,直到余额用完;每一轮走 ROUND_STEPS
|
|
"export", // 5. 导出
|
|
] as const;
|
|
export type StudyStepId = (typeof STUDY_STEPS)[number];
|
|
|
|
/** 每一轮的步骤,按先后排;注释里的编号是结构.md 第三部分"每一轮"的编号。 */
|
|
export const ROUND_STEPS = [
|
|
"openRound", // 1. 开轮:冻结本轮名单和提案上限
|
|
"carryOverPool", // 1. 开轮:处理留池候选
|
|
"proposal", // 2. 提案
|
|
"dedupe", // 3. 判重合并
|
|
"firstPrediction", // 4. 第一次预测(看原始提案)
|
|
"critique", // 5. 挑刺
|
|
"response", // 6. 回应与统一
|
|
"freezeProtocol", // 6. 协议冻结:挑刺与回应关掉时也要冻结
|
|
"secondPrediction", // 7. 第二次预测(看冻结协议)
|
|
"selectBatch", // 8. 选批
|
|
"implementation", // 9. 实现
|
|
"verification", // 9. 核验
|
|
"repair", // 9. 核验不通过:修复,再核验
|
|
"forwardRun", // 9. 正式运行
|
|
"publish", // 10. 整批公开,记分数账
|
|
"informationAccount", // 10. 记信息账(世界比特、惊讶、落点)
|
|
"adopt", // 11. 采纳
|
|
"listCells", // 12. 列消融格
|
|
"explanation", // 12. 解释
|
|
"selectCells", // 12. 选格
|
|
"ablationImplementation", // 12. 消融实现
|
|
"ablationVerification", // 12. 消融核验
|
|
"ablationRun", // 12. 消融运行
|
|
"settle", // 12. 结算(含押的因子、消融中发现)
|
|
"wrapUp", // 13. 收尾
|
|
] as const;
|
|
export type RoundStepId = (typeof ROUND_STEPS)[number];
|
|
|
|
/** 决策点用哪条规则:规则名和版本,从 rules 的登记表取。 */
|
|
export type RuleRef = { name: BatchRuleName | CellRuleName; version: number };
|
|
|
|
type Status = { status: "execute" } | { status: "skip"; reason: string };
|
|
|
|
/** 一步:执行,或按配置跳过(带原因);只有选批、选格两个决策点带规则,其余为 null。 */
|
|
export type Step<S extends string> = { id: S; rule: RuleRef | null } & Status;
|
|
|
|
export interface StepList {
|
|
/** 完整流程还是朴素循环;materials 据此决定给什么材料(朴素循环没有信念本和私人笔记)。 */
|
|
preset: ResearchConfig["flow"]["preset"];
|
|
study: Step<StudyStepId>[];
|
|
round: Step<RoundStepId>[];
|
|
}
|
|
|
|
const EXECUTE: Status = { status: "execute" };
|
|
const skip = (reason: string): Status => ({ status: "skip", reason });
|
|
|
|
/**
|
|
* 朴素循环照常做的步骤(方法.md 第十一部分):提案、判重、提案直接冻结、均匀随机选批、实现到正式运行、公开分数、采纳。
|
|
* 其余都不做:不预测、不挑刺不回应、不消融,没有第零轮、收尾,候选不留池;没有预测,也就没有信息账。
|
|
*/
|
|
const NAIVE_STEPS: ReadonlySet<StudyStepId | RoundStepId> = new Set([
|
|
"freezeConfig", "baselineRun", "rounds", "export",
|
|
"openRound", "proposal", "dedupe", "freezeProtocol", "selectBatch",
|
|
"implementation", "verification", "repair", "forwardRun", "publish", "adopt",
|
|
] as const);
|
|
|
|
/** 把配置翻译成步骤清单。纯函数:同一份配置永远得到同一份清单。 */
|
|
export function stepList(config: ResearchConfig): StepList {
|
|
const flow = config.flow;
|
|
if (flow.preset === "naive") {
|
|
const naive = skip("朴素循环不做这一步(方法.md 第十一部分)");
|
|
return assemble("naive", (id) => (NAIVE_STEPS.has(id) ? EXECUTE : naive), batchRule("uniformRandom"), null);
|
|
}
|
|
|
|
// 完整流程:只有开关关掉的步骤跳过。
|
|
const skipped: Partial<Record<StudyStepId | RoundStepId, Status>> = {};
|
|
const off = (ids: Array<StudyStepId | RoundStepId>, reason: string) => {
|
|
for (const id of ids) skipped[id] = skip(reason);
|
|
};
|
|
const zero = flow.roundZero;
|
|
if (!zero.enabled) {
|
|
off(["entryDraft", "discussion1", "discussion2", "claim", "assignEntries"], "配置关掉了第零轮");
|
|
} else if (config.models.researchers.length === 1) {
|
|
// N = 1 时不讨论,直接把第一志愿分给他(方法.md 4.2"分配规则")。
|
|
off(["discussion1", "discussion2"], "只有一位研究者,第零轮不讨论(方法.md 4.2)");
|
|
} else if (zero.discussions === 1) {
|
|
off(["discussion2"], "第零轮讨论次数配置为 1");
|
|
}
|
|
if (!flow.firstPrediction) off(["firstPrediction"], "配置关掉了第一次预测");
|
|
// 挑刺与回应关掉时,协议照样冻结(freezeProtocol 不跟着关)。
|
|
if (!flow.critique) off(["critique", "response"], "配置关掉了挑刺与回应");
|
|
// 消融关掉时解释照样写文字,只是不给每格的三个数(方法.md 第十一部分"步骤开关"),所以 explanation 不跟着关。
|
|
if (!flow.ablation.enabled) {
|
|
off(
|
|
["listCells", "selectCells", "ablationImplementation", "ablationVerification", "ablationRun", "settle"],
|
|
"配置关掉了消融",
|
|
);
|
|
}
|
|
const cells = flow.ablation.enabled ? cellRule(flow.ablation.cellRule) : null;
|
|
return assemble("full", (id) => skipped[id] ?? EXECUTE, batchRule(flow.batchRule), cells);
|
|
}
|
|
|
|
function batchRule(name: BatchRuleName): RuleRef {
|
|
return { name, version: BATCH_RULES[name].version };
|
|
}
|
|
|
|
function cellRule(name: CellRuleName): RuleRef {
|
|
return { name, version: CELL_RULES[name].version };
|
|
}
|
|
|
|
function assemble(
|
|
preset: StepList["preset"],
|
|
status: (id: StudyStepId | RoundStepId) => Status,
|
|
batch: RuleRef,
|
|
cells: RuleRef | null,
|
|
): StepList {
|
|
const ruleOf = (id: RoundStepId) => (id === "selectBatch" ? batch : id === "selectCells" ? cells : null);
|
|
return {
|
|
preset,
|
|
study: STUDY_STEPS.map((id) => ({ id, rule: null, ...status(id) })),
|
|
round: ROUND_STEPS.map((id) => ({ id, rule: ruleOf(id), ...status(id) })),
|
|
};
|
|
}
|